--- /dev/null
+#! /usr/bin/env python
+
+from com import Container
+from utils import logging
+import values
+import os
+import sys
+import getopt
+
+
+_LOG_LEVELS = [logging.OFF, logging.ERROR, logging.WARNING,
+ logging.INFO, logging.DEBUG]
+
+
+try:
+ opts, args = getopt.getopt(sys.argv[1:],
+ "vqh", ["help"])
+except:
+ opts = [("--help", None)]
+
+log_count = 1
+for o, v in opts:
+ if (o == "-v"):
+ log_count += 1
+ elif (o == "-q"):
+ log_count = 0
+ elif (o == "-h" or o == "--help"):
+ print "Usage: %s [-v|-q] [-h|--help]" \
+ % os.path.basename(sys.argv[0])
+ print ""
+ print " -h, --help Show this help."
+ print " -q Turn off logging."
+ print " -v Increase logging verbosity. Use up to three -v."
+ sys.exit(0)
+#end for
+
+logging.set_level(_LOG_LEVELS[min(4, log_count)])
+
+
+logging.debug("initializing application")
+compdir = os.path.join(values.APP_DIR, "components")
+print compdir
+container = Container([compdir])
+
+logging.debug("running application")
+import gtk
+gtk.main()
+
--- /dev/null
+"""
+Base class for all components.
+"""
+
+from Mediator import Mediator
+
+
+class Component(Mediator):
+ """
+ Base class for all components. Any object derived from this
+ class automatically connects to the message bus upon instantiation.
+
+ More specialized component classes inherit from C{Component}, e.g.
+ - L{Viewer}
+ - L{Configurator}
+
+ Methods with prefix C{handle_} (since 0.96.5) get invoked when an
+ appropriate message is on the message bus. For example, if you wanted to
+ react on the CORE_EV_APP_STARTED message (application startup complete)
+ you'd implement the message C{handle_CORE_EV_APP_STARTED}.
+
+ Example::
+
+ from com import Component, msgs
+
+
+ class MyComponent(Component):
+
+ def __init__(self):
+
+ # do not forget to invoke the constructor of the super class, as
+ # this is what connects your component to the message bus
+ Component.__init__(self)
+
+
+ def handle_CORE_EV_APP_STARTED(self):
+
+ print "Application startup complete!"
+
+
+ The old way of message handling by implementing the dispatcher method
+ C{handle_message} is deprecated and should not be used in newly written
+ code.
+
+ Messages are emitted by calling the L{emit_message} method along with
+ the message ID (from module L{com.msgs}) and the appropriate number of
+ parameters.
+
+ Example::
+
+ # stop playing whatever is currently playing
+ self.emit_message(msgs.MEDIA_ACT_STOP)
+
+ As the sender, your component will not receive the message it emitted. This
+ is intentional.
+
+ There is a special message type service (C{SVC}) which only reaches one
+ component and may yield a return value. Do not use L{emit_message} for
+ calling services. Use L{call_service} instead.
+
+ Example::
+
+ # show a question dialog
+ response = self.call_service(msgs.DIALOG_SVC_QUESTION,
+ "Question",
+ "Do you feel good?")
+ if (response == 0):
+ # yes
+ print "That's fine!"
+ elif (response == 1):
+ # no
+ print "Too bad..."
+
+
+ @since: 0.96
+ """
+
+
+ def __init__(self):
+
+ Mediator.__init__(self)
+
--- /dev/null
+"""
+Runtime container for running components.
+"""
+
+from Component import Component
+import msgs
+from utils import logging
+
+import os
+import sys
+
+
+class Container(Component):
+ """
+ Runtime container for running components.
+ The Container loads and instantiates components.
+ """
+
+ def __init__(self, paths, whitelist = []):
+ """
+ @param paths: paths where to look for components
+ """
+
+ self.__components = []
+ self.__devices = []
+ self.__whitelist = whitelist
+
+ Component.__init__(self)
+
+ for p in paths:
+ if (os.path.exists(p)):
+ self.load_path(p)
+
+ for c in self.__components:
+ self.emit_message(msgs.COM_EV_COMPONENT_LOADED, c)
+
+ for dev in self.__devices:
+ self.emit_message(msgs.CORE_EV_DEVICE_ADDED, dev.get_device_id(), dev)
+
+
+
+ def __find_modules(self, path):
+ """
+ Returns a list of the modules of all components under the given path.
+ """
+
+ modules = []
+
+ dirs = os.listdir(path)
+ dirs.sort()
+ # a module called "core" gets loaded first
+ if ("core" in dirs):
+ dirs.remove("core")
+ dirs = ["core"] + dirs
+
+ for f in dirs:
+ comppath = os.path.join(path, f)
+ if (not os.path.isdir(comppath) or f.startswith(".")):
+ continue
+
+ elif (self.__whitelist and not f in self.__whitelist and f != "core"):
+ logging.info("not loading component: %s", f)
+ continue
+
+ mod = self.__load_module(comppath)
+ if (mod):
+ modules.append(mod)
+ #end for
+
+ return modules
+
+
+ def __load_module(self, path):
+ """
+ Loads and returns the module from the given path. Returns None if
+ the module could not be loaded.
+ """
+
+ syspath = sys.path[:]
+ sys.path = [os.path.dirname(path)] + syspath
+
+ try:
+ mod = __import__(os.path.basename(path))
+ mod._syspath = os.path.dirname(path)
+ sys.path = syspath
+ return mod
+ except:
+ logging.error("could not load component [%s]:\n%s" \
+ % (path, logging.stacktrace()))
+ sys.path = syspath
+ return None
+
+
+ def __register_messages(self, mod):
+ """
+ Registers the messages of the given module.
+ """
+
+ if (hasattr(mod, "messages")):
+ for msg in mod.messages:
+ logging.debug("registering message: %s", msg)
+ msgs._register(msg)
+ #end if
+
+
+ def __load_components(self, mod):
+ """
+ Loads the components of the given module.
+ """
+
+ syspath = sys.path[:]
+ sys.path = [mod._syspath] + syspath
+
+ logging.debug("loading module [%s]", mod.__file__)
+
+ try:
+ classes = mod.get_classes()
+ except AttributeError:
+ classes = []
+ except:
+ logging.error(logging.stacktrace())
+ classes = []
+
+ for c in classes:
+ try:
+ logging.debug("creating [%s]" % c.__name__)
+ comp = c()
+ #comp._attach_to_message_bus()
+ self.__components.append(comp)
+
+ except:
+ logging.error("could not instantiate class [%s]:\n%s" %
+ (`c`, logging.stacktrace()))
+ #end for
+
+ try:
+ device_classes = mod.get_devices()
+ except AttributeError:
+ device_classes = []
+ except:
+ logging.error(logging.stacktrace())
+ device_classes = []
+
+ for c in device_classes:
+ try:
+ logging.debug("adding device [%s]" % c.__name__)
+ comp = c()
+ #comp._attach_to_message_bus()
+ self.__devices.append(comp)
+
+ except:
+ logging.error("could not instantiate class [%s]:\n%s" %
+ (`c`, logging.stacktrace()))
+ #end for
+
+ sys.path = syspath
+
+
+
+
+
+ def load_path(self, path):
+ """
+ Loads all components from the given path.
+
+ @param path: path of components directory
+ """
+
+ #self.__components = []
+ #self.__devices = []
+
+ mods = self.__find_modules(path)
+ for mod in mods:
+ self.__register_messages(mod)
+ for mod in mods:
+ self.emit_message(msgs.COM_EV_LOADING_MODULE, mod.__name__)
+ self.__load_components(mod)
+
--- /dev/null
+"""
+B{Used internally.}
+"""
+
+from MessageBus import MessageBus
+from utils import logging
+
+
+class Mediator(object):
+ """
+ Base class for mediator objects. These are objects receiving and emitting
+ messages.
+
+ Do not derive from this class directly. Derive from L{Component} or one of
+ its subclasses instead.
+
+ @since: 0.96
+ """
+
+ PASS_TYPE_INVALID = 0
+ PASS_TYPE_DROP = 1
+ PASS_TYPE_PASS_ON = 2
+
+
+ def __init__(self):
+
+ self.__pass_type = self.PASS_TYPE_PASS_ON
+ self.__event_bus = MessageBus()
+ self._attach_to_message_bus()
+
+
+ def _attach_to_message_bus(self):
+
+ try:
+ self.__event_bus.add_mediator(self)
+ except AttributeError:
+ raise AttributeError("event bus not present. most likely the "
+ "component was not initialized properly")
+
+
+ def __repr__(self):
+ """
+ Returns a string representation of this component. This is the class
+ name.
+
+ @return: string representation
+ """
+
+ return self.__class__.__name__
+
+
+ def set_pass_type(self, ptype):
+
+ self.__pass_type = ptype
+
+
+ def get_pass_type(self):
+
+ return self.__pass_type
+
+
+ def handle_message(self, msg, *args):
+ """
+ Gets invoked when a message arrives on the message bus.
+ Override this method in subclasses to listen for messages.
+ @since: 0.96
+ @deprecated: implement C{handle_<MESSAGE>} instead for the messages
+ you're interested in, e.g. C{handle_CORE_EV_APP_STARTED}
+
+ @param msg: message
+ @param args: variable list of arguments
+ """
+
+ self.pass_on_event()
+
+
+ def drop_event(self):
+
+ self.__pass_type = self.PASS_TYPE_DROP
+
+
+ def pass_on_event(self):
+
+ self.__pass_type = self.PASS_TYPE_PASS_ON
+
+
+ def emit_message(self, msg, *args):
+ """
+ Emits the given message.
+ @since: 0.96.1
+
+ @param msg: message
+ @param args: variable list of arguments
+ """
+
+ self.__event_bus.send_event(self, msg, *args)
+
+
+
+
+ def call_service(self, svc, *args):
+ """
+ Calls the given service and returns the return value of the service.
+ Returns C{None} if the service was not found.
+ @since: 0.96
+
+ @param svc: service message
+ @param args: variable list of arguments
+ @return: return value of service
+ """
+
+ return self.__event_bus.call_service(svc, *args)
+
--- /dev/null
+"""
+B{Used internally.}
+"""
+
+from exc import *
+import msgs
+from utils import logging
+
+
+class _MessageBus(object):
+ """
+ Application message bus singleton. This class is intented to be used by
+ the Mediator class only.
+ """
+
+ def __init__(self):
+
+ #self.__mediators = []
+ self.__handlers = []
+
+
+ def add_mediator(self, mediator):
+
+ #self.__mediators.append(mediator)
+ self.__inspect_mediator(mediator)
+
+
+ def __inspect_mediator(self, mediator):
+
+ supported_events = [ ev[7:] for ev in dir(mediator)
+ if ev.startswith("handle_") ]
+ for ev in supported_events:
+ ev_id = msgs._name_to_id(ev)
+
+ if (ev_id != -1):
+ while (len(self.__handlers) <= ev_id):
+ self.__handlers.append([])
+
+ handler = getattr(mediator, "handle_" + ev)
+ self.__handlers[ev_id].append(handler)
+ #end if
+ #end for
+
+
+ def send_event(self, src, event, *args):
+
+ if (logging.is_level(logging.DEBUG)):
+ logging.debug("*** %s%s ***", msgs._id_to_name(event), `args`[:30])
+
+ try:
+ handlers = self.__handlers[event]
+ except:
+ handlers = []
+
+ for handler in handlers:
+ try:
+ handler(*args)
+ except:
+ logging.error("error during event call: %s\n%s",
+ msgs._id_to_name(event), logging.stacktrace())
+ #end for
+
+ """
+ handler_name = "handle_" + msgs._id_to_name(event)
+ for mediator in self.__mediators:
+ if (mediator == src): continue
+
+ # TODO: the pass type was never really used and should go away
+ mediator.set_pass_type(mediator.PASS_TYPE_PASS_ON)
+ try:
+ if (hasattr(mediator, handler_name)):
+ getattr(mediator, handler_name)(*args)
+ else:
+ mediator.handle_message(event, *args)
+ except:
+ import traceback; traceback.print_exc()
+ continue
+
+
+
+ ptype = mediator.get_pass_type()
+ if (ptype == mediator.PASS_TYPE_DROP):
+ break
+ elif (ptype == mediator.PASS_TYPE_PASS_ON):
+ continue
+ else:
+ raise SyntaxError("mediator '%s' must specify pass type" \
+ % mediator)
+ #end for
+ """
+
+
+ def call_service(self, svc, *args):
+
+ if (logging.is_level(logging.DEBUG)):
+ logging.debug("*** %s%s ***", msgs._id_to_name(svc), `args`[:30])
+
+ try:
+ handlers = self.__handlers[svc]
+ except:
+ handlers = []
+
+ for handler in handlers:
+ try:
+ return handler(*args)
+ except:
+ logging.error("error during service call: %s\n%s",
+ msgs._id_to_name(svc), logging.stacktrace())
+ #end for
+
+
+ """
+ handler_name = "handle_" + msgs._id_to_name(svc)
+ handler = self.__services.get(svc)
+
+ if (not handler):
+ for mediator in self.__mediators:
+ try:
+ if (hasattr(mediator, handler_name)):
+ ret = getattr(mediator, handler_name)(*args)
+ else:
+ ret = mediator.handle_message(svc, *args)
+ except:
+ import traceback; traceback.print_exc()
+ pass
+
+ if (ret != None):
+ self.__services[svc] = mediator
+ return ret
+ #end for
+
+ raise ServiceNotAvailableError(msgs._id_to_name(svc))
+
+ else:
+ if (hasattr(handler, handler_name)):
+ ret = getattr(handler, handler_name)(*args)
+ else:
+ ret = handler.handle_message(svc, *args)
+
+ return ret
+ """
+
+_singleton = _MessageBus()
+def MessageBus(): return _singleton
--- /dev/null
+"""
+Component Subsystem
+===================
+
+Component subsystem for extending the application.
+@since: 0.96
+"""
+
+import msgs
+from exc import *
+from Component import Component
+from Container import Container
--- /dev/null
+"""
+Exceptions used by the component subsystem.
+"""
+
+class ServiceNotAvailableError(StandardError): pass
+"""The requested service is not available."""
--- /dev/null
+"""
+Application message types. These are dynamically populated by plugins.
+
+Import this module to get access to all known message types::
+
+ from com import Component, msgs
+
+ class MyComponent(Component):
+
+ def __init__(self):
+
+ Component.__init__(self)
+
+
+ def handle_CORE_EV_APP_STARTED(self):
+
+ self.call_service(msgs.NOTIFY_SVC_SHOW_MESSAGE,
+ "Application started")
+
+
+"""
+
+_cnt = 0
+_names = []
+
+
+def _id_to_name(ident):
+ """
+ Returns the name of the message given by the ID.
+ This is an expensive operation and should only be used when logging
+ error messages to the console. Even then, this function should only be
+ used by the com subsystem internally.
+
+ @param ident: ID of the message, e.g. C{msgs.CORE_APPLICATION_SHUTDOWN}
+ @return: printable name of the message, e.g. "C{CORE_APPLICATION_SHUTDOWN}"
+ """
+
+ try:
+ return _names[ident]
+ except:
+ return "<undefined>"
+ """
+ for k, v in globals().items():
+ if (v == ident):
+ return k
+ #end for
+
+ return "<undefined>"
+ """
+
+
+def _name_to_id(name):
+ """
+ Returns the ID of a registered message given by name, or C{-1} if the
+ message name is unknown.
+
+ @param ident: name of the message, e.g. "C{CORE_APPLICATION_SHUTDOWN}"
+ @return: ID of the message, e.g. L{msgs.CORE_APPLICATION_SHUTDOWN}
+ """
+ try:
+ return _names.index(name)
+ except:
+ return -1
+
+
+
+def _register(name):
+ """
+ Registers a new message. This method is only used internally by
+ L{com.Container}.
+
+ @param name: name of message
+ """
+ global _cnt
+
+ globals()[name] = _cnt
+ _names.append(name)
+ _cnt += 1
+
--- /dev/null
+from com import Component, msgs
+import platforms
+import values
+from theme import theme
+
+
+class Initialiser(Component):
+ """
+ Performs initialisation tasks.
+ """
+
+ def __init__(self):
+
+ if (platforms.PLATFORM == platforms.MAEMO5):
+ platforms.create_osso_context(values.OSSO_NAME, "1.0", False)
+
+ Component.__init__(self)
+
--- /dev/null
+def get_classes():
+
+ from Initialiser import Initialiser
+
+ return [Initialiser]
+
+
+
+import __messages__
+messages = [ m for m in dir(__messages__) if not m.startswith("__") ]
+
--- /dev/null
+def COM_EV_COMPONENT_LOADED(component): pass
+"""
+Gets emitted when a component is loaded.
+
+@param component: component object
+"""
+
+def COM_EV_LOADING_MODULE(name): pass
+"""
+Gets emitted when a module gets loaded. A module is a collection of components.
+
+@param name: name of the module
+"""
+
+def CORE_EV_APP_STARTED(): pass
+"""
+Gets emitted when the core application has finished initialising.
+"""
+
+def CORE_EV_APP_SHUTDOWN(): pass
+"""
+Gets emitted when the application is shutting down. Plugins should listen for
+this message if they need to clean up at exit.
+"""
+
+def CORE_ACT_APP_CLOSE(): pass
+"""
+Closes the application.
+"""
+
+def HWKEY_EV_DECREMENT(): pass
+"""
+DECREMENT hardware key.
+"""
+
+def HWKEY_EV_INCREMENT(): pass
+"""
+INCREMENT hardware key.
+"""
+
+def HWKEY_EV_ENTER(): pass
+"""
+ENTER hardware key.
+"""
+
+def HWKEY_EV_FULLSCREEN(): pass
+"""
+FULLSCREEN hardware key.
+"""
+
+def HWKEY_EV_MENU(): pass
+"""
+MENU hardware key.
+"""
+
+def HWKEY_EV_ESCAPE(): pass
+"""
+ESCAPE hardware key.
+"""
+
+def HWKEY_EV_EJECT(): pass
+"""
+EJECT hardware key.
+"""
+
+def HWKEY_EV_BACKSPACE(): pass
+"""
+BACKSPACE hardware key.
+"""
+
+def HWKEY_EV_HEADSET(): pass
+"""
+HEADSET hardware key.
+"""
+
+def HWKEY_EV_HEADSET_DOUBLE(): pass
+"""
+HEADSET hardware key double click.
+"""
+
+def HWKEY_EV_HEADSET_TRIPLE(): pass
+"""
+HEADSET hardware key triple click.
+"""
+
+def HWKEY_EV_UP(): pass
+"""
+UP hardware key.
+"""
+
+def HWKEY_EV_DOWN(): pass
+"""
+DOWN hardware key.
+"""
+
+def HWKEY_EV_LEFT(): pass
+"""
+LEFT hardware key.
+"""
+
+def HWKEY_EV_RIGHT(): pass
+"""
+RIGHT hardware key.
+"""
+
+def HWKEY_EV_F1(): pass
+"""
+F1 hardware key.
+"""
+
+def HWKEY_EV_F2(): pass
+"""
+F2 hardware key.
+"""
+
+def HWKEY_EV_F3(): pass
+"""
+F3 hardware key.
+"""
+
+def HWKEY_EV_F4(): pass
+"""
+F4 hardware key.
+"""
+
+def HWKEY_EV_F5(): pass
+"""
+F5 hardware key.
+"""
+
+def HWKEY_EV_F6(): pass
+"""
+F6 hardware key.
+"""
+
+def HWKEY_EV_F7(): pass
+"""
+F7 hardware key.
+"""
+
+def HWKEY_EV_F8(): pass
+"""
+F8 hardware key.
+"""
+
+def HWKEY_EV_F9(): pass
+"""
+F9 hardware key.
+"""
+
+def HWKEY_EV_F10(): pass
+"""
+F10 hardware key.
+"""
+
+def HWKEY_EV_F11(): pass
+"""
+F11 hardware key.
+"""
+
+def HWKEY_EV_F12(): pass
+"""
+F12 hardware key.
+"""
+
+def HWKEY_EV_KEY(key): pass
+"""
+Any letter hardware key.
+
+@param key: key code
+"""
+
--- /dev/null
+from com import Component, msgs
+from ui.Button import Button
+from ui.Label import Label
+from ui.layout import Arrangement
+from ui.Window import Window
+from theme import theme
+
+import gtk
+
+
+_PORTRAIT_ARRANGEMENT = """
+ <arrangement>
+ <widget name="lbl_code" x1="1%" y1="0%" x2="99%" y2="15%"/>
+
+ <widget name="btn_1" x1="1%" y1="15%" x2="33%" y2="31%"/>
+ <widget name="btn_2" x1="34%" y1="15%" x2="66%" y2="31%"/>
+ <widget name="btn_3" x1="67%" y1="15%" x2="99%" y2="31%"/>
+
+ <widget name="btn_4" x1="1%" y1="32%" x2="33%" y2="48%"/>
+ <widget name="btn_5" x1="34%" y1="32%" x2="66%" y2="48%"/>
+ <widget name="btn_6" x1="67%" y1="32%" x2="99%" y2="48%"/>
+
+ <widget name="btn_7" x1="1%" y1="49%" x2="33%" y2="65%"/>
+ <widget name="btn_8" x1="34%" y1="49%" x2="66%" y2="65%"/>
+ <widget name="btn_9" x1="67%" y1="49%" x2="99%" y2="65%"/>
+
+ <widget name="btn_star" x1="1%" y1="66%" x2="33%" y2="82%"/>
+ <widget name="btn_0" x1="34%" y1="66%" x2="66%" y2="82%"/>
+ <widget name="btn_hash" x1="67%" y1="66%" x2="99%" y2="82%"/>
+
+ <widget name="btn_send" x1="1%" y1="83%" x2="66%" y2="99%"/>
+ <widget name="btn_back" x1="67%" y1="83%" x2="99%" y2="99%"/>
+ </arrangement>
+"""
+
+
+class MainWindow(Component, Window):
+
+ def __init__(self):
+
+ self.__current_code = ""
+
+
+ Component.__init__(self)
+ Window.__init__(self, Window.TYPE_TOPLEVEL)
+ self.set_title("USSD Pad")
+ self.set_portrait_mode(True)
+ self.connect_closed(self.__on_close_window)
+
+ self.__arr = Arrangement()
+ self.add(self.__arr)
+
+ self.__lbl_code = Label("", theme.font_ui_plain, theme.color_ui_text)
+ self.__arr.add(self.__lbl_code, "lbl_code")
+
+ for lbl, name in [("1", "btn_1"),
+ ("2", "btn_2"),
+ ("3", "btn_3"),
+ ("4", "btn_4"),
+ ("5", "btn_5"),
+ ("6", "btn_6"),
+ ("7", "btn_7"),
+ ("8", "btn_8"),
+ ("9", "btn_9"),
+ ("0", "btn_0"),
+ ("*", "btn_star"),
+ ("#", "btn_hash"),
+ ("SEND", "btn_send"),
+ ("BACK", "btn_back")]:
+ btn = Button(lbl)
+ btn.connect_clicked(self.__on_button, lbl)
+ self.__arr.add(btn, name)
+ #end for
+
+ self.__arr.set_xml(_PORTRAIT_ARRANGEMENT)
+
+ self.set_visible(True)
+
+
+ def set_size(self, w, h):
+
+ old_w, old_h = self.get_size()
+ if ((w, h) != (old_w, old_h)):
+ Window.set_size(self, w, h)
+ self.__arr.set_geometry(0, 0, w, h)
+
+
+ def render_this(self):
+
+ w, h = self.get_size()
+ screen = self.get_screen()
+
+ screen.fill_area(0, 0, w, h, theme.color_ui_background)
+
+ #self.__arr.set_geometry(0, 0, w, h)
+
+
+ def __on_close_window(self):
+
+ gtk.main_quit()
+
+
+ def __on_button(self, lbl):
+
+ if (lbl == "SEND"):
+ msg = self.call_service(msgs.USSD_SVC_SEND, self.__current_code)
+ self.__set_code("")
+ print "MESSAGE", msg
+ self.__lbl_code.set_text(msg or " ")
+
+ elif (lbl == "BACK"):
+ self.__set_code(self.__current_code[:-1])
+
+ else:
+ self.__set_code(self.__current_code + lbl)
+
+
+ def __set_code(self, new_code):
+
+ self.__current_code = new_code
+ self.__lbl_code.set_text(new_code or " ")
+
--- /dev/null
+def get_classes():
+
+ from MainWindow import MainWindow
+ return [MainWindow]
+
--- /dev/null
+from com import Component, msgs
+
+import pexpect
+import time
+
+
+class USSDService(Component):
+
+ def __init__(self):
+
+ Component.__init__(self)
+
+
+ def __send_ussd(self, ussd_code):
+
+ # thanks to KiberGus from talk.maemo.org for these lines
+ child = pexpect.spawn("pnatd")
+
+ child.send("AT\r")
+ time.sleep(0.25)
+ child.send('AT+CUSD=1,"%s",15\r' % ussd_code);
+ time.sleep(0.25)
+
+ child.readline()
+ child.readline()
+ child.readline()
+
+ response = child.readline()
+ child.sendeof()
+
+ msg = self.__parse_response(response)
+ return msg
+
+
+ def __parse_response(self, s):
+
+ idx1 = s.find("\"")
+ idx2 = s.rfind("\"")
+ text = s[idx1 + 1:idx2]
+
+ return text
+
+
+ def handle_USSD_SVC_SEND(self, ussd_code):
+
+ try:
+ return self.__send_ussd(ussd_code)
+ except:
+ import traceback; traceback.print_exc()
+ return "Error: cannot send USSD code"
+
--- /dev/null
+def get_classes():
+
+ from USSDService import USSDService
+ return [USSDService]
+
+
+import __messages__
+messages = [ m for m in dir(__messages__) if not m.startswith("__") ]
+
--- /dev/null
+def USSD_SVC_SEND(ussd_code): pass
+
--- /dev/null
+from utils import logging
+import os
+
+
+# available platforms
+MAEMO4 = "maemo4"
+MAEMO5 = "maemo5"
+MER = "mer"
+COMPUTER = "computer"
+HTPC = "htpc"
+
+
+def _check_maemo4():
+
+ v = os.system("cat /etc/apt/sources.list.d/hildon-application-manager.list " \
+ "| egrep 'gregale|bora|chinook|diablo' >/dev/null")
+ return (v == 0)
+
+
+def _check_maemo5():
+
+ v = os.system("cat /etc/apt/sources.list.d/hildon-application-manager.list " \
+ "| egrep fremantle >/dev/null")
+ return (v == 0)
+
+
+def _check_mer():
+
+ v = os.system("dpkg -l | grep maemo-launcher | grep mer >/dev/null")
+ return (v == 0)
+
+
+def _check_htpc():
+
+ v = os.system("lsmod | grep appleir >/dev/null")
+ return (v == 0)
+
+
+def _check_computer():
+
+ return True
+
+
+if _check_maemo5():
+ from maemo5 import *
+elif _check_maemo4():
+ from maemo4 import *
+elif _check_mer():
+ from mer import *
+elif _check_htpc():
+ from htpc import *
+elif _check_computer():
+ from computer import *
+
+
+logging.info("running on '%s' platform" % PLATFORM)
+
--- /dev/null
+PLATFORM = "computer"
+
+
+def get_product_code():
+
+ return "?"
--- /dev/null
+PLATFORM = "htpc"
--- /dev/null
+PLATFORM = "maemo4"
+
+_osso_ctx = None
+
+
+def create_osso_context(name, version, v):
+ global _osso_ctx
+
+ import osso
+ _osso_ctx = osso.Context(name, version, v)
+
+
+def get_system_bus():
+ """
+ Returns the DBus system bus.
+ @since: 0.96
+
+ @return: dbus system bus
+ """
+
+ return _system_bus
+
+
+def get_session_bus():
+ """
+ Returns the DBus session bus.
+ @since: 0.96
+
+ @return: dbus session bus
+ """
+
+ return _session_bus
+
+
+def get_device_state():
+ """
+ Returns the OSSO device state object.
+ @since: 0.96.3
+
+ @return: OSSO device state
+ """
+
+ import osso
+ return osso.DeviceState(_osso_ctx)
+
+
+def is_offline_mode():
+ """
+ Returns whether the device is in offline (flight) mode.
+
+ @return: whether the device is in offline mode.
+ """
+
+ import dbus
+ bus = get_system_bus()
+ obj = bus.get_object("com.nokia.mce", "/com/nokia/mce/request")
+ req = dbus.Interface(obj, "com.nokia.mce.request")
+ mode = req.get_device_mode()
+
+ return (mode in ["offline", "flight"])
+
+
+def inhibit_screen_blanking():
+ """
+ Inhibits screen blanking. This function must be called repeatedly as long
+ as blanking must not take place.
+ """
+
+ devstate = get_device_state()
+ devstate.display_blanking_pause()
+
+
+def get_product_code():
+ """
+ Returns the product code of the device.
+
+ - Nokia 770: SU-18
+ - Nokia N800: RX-34
+ - Nokia N810: RX-44
+ - Nokia N810WE: RX-48
+ - Nokia N900: RX-51
+ - Unknown: ?
+
+ @since: 0.96
+
+ @return: product code
+ """
+
+ # you can override the product code by setting the environment variable
+ # MEDIABOX_MAEMO_DEVICE
+ import os
+ product = os.environ.get("MEDIABOX_MAEMO_DEVICE")
+
+ if (not product):
+ try:
+ lines = open("/proc/component_version", "r").readlines()
+ except:
+ lines = []
+
+ product = "?"
+ for line in lines:
+ line = line.strip()
+ if (line.startswith("product")):
+ parts = line.split()
+ product = parts[1].strip()
+ break
+ #end for
+ #end if
+
+ return product
+
+
+def request_connection():
+ """
+ If the device is not connected, tries to establish the default connection
+ or pop up the connection dialog.
+ Does nothing if the device does already have a network connection.
+ @since: 0.96.3
+ """
+
+ # dbus-send --type=method_call --system --dest=com.nokia.icd/com/nokia/icd com.nokia.icd.connect
+ # dbus-send --system --dest=com.nokia.icd /com/nokia/icd_ui com.nokia.icd_ui.disconnect boolean:true
+
+ try:
+ import conic
+ conn = conic.Connection()
+ conn.request_connection(conic.CONNECT_FLAG_NONE)
+ except:
+ pass
+
+
+def plugin_execute(so_file):
+
+ import osso
+ plugin = osso.Plugin(_osso_ctx)
+ plugin.plugin_execute(so_file, True)
+
+
+if (get_product_code() == "SU-18"):
+ # bad hack!
+ # work around broken D-Bus bindings on OS 2006; this breaks urllib2 for us,
+ # but we don't use it anyway
+ def _f(*args): raise RuntimeError("Ignore me...")
+ import urllib2
+ urllib2.AbstractHTTPHandler.do_open = _f
+#end if
+
+import dbus, dbus.glib
+_system_bus = dbus.SystemBus()
+_session_bus = dbus.SessionBus()
--- /dev/null
+from maemo4 import *
+
+PLATFORM = "maemo5"
+
+
+def request_fmradio():
+
+ import dbus
+ bus = get_system_bus()
+ obj = bus.get_object("de.pycage.FMRXEnabler", "/de/pycage/FMRXEnabler")
+ enabler = dbus.Interface(obj, "de.pycage.FMRXEnabler")
+ retval, device = enabler.request()
+
+ return (retval, device)
+
--- /dev/null
+PLATFORM = "mer"
--- /dev/null
+"""
+B{Used internally}
+"""
+
+class Color(object):
+ """
+ Wrapper class for color theme elements.
+ @since: 0.96
+ """
+
+ def __init__(self, value):
+
+ self.__value = value
+ self.__needs_reload = False
+
+
+ def set_objdef(self, value):
+
+ self.__value = value
+ self.__needs_reload = True
+
+
+ def reload(self):
+
+ self.__needs_reload = False
+
+
+ def __str__(self):
+
+ return self.__value
+
--- /dev/null
+"""
+B{Used internally}
+"""
+
+import pango
+
+
+class Font(pango.FontDescription):
+ """
+ Wrapper class for font theme elements.
+ @since: 0.96
+ """
+
+ def __init__(self, desc):
+
+ self.__desc = desc
+ self.__needs_reload = False
+
+ pango.FontDescription.__init__(self, desc)
+
+
+ def set_objdef(self, desc):
+
+ self.__desc = desc
+ self.__needs_reload = True
+
+
+ def reload(self):
+
+ if (self.__needs_reload):
+ font = pango.FontDescription(self.__desc)
+ self.merge(font, True)
+ self.__needs_reload = False
--- /dev/null
+"""
+B{Used internally}
+"""
+
+import gtk
+
+
+class Pixbuf(gtk.gdk.Pixbuf):
+ """
+ Wrapper class for pixbuf theme elements.
+ @since: 0.96.3
+ """
+
+ def __init__(self, path):
+
+ self.__path = path
+ self.__needs_reload = False
+
+
+ pbuf = gtk.gdk.pixbuf_new_from_file(path)
+
+ gtk.gdk.Pixbuf.__init__(self, gtk.gdk.COLORSPACE_RGB, True, 8,
+ pbuf.get_width(), pbuf.get_height())
+ self.fill(0x00000000)
+ pbuf.scale(self, 0, 0,
+ pbuf.get_width(), pbuf.get_height(), 0, 0, 1, 1,
+ gtk.gdk.INTERP_NEAREST)
+ del pbuf
+
+
+ def set_objdef(self, path):
+
+ self.__path = path
+ self.__needs_reload = True
+
+
+ def get_path(self):
+
+ return self.__path
+
+
+ def reload(self):
+
+ if (self.__needs_reload):
+ pbuf = gtk.gdk.pixbuf_new_from_file(self.__path)
+
+ self.fill(0x00000000)
+ pbuf.scale(self, 0, 0,
+ pbuf.get_width(), pbuf.get_height(), 0, 0, 1, 1,
+ gtk.gdk.INTERP_NEAREST)
+ del pbuf
+ self.__needs_reload = False
--- /dev/null
+"""
+Theming
+=======
+
+Package for theming.
+Themes are subdirectories in this package and are detected automatically.
+
+Import the C{theme} object from this package for accessing the elements of
+the current theme by name::
+
+ from theme import theme
+
+ ...
+
+ icon = theme.foo_icon
+
+@since: 0.96
+"""
+
+from Color import Color
+from Font import Font
+from Pixbuf import Pixbuf
+import values
+from utils import logging
+
+import gtk
+import pango
+import os
+
+
+_THEMES_DIR = os.path.dirname(__file__)
+_USER_THEMES_DIR = os.path.join(values.USER_DIR, "themes")
+_DEFAULT_THEME_DIR = os.path.join(_THEMES_DIR, "default")
+#os.system("mkdir -p " + _USER_THEMES_DIR)
+
+
+_TYPE_PBUF = 0
+_TYPE_COLOR = 1
+_TYPE_FONT = 2
+
+
+def _get_info(themepath):
+
+ name, description, author = (os.path.basename(themepath), "", "")
+
+ try:
+ lines = open(os.path.join(themepath, "info")).readlines()
+ except:
+ import traceback; traceback.print_exc()
+ return (name, description, author)
+
+ for line in lines:
+ line = line.strip()
+ if (not line or line.startswith("#")):
+ continue
+
+ elif (line.startswith("name:")):
+ idx = line.find(":")
+ name = line[idx + 1:].strip()
+ elif (line.startswith("description:")):
+ idx = line.find(":")
+ description = line[idx + 1:].strip()
+ elif (line.startswith("author:")):
+ idx = line.find(":")
+ author = line[idx + 1:].strip()
+ #end for
+
+ return (name, description, author)
+
+
+
+
+class _Theme(object):
+ """
+ Singleton class for loading themes.
+ @since: 0.96
+ """
+
+ def __init__(self):
+
+ # table: name -> (type, definition, obj)
+ self.__objects = {}
+
+
+ def __getattr__(self, name):
+
+ if (name in self.__objects):
+ objtype, objdef, obj = self.__objects[name]
+ if (not obj):
+ obj = self.__load_object(objtype, objdef)
+ self.__objects[name] = (objtype, objdef, obj)
+ #end if
+ return obj
+
+ else:
+ logging.error("theme item not found: %s", name)
+ raise AttributeError(name)
+
+
+ def list_themes(self):
+ """
+ Lists the available themes.
+ @since: 0.96
+
+ @return: list of (theme_path, preview_icon_path, name, description, author)
+ tuples
+ """
+
+ themes = []
+ for themes_dir in [_THEMES_DIR, _USER_THEMES_DIR]:
+ try:
+ files = os.listdir(themes_dir)
+ except:
+ continue
+ #files.sort()
+
+ for d in files:
+ path = os.path.join(themes_dir, d)
+ if (os.path.isdir(path) and not d.startswith(".")):
+ preview = os.path.join(path, "PREVIEW.png")
+ name, description, author = _get_info(path)
+ themes.append((d, preview, name, description, author))
+ #end for
+ #end for
+
+ themes.sort(lambda a,b:cmp(a[2],b[2]))
+ return themes
+
+
+
+ def set_theme(self, name):
+ """
+ Changes the current theme.
+ @since: 0.96
+
+ @param name: name of new theme
+ """
+
+ self.__set_theme("default")
+ if (name != "default"):
+ self.__set_theme(name)
+
+
+
+ def __set_theme(self, name):
+
+ theme_dir = _DEFAULT_THEME_DIR
+ for themes_dir in [_THEMES_DIR, _USER_THEMES_DIR]:
+ theme_dir = os.path.join(themes_dir, name)
+ if (os.path.exists(theme_dir)):
+ name, description, author = _get_info(theme_dir)
+ break
+ #end for
+
+ self.__load_recursively(theme_dir)
+
+
+ def __load_recursively(self, theme_dir):
+
+ for i in os.listdir(theme_dir):
+ name = os.path.splitext(i)[0]
+ path = os.path.join(theme_dir, i)
+
+ if (os.path.isdir(path)):
+ self.__load_recursively(path)
+
+ elif (i.endswith(".def")):
+ self.__read_def_file(path)
+
+ elif (i.endswith(".png") or i.endswith(".jpg")):
+ self.__read_image(name, path)
+ #end for
+
+
+ def __read_image(self, name, path):
+
+ if (name in self.__objects):
+ nil, nil, obj = self.__objects[name]
+ else:
+ obj = None
+
+ self.__objects[name] = (_TYPE_PBUF, path, obj)
+ if (obj): obj.set_objdef(path)
+
+
+ def __read_def_file(self, f):
+
+ lines = open(f).readlines()
+
+ for line in lines:
+ line = line.strip()
+ if (not line or line.startswith("#")):
+ continue
+
+ idx = line.find(":")
+ name = line[:idx].strip()
+
+ if (name in self.__objects):
+ nil, nil, obj = self.__objects[name]
+ else:
+ obj = None
+
+ if (name.startswith("color_")):
+ colorname = line[idx + 1:].strip()
+ self.__objects[name] = (_TYPE_COLOR, colorname, obj)
+ if (obj): obj.set_objdef(colorname)
+
+ elif (name.startswith("font_")):
+ fontname = line[idx + 1:].strip()
+ self.__objects[name] = (_TYPE_FONT, fontname, obj)
+ if (obj): obj.set_objdef(fontname)
+
+
+ def __load_object(self, objtype, objdef):
+
+ logging.debug("loading theme item: %s", objdef)
+
+ if (objtype == _TYPE_PBUF):
+ obj = Pixbuf(objdef)
+
+ elif (objtype == _TYPE_COLOR):
+ obj = Color(objdef)
+
+ elif (objtype == _TYPE_FONT):
+ obj = Font(objdef)
+
+ else:
+ obj = None
+
+ return obj
+
+
+
+theme = _Theme()
+"""the theme singleton object"""
+theme.set_theme("default")
+
--- /dev/null
+name: Default
+description: Black theme for Maemo5
+author: Martin Grimme
+
--- /dev/null
+color_ui_background: #141414
+color_ui_text: #efeff1
+
+font_ui_plain: Nokia Sans 32
+
--- /dev/null
+from ImageButton import ImageButton
+from Pixmap import text_extents
+from theme import theme
+
+
+class Button(ImageButton):
+
+ def __init__(self, label):
+
+ self.__label = label
+
+ ImageButton.__init__(self, theme.ui_button_1, theme.ui_button_2)
+ w, h = text_extents(label, theme.font_ui_plain)
+ self.set_size(w + 24, h + 24)
+
+
+ def set_text(self, text):
+ """
+ @since: 0.96.5
+ """
+
+ self.__label = text
+ #self.render()
+
+
+ def _render_content(self, cnv):
+
+ w, h = self.get_size()
+
+ cnv.draw_centered_text(self.__label, theme.font_ui_plain,
+ 0, 0, w, h, "#ffffff")
+
--- /dev/null
+from Widget import Widget
+from Pixmap import Pixmap, TEMPORARY_PIXMAP
+from utils import logging
+
+
+class ImageButton(Widget):
+
+ def __init__(self, img1, img2, manual = False):
+
+ self.__bg = None
+ self.__buffer = None
+ self.__state = 0
+
+ self.__img1 = img1
+ self.__img2 = img2
+
+ Widget.__init__(self)
+ #self.set_size(img1.get_width(), img1.get_height())
+ self.set_size(64, 64)
+
+ if (not manual):
+ self.connect_button_pressed(self.__on_click, True)
+ self.connect_button_released(self.__on_click, False)
+
+
+ def _reload(self):
+
+ #self.__bg = None
+ self.set_images(self.__img1, self.__img2)
+
+
+ def set_size(self, w, h):
+
+ Widget.set_size(self, w ,h)
+ self.__bg = Pixmap(None, w, h)
+ self.__buffer = TEMPORARY_PIXMAP #Pixmap(None, w, h)
+
+
+ def render_this(self):
+
+ if (not self.may_render()): return
+
+ x, y = self.get_screen_pos()
+ w, h = self.get_size()
+ screen = self.get_screen()
+
+ # save background
+ #if (not self.__bg):
+ # self.__bg = Pixmap(None, w, h)
+ self.__bg.copy_buffer(screen, x, y, 0, 0, w, h)
+
+ self.__render_button()
+
+
+ def __on_click(self, px, py, clicked):
+
+ if (clicked):
+ self.__state = 1
+ #self.__render_button(1)
+ else:
+ self.__state = 0
+ #self.__render_button(0)
+ self.__render_button()
+
+
+
+ def _render_content(self, cnv):
+
+ pass
+
+
+
+ def __render_button(self):
+
+ #self.__state = state
+ if (not self.may_render()): return
+
+ x, y = self.get_screen_pos()
+ w, h = self.get_size()
+ screen = self.get_screen()
+
+ if (self.__state == 0):
+ img = self.__img1
+ else:
+ img = self.__img2
+
+ self.__buffer.copy_pixmap(self.__bg, 0, 0, 0, 0, w, h)
+ #self.__buffer.draw_pixbuf(img,
+ # (w - img.get_width()) / 2,
+ # (h - img.get_height()) / 2)
+ logging.debug("size of button image: %d x %d",
+ img.get_width(), img.get_height())
+ if ((w, h) != (img.get_width(), img.get_height())):
+ self.__buffer.draw_frame(img, 0, 0, w, h, True)
+ else:
+ self.__buffer.draw_pixbuf(img, 0, 0)
+
+ self._render_content(self.__buffer)
+
+ screen.copy_pixmap(self.__buffer, 0, 0, x, y, w, h)
+
+
+
+ def set_images(self, img1, img2):
+
+ self.__img1 = img1
+ self.__img2 = img2
+
+ #self.set_size(img1.get_width(), img1.get_height())
+ #self.__render_button(self.__state)
+ self.__render_button()
+
+
+ def set_active(self, active):
+
+ if (active):
+ self.__state = 1
+ #self.__render_button(1)
+ else:
+ self.__state = 0
+ #self.__render_button(0)
+ self.__render_button()
+
--- /dev/null
+"""
+A text label with autoscrolling.
+"""
+
+from Widget import Widget
+from Pixmap import Pixmap, pixmap_for_text
+
+import gobject
+import pango
+import time
+
+
+
+class Label(Widget):
+
+ # text alignments
+ LEFT = 0
+ RIGHT = 1
+ CENTERED = 2
+
+ def __init__(self, text, font, color):
+
+ self.__text_pmap = None
+ self.__bg = None
+ self.__is_new_text = True
+ self.__alignment = self.LEFT
+ self.__render_timer = None
+
+ self.__text = text
+ self.__font = font
+ self.__color = color
+
+ Widget.__init__(self)
+ self.__create_text_pmap()
+
+
+ def _reload(self):
+
+ self.__is_new_text = True
+ self.__bg = None
+ #self.__create_text_pmap()
+
+
+ def set_size(self, w, h):
+
+ Widget.set_size(self, w, h)
+ self.__is_new_text = True
+ self.__bg = None
+
+
+ def __create_text_pmap(self):
+
+ if (not self.__is_new_text): return
+
+ self.__text_pmap = pixmap_for_text(self.__text or " ", self.__font)
+
+
+ def __acquire_background(self):
+
+ if (not self.__is_new_text): return
+
+ x, y = self.get_screen_pos()
+ w, h = self.get_size()
+ screen = self.get_screen()
+ text_w, text_h = self.__text_pmap.get_size()
+
+ if (w <= 0): w = text_w
+ if (h <= 0): h = text_h
+
+ self.__bg = Pixmap(None, w, text_h)
+ y += (h - text_h) / 2
+ self.__bg.copy_buffer(screen, x, y, 0, 0, w, text_h)
+
+
+ def __restore_background(self):
+
+ if (not self.__is_new_text or not self.__bg): return
+ if (not self.may_render()): return
+
+ x, y = self.get_screen_pos()
+ w, h = self.get_size()
+ screen = self.get_screen()
+ text_w, text_h = self.__text_pmap.get_size()
+
+ if (not w): w = text_w
+ if (not h): h = text_h
+
+ bg_w, bg_h = self.__bg.get_size()
+ y += (h - text_h) / 2
+ screen.copy_pixmap(self.__bg, 0, 0, x, y, bg_w, bg_h)
+
+
+ def __create_text(self):
+
+ if (not self.__is_new_text): return
+
+ x, y = self.get_screen_pos()
+ w, h = self.get_size()
+ screen = self.get_screen()
+ text_w, text_h = self.__text_pmap.get_size()
+
+ if (not w): w = text_w
+ if (not h): h = text_h
+
+ if (text_w <= w):
+ if (self.__alignment == self.LEFT): text_x = 0
+ elif (self.__alignment == self.CENTERED): text_x = (w - text_w) / 2
+ else: text_x = w - text_w
+ else:
+ text_x = 0
+
+ # tile background
+ for i in range(0, text_w, w):
+ self.__text_pmap.copy_pixmap(self.__bg, 0, 0, i - text_x, 0,
+ w, text_h)
+
+ # draw text
+ self.__text_pmap.draw_text(self.__text, self.__font, 0, 0, self.__color)
+
+
+ def set_alignment(self, alignment):
+
+ self.__alignment = alignment
+
+
+ def get_physical_size(self):
+
+ w, h = self.get_size()
+ if (self.__text_pmap):
+ text_w, text_h = self.__text_pmap.get_size()
+ if (not w): w = text_w
+ if (not h): h = text_h
+
+ return (w, h)
+
+
+ def set_text(self, text):
+
+ if (self.__text == text): return
+
+ self.__is_new_text = True
+ self.__text = text
+
+ if (self.may_render()):
+ if (self.__render_timer):
+ gobject.source_remove(self.__render_timer)
+ self.__render_text(0, 1)
+
+
+ def get_text(self):
+
+ return self.__text
+
+
+ def render_this(self):
+
+ self.__is_new_text = True
+ self.__create_text_pmap()
+ self.__acquire_background()
+ self.__create_text()
+ self.__is_new_text = False
+
+ if (self.may_render()):
+ if (self.__render_timer):
+ gobject.source_remove(self.__render_timer)
+ self.__render_text(0, 1)
+
+
+ def __render_text(self, pos, direction, begin_time = 0):
+
+ if (not self.may_render()): return
+
+ if (not begin_time): begin_time = time.time()
+
+ if (self.__is_new_text):
+ self.__restore_background()
+ self.__create_text_pmap()
+ self.__acquire_background()
+ self.__create_text()
+ self.__is_new_text = False
+ pos = 0
+ direction = 1
+ #end if
+
+ text_w, text_h = self.__text_pmap.get_size()
+ x, y = self.get_screen_pos()
+ w, h = self.get_size()
+ screen = self.get_screen()
+
+ if (not w): w = text_w
+ if (not h): h = text_h
+
+ # render currently visible text portion
+ if (text_w <= w):
+ if (self.__alignment == self.LEFT): text_x = 0
+ elif (self.__alignment == self.CENTERED): text_x = (w - text_w) / 2
+ else: text_x = w - text_w
+ else:
+ text_x = 0
+
+ if (self.may_render()):
+ text_y = (h - text_h) / 2
+ #self.use_clipping(True)
+ screen.copy_pixmap(self.__text_pmap, pos, 0, x + text_x, y + text_y,
+ min(text_w, w), text_h)
+ #self.use_clipping(False)
+
+ # handle scrolling
+ # (only scroll for 10 minutes, then stop to save battery)
+ if (text_w > w and self.may_render() and begin_time + 600 > time.time()):
+ if (pos == 0):
+ direction = 1
+ delay = 1500
+ elif (pos == text_w - w):
+ direction = -1
+ delay = 1500
+ else:
+ delay = 50
+
+ if (direction == 1):
+ pos += 1
+ else:
+ pos -= 1
+
+ self.__render_timer = \
+ gobject.timeout_add(delay, self.__render_text, pos, direction,
+ begin_time)
+
+ else:
+ self.__render_timer = None
+ #end if
+
--- /dev/null
+"""
+Class for on-screen and off-screen drawables.
+"""
+
+import gtk
+import pango
+
+try:
+ import cairo
+except:
+ _HAVE_CAIRO = False
+else:
+ _HAVE_CAIRO = True
+
+
+
+#_PANGO_CTX = gtk.HBox().get_pango_context()
+#_PANGO_LAYOUT = pango.Layout(_PANGO_CTX)
+
+# table: bpp -> layout
+_pango_layouts = {}
+
+
+def _get_colormap_and_depth():
+
+ screen = gtk.gdk.screen_get_default()
+ try:
+ have_rgba = screen.is_composited()
+ except:
+ have_rgba = False
+ if (have_rgba):
+ cmap = screen.get_rgba_colormap()
+ else:
+ cmap = screen.get_rgb_colormap()
+
+ depth = cmap.get_visual().depth
+
+ return (cmap, depth)
+
+
+def _get_pango_layout():
+
+ if (not _DEPTH in _pango_layouts):
+ w = gtk.HBox()
+ w.set_colormap(_CMAP)
+ ctx = gtk.HBox().get_pango_context()
+ layout = pango.Layout(ctx)
+ _pango_layouts[_DEPTH] = layout
+
+ return _pango_layouts[_DEPTH]
+
+
+
+def _reload(*items):
+ """
+ Attempts reloading the given items.
+ """
+
+ for item in items:
+ try:
+ item.reload()
+ except:
+ pass
+
+
+def text_extents(text, font):
+ """
+ Returns the width and height required for the given text with the given
+ font.
+ """
+
+ _reload(font)
+ layout = _get_pango_layout()
+ layout.set_font_description(font)
+ layout.set_text(text)
+
+ rect_a, rect_b = layout.get_extents()
+ nil, nil, w, h = rect_b
+ w /= pango.SCALE
+ h /= pango.SCALE
+
+ return (w, h)
+
+
+def pixmap_for_text(text, font):
+ """
+ Creates and returns a new Pixmap object fitting the given text with the
+ given font. The text is not rendered on the Pixmap.
+ """
+
+ _reload(font)
+ w, h = text_extents(text, font)
+ return Pixmap(None, w, h)
+
+
+_CMAP, _DEPTH = _get_colormap_and_depth()
+
+
+class Pixmap(object):
+ """
+ Class for on-screen and off-screen server-side drawables.
+ All rendering operations should be performed using this class.
+ """
+
+ TOP = 1
+ BOTTOM = 2
+ LEFT = 4
+ RIGHT = 8
+
+
+ def __init__(self, pmap, w = 0, h = 0):
+ """
+ Creates a new Pixmap object. If C{pmap} is C{None}, the pixmap will
+ be off-screen, otherwise it will be on-screen.
+
+ @param pmap: GDK drawable to render on
+ @param w: width
+ @param h: height
+ """
+ self.__layout = _get_pango_layout()
+
+ self.__pixmap = None
+ self.__cmap = None
+ self.__gc = None
+ self.__cairo_ctx = None
+
+ self.__buffer = None
+ self.__buffer_cmap = None
+ self.__buffer_gc = None
+
+
+ if (pmap):
+ self.__pixmap = pmap
+ self.__buffered = True
+ self.__create_buffer()
+ else:
+ self.__pixmap = gtk.gdk.Pixmap(None, w, h, _DEPTH)
+ self.__buffered = False
+
+ self.__width = w
+ self.__height = h
+
+ self.__cmap = self.__pixmap.get_colormap() or _CMAP
+ self.__pixmap.set_colormap(self.__cmap)
+ self.__gc = self.__pixmap.new_gc()
+
+ if (_HAVE_CAIRO):
+ self.__cairo_ctx = self.__pixmap.cairo_create()
+
+
+ def _get_pixmap(self):
+
+ return self.__pixmap
+
+
+ def _get_cairo_context(self):
+
+ return self.__cairo_ctx
+
+
+ def _get_buffer(self):
+
+ return self.__buffer
+
+
+ def __create_buffer(self):
+
+ w, h = self.__pixmap.get_size()
+ self.__buffer = gtk.gdk.Pixmap(None, w, h, _DEPTH)
+ self.__buffer_cmap = self.__buffer.get_colormap() or _CMAP
+ self.__buffer.set_colormap(self.__buffer_cmap)
+ self.__buffer_gc = self.__buffer.new_gc()
+
+
+
+ def __to_buffer(self, x, y, w, h):
+ """
+ Copies the given area to the buffer.
+ """
+
+ if (not self.__buffer): return
+
+ self.__buffer.draw_drawable(self.__buffer_gc, self.__pixmap,
+ x, y, x, y, w, h)
+
+
+
+ def clone(self):
+ """
+ Returns an exact clone of this pixmap.
+
+ @return: clone of this pixmap
+ """
+
+ if (self.__buffered):
+ new_pmap = Pixmap(self.__pixmap, self.__width, self.__height)
+ else:
+ new_pmap = Pixmap(None, self.__width, self.__height)
+ new_pmap.draw_pixmap(self, 0, 0)
+
+
+ return new_pmap
+
+
+ def subpixmap(self, x, y, w, h):
+ """
+ Returns a new pixmap containing the given portion.
+
+ @param x y w h: coordinates of subpixmap
+ """
+
+ new_pmap = Pixmap(None, w, h)
+ new_pmap.copy_buffer(self, x, y, 0, 0, w, h)
+
+ return new_pmap
+
+
+ def resize(self, w, h):
+ """
+ Resizes this pixmap and discards its contents.
+
+ @param w: width
+ @param h: height
+ """
+
+ cmap, depth = _get_colormap_and_depth()
+
+ new_pmap = gtk.gdk.Pixmap(None, w, h, depth)
+ self.__gc = new_pmap.new_gc()
+ self.__cmap = new_pmap.get_colormap() or cmap
+ new_pmap.set_colormap(self.__cmap)
+ del self.__pixmap
+ self.__pixmap = new_pmap
+ if (_HAVE_CAIRO):
+ self.__cairo_ctx = self.__pixmap.cairo_create()
+
+ self.__width = w
+ self.__height = h
+
+
+ def rotate(self, angle):
+ """
+ Rotates this pixmap by the given angle. Angle must be one of
+ 0, 90, 180, 270.
+
+ @param angle: angle in degrees
+ """
+ assert angle in (0, 90, 180, 270)
+
+ if (angle == 0):
+ method = gtk.gdk.PIXBUF_ROTATE_NONE
+ elif (angle == 90):
+ method = gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE
+ elif (angle == 180):
+ method = gtk.gdk.PIXBUF_ROTATE_UPSIDEDOWN
+ else:
+ method = gtk.gdk.PIXBUF_ROTATE_CLOCKWISE
+ pbuf = self.render_on_pixbuf()
+
+ rpbuf = pbuf.rotate_simple(method)
+ self.resize(rpbuf.get_width(), rpbuf.get_height())
+ del pbuf
+ self.draw_pixbuf(rpbuf, 0, 0)
+ del rpbuf
+
+
+
+
+
+ def is_buffered(self):
+ """
+ Returns whether this pixmap is buffered. Offscreen pixmaps are never
+ buffered.
+
+ @return: whether this pixmap is buffered
+ """
+
+ return (self.__buffer != None)
+
+
+ def is_offscreen(self):
+ """
+ @return: whether this pixmap is offscreen
+ """
+
+ return (self.__pixmap == None)
+
+
+ def get_color_depth(self):
+ """
+ Returns the color depth in bits per pixel.
+
+ @return: color depth
+ """
+
+ return _DEPTH
+
+
+ def render_on_pixbuf(self, target = None):
+ """
+ Renders this pixmap to a pixbuf and returns the pixbuf. If target is
+ given, it does not create a new pixbuf.
+
+ @param target: target pixbuf to use
+ @return: pixbuf
+ """
+
+ if (not target):
+ target = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8,
+ self.__width, self.__height)
+
+ w = target.get_width()
+ h = target.get_height()
+ pbuf = target.get_from_drawable(self.__pixmap, self.__cmap, 0, 0, 0, 0,
+ min(w, self.__width), min(h, self.__height))
+ #del pbuf
+
+ return target
+
+
+ def set_clip_rect(self, *args):
+ """
+ Sets the clipping rectangle.
+ Pass None to disable clipping.
+ """
+
+ if (len(args) == 4):
+ cx, cy, cw, ch = args
+ rect = gtk.gdk.Rectangle(cx, cy, cw, ch)
+
+ else:
+ rect = gtk.gdk.Rectangle(0, 0, self.__width, self.__height)
+
+ self.__gc.set_clip_rectangle(rect)
+ #if (self.__buffered):
+ # self.__buffer_gc.set_clip_rectangle(rect)
+
+
+ def set_clip_mask(self, mask = None):
+ """
+ @todo: DEPRECATED
+ """
+
+ if (mask):
+ self.__gc.set_clip_mask(mask)
+ if (self.__buffered):
+ self.__buffer_gc.set_clip_mask(mask)
+ else:
+ rect = gtk.gdk.Rectangle(0, 0, self.__width, self.__height)
+ self.__gc.set_clip_rectangle(rect)
+ #if (self.__buffered):
+ # self.__buffer_gc.set_clip_rectangle(rect)
+
+
+
+
+ def get_size(self):
+ """
+ Returns the width and height of this pixmap.
+
+ @return: a tuple (width, height) holding the size
+ """
+
+ return (self.__width, self.__height)
+
+
+ def __parse_color(self, color):
+ """
+ Parses a color string and returns the RGBA values as quadruple.
+ """
+
+ color = str(color)
+ if (len(color) < 9):
+ color += "ff"
+
+ r = int(color[1:3], 16)
+ g = int(color[3:5], 16)
+ b = int(color[5:7], 16)
+ a = int(color[7:9], 16)
+
+ return (r, g, b, a)
+
+
+
+ def clear_translucent(self):
+
+ if (_HAVE_CAIRO):
+ self.__cairo_ctx.save()
+ self.__cairo_ctx.set_source_rgba(1, 1, 1, 0)
+ self.__cairo_ctx.set_operator(cairo.OPERATOR_SOURCE)
+ self.__cairo_ctx.paint()
+ self.__cairo_ctx.restore()
+
+ w, h = self.get_size()
+ self.__to_buffer(0, 0, w, h)
+ #end if
+
+
+ def fill_area(self, x, y, w, h, color):
+ """
+ Fills the given area with the given color (opaquely).
+
+ @param x y w h: area to fill
+ @param color: fill color
+ """
+ assert (w > 0 and h > 0)
+
+ _reload(color)
+
+ if (_HAVE_CAIRO):
+ r, g, b, a = self.__parse_color(color)
+ r /= 255.0
+ g /= 255.0
+ b /= 255.0
+ a /= 255.0
+ self.__cairo_ctx.save()
+ if (a < 0.999):
+ self.__cairo_ctx.set_source_rgba(r, g, b, a)
+ else:
+ self.__cairo_ctx.set_source_rgb(r, g, b)
+ self.__cairo_ctx.rectangle(x, y, w, h)
+ self.__cairo_ctx.fill()
+ self.__cairo_ctx.restore()
+
+ else:
+ if (len(str(color)) == 9):
+ # RGBA
+ pbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, w, h)
+ pbuf.fill(long(str(color)[1:], 16))
+ self.draw_pixbuf(pbuf, x, y)
+ del pbuf
+
+ else:
+ # RGB
+ col = self.__cmap.alloc_color(str(color))
+ self.__gc.set_foreground(col)
+ self.__pixmap.draw_rectangle(self.__gc, True, x, y, w, h)
+
+ self.__to_buffer(x, y, w, h)
+
+
+ def move_area(self, x, y, w, h, dx, dy):
+ """
+ Moves the given area on this pixmap by the given amount.
+
+ @param x y w h: area to move
+ @param dx dy: amount
+ """
+
+ self.copy_buffer(self, x, y, x + dx, y + dy, w, h)
+
+
+
+ def draw_line(self, x1, y1, x2, y2, color):
+ """
+ Draws a line of the given color.
+
+ @param x1 y1: start point
+ @param x2 y2: end point
+ @param color: color
+ """
+
+ _reload(color)
+ col = self.__cmap.alloc_color(str(color))
+ self.__gc.set_foreground(col)
+ self.__pixmap.draw_line(self.__gc, x1, y1, x2, y2)
+
+ self.__to_buffer(min(x1, x2), min(y1, y2),
+ abs(x1 - x2), abs(y1 - y2))
+
+
+ def draw_rect(self, x, y, w, h, color):
+ """
+ Draws a rectangle of the given color.
+
+ @param x y w h: rectangle
+ @param color: border color
+ """
+
+ _reload(color)
+ w -= 1
+ h -= 1
+ col = self.__cmap.alloc_color(str(color))
+ self.__gc.set_foreground(col)
+ self.__pixmap.draw_rectangle(self.__gc, False, x, y, w, h)
+
+ self.__to_buffer(x, y, w, h)
+
+
+ def draw_centered_text(self, text, font, x, y, w, h, color,
+ use_markup = False):
+ """
+ Centers the given text string within the given area.
+
+ @param text: text string
+ @param font: font
+ @param x y w h: area
+ @param color: text color
+ @param use_markup: whether text contains Pango markup
+ """
+
+ _reload(font, color)
+ tw, th = text_extents(text, font)
+ tx = x + (w - tw) / 2
+ ty = y + (h - th) / 2
+ self.draw_text(text, font, tx, ty, color, use_markup)
+
+
+
+ def draw_text(self, text, font, x, y, color, use_markup = False):
+ """
+ Renders the given text string.
+
+ @param text: text string
+ @param font: font
+ @param x y: coordinates of top-left corner
+ @param color: text color
+ @param use_markup: whether text contains Pango markup
+ """
+
+ _reload(font, color)
+ self.__layout.set_font_description(font)
+ self.__layout.set_text("")
+ self.__layout.set_markup("")
+ if (use_markup):
+ self.__layout.set_markup(text)
+ else:
+ self.__layout.set_text(text)
+ self.__gc.set_foreground(self.__cmap.alloc_color(str(color)))
+
+ rect_a, rect_b = self.__layout.get_extents()
+ nil, nil, w, h = rect_b
+ w /= pango.SCALE
+ h /= pango.SCALE
+ w = min(w, self.__width - x)
+ h = min(h, self.__height - y)
+
+ self.__pixmap.draw_layout(self.__gc, x, y, self.__layout)
+
+ self.__to_buffer(x, y, w, h)
+
+
+ def draw_pixbuf(self, pbuf, x, y, w = -1, h = -1, scale = False):
+ """
+ Renders the given pixbuf.
+
+ @param pbuf: pixbuf image
+ @param x y: coordinates
+ @param w h: size
+ @param scale: scale to given size or simply crop to size
+ """
+
+ _reload(pbuf)
+ if (scale):
+ pbuf = pbuf.scale_simple(w, h, gtk.gdk.INTERP_BILINEAR)
+ self.__pixmap.draw_pixbuf(self.__gc, pbuf,
+ 0, 0, x, y, w, h)
+
+ if (w == -1): w = pbuf.get_width()
+ if (h == -1): h = pbuf.get_height()
+ self.__to_buffer(x, y, w, h)
+
+ del pbuf
+
+
+ def draw_subpixbuf(self, pbuf, srcx, srcy, dstx, dsty, w, h):
+ """
+ Renders a part of the pixbuf.
+
+ @param pbuf: pixbuf image
+ @param srcx srcy: offset on pixbuf image
+ @param dstx dsty: position on this pixmap
+ @param w h: size of subpixbuf
+ """
+
+ _reload(pbuf)
+ if (srcx < 0 or srcy < 0 or dstx < 0 or dsty < 0):
+ return
+
+ self.__pixmap.draw_pixbuf(self.__gc, pbuf, srcx, srcy, dstx, dsty, w, h)
+
+ self.__to_buffer(dstx, dsty, w, h)
+
+
+ def fit_pixbuf(self, pbuf, x, y, w, h):
+ """
+ Renders the given pixbuf so that it fits the given area. The pixbuf
+ is scaled while retaining the original aspect ratio.
+
+ @param pbuf: pixbuf image
+ @param x y w h: constraining area to fit pixbuf into
+ """
+
+ _reload(pbuf)
+ pbuf_width = pbuf.get_width()
+ pbuf_height = pbuf.get_height()
+
+ sx = w / float(pbuf_width)
+ sy = h / float(pbuf_height)
+
+ scale = min(sx, sy)
+ offx = (w - pbuf_width * scale) / 2
+ offy = (h - pbuf_height * scale) / 2
+
+ if (_HAVE_CAIRO):
+ fmt = pbuf.get_has_alpha() and cairo.FORMAT_ARGB32 \
+ or cairo.FORMAT_RGB24
+ surface = cairo.ImageSurface(fmt, pbuf_width, pbuf_height)
+ gdkctx = gtk.gdk.CairoContext(self.__cairo_ctx)
+ gdkctx.save()
+ gdkctx.translate(int(x + offx), int(y + offy))
+ gdkctx.scale(scale, scale)
+ gdkctx.set_source_pixbuf(pbuf, 0, 0)
+ gdkctx.paint()
+ gdkctx.restore()
+
+ else:
+ self.draw_pixbuf(pbuf, int(x + offx), int(y + offy),
+ int(pbuf_width * scale), int(pbuf_height * scale),
+ scale = True)
+
+ self.__to_buffer(x, y, w, h)
+
+
+
+ def draw_pixmap(self, pmap, x, y):
+ """
+ Draws the given pixmap.
+
+ @param pmap: pixmap
+ @param x y: coordinates
+ """
+
+ w, h = pmap.get_size()
+ self.copy_pixmap(pmap, 0, 0, x, y, w, h)
+
+
+ def __split_frame(self, img):
+
+ w, h = img.get_width(), img.get_height()
+ w3 = w / 3
+ h3 = h / 3
+
+ tl = img.subpixbuf(0, 0, 10, 10)
+ t = img.subpixbuf(10, 0, w - 20, 10)
+ tr = img.subpixbuf(w - 10, 0, 10, 10)
+ r = img.subpixbuf(w - 10, 10, 10, h - 20)
+ br = img.subpixbuf(w - 10, h - 10, 10, 10)
+ b = img.subpixbuf(10, h - 10, w - 20, 10)
+ bl = img.subpixbuf(0, h - 10, 10, 10)
+ l = img.subpixbuf(0, 10, 10, h - 20)
+ c = img.subpixbuf(10, 10, w - 20, h - 20)
+
+ #tl = img.subpixbuf(0, 0, w3, h3)
+ #t = img.subpixbuf(w3, 0, w3, h3)
+ #tr = img.subpixbuf(w - w3, 0, w3, h3)
+ #r = img.subpixbuf(w - w3, h3, w3, h3)
+ #br = img.subpixbuf(w - w3, h - h3, w3, h3)
+ #b = img.subpixbuf(w3, h - h3, w3, h3)
+ #bl = img.subpixbuf(0, h - h3, w3, h3)
+ #l = img.subpixbuf(0, h3, w3, h3)
+ #c = img.subpixbuf(w3, h3, w3, h3)
+
+ return (tl, t, tr, r, br, b, bl, l, c)
+
+
+ def draw_frame(self, framepbuf, x, y, w, h, filled, parts = 0xf):
+ """
+ Draws a frame by stretching and tiling the given pixbuf.
+
+ @param framepbuf: pixbuf to use for the frame
+ @param x y w h: frame position and size
+ @param filled: whether to fill the frame or only draw the border
+ @param parts: bit composition of parts to draw (C{Pixbuf.TOP | Pixbuf.BOTTOM | Pixbuf.LEFT | Pixbuf.RIGHT})
+ """
+
+ _reload(framepbuf)
+ tl, t, tr, r, br, b, bl, l, c = self.__split_frame(framepbuf)
+ w1, h1 = tl.get_width(), tl.get_height()
+ w1 = min(w1, w / 3)
+ h1 = min(h1, h / 3)
+ w2 = w1
+ h2 = h1
+
+ if (not parts & self.TOP):
+ h1 = 0
+ if (not parts & self.BOTTOM):
+ h2 = 0
+ if (not parts & self.LEFT):
+ w1 = 0
+ if (not parts & self.RIGHT):
+ w2 = 0
+
+ if (parts & self.TOP):
+ if (parts & self.LEFT):
+ self.draw_pixbuf(tl, x, y, w1, h1)
+ if (parts & self.RIGHT):
+ self.draw_pixbuf(tr, x + w - w2, y, w2, h1)
+ if (parts & self.BOTTOM):
+ if (parts & self.LEFT):
+ self.draw_pixbuf(bl, x, y + h - h2, w1, h2)
+ if (parts & self.RIGHT):
+ self.draw_pixbuf(br, x + w - w2, y + h - h2, w2, h2)
+
+ if (parts & self.TOP):
+ self.draw_pixbuf(t, x + w1, y, w - w1 - w2, h1, True)
+ if (parts & self.BOTTOM):
+ self.draw_pixbuf(b, x + w1, y + h - h2, w - w1 - w2, h2, True)
+ if (parts & self.LEFT):
+ self.draw_pixbuf(l, x, y + h1, w1, h - h1 - h2, True)
+ if (parts & self.RIGHT):
+ self.draw_pixbuf(r, x + w - w2, y + h1, w2, h - h1 - h2, True)
+
+ if (filled):
+ self.draw_pixbuf(c, x + w1, y + h1, w - w1 - w2, h - h1 - h2, True)
+
+
+ def copy_pixmap(self, pmap, srcx, srcy, dstx, dsty, w, h):
+ """
+ Copies content from another pixmap onto this.
+
+ @param pmap: source pixmap
+ @param srcx srcy: offset on source pixmap
+ @param dstx dsty: position on this pixmap
+ @param w h: size of area to copy
+ """
+
+ self.__pixmap.draw_drawable(self.__gc, pmap._get_pixmap(),
+ srcx, srcy, dstx, dsty, w, h)
+
+
+ if (self.__buffer):
+ if (pmap == self):
+ self.__buffer.draw_drawable(self.__buffer_gc, self.__buffer,
+ srcx, srcy, dstx, dsty, w, h)
+ else:
+ self.__buffer.draw_drawable(self.__buffer_gc, pmap._get_pixmap(),
+ srcx, srcy, dstx, dsty, w, h)
+
+
+ def copy_buffer(self, pmap, srcx, srcy, dstx, dsty, w, h):
+ """
+ Copies content from another pixmap to this. If the other pixmap is
+ buffered, the contents of the buffer are used. You will normally want
+ to use this method instead of 'copy_pixmap' when copying from a buffered
+ pixmap.
+
+ @param pmap: source pixmap
+ @param srcx srcy: offset on source pixmap
+ @param dstx dsty: position on this pixmap
+ @param w h: size of area to copy
+ """
+
+ if (pmap.is_buffered()):
+ self.__pixmap.draw_drawable(self.__gc, pmap._get_buffer(),
+ srcx, srcy, dstx, dsty, w, h)
+ if (self.__buffer):
+ self.__buffer.draw_drawable(self.__buffer_gc, pmap._get_buffer(),
+ srcx, srcy, dstx, dsty, w, h)
+ else:
+ self.copy_pixmap(pmap, srcx, srcy, dstx, dsty, w, h)
+
+
+
+ def restore(self, x, y, w, h):
+ """
+ Restores the given area from buffer, if this pixmap is buffered.
+ Otherwise, it does nothing.
+
+ @param x y w h: area to restore
+ """
+
+ if (not self.__buffer): return
+
+ self.__pixmap.draw_drawable(self.__gc, self.__buffer,
+ x, y, x, y, w, h)
+
+
+screen_size = max(gtk.gdk.screen_width(), gtk.gdk.screen_height())
+TEMPORARY_PIXMAP = Pixmap(None, screen_size, screen_size)
+"""
+The temporary pixmap can be used for temporary drawing operations without
+having to create a new pixmap.
+When using it, make sure that no other code can interfere.
+"""
+
--- /dev/null
+"""
+Every widget is derived from this base class.
+"""
+
+from utils.EventEmitter import EventEmitter
+from utils import logging
+
+import time
+import threading
+import gtk
+
+
+class Widget(EventEmitter):
+ """
+ Base class for GDK based lightweight widgets.
+ """
+
+ EVENT_BUTTON_PRESS = "button-pressed"
+ EVENT_BUTTON_RELEASE = "button-released"
+ EVENT_MOTION = "motion"
+ EVENT_KEY_PRESSED = "key-pressed"
+ EVENT_KEY_RELEASED = "key-released"
+
+ SLIDE_LEFT = 0
+ SLIDE_RIGHT = 1
+ SLIDE_UP = 2
+ SLIDE_DOWN = 3
+
+ # static lock for animations
+ __animation_lock = threading.Event()
+
+ # all widget instances
+ __instances = []
+
+ # event zones; table: ident -> (window, x, y, w, h, tstamp, cb)
+ #_zones = {}
+
+
+
+ def __init__(self):
+ """
+ Creates and initializes a new Widget object.
+ Be sure to invoke this constructor when deriving from this class.
+ """
+
+ # known actors
+ #self.__actors_stack = []
+
+ # younger widgets have precedence over older ones in event handling
+ #self.__age_tstamp = 0
+
+ self.__children = []
+ self.__parent = None
+
+ self.__locked_zone = None
+ #self.__need_to_check_zones = False
+
+ self.__is_enabled = True
+ self.__is_frozen = False
+ self.__is_visible = True
+ self.__can_be_visible = True
+ self.__skip_next_render = False
+
+ self._input_focus_widget = None
+
+ self.__position = (0, 0)
+ self.__size = (0, 0)
+
+ # render overlay handlers
+ self.__overlayers = []
+
+ self.__clip_rect = None
+
+ self.__screen = None
+ self.__window = None
+ self.__instances.append(self)
+
+ EventEmitter.__init__(self)
+
+
+ """
+ def push_actor(self, w):
+ ""
+ Pushes an actor widget onto the actors stack. All but the topmost actor
+ on the stack are frozen.
+ @since: 0.96.5
+
+ @param w: a widget
+ ""
+
+ if (self.__parent):
+ self.__parent.push_actor(w)
+ else:
+ self.__actors_stack.append(w)
+ self.__update_actors()
+
+
+ def pop_actor(self):
+ ""
+ Pops the topmost actor widget from the actors stack.
+ @since: 0.96.5
+ ""
+
+ if (self.__parent):
+ self.__parent.pop_actor()
+ elif (self.__actors_stack):
+ actor = self.__actors_stack.pop()
+ actor.set_frozen(True)
+ self.__update_actors()
+
+
+ def __update_actors(self):
+
+ if (self.__actors_stack):
+ for actor in self.__actors_stack[:-1]:
+ actor.set_frozen(True)
+
+ self.__actors_stack[-1].set_frozen(False)
+ """
+
+
+ def grab_focus(self):
+ """
+ Grabs the focus for keyboard input.
+ """
+
+ win = self.get_window()
+ win._input_focus_widget = self
+
+
+ def _find_zone_at(self, px, py, ev_name):
+
+ #print "CHECKING", self
+ if (self.is_frozen() or not self.is_visible()):
+ return None
+
+ x, y = self.get_screen_pos()
+ w, h = self.get_size()
+ if (not x <= px <= x + w or not y <= py <= y + h):
+ return None
+
+ #print "CHILDREN", self.__children
+ for i in range(len(self.__children) - 1, -1, -1):
+ c = self.__children[i]
+ zone = c._find_zone_at(px, py, ev_name)
+ #print "CHILD", c, zone
+ if (zone):
+ return zone
+ #end for
+
+ if (self.has_events): #(ev_name)):
+ return self.__on_action
+ else:
+ return None
+
+
+
+ def _handle_event(self, ev, px, py, *args):
+
+ zone = None
+ #zone_tstamp = -1
+
+ if (ev == Widget.EVENT_BUTTON_PRESS):
+ zone = self._find_zone_at(px, py, ev)
+ #print "ZONE", px, py, zone
+
+ """
+ for wdgt in self._zones:
+ if (wdgt.is_frozen()):
+ continue
+
+ window, x, y, w, h, tstamp, cb = self._zones[wdgt]
+
+ if (window != self.get_window()):
+ continue
+
+ if (x <= px <= x + w and y <= py <= y + h and tstamp > zone_tstamp):
+ zone = cb
+ zone_tstamp = tstamp
+ #end for
+ """
+
+ if (zone):
+ self.__locked_zone = zone
+ else:
+ self.__locked_zone = None
+
+ else:
+ zone = self.__locked_zone
+
+
+ if (zone):
+ self.__locked_zone = zone
+ cb = zone
+ cb(ev, px, py, *args)
+
+
+
+ def send_event(self, ev, *args):
+ """
+ Sends the given event to this widget.
+ The widget emits the event to all event handlers that are registered
+ for the event.
+
+ Use this method in your own widgets for emitting events.
+
+ @param ev: Type of event
+ @param *args: variable number of arguments (depending on the
+ event type)
+ """
+
+ self.emit_event(ev, *args)
+
+ """
+ if (ev in (self.EVENT_KEY_PRESS, self.EVENT_KEY_RELEASE) and
+ self._input_focus_widget and
+ self._input_focus_widget.is_visible()):
+ self._input_focus_widget.send_event(ev, *args)
+
+ else:
+ self.emit_event(ev, *args)
+ """
+
+
+ def __on_action(self, etype, px, py):
+
+ x, y = self.get_screen_pos()
+ px2 = px - x
+ py2 = py - y
+
+ self.send_event(etype, px2, py2)
+
+
+ def set_animation_lock(self, value):
+ """
+ Sets the global lock for blocking animations.
+
+ @param value: whether the lock is set.
+ """
+
+ if (value):
+ self.__animation_lock.set()
+ else:
+ self.__animation_lock.clear()
+
+
+ def have_animation_lock(self):
+ """
+ Returns whether the animation lock is set.
+
+ @return: whether the lock is set
+ """
+
+ return self.__animation_lock.isSet()
+
+
+ def add_overlayer(self, overlayer):
+
+ self.__overlayers.append(overlayer)
+
+
+ def get_children(self):
+ """
+ Returns a list of all child widgets of this widget.
+ """
+
+ return self.__children[:]
+
+
+ def add(self, child):
+ """
+ Adds a new child to this widget. Every widget is a container and may
+ thus have child widgets.
+
+ @param child: child widget
+ """
+
+ self.__children.append(child)
+ child.set_parent(self)
+ child.set_screen(self.get_screen())
+ #self.__check_zone()
+
+
+ def remove(self, child):
+ """
+ Removes the given child widget from this widget.
+
+ @param child: child widget
+ """
+
+ self.__children.remove(child)
+ child.set_visible(False)
+ #self.__check_zone()
+
+
+ def set_parent(self, parent):
+ """
+ Sets the parent of this widget. You usually don't need this method in
+ your code.
+
+ @param parent: new parent widget
+ """
+
+ self.__parent = parent
+ self.__age_tstamp = time.time()
+
+
+ def get_parent(self):
+ """
+ Returns the parent of this widget. The parent of the root widget is
+ always C{None}.
+
+ @return: parent widget
+ """
+
+ return self.__parent
+
+
+ def set_screen(self, screen):
+ """
+ Changes the screen pixmap to render on.
+
+ @param screen: screen pixmap for rendering
+ """
+
+ self.__screen = screen
+ for c in self.__children:
+ c.set_screen(screen)
+ #self.__check_zones()
+
+
+ def get_screen(self):
+ """
+ Returns the current screen pixmap for rendering.
+
+ @return: screen pixmap for rendering
+ """
+
+ return self.__screen
+
+
+ def set_clip_rect(self, *args):
+ """
+ Sets the clipping rectangle that can be used for this widget.
+ """
+
+ self.__clip_rect = args
+
+
+ def use_clipping(self, value):
+ """
+ Activates or deactivates clipping.
+
+ @param value: whether to activate (True) or deactivate (False) clipping
+ """
+
+ if (not self.__clip_rect): return
+
+ screen = self.get_screen()
+ if (value):
+ screen.set_clip_rect(*self.__clip_rect)
+ else:
+ screen.set_clip_rect(None)
+
+
+ def __set_zone(self, ident, x, y, w, h):
+ """
+ Registers an event zone. If the zone already exists, only its
+ coordinates are updated.
+ """
+
+ #print "ZONE", x, y, w, h, ident, self.get_window()
+ self._zones[ident] = (self.get_window(), x, y, w, h, self.__age_tstamp, self.__on_action)
+
+ #if (self.__event_sensor):
+ # self.__event_sensor.set_zone(ident, x, y, w, h, time.time(),
+ # self.__on_action)
+
+
+ def __remove_zone(self, ident):
+ """
+ Removes the given event zone.
+ """
+
+ if (ident in self._zones):
+ del self._zones[ident]
+
+
+ def set_enabled(self, value):
+ """
+ Enables or disables this widget. A disabled widget is still visible
+ but does not react to user events.
+
+ @param value: whether this widget is enabled
+ """
+
+ self.__is_enabled = value
+ #print self, value
+ #self.__check_zone()
+
+ for c in self.__children:
+ c.set_enabled(value)
+
+
+ def is_enabled(self):
+ """
+ Returns whether this widget is currently enabled and reacts to user
+ events.
+
+ @return: whether this widget is currently enabled
+ """
+
+ if (not self.is_visible()):
+ return False
+ else:
+ return self.__is_enabled
+
+
+ def set_frozen(self, value):
+ """
+ Freezes or thaws this widget. A frozen widget does not get rendered.
+ This is useful for big update operations where you don't want the
+ widget to render itself after every single step.
+
+ Freezing a widget freezes all child widgets as well.
+
+ @param value: whether to freeze (True) or thaw (False) this widget
+ """
+
+ self.__is_frozen = value
+ self._visibility_changed()
+ for c in self.__children:
+ c.set_frozen(value)
+
+
+ def is_frozen(self):
+ """
+ Returns whether this widget is currently frozen.
+
+ @return: whether this widget is currently frozen.
+ """
+
+ return self.__is_frozen
+
+
+ def set_visible(self, value):
+ """
+ Shows or hides this widget. An invisible widget does not get rendered
+ and does not react to events and all descendants are invisible as well.
+
+ Newly created widgets are initially visible.
+
+ @param value: whether this widget is visible
+ """
+
+ def f(w):
+ w._visibility_changed()
+ for c in w.get_children():
+ f(c)
+
+ self.__is_visible = value
+ #self.__check_zones()
+ f(self)
+
+
+ def is_visible(self):
+ """
+ Returns whether this widget is currently visible.
+
+ @return: whether this widget is currently visible
+ """
+
+ if (not self._can_be_visible()):
+ return False
+ else:
+ return self.__is_visible
+
+
+ def _visibility_changed(self):
+ """
+ Widgets can override this method if they want to get notified when
+ the visibility changes, e.g. if an ancestor widget became invisible.
+ """
+
+ pass
+
+
+ def _can_be_visible(self):
+ """
+ Internal method for determining whether this widget can be visible.
+ If ancestor widget is invisible, then this widget is invisible as well.
+ """
+
+ if (self.__parent):
+ return self.__is_visible and self.__parent._can_be_visible()
+ else:
+ return self.__is_visible
+
+
+ def _set_can_be_visible(self, value):
+
+ self.__can_be_visible = value
+ #self.__check_zone()
+
+ for c in self.__children:
+ c._set_can_be_visible(value)
+
+
+ def may_render(self):
+ """
+ Returns whether this widget may currently render itself.
+
+ If your widget is rendering without being initiated by the
+ C{render_this} method, you have to use this method to check whether
+ the widget may currently render on screen.
+
+ A widget may only render if it has a screen, is visible, and is not
+ frozen.
+
+ @return: whether this widget may currently render
+ """
+
+ return (self.__screen and self.is_visible() and not self.is_frozen())
+
+
+ """
+ def __check_zone(self):
+
+ # don't check zone when widget does not have event handlers
+ #if (not self.__event_handlers): return
+ if (not self.has_events()): return
+
+ if (self.is_enabled()):
+ x, y = self.get_screen_pos()
+ w, h = self.get_size()
+ self.__set_zone(self, x, y, w, h)
+ else:
+ self.__remove_zone(self)
+
+
+ def __check_zones(self):
+
+ self.__need_to_check_zones = False
+ self.__check_zone()
+ for c in self.__children:
+ c.__check_zones()
+ """
+
+
+
+ """
+ def _connect(self, etype, cb, *args):
+ ""
+ Connects a callback to an event type. This is a low-level function
+ and should only be used when implementing new widgets.
+ ""
+
+ EventEmitter._connect(self, etype, cb, *args)
+ self.__check_zone()
+ """
+
+
+ def connect_clicked(self, cb, *args):
+ """
+ Connects a callback to mouse clicks. A click consists of pressing
+ and releasing a button.
+
+ @param cb: the callback function
+ @param *args: variable list of user arguments
+ """
+
+ self._connect(self.EVENT_BUTTON_RELEASE,
+ lambda x,y,*a:cb(*a),
+ *args)
+
+
+ def connect_button_pressed(self, cb, *args):
+ """
+ Connects a callback to pressing a mouse button.
+
+ @param cb: the callback function
+ @param *args: variable list of user arguments
+ """
+
+ self._connect(self.EVENT_BUTTON_PRESS,
+ lambda x,y,*a:cb(x, y, *a),
+ *args)
+
+
+ def connect_button_released(self, cb, *args):
+ """
+ Connects a callback to releasing a mouse button.
+
+ @param cb: the callback function
+ @param *args: variable list of user arguments
+ """
+
+
+ self._connect(self.EVENT_BUTTON_RELEASE,
+ lambda x,y,*a:cb(x, y, *a),
+ *args)
+
+
+ def connect_pointer_moved(self, cb, *args):
+ """
+ Connects a callback to moving the mouse.
+
+ @param cb: the callback function
+ @param *args: variable list of user arguments
+ """
+
+ self._connect(self.EVENT_MOTION,
+ lambda x,y,*a:cb(x, y, *a),
+ *args)
+
+
+ def connect_key_pressed(self, cb, *args):
+ """
+ Connects a callback to pressing a key.
+
+ @param cb: the callback function
+ @param *args: variable list of user arguments
+ """
+
+ self._connect(self.EVENT_KEY_PRESSED,
+ lambda key,*a:cb(key, *a),
+ *args)
+
+
+ def connect_key_released(self, cb, *args):
+ """
+ Connects a callback to releasing a key.
+
+ @param cb: the callback function
+ @param *args: variable list of user arguments
+ """
+
+ self._connect(self.EVENT_KEY_RELEASED,
+ lambda key,*a:cb(key, *a),
+ *args)
+
+ def set_pos(self, x, y):
+ """
+ Sets the position of this widget relative to its parent's coordinates.
+
+ @param x: x coordinate
+ @param y: y coordinate
+ """
+
+ if ((x, y) != self.__position):
+ self.__position = (x, y)
+ #self.__need_to_check_zones = True
+ #self.__check_zones()
+
+
+ def get_pos(self):
+ """
+ Returns the position of this widget relative to its parent's
+ coordinates.
+
+ @return: a tuple (x, y) containing the coordinates
+ """
+
+ return self.__position
+
+
+ def get_screen_pos(self):
+ """
+ Returns the absolute position of this widget, i.e. the position it
+ has on the root widget.
+
+ @return: a tuple (x, y) containing the coordinates
+ """
+
+ if (self.__parent):
+ parx, pary = self.__parent.get_screen_pos()
+ else:
+ parx, pary = (0, 0)
+ x, y = self.get_pos()
+
+ return (parx + x, pary + y)
+
+
+ def set_size(self, w, h):
+ """
+ Sets the size of this widget.
+
+ @param w: width
+ @param h: height
+ """
+
+ if ((w, h) != self.__size):
+ self.__size = (w, h)
+ #self.__need_to_check_zones = True
+ #self.__check_zone()
+
+
+ def get_size(self):
+ """
+ Returns the size of this widget. With some widgets this may differ
+ from the actual physical size.
+
+ @return: a tuple (width, height) holding the size
+ """
+
+ return self.__size
+
+
+ def set_geometry(self, x, y, w, h):
+ """
+ Convenience method for setting the position and size at the same time.
+
+ @param x: x coordinate
+ @param y: y coordinate
+ @param w: width
+ @param h: height
+ """
+
+ self.set_pos(x, y)
+ self.set_size(w, h)
+
+
+ def get_physical_size(self):
+ """
+ Returns the physical size of this widget. Widgets whose physical size
+ can differ from the size must override this method. E.g. a height of
+ '0' means dynamic height for a label, but the physical size contains
+ the actual height.
+
+ @return: a tuple (width, height) holding the size
+ """
+
+ return self.get_size()
+
+
+ def render_this(self):
+ """
+ Widgets override this method for drawing operations. This is also the
+ correct place for layouters to change the geometry of their child
+ widgets dynamically.
+
+ This method gets invoked automatically by the framework. Do not place
+ calls of this method in your code. Use the C{render} method instead.
+ """
+
+ pass
+
+
+ def render_overlays(self, screen):
+ """
+ Invokes the registered overlay handlers on the given screen. The screen
+ is usually an offscreen buffer.
+ """
+
+ for o in self.__overlayers:
+ try:
+ o(self, screen)
+ except:
+ pass
+ #end for
+
+
+ def overlay_this(self):
+ """
+ Widgets may override this method for overlay effects. This method is
+ invoked after all children of this widget have been rendered.
+
+ This method gets invoked automatically by the framework. Do not place
+ calls of this method in your code. Use the C{render} method instead.
+ """
+
+ pass
+
+
+ def render(self):
+ """
+ Renders this widget onto the current screen pixmap.
+ """
+
+ if (self.__skip_next_render):
+ self.__skip_next_render = False
+ return
+
+ if (not self.may_render()):
+ return
+
+ logging.debug("rendering widget %s", `self`)
+ #if (self.__need_to_check_zones):
+ # self.__check_zones()
+
+ self.render_this()
+
+ for c in self.__children:
+ if (c.is_visible()):
+ c.render()
+
+ self.overlay_this()
+
+
+ def render_all(self):
+ """
+ Renders the whole widget hierarchy beginning at the root widget.
+ """
+
+ if (self.__parent):
+ self.__parent.render_all()
+ else:
+ self.render()
+
+
+ def render_at(self, screen, x = 0, y = 0):
+ """
+ Renders this widget onto the given pixmap at the given position.
+ This is used for offscreen rendering.
+
+ @param screen: screen pixmap to render on
+ @param x: x coordinate
+ @param y: y coordinate
+ """
+
+ real_x, real_y = self.__position
+ parent = self.__parent
+ real_screen = self.__screen
+
+ self.__parent = None
+ self.set_screen(screen)
+ self.set_pos(x, y)
+
+ self.render()
+
+ self.__parent = parent
+ self.set_screen(real_screen)
+ self.set_pos(real_x, real_y)
+ #self.__check_zones()
+
+
+ def skip_next_render(self):
+ """
+ Skips the next rendering action.
+
+ @todo: this sounds like a hack. is this method still used?
+ """
+
+ self.__skip_next_render = True
+
+
+
+ def propagate_theme_change(self):
+ """
+ Propagates a theme change along the entire widget hierarchy when
+ issued on the root widget. This causes all widgets to reload their
+ graphics.
+ """
+
+ #self._reload()
+ for c in self.__instances:
+ c._reload()
+ #c.propagate_theme_change()
+
+
+ def _reload(self):
+ """
+ Widgets which have to clear caches when the theme changes have to
+ override this method so that they can get notified about a theme
+ change.
+ """
+
+ pass
+
+
+ def get_event_sensor(self):
+ """
+ Returns the event sensor of the widget hierarchy. This is a GTK widget.
+
+ @return: event sensor
+ """
+
+ return self.__event_sensor
+
+
+ def set_window(self, win):
+
+ self.__window = win
+ #try:
+ #win.set_widget_for_events(self)
+ self.__need_to_check_zones = True
+ #except:
+ # logging.error("window object %s must implement method " \
+ # "set_widget_for_events" % win)
+
+
+ def get_window(self):
+ """
+ Returns the GTK window of the widget hierarchy.
+
+ @return: window
+ """
+
+ if (self.__parent):
+ return self.__parent.get_window()
+ else:
+ return self.__window
+
+
+ def animate(self, fps, cb, *args):
+ """
+ Runs an animation with the given number of frames per second.
+ Invokes the given callback for each frame.
+ Events occuring during the animation get discarded.
+
+ @param fps: frames per second
+ @param cb: frame callback
+ @param *args: variable list of arguments for callback
+ """
+
+ delta = 1.0 / float(fps)
+ next = time.time()
+ while (True):
+ #print next
+ while (time.time() < next): time.sleep(0.00001)
+ ret = cb(*args)
+ gtk.gdk.window_process_all_updates()
+ next += delta
+ if (not ret): break
+ #end for
+
+ # kill queued events
+ while (True):
+ e = gtk.gdk.event_get()
+ if (not e): break
+
+
+ def animate_with_events(self, fps, cb, *args):
+ """
+ Runs an animation with the given number of frames per second.
+ Invokes the given callback for each frame.
+ Events are detected during animation.
+
+ @param fps: frames per second
+ @param cb: frame callback
+ @param *args: variable list of arguments for callback
+ """
+
+ delta = 1.0 / float(fps)
+ next = time.time()
+ while (True):
+ # TODO: this stuff should be put into a function in utils
+ gobject.timeout_add(int(delta*1000), lambda : False)
+ while (time.time() < next):
+ cnt = 0
+ while (gtk.events_pending() and cnt < 10):
+ gtk.main_iteration(True)
+ cnt += 1
+
+ ret = cb(*args)
+ gtk.gdk.window_process_all_updates()
+ next += delta
+ if (not ret): break
+ #end for
+
+
+
+ def fx_slide_horizontal(self, buf, x, y, w, h, direction):
+
+ def fx(params):
+ from_x, to_x = params
+ dx = (to_x - from_x) / 3
+
+ if (dx > 0):
+ if (direction == self.SLIDE_LEFT):
+ screen.move_area(scr_x + x + dx, scr_y + y,
+ w - dx, h,
+ -dx, 0)
+ screen.copy_pixmap(buf,
+ x + from_x, y,
+ scr_x + x + w - dx, scr_y + y,
+ dx, h)
+ else:
+ screen.move_area(scr_x + x, scr_y + y,
+ w - dx, h,
+ dx, 0)
+ screen.copy_pixmap(buf,
+ x + w - from_x - dx, y,
+ scr_x + x, scr_y + y,
+ dx, h)
+
+ params[0] = from_x + dx
+ params[1] = to_x
+ return True
+
+ else:
+ dx = to_x - from_x
+ if (direction == self.SLIDE_LEFT):
+ screen.move_area(scr_x + x + dx, scr_y + y,
+ w - dx, h,
+ -dx, 0)
+ screen.copy_pixmap(buf,
+ x + from_x, y,
+ scr_x + x + w - dx,
+ scr_y + y,
+ dx, h)
+ else:
+ screen.move_area(scr_x + x, scr_y + y,
+ w - dx, h,
+ dx, 0)
+ screen.copy_pixmap(buf,
+ x + w - from_x - dx, y,
+ scr_y + x, scr_y + y,
+ dx, h)
+
+ return False
+
+
+ if (not self.may_render()): return
+
+ scr_x, scr_y = self.get_screen_pos()
+ screen = self.get_screen()
+ self.animate(50, fx, [0, w])
+
+
+ def fx_slide_vertical(self, buf, x, y, w, h, direction):
+
+ def fx(params):
+ from_y, to_y = params
+ dy = (to_y - from_y) / 3
+
+ if (dy > 0):
+ if (direction == self.SLIDE_UP):
+ screen.move_area(scr_x + x, scr_y + y + dy,
+ w, h - dy,
+ 0, -dy)
+ screen.copy_pixmap(buf,
+ x, y + from_y,
+ scr_x + x, scr_y + y + h - dy,
+ w, dy)
+ else:
+ screen.move_area(scr_x + x, scr_y + y,
+ w, h - dy,
+ 0, dy)
+ screen.copy_pixmap(buf,
+ x, y + h - from_y - dy,
+ scr_x + x, scr_y + y,
+ w, dy)
+
+ params[0] = from_y + dy
+ params[1] = to_y
+ return True
+
+ else:
+ dy = to_y - from_y
+ if (direction == self.SLIDE_UP):
+ screen.move_area(scr_x + x, scr_y + y + dy,
+ w, h - dy,
+ 0, -dy)
+ screen.copy_pixmap(buf,
+ x, y + from_y,
+ scr_x + x,
+ scr_y + y + h - dy,
+ w, dy)
+ else:
+ screen.move_area(scr_x + x, scr_y + y,
+ w, h - dy,
+ 0, dy)
+ screen.copy_pixmap(buf,
+ x, y + h - from_y,
+ scr_y + x, scr_y + y,
+ w, dy)
+
+ return False
+
+
+ if (not self.may_render()): return
+
+ scr_x, scr_y = self.get_screen_pos()
+ screen = self.get_screen()
+ self.animate(50, fx, [0, h])
+
--- /dev/null
+from Widget import Widget
+from ui import try_rgba
+from Pixmap import Pixmap
+from Widget import Widget
+from utils.MiniXML import MiniXML
+import platforms
+from theme import theme
+
+import gtk
+import os
+
+
+class Window(Widget):
+ """
+ Base class for windows.
+ """
+
+ TYPE_TOPLEVEL = 0
+ TYPE_DIALOG = 1
+
+
+ EVENT_CLOSED = "event-closed"
+
+
+ def __init__(self, win_type):
+ """
+ Creates a new window.
+
+ @param win_type: type of window
+ """
+
+ self.__type = win_type
+ self.__size = (0, 0)
+ self.__fixed = None
+
+ Widget.__init__(self)
+
+ if (self.__type == self.TYPE_TOPLEVEL):
+ if (platforms.PLATFORM in (platforms.MAEMO5, platforms.MER)):
+ import hildon
+ self.__window = hildon.StackableWindow()
+
+ elif (platforms.PLATFORM == platforms.MAEMO4):
+ import hildon
+ self.__window = hildon.Window()
+ self.__window.fullscreen()
+
+ else:
+ self.__window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ self.__window.set_default_size(800, 480)
+
+ elif (self.__type == self.TYPE_DIALOG):
+ self.__window = gtk.Dialog()
+
+ self.__window.connect("configure-event", self.__on_configure)
+ self.__window.connect("expose-event", self.__on_expose)
+ self.__window.connect("button-press-event", self.__on_button_pressed)
+ self.__window.connect("button-release-event", self.__on_button_released)
+ self.__window.connect("motion-notify-event", self.__on_motion)
+ self.__window.connect("key-press-event", self.__on_key_pressed)
+ self.__window.connect("key-release-event", self.__on_key_released)
+ self.__window.connect("delete-event", self.__on_close_window)
+
+
+ self.__window.set_events(gtk.gdk.BUTTON_PRESS_MASK |
+ gtk.gdk.BUTTON_RELEASE_MASK |
+ gtk.gdk.POINTER_MOTION_MASK |
+ gtk.gdk.POINTER_MOTION_HINT_MASK |
+ gtk.gdk.KEY_PRESS_MASK |
+ gtk.gdk.KEY_RELEASE_MASK)
+
+
+ self.set_visible(False)
+ try_rgba(self.__window)
+ self.__window.set_app_paintable(True)
+ self.__window.realize()
+
+ if (platforms.PLATFORM == platforms.MAEMO5):
+ self.__set_portrait_property("_HILDON_PORTRAIT_MODE_SUPPORT", 1)
+
+ """
+ if (platforms.PLATFORM in (platforms.MAEMO5, platforms.MER)):
+ # we need to notify Maemo5 that we want to use the volume keys
+ self.__window.window.property_change("_HILDON_ZOOM_KEY_ATOM",
+ "XA_INTEGER", 32,
+ gtk.gdk.PROP_MODE_REPLACE,
+ [1])
+ """
+
+ self.__fixed = gtk.Fixed()
+ self.__fixed.show()
+
+ if (self.__type == self.TYPE_TOPLEVEL):
+ self.__window.add(self.__fixed)
+ else:
+ self.__window.vbox.add(self.__fixed)
+
+ self.__screen = None
+
+ """
+ # hide mouse cursor
+ if (hide_cursor):
+ pbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 8, 8)
+ pbuf.fill(0x00000000)
+ csr = gtk.gdk.Cursor(gtk.gdk.display_get_default(), pbuf, 0, 0)
+ self.__window.window.set_cursor(csr)
+ del pbuf
+ #end if
+ """
+
+ self.set_window(self)
+
+
+ def get_gtk_window(self):
+ """
+ Returns the associated GtkWindow of this window.
+
+ @return: GtkWindow object
+ """
+
+ return self.__window
+
+
+ def __check_window_size(self):
+
+ if (self.__size != self.__window.get_size()):
+ w, h = self.__window.get_size()
+ self.__screen = Pixmap(self.__window.window)
+ self.set_screen(self.__screen)
+ self.__size = (w, h)
+
+ Widget.set_size(self, w, h)
+ for c in self.get_children():
+ c.set_size(w, h)
+
+ self.render()
+
+
+ def __on_configure(self, src, ev):
+
+ self.__check_window_size()
+
+
+ def __on_expose(self, src, ev):
+
+ if (self.__screen):
+ x, y, w, h = ev.area
+ self.__screen.restore(x, y, w, h)
+
+
+ def __on_close_window(self, src, ev):
+
+ self.send_event(self.EVENT_CLOSED)
+ return True
+
+
+ def __on_button_pressed(self, src, ev):
+
+ px, py = src.get_pointer()
+ self._handle_event(self.EVENT_BUTTON_PRESS, px, py)
+ return True
+
+
+ def __on_button_released(self, src, ev):
+
+ px, py = src.get_pointer()
+ self._handle_event(self.EVENT_BUTTON_RELEASE, px, py)
+ return True
+
+
+ def __on_motion(self, src, ev):
+
+ px, py = src.get_pointer()
+ self._handle_event(self.EVENT_MOTION, px, py)
+ return True
+
+
+ def __on_key_pressed(self, src, ev):
+
+ keyval = ev.keyval
+ c = gtk.gdk.keyval_to_unicode(keyval)
+ if (c > 31):
+ key = unichr(c)
+ else:
+ key = gtk.gdk.keyval_name(keyval)
+
+ self.send_event(self.EVENT_KEY_PRESSED, key)
+
+ # kill queued events
+ if (key in ["Up", "Down", "Left", "Right"]):
+ while (True):
+ e = gtk.gdk.event_get()
+ if (not e): break
+
+ return True
+
+
+ def __on_key_released(self, src, ev):
+
+ keyval = ev.keyval
+ c = gtk.gdk.keyval_to_unicode(keyval)
+ if (c > 31):
+ key = unichr(c)
+ else:
+ key = gtk.gdk.keyval_name(keyval)
+
+ self.send_event(self.EVENT_KEY_RELEASED, key)
+
+ return True
+
+
+ def connect_closed(self, cb, *args):
+
+ self._connect(self.EVENT_CLOSED, cb, *args)
+
+
+ def get_pos(self):
+
+ return self.__window.window.get_position()
+
+
+ def get_screen_pos(self):
+
+ return (0, 0)
+
+
+ def set_pos(self, x, y):
+
+ Widget.set_pos(self, 0, 0)
+ #self.__window.move(x, y)
+
+
+ def set_size(self, w, h):
+
+ Widget.set_size(self, w, h)
+ #self.__window.set_size_request(w, h)
+ self.__window.resize(w, h)
+
+
+ def get_size(self):
+
+ w, h = self.__window.get_size()
+ return (w, h)
+
+
+ def _visibility_changed(self):
+
+ if (self.is_visible()):
+ self.__window.show()
+ self.render()
+ else:
+ self.__window.hide()
+
+
+
+ def put(self, child, x, y):
+
+ if (not self.__fixed):
+ self.__fixed = gtk.Fixed()
+ self.__fixed.show()
+ self.__window.add(self.__fixed)
+
+ self.__fixed.put(child, x, y)
+
+
+ def move(self, child, x, y):
+ assert self.__fixed
+
+ self.__fixed.move(child, x, y)
+
+
+ def present(self):
+
+ self.__window.present()
+
+
+ def iconify(self):
+
+ self.__window.iconify()
+
+
+ def set_fullscreen(self, v):
+
+ if (v):
+ self.__window.fullscreen()
+ else:
+ self.__window.unfullscreen()
+
+
+ def set_title(self, title):
+
+ self.__window.set_title(title)
+
+
+ def __set_portrait_property(self, prop, value):
+
+ self.__window.window.property_change(prop, "CARDINAL", 32,
+ gtk.gdk.PROP_MODE_REPLACE,
+ [value])
+
+
+ def set_portrait_mode(self, v):
+ """
+ Switches between landscape and portrait mode on supported platforms.
+ @since: 2009.09
+ """
+
+ if (platforms.PLATFORM == platforms.MAEMO5):
+ if (v):
+ self.__set_portrait_property("_HILDON_PORTRAIT_MODE_SUPPORT", 1)
+ self.__set_portrait_property("_HILDON_PORTRAIT_MODE_REQUEST", 1)
+ else:
+ self.__set_portrait_property("_HILDON_PORTRAIT_MODE_SUPPORT", 1)
+ self.__set_portrait_property("_HILDON_PORTRAIT_MODE_REQUEST", 0)
+
+ elif (platforms.PLATFORM == platforms.MAEMO4):
+ if (v):
+ os.system("xrandr -o left")
+ else:
+ os.system("xrandr -o normal")
+
+ #end if
+
+
+ def set_menu_xml(self, xml, bindings):
+ """
+ Sets the window menu from a XML description.
+ @since 2009.11.19
+
+ @param xml: XML description of the menu
+ @param bindings: dictionary mapping XML node IDs to callback handlers
+ """
+
+ if (platforms.PLATFORM != platforms.MAEMO5): return
+ import hildon
+
+ dom = MiniXML(xml).get_dom()
+ menu = hildon.AppMenu()
+
+ for node in dom.get_children():
+ name = node.get_name()
+ if (name == "item"):
+ item_id = node.get_attr("id")
+ item_label = node.get_attr("label")
+ callback = bindings.get(item_id)
+
+ btn = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+ btn.set_label(item_label)
+ if (callback):
+ btn.connect("clicked",
+ lambda x, cb:cb(), callback)
+ menu.append(btn)
+
+ elif (name == "choice"):
+ choice_id = node.get_attr("id")
+ choice_selected = int(node.get_attr("selected") or "0")
+ callback = bindings.get(choice_id)
+
+ group = None
+ buttons = []
+ cnt = 0
+ for option in node.get_children():
+ item_label = option.get_attr("label")
+ item_icon = option.get_attr("icon")
+
+ btn = hildon.GtkRadioButton(gtk.HILDON_SIZE_AUTO, group)
+ btn.set_mode(False)
+
+ if (item_label):
+ btn.set_label(item_label)
+ if (item_icon):
+ img = gtk.Image()
+ img.set_from_pixbuf(getattr(theme, item_icon))
+ btn.set_image(img)
+
+ if (cnt == choice_selected):
+ btn.set_active(True)
+
+ menu.add_filter(btn)
+ group = btn
+ buttons.append(btn)
+ cnt += 1
+ #end for
+
+ cnt = 0
+ for btn in buttons:
+ if (callback):
+ btn.connect("clicked",
+ lambda x, i, cb:cb(i), cnt, callback)
+ cnt += 1
+ #end for
+ #end if
+ #end for
+
+ menu.show_all()
+ self.__window.set_app_menu(menu)
+
--- /dev/null
+"""
+User Interface Classes
+======================
+
+This package contains user interface widgets and classes. The widget framework
+is based on GDK.
+
+@copyright: 2008, 2009
+@author: Martin Grimme <martin.grimme@lintegra.de>
+
+@license: This package is licensed under the terms of the GNU LGPL.
+"""
+
+
+def try_rgba(w):
+
+ import gtk
+ screen = gtk.gdk.screen_get_default()
+ try:
+ have_rgba = screen.is_composited()
+ except:
+ have_rgba = False
+
+ if (have_rgba):
+ cmap = screen.get_rgba_colormap()
+ else:
+ cmap = screen.get_rgb_colormap()
+
+ w.set_colormap(cmap)
+
--- /dev/null
+import gtk
+
+
+class InputDialog(gtk.Dialog):
+
+ def __init__(self, title):
+
+ self.__inputs = []
+
+ gtk.Dialog.__init__(self)
+ self.set_title(title)
+
+ btn = gtk.Button("OK")
+ btn.connect("clicked", lambda x: self.response(gtk.RESPONSE_ACCEPT))
+ btn.show()
+ self.action_area.add(btn)
+
+ self.realize()
+ self.window.property_change("_HILDON_PORTRAIT_MODE_SUPPORT",
+ "CARDINAL", 32,
+ gtk.gdk.PROP_MODE_REPLACE,
+ [1])
+
+
+ def add_input(self, label, default):
+
+ hbox = gtk.HBox()
+ hbox.show()
+ self.vbox.add(hbox)
+
+ lbl = gtk.Label(label)
+ lbl.show()
+ hbox.add(lbl)
+
+ entry = gtk.Entry()
+ entry.show()
+ hbox.add(entry)
+
+ self.__inputs.append(entry)
+
+
+ def get_values(self):
+
+ return [ i.get_text() for i in self.__inputs ]
+
+
+ def run(self):
+
+ self.show()
+ resp = gtk.Dialog.run(self)
+ self.destroy()
+
+ if (resp == gtk.RESPONSE_ACCEPT):
+ return 0
+ else:
+ return 1
+
--- /dev/null
+from ui.Window import Window
+from ui.itemview import ThumbableGridView
+from ui.Slider import VSlider
+from theme import theme
+
+import gtk
+
+
+class ListDialog(Window):
+
+ def __init__(self, title):
+
+ self.__choice = None
+
+
+ Window.__init__(self, Window.TYPE_DIALOG)
+ self.set_title(title)
+ self.get_gtk_window().set_size_request(800, 320)
+
+ self.__list = ThumbableGridView()
+ self.add(self.__list)
+
+ self.__slider = VSlider(theme.mb_list_slider)
+ self.add(self.__slider)
+ self.__list.associate_with_slider(self.__slider)
+
+
+ def render_this(self):
+
+ w, h = self.get_size()
+ screen = self.get_screen()
+
+ self.__slider.set_geometry(0, 0, 40, h)
+ self.__list.set_geometry(40, 0, w - 40, h)
+
+
+ def __on_click_item(self, item):
+
+ self.__choice = item
+ self.get_gtk_window().response(gtk.RESPONSE_ACCEPT)
+
+
+ def add_item(self, item):
+
+ item.connect_clicked(self.__on_click_item, item)
+ self.__list.append_item(item)
+
+
+
+ def get_choice(self):
+
+ return self.__choice
+
+
+ def run(self):
+
+ win = self.get_gtk_window()
+
+ self.set_visible(True)
+ resp = win.run()
+ win.destroy()
+
+ if (resp == gtk.RESPONSE_ACCEPT):
+ return 0
+ else:
+ return 1
--- /dev/null
+import gtk
+
+
+class OptionDialog(gtk.Dialog):
+
+ def __init__(self, title):
+
+ self.__num_of_options = 0
+ self.__choice = -1
+
+ gtk.Dialog.__init__(self)
+ self.set_title(title)
+
+ self.realize()
+
+ self.window.property_change("_HILDON_PORTRAIT_MODE_SUPPORT",
+ "CARDINAL", 32,
+ gtk.gdk.PROP_MODE_REPLACE,
+ [1])
+
+ def add_option(self, icon, label):
+
+ def on_choice(src, i):
+ self.__choice = i
+ self.response(gtk.RESPONSE_ACCEPT)
+
+ hbox = gtk.HBox()
+ hbox.show()
+ if (icon):
+ img = gtk.Image()
+ img.set_from_pixbuf(icon)
+ img.show()
+ hbox.add(img)
+ #end if
+ lbl = gtk.Label(label)
+ lbl.show()
+ hbox.add(lbl)
+
+ btn = gtk.Button()
+ btn.set_size_request(-1, 70)
+ self.vbox.add(btn)
+ btn.show()
+ btn.add(hbox)
+ btn.connect("clicked", on_choice, self.__num_of_options)
+ self.__num_of_options += 1
+
+
+ def get_choice(self):
+
+ return self.__choice
+
+
+ def run(self):
+
+ self.show()
+ resp = gtk.Dialog.run(self)
+ self.destroy()
+
+ if (resp == gtk.RESPONSE_ACCEPT):
+ return 0
+ else:
+ return 1
+
--- /dev/null
+from InputDialog import InputDialog
+from ListDialog import ListDialog
+from OptionDialog import OptionDialog
+
--- /dev/null
+from ui.Widget import Widget
+from utils.MiniXML import MiniXML
+
+_PX = 0
+_PCT = 1
+_LIKE_X1 = 2
+_LIKE_Y1 = 3
+_LIKE_X2 = 4
+_LIKE_Y2 = 5
+
+
+class Arrangement(Widget):
+ """
+ Layouter for complex arrangements of widgets.
+ @since: 2009.10.29
+ """
+
+ EVENT_RESIZED = "event-resized"
+
+
+ def __init__(self):
+
+ # table: name -> widget
+ self.__names = {}
+
+ # table: widget -> (x1, y1, x2, y2)
+ self.__constraints = {}
+
+ # cache of arrangements, table: widget -> (x1, y1, x2, y2)
+ self.__cache = {}
+
+ Widget.__init__(self)
+
+
+ def connect_resized(self, cb, *args):
+
+ self._connect(self.EVENT_RESIZED, cb, *args)
+
+
+ @staticmethod
+ def px(v):
+
+ return (_PX, v)
+
+
+ @staticmethod
+ def pct(v):
+
+ return (_PCT, v)
+
+
+ @staticmethod
+ def like_x1(child):
+
+ return (_LIKE_X1, child)
+
+
+ @staticmethod
+ def like_y1(child):
+
+ return (_LIKE_Y1, child)
+
+
+ @staticmethod
+ def like_x2(child):
+
+ return (_LIKE_X2, child)
+
+
+ @staticmethod
+ def like_y2(child):
+
+ return (_LIKE_Y2, child)
+
+
+
+ def __resolve_child(self, child):
+
+ if (not child in self.__constraints):
+ return (0, 0, 10, 10)
+
+ x1, y1, x2, y2 = self.__constraints[child]
+ w, h = self.get_size()
+
+ if (child in self.__cache):
+ real_x1, real_y1, real_x2, real_y2 = self.__cache[child]
+ else:
+ real_x1 = self.__resolve(x1, w)
+ real_y1 = self.__resolve(y1, h)
+ real_x2 = self.__resolve(x2, w)
+ real_y2 = self.__resolve(y2, h)
+ self.__cache[child] = (real_x1, real_y1, real_x2, real_y2)
+
+ return (real_x1, real_y1, real_x2, real_y2)
+
+
+ def __resolve(self, v, full_width):
+
+ vtype, value = v
+ if (vtype == _PX):
+ if (value >= 0):
+ return value
+ else:
+ return full_width + value
+
+ elif (vtype == _PCT):
+ if (value >= 0):
+ return int(full_width * (value / 100.0))
+ else:
+ return full_width + int(full_width * (value / 100.0))
+
+ elif (vtype == _LIKE_X1):
+ x1, y1, x2, y2 = self.__resolve_child(value)
+ return x1
+
+ elif (vtype == _LIKE_Y1):
+ x1, y1, x2, y2 = self.__resolve_child(value)
+ return y1
+
+ elif (vtype == _LIKE_X2):
+ x1, y1, x2, y2 = self.__resolve_child(value)
+ return x2
+
+ elif (vtype == _LIKE_Y2):
+ x1, y1, x2, y2 = self.__resolve_child(value)
+ return y2
+
+
+ def set_size(self, w, h):
+
+ prev_w, prev_h = self.get_size()
+ Widget.set_size(self, w, h)
+ if ((w, h) != (prev_w, prev_h)):
+ self.__cache.clear()
+ self.emit_event(self.EVENT_RESIZED)
+
+
+ def add(self, child, name = ""):
+ """
+ Adds a child widget. A unique name must be given when using arrangement
+ definitions in XML format.
+
+ @param child: child widget
+ @param name: name string
+ """
+
+ if (name):
+ self.__names[name] = child
+ Widget.add(self, child)
+
+
+ def place(self, child, x1, y1, x2, y2):
+ """
+ Changes the placement of the given child widget.
+
+ @param child: child widget
+ @param x1: x-coordinate of top-left corner
+ @param y1: y-coordinate of top-left corner
+ @param x2: x-coordinate of bottom-right corner
+ @param y2: y-coordinate of botton-right corner
+ """
+
+ self.__constraints[child] = (x1, y1, x2, y2)
+
+ if (child in self.__cache):
+ del self.__cache[child]
+
+
+ def set_xml(self, xml):
+ """
+ Loads an arrangement from an XML arrangement definition.
+
+ @param xml: XML string containing the arrangement
+ """
+
+ dom = MiniXML(xml).get_dom()
+ self.__parse_node(dom, [], [])
+
+
+ def __parse_node(self, node, required_visibles, required_invisibles):
+
+ name = node.get_name()
+ if (name == "if-visible"):
+ child = self.__names[node.get_attr("name")]
+ required_visibles.append(child)
+ elif (name == "if-invisible"):
+ child = self.__names[node.get_attr("name")]
+ required_invisibles.append(child)
+ elif (name == "widget"):
+ child = self.__names[node.get_attr("name")]
+ x1 = self.__parse_value(node.get_attr("x1"))
+ y1 = self.__parse_value(node.get_attr("y1"))
+ x2 = self.__parse_value(node.get_attr("x2"))
+ y2 = self.__parse_value(node.get_attr("y2"))
+
+ if (self.__check_visibles(required_visibles, required_invisibles)):
+ self.place(child, x1, y1, x2, y2)
+ #end if
+
+ for c in node.get_children():
+ self.__parse_node(c, required_visibles[:], required_invisibles[:])
+ #end for
+
+
+ def __parse_value(self, v):
+
+ if (v[-1] == "%"):
+ return self.pct(int(v[:-1]))
+ else:
+ return self.px(int(v))
+
+
+ def __check_visibles(self, required_visibles, required_invisibles):
+
+ for v in required_visibles:
+ if (not v.is_visible()):
+ return False
+ #end for
+
+ for v in required_invisibles:
+ if (v.is_visible()):
+ return False
+ #end for
+
+ return True
+
+
+ def render_this(self):
+
+ for child in self.get_children():
+ x1, y1, x2, y2 = self.__resolve_child(child)
+ child.set_geometry(x1, y1, x2 - x1, y2 - y1)
+ #end for
+
--- /dev/null
+from ui.Widget import Widget
+
+
+class Box(Widget):
+
+ HALIGN_LEFT = 0
+ HALIGN_CENTER = 1
+ HALIGN_RIGHT = 2
+
+ VALIGN_TOP = 0
+ VALIGN_CENTER = 1
+ VALIGN_BOTTOM = 2
+
+ HORIZONTAL = 0
+ VERTICAL = 1
+
+
+ def __init__(self):
+
+ self.__mode = self.HORIZONTAL
+ self.__halign = self.HALIGN_LEFT
+ self.__valign = self.VALIGN_TOP
+ self.__spacing = 0
+ self.__is_dynamic = {}
+
+ Widget.__init__(self)
+
+
+ def set_orientation(self, mode):
+
+ self.__mode = mode
+
+
+ def add(self, c, dynamic_size = False):
+
+ Widget.add(self, c)
+ self.__is_dynamic[c] = dynamic_size
+
+
+ def set_spacing(self, spacing):
+
+ self.__spacing = spacing
+
+
+ def set_halign(self, align):
+
+ self.__halign = align
+
+
+ def set_valign(self, align):
+
+ self.__valign = align
+
+
+ def render_this(self):
+
+ w, h = self.get_size()
+
+ children = [ c for c in self.get_children() if c.is_visible() ]
+ fixed_children = [ c for c in children
+ if not self.__is_dynamic[c] ]
+ dynamic_children = [ c for c in children
+ if self.__is_dynamic[c] ]
+
+ if (self.__mode == self.HORIZONTAL):
+ fixed_width = reduce(lambda a,b:a+b,
+ [ c.get_physical_size()[0]
+ for c in fixed_children ], 0)
+ fixed_width += (len(children) - 1) * self.__spacing
+
+ if (dynamic_children):
+ dynamic_width = (w - fixed_width) / len(dynamic_children)
+ else:
+ dynamic_width = 0
+
+
+ if (not dynamic_children):
+ if (self.__halign == self.HALIGN_LEFT):
+ x = 0
+ elif ( self.__halign == self.HALIGN_CENTER):
+ x = (w - fixed_width) / 2
+ else:
+ x = w - fixed_width
+ else:
+ x = 0
+
+ for c in children:
+ width, height = c.get_physical_size()
+
+ if (self.__is_dynamic[c]):
+ width = dynamic_width
+ height = h
+ c.set_size(width, height)
+ #print c, width, height
+
+ if (self.__valign == self.VALIGN_TOP):
+ c.set_pos(x, 0)
+ elif (self.__valign == self.VALIGN_CENTER):
+ c.set_pos(x, (h - height) / 2)
+ else:
+ c.set_pos(x, h - height)
+
+ x += width + self.__spacing
+ #end for
+
+
+ elif (self.__mode == self.VERTICAL):
+ fixed_width = reduce(lambda a,b:a+b,
+ [ c.get_physical_size()[1]
+ for c in fixed_children ], 0)
+ fixed_width += (len(children) - 1) * self.__spacing
+
+ if (dynamic_children):
+ dynamic_width = (h - fixed_width) / len(dynamic_children)
+ else:
+ dynamic_width = 0
+
+
+ if (not dynamic_children):
+ if (self.__valign == self.VALIGN_TOP):
+ y = 0
+ elif ( self.__valign == self.VALIGN_CENTER):
+ y = (h - fixed_width) / 2
+ else:
+ y = h - fixed_width
+ else:
+ y = 0
+
+ for c in children:
+ width, height = c.get_physical_size()
+
+ if (self.__is_dynamic[c]):
+ width = w
+ height = dynamic_width
+ c.set_size(width, height)
+
+ if (self.__halign == self.HALIGN_LEFT):
+ c.set_pos(0, y)
+ elif (self.__halign == self.HALIGN_CENTER):
+ c.set_pos((w - width) / 2, y)
+ else:
+ c.set_pos(w - width, y)
+
+ y += height + self.__spacing
+ #end for
+
+ #end if
+
+ """
+ # debugging stuff...
+
+ x, y = self.get_screen_pos()
+ w, h = self.get_size()
+ screen = self.get_screen()
+ if (self.__mode == self.HORIZONTAL):
+ screen.fill_area(x + 1, y + 1, w - 2, h - 2, "#ff000020")
+ else:
+ screen.fill_area(x + 1, y + 1, w - 2, h - 2, "#0000ff20")
+ """
+
--- /dev/null
+from Box import Box
+
+
+class HBox(Box):
+
+ def __init__(self):
+
+ Box.__init__(self)
+ self.set_orientation(self.HORIZONTAL)
+
--- /dev/null
+from Box import Box
+
+
+class VBox(Box):
+
+ def __init__(self):
+
+ Box.__init__(self)
+ self.set_orientation(self.VERTICAL)
+
--- /dev/null
+from Box import Box
+from HBox import HBox
+from VBox import VBox
+from Arrangement import Arrangement
+
--- /dev/null
+"""
+Various functions for working with pixbufs.
+"""
+
+import gtk
+
+
+TOP = 1
+BOTTOM = 2
+LEFT = 4
+RIGHT = 8
+
+
+def _reload(*items):
+ """
+ Attempts reloading the given items.
+ """
+
+ for item in items:
+ try:
+ item.reload()
+ except:
+ pass
+
+
+def _split_frame(img):
+
+ w, h = img.get_width(), img.get_height()
+ w3 = w / 3
+ h3 = h / 3
+
+ tl = img.subpixbuf(0, 0, 10, 10)
+ t = img.subpixbuf(10, 0, w - 20, 10)
+ tr = img.subpixbuf(w - 10, 0, 10, 10)
+ r = img.subpixbuf(w - 10, 10, 10, h - 20)
+ br = img.subpixbuf(w - 10, h - 10, 10, 10)
+ b = img.subpixbuf(10, h - 10, w - 20, 10)
+ bl = img.subpixbuf(0, h - 10, 10, 10)
+ l = img.subpixbuf(0, 10, 10, h - 20)
+ c = img.subpixbuf(10, 10, w - 20, h - 20)
+
+
+ #tl = img.subpixbuf(0, 0, w3, h3)
+ #t = img.subpixbuf(w3, 0, w3, h3)
+ #tr = img.subpixbuf(w - w3, 0, w3, h3)
+ #r = img.subpixbuf(w - w3, h3, w3, h3)
+ #br = img.subpixbuf(w - w3, h - h3, w3, h3)
+ #b = img.subpixbuf(w3, h - h3, w3, h3)
+ #bl = img.subpixbuf(0, h - h3, w3, h3)
+ #l = img.subpixbuf(0, h3, w3, h3)
+ #c = img.subpixbuf(w3, h3, w3, h3)
+
+ return (tl, t, tr, r, br, b, bl, l, c)
+
+
+def draw_pbuf(pbuf1, pbuf2, x, y, w = -1, h = -1):
+
+ _reload(pbuf1, pbuf2)
+ if (w > 0 and h > 0):
+ pbuf2 = pbuf2.scale_simple(w, h, gtk.gdk.INTERP_BILINEAR)
+ else:
+ w = pbuf2.get_width()
+ h = pbuf2.get_height()
+
+ subpbuf = pbuf1.subpixbuf(x, y, w, h)
+ pbuf2.composite(subpbuf, 0, 0, w, h, 0, 0, 1, 1,
+ gtk.gdk.INTERP_NEAREST, 0xff)
+ del subpbuf
+
+
+def fit_pbuf(pbuf1, pbuf2, x, y, w, h):
+
+ _reload(pbuf1, pbuf2)
+ pbuf1_w = pbuf1.get_width()
+ pbuf1_h = pbuf1.get_height()
+ pbuf2_w = pbuf2.get_width()
+ pbuf2_h = pbuf2.get_height()
+
+ sx = w / float(pbuf2_w)
+ sy = h / float(pbuf2_h)
+ scale = min(sx, sy)
+
+ new_w = int(pbuf2_w * scale)
+ new_h = int(pbuf2_h * scale)
+ offx = (w - new_w) / 2
+ offy = (h - new_h) / 2
+
+ pbuf2 = pbuf2.scale_simple(new_w, new_h, gtk.gdk.INTERP_BILINEAR)
+ subpbuf = pbuf1.subpixbuf(x + offx, y + offy, new_w, new_h)
+ pbuf2.composite(subpbuf, 0, 0, new_w, new_h, 0, 0, 1, 1,
+ gtk.gdk.INTERP_NEAREST, 0xff)
+ del subpbuf
+
+
+def make_frame(pbuf, w, h, filled, parts = 0xf):
+ """
+ Draws a frame by stretching and tiling the given pixbuf.
+ """
+
+ _reload(pbuf)
+ tl, t, tr, r, br, b, bl, l, c = _split_frame(pbuf)
+ w1, h1 = tl.get_width(), tl.get_height()
+ w1 = min(w1, w / 3)
+ h1 = min(h1, h / 3)
+ w2 = w1
+ h2 = h1
+
+ if (not parts & TOP):
+ h1 = 0
+ if (not parts & BOTTOM):
+ h2 = 0
+ if (not parts & LEFT):
+ w1 = 0
+ if (not parts & RIGHT):
+ w2 = 0
+
+ new_pbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, w, h)
+ new_pbuf.fill(0x00000000)
+
+ if (parts & TOP):
+ if (parts & LEFT):
+ draw_pbuf(new_pbuf, tl, 0, 0)
+ if (parts & RIGHT):
+ draw_pbuf(new_pbuf, tr, w - w2, 0)
+ if (parts & BOTTOM):
+ if (parts & LEFT):
+ draw_pbuf(new_pbuf, bl, 0, h - h2)
+ if (parts & RIGHT):
+ draw_pbuf(new_pbuf, br, w - w2, h - h2)
+
+ if (parts & TOP):
+ draw_pbuf(new_pbuf, t, w1, 0, w - w1 - w2, h1)
+ #self.draw_pixbuf(t, x + w1, y, w - w1 - w2, h1, True)
+ if (parts & BOTTOM):
+ draw_pbuf(new_pbuf, b, w1, h - h2, w - w1 - w2, h2)
+ #self.draw_pixbuf(b, x + w1, y + h - h2, w - w1 - w2, h2, True)
+ if (parts & LEFT):
+ draw_pbuf(new_pbuf, l, 0, h1, w1, h - h1 - h2)
+ #self.draw_pixbuf(l, x, y + h1, w1, h - h1 - h2, True)
+ if (parts & RIGHT):
+ draw_pbuf(new_pbuf, r, w - w2, h1, w2, h - h1 - h2)
+ #self.draw_pixbuf(r, x + w - w2, y + h1, w2, h - h1 - h2, True)
+
+ if (filled):
+ draw_pbuf(new_pbuf, c, w1, h1, w - w1 - w2, h - h1 - h2)
+ #self.draw_pixbuf(c, x + w1, y + h1, w - w1 - w2, h - h1 - h2, True)
+
+ return new_pbuf
+
--- /dev/null
+"""
+Class for storing and retrieving configuration values via GConf.
+"""
+
+
+try:
+ # GNOME
+ import gconf
+except:
+ try:
+ # Maemo
+ import gnome.gconf as gconf
+ except:
+ # last resort...
+ from utils import gconftool as gconf
+
+
+_CLIENT = gconf.client_get_default()
+
+
+class Config(object):
+ """
+ Class for storing and retrieving configuration values via GConf.
+
+ Example::
+ cfg = Config("my-plugin", [("foo", Config.STRING, "foobar"),
+ ("bar", Config.INTEGER, 10)])
+ cfg["foo"] = "Some string"
+ print cfg["bar"]
+
+ @since: 0.96
+ """
+
+ INTEGER = 0
+ STRING = 1
+ BOOL = 2
+ INTEGER_LIST = 3
+ STRING_LIST = 4
+
+
+ def __init__(self, prefix, schema):
+ """
+ Creates a new Config object for storing configuration under the given
+ prefix with the given schema. Prefix must end with "/".
+
+ Schema is a list of tuples of the form
+ C{(key_name, datatype, default_value)}
+ describing the valid keys that can be stored.
+ @since: 0.96
+
+ @param prefix: prefix for keys
+ @param schema: list of schema tuples
+ """
+
+ self.__prefix = prefix
+ self.__schema = schema
+
+
+ def __lookup_schema(self, key):
+
+ for k, t, d in self.__schema:
+ if (k == key):
+ return (t, d)
+ #end for
+ raise KeyError("no such key %s" % key)
+
+
+ def __getitem__(self, key):
+
+ return self.__get(key)
+
+
+ def __setitem__(self, key, value):
+
+ self.__set(key, value)
+
+
+ def __get(self, key):
+
+ dtype, default = self.__lookup_schema(key)
+
+ if (dtype == self.STRING):
+ return _CLIENT.get_string(self.__prefix + key) \
+ or default
+ elif (dtype == self.STRING_LIST):
+ l = _CLIENT.get_list(self.__prefix + key, gconf.VALUE_STRING) or []
+ return l[:] or default[:]
+ elif (dtype == self.INTEGER):
+ try:
+ have_key = _CLIENT.get_without_default(self.__prefix + key)
+ except:
+ have_key = False
+
+ v = _CLIENT.get_int(self.__prefix + key)
+ if (not have_key): v = default
+ return v
+ elif (dtype == self.INTEGER_LIST):
+ l = _CLIENT.get_list(self.__prefix + key, gconf.VALUE_INT) or []
+ return l[:] or default[:]
+ elif (dtype == self.BOOL):
+ try:
+ have_key = _CLIENT.get_without_default(self.__prefix + key)
+ except:
+ have_key = False
+
+ v = _CLIENT.get_bool(self.__prefix + key)
+ if (not have_key): v = default
+ return v
+ else:
+ return None
+
+
+ def __set(self, key, value):
+
+ dtype, default = self.__lookup_schema(key)
+
+ if (dtype == self.STRING):
+ _CLIENT.set_string(self.__prefix + key, value)
+ elif (dtype == self.STRING_LIST):
+ _CLIENT.set_list(self.__prefix + key, gconf.VALUE_STRING, value)
+ elif (dtype == self.INTEGER):
+ _CLIENT.set_int(self.__prefix + key, value)
+ elif (dtype == self.INTEGER_LIST):
+ _CLIENT.set_list(self.__prefix + key, gconf.VALUE_INT, value)
+ elif (dtype == self.BOOL):
+ _CLIENT.set_bool(self.__prefix + key, value)
+
--- /dev/null
+import threading
+
+
+class EventEmitter(object):
+ """
+ Abstract base class for UI objects emitting events.
+ @since: 0.97
+ """
+
+ # static lock for blocking event handling
+ __events_lock = threading.Event()
+
+
+ def __init__(self):
+
+ self.__handlers = {}
+
+
+ def has_events(self):
+
+ return (len(self.__handlers) > 0)
+
+
+ def set_events_blocked(self, v):
+
+ """
+ Sets the global lock for blocking events. While events are blocked,
+ no event handling takes place.
+
+ @param value: whether events are blocked (True) or not (False)
+ """
+
+ if (value):
+ self.__events_lock.set()
+ else:
+ self.__events_lock.clear()
+
+
+ def _connect(self, ev_name, cb, *args):
+
+ if (not ev_name in self.__handlers):
+ self.__handlers[ev_name] = []
+
+ if (not (cb, args) in self.__handlers[ev_name]):
+ self.__handlers[ev_name].append((cb, args))
+
+
+ def emit_event(self, ev_name, *args):
+
+ if (self.__events_lock.isSet()): return
+
+ for item in self.__handlers.get(ev_name, []):
+ cb, u_args = item
+ try:
+ a = args + u_args
+ cb(*a)
+ except:
+ import traceback; traceback.print_exc()
+ #end for
+
--- /dev/null
+"""
+Lightweight XML parser with namespace support.
+
+@copyright: 2006 - 2008
+@author: Martin Grimme <martin.grimme@lintegra.de>
+
+@license: This module is licensed under the terms of the GNU LGPL.
+"""
+
+class MiniXML(object):
+ """
+ This is a tiny parser for a subset of XML with namespace support.
+ This is useful when RAM is limited and we don't need a full-featured
+ XML parser. Well, actually, it's almost full-featured by now, while still
+ being tiny. :)
+
+ @since: 0.96
+ """
+
+ def __init__(self, data, namespace = "", callback = None):
+ """
+ Creates a new MiniXML object and parses the given XML data.
+ If a namespace is given, it is used as the default namespace unless
+ the XML overrides the default namespace.
+ If a callback is given, the callback is being called every time a
+ node has been closed and is thus finished.
+ @since: 0.96
+
+ @param data: string of XML data
+ @param namespace: namespace URI
+ @param callback: callback function
+ """
+
+ self.__data = data
+ self.__position = 0
+
+ self.__callback = callback
+ self.__cancelled = False
+
+ self.__dom = None
+
+ # stack of ancestor nodes
+ self.__stack = []
+
+ # stack for resolving namespaces
+ self.__ns_stack = [{"": namespace}]
+
+ self.__parse_document()
+
+
+ def get_dom(self):
+ """
+ Returns the DOM representation of the XML tree. This does not
+ adhere to the DOM specification.
+ @since: 0.96
+
+ @return: DOM tree
+ """
+
+ return self.__dom
+
+
+ def __parse_document(self):
+
+ def f():
+ #while (self.__parser_iteration()): pass
+ if (self.__parser_iteration()):
+ gobject.idle_add(f)
+
+ if (self.__callback):
+ # run async
+ import gobject
+ f()
+
+ else:
+ # run sync
+ while (self.__parser_iteration()): pass
+
+
+ def __parser_iteration(self):
+
+ if (self.__cancelled): return False
+ # find next tag
+ index = self.__data.find("<", self.__position)
+ if (index != -1):
+ # skip comments
+ if (self.__data[index:index + 4] == "<!--"):
+ self.__position = self.__data.find("-->", index) + 3
+ return True
+
+ # read text up to next tag
+ self.__read_text(self.__position, index)
+ self.__position = index
+
+ # check if its an opening or closing tag
+ if (self.__data[index + 1] == "/"):
+ self.__close_tag()
+ else:
+ self.__open_tag()
+
+ return True
+
+ else:
+ return False
+
+
+
+ def __read_text(self, begin, end):
+
+ text = self.__data[begin:end]
+ text = text.replace("\n", " ")
+ text = text.replace("<", "<") \
+ .replace(">", ">") \
+ .replace(""", "\"") \
+ .replace("'", "'") \
+ .replace("&", "&")
+ if (text.strip()):
+ node = _Node("", {})
+ node.set_value(text)
+ self.__push_node(node)
+ self.__pop_node()
+
+
+ def __parse_tag(self, tag):
+
+ # normalize
+ tag = tag.strip()
+ if (tag[-1] == "/"): tag = tag[:-1]
+ tag = tag.replace("\n", " ")
+
+ # read tag name
+ idx = tag.find(" ")
+ if (idx > 0):
+ tagname = tag[:idx]
+ tag = tag[idx:].strip()
+ else:
+ tagname = tag
+ tag = ""
+
+ # read attributes
+ # XML makes it easy to parse this since escaped quotes don't contain the
+ # quote character and since every value must be enclosed in quotes
+ attrs = {}
+ while (tag):
+ # key
+ idx = tag.find("=")
+ if (idx == -1): break
+ key = tag[:idx].strip()
+ tag = tag[idx + 1:].strip()
+
+ # value
+ quote = tag[0]
+ idx = tag.find(quote, 1)
+ if (idx == -1): break
+ value = tag[1:idx]
+ tag = tag[idx + 1:].strip()
+
+ attrs[key] = value
+ #end while
+
+ return (tagname, attrs)
+
+
+ def __lookup_namespace(self, ns):
+
+ for i in range(len(self.__ns_stack) - 1, 0, -1):
+ ns_table = self.__ns_stack[i]
+ if (ns in ns_table):
+ return ns_table[ns]
+ #end for
+
+ return ns
+
+
+ def __resolve_namespaces(self, tagname, attrs):
+
+ ns_table = {}
+ self.__ns_stack.append(ns_table)
+
+ # check attrs for new namespace definitions
+ for key, value in attrs.items():
+ if (key.startswith("xmlns:")):
+ ns_name = key.split(":")[1]
+ ns_table[ns_name] = value
+ elif (key == "xmlns"):
+ ns_table[""] = value
+ #end for
+
+ # check tagname
+ if (":" in tagname):
+ ns_name, tagname = tagname.split(":")
+ ns = self.__lookup_namespace(ns_name)
+ if (ns):
+ tagname = "{" + ns + "}" + tagname
+ else:
+ ns = self.__lookup_namespace("")
+ if (ns):
+ tagname = "{" + ns + "}" + tagname
+ #end if
+
+ # check attrs
+ items = attrs.items()
+ for k, v in items:
+ if (":" in k):
+ ns, key = k.split(":")
+ key = self.__lookup_namespace(ns) + ":" + key
+ del attrs[k]
+ attrs[key] = v
+ else:
+ ns = self.__lookup_namespace("")
+ if (ns):
+ key = "{" + ns + "}" + k
+ del attrs[k]
+ attrs[key] = v
+ #end if
+ #end for
+
+ return (tagname, attrs)
+
+
+ def __open_tag(self):
+
+ index = self.__data.find(">", self.__position + 1)
+ tagdata = self.__data[self.__position + 1:index]
+ self.__position = index + 1
+ # skip control tags
+ if (tagdata[0] == "?"):
+ return
+
+ tagname, attrs = self.__parse_tag(tagdata)
+ tagname, attrs = self.__resolve_namespaces(tagname, attrs)
+
+ if (tagname[-1] == "/"): tagname = tagname[:-1]
+
+ node = _Node(tagname, attrs)
+ self.__push_node(node)
+
+ if (self.__data[index - 1] == "/"):
+ self.__pop_node()
+ self.__ns_stack.pop()
+
+
+ def __close_tag(self):
+
+ index = self.__data.find(">", self.__position + 1)
+ #tagname = self.__data[self.__position + 2:index].rstrip()
+
+ node = self.__pop_node()
+ self.__ns_stack.pop()
+ self.__position = index + 1
+
+ if (self.__callback):
+ try:
+ v = self.__callback(node)
+ if (not v): self.__cancelled = True
+ except:
+ import traceback; traceback.print_exc()
+
+
+ def __push_node(self, node):
+
+ if (self.__stack):
+ parent = self.__stack[-1]
+ else:
+ parent = None
+
+ if (parent):
+ parent.add_child(node)
+ else:
+ self.__dom = node
+
+ self.__stack.append(node)
+
+
+ def __pop_node(self):
+
+ return self.__stack.pop()
+
+
+
+
+class _Node(object):
+ """
+ Class for representing a node in the DOM.
+ @since: 0.96
+ """
+
+ __slots__ = ["__tagname", "__attrs", "__children", "__value"]
+
+ def __init__(self, tagname, attrs):
+ """
+ Creates a new node with the given tagname and attributes.
+ Pass an empty tagname for PCDATA nodes.
+ @since: 0.96
+ """
+
+ self.__tagname = tagname
+ self.__attrs = attrs
+ self.__children = []
+ self.__value = ""
+
+
+ def add_child(self, child):
+ """
+ Adds a child node to this node.
+ @since: 0.96
+ """
+
+ self.__children.append(child)
+
+ def get_name(self):
+ """
+ Returns the node name. The name is prefixed by its namespace, if any.
+ PCDATA nodes have an empty node name.
+ @since: 0.96
+ """
+
+ return self.__tagname
+
+
+ def get_attrs(self):
+ """
+ Returns the attributes of this node as a dictionary.
+ @since: 0.96
+ """
+
+ return self.__attrs
+
+
+ def get_attr(self, key):
+ """
+ Returns the value of the given attribute.
+ Raises a KeyError if the attribute does not exist.
+ @since: 0.96
+ """
+
+ return self.__attrs[key]
+
+
+ def get_children(self):
+ """
+ Returns a list of this node's child nodes.
+ @since: 0.96
+ """
+
+ return self.__children
+
+
+ def get_child(self, name = ""):
+ """
+ Convenience method for returning the child node with the given name,
+ or the first child node if no name is given.
+ Returns None if the child does not exist.
+ @since: 0.96
+ """
+
+ if (not name):
+ return self.__children[0]
+ else:
+ for c in self.__children:
+ if (name == c.get_name()):
+ return c
+ return None
+
+
+ def get_pcdata(self, name = ""):
+ """
+ Convenience function for returning the value of the PCDATA child
+ node of this node.
+ Returns "" if PCDATA is not available.
+ @since: 0.96
+ """
+
+ try:
+ if (name):
+ node = self.get_child(name)
+ else:
+ node = self
+ return node.get_child().get_value()
+ except:
+ #import traceback; traceback.print_exc()
+ return ""
+
+
+ def set_value(self, value):
+ """
+ Sets the PCDATA value.
+ @since: 0.96
+ """
+ self.__value = value
+
+
+ def get_value(self):
+ """
+ Returns the PCDATA value.
+ @since: 0.96
+ """
+
+ return self.__value
+
+
+ def _dump(self, indent = 0):
+ """
+ Recursively dumps the subtree beginning at this node.
+ @since: 0.96
+ """
+
+ out = ""
+ if (self.__tagname):
+ out += " " * indent
+ out += "<%s" % self.__tagname
+ attr_indent = indent + 1 + len(self.__tagname) + 1
+ cnt = 0
+ for k, v in self.__attrs.items():
+ if (cnt > 0):
+ out += " " * attr_indent
+ out += " %s=\"%s\"\n" % (k, v)
+ cnt += 1
+ out += ">\n"
+ for c in self.__children:
+ out += c._dump(indent + 2)
+ out += " " * indent
+ out += "</%s>\n" % self.__tagname
+ else:
+ out += " " * indent
+ out += self.__value + "\n"
+
+ return out
+
+
+ def __str__(self):
+
+ return self._dump()
+
+
+
+
+if (__name__ == "__main__"):
+ import sys
+ _xml = open(sys.argv[1]).read()
+ _m = MiniXML(_xml)
+ print _m.get_dom()
+
--- /dev/null
+"""
+Various utility classes and modules.
+"""
--- /dev/null
+"""
+This module implements gconf functionality on systems where the gconf Python
+bindings are not available. It calls C{gconftool-2} instead.
+@since: 0.96
+"""
+
+
+import commands
+
+
+_GCONFTOOL = "LANG=C gconftool-2"
+
+VALUE_BOOL = "bool"
+VALUE_FLOAT = "float"
+VALUE_INT = "int"
+VALUE_LIST = "list"
+VALUE_PAIR = "pair"
+VALUE_STRING = "string"
+
+
+
+class _Client(object):
+
+ def __init__(self):
+
+ pass
+
+
+ def get_without_default(self, key):
+
+ return self.get_string(key)
+
+
+ def get_list(self, key, ktype):
+
+ fail, out = commands.getstatusoutput("%s --get %s" \
+ % (_GCONFTOOL, key))
+ if (fail):
+ return None
+
+ elif (out.startswith("No value set for ")):
+ return None
+
+ out = out.strip()
+ out = out[1:-1]
+ values = [ v.strip() for v in out.split(",") if v ]
+
+ if (ktype == VALUE_INT):
+ values = [ int(v) for v in values if v.isdigit() ]
+
+ return values
+
+
+ def set_list(self, key, ktype, value):
+
+ if (ktype == VALUE_INT):
+ value = [ str(v) for v in value ]
+ s_value = ",".join(value)
+ #print "%s --type list --list-type %s --set %s \"[%s]\"" \
+ # % (_GCONFTOOL, ktype, key, s_value)
+ fail, out = commands.getstatusoutput("%s --type list --list-type %s --set %s \"[%s]\"" \
+ % (_GCONFTOOL, ktype, key, s_value))
+ #print fail, out
+
+
+ def get_string(self, key):
+
+ fail, out = commands.getstatusoutput("%s --get %s" \
+ % (_GCONFTOOL, key))
+ if (fail):
+ return None
+
+ elif (out.startswith("No value set for ")):
+ return None
+
+ value = out.strip()
+
+ return value
+
+
+ def set_string(self, key, value):
+
+ fail, out = commands.getstatusoutput("%s --type %s --set %s \"%s\"" \
+ % (_GCONFTOOL, VALUE_STRING, key, value))
+
+
+ def get_int(self, key):
+
+ fail, out = commands.getstatusoutput("%s --get %s" \
+ % (_GCONFTOOL, key))
+ if (fail):
+ return None
+
+ elif (out.startswith("No value set for ")):
+ return None
+
+ value = out.strip()
+
+ return int(value)
+
+
+ def set_int(self, key, value):
+
+ fail, out = commands.getstatusoutput("%s --type %s --set %s \"%s\"" \
+ % (_GCONFTOOL, VALUE_INT, key, value))
+
+
+ def get_bool(self, key):
+
+ fail, out = commands.getstatusoutput("%s --get %s" \
+ % (_GCONFTOOL, key))
+ if (fail):
+ return None
+
+ elif (out.startswith("No value set for ")):
+ return None
+
+ value = out.strip()
+
+ return bool(value)
+
+
+ def set_bool(self, key, value):
+
+ fail, out = commands.getstatusoutput("%s --type %s --set %s \"%s\"" \
+ % (_GCONFTOOL, VALUE_BOOL, key, value))
+
+
+ def unset(self, key):
+
+ fail, out = commands.getstatusoutput("%s --unset %s" \
+ % (_GCONFTOOL, key))
+
+
+_singleton = _Client()
+def client_get_default(): return _singleton
+
--- /dev/null
+"""
+Lightweight logging module with formatted output. Logs are written to C{stdout}.
+"""
+
+import time
+import traceback
+
+
+OFF = 0
+"""log level: log nothing"""
+ERROR = 1
+"""log level: log errors"""
+WARNING = 2
+"""log level: log errors and warnings"""
+INFO = 3
+"""log level: log errors, warnings, and info"""
+DEBUG = 4
+"""log level: log errors, warnings, info, and debugging"""
+
+
+_level = ERROR
+
+
+def set_level(level):
+ """
+ Sets the log level. This is a global setting. The default log level
+ is C{ERROR}.
+ @since: 0.96
+
+ @param level: log level
+ """
+ global _level
+
+ _level = level
+
+
+def get_level():
+ """
+ Returns the current log level.
+ @since: 0.96.4
+
+ @return: log level
+ """
+
+ return _level
+
+
+def is_level(level):
+ """
+ Returns whether messages of the given level are logged.
+ @since: 0.96
+
+ @param level: log level
+ @return: whether messages are logged
+ """
+
+ return (_level >= level)
+
+
+def _log(ltype, s):
+
+ now = time.time()
+ msecs = (now - int(now)) * 1000
+ now = time.strftime("%F %T", time.localtime(now))
+ print "%s.%03d - %s ---" % (now, msecs, ltype),
+
+ first_line = True
+ for line in s.splitlines():
+ if (first_line):
+ print line
+ first_line = False
+ else:
+ print "> " + line
+ #end for
+
+
+def error(msg, *args):
+ """
+ @since: 0.96
+ """
+
+ if (_level < ERROR): return
+ if (args):
+ msg = msg % args
+
+ _log("ERROR ", msg)
+
+
+def warning(msg, *args):
+ """
+ @since: 0.96
+ """
+
+ if (_level < WARNING): return
+ if (args):
+ msg = msg % args
+
+ _log("WARNING", msg)
+
+
+def info(msg, *args):
+ """
+ @since: 0.96
+ """
+
+ if (_level < INFO): return
+ if (args):
+ msg = msg % args
+
+ _log("INFO ", msg)
+
+
+def debug(msg, *args):
+ """
+ @since: 0.96
+ """
+
+ if (_level < DEBUG): return
+ if (args):
+ msg = msg % args
+
+ _log("DEBUG ", msg)
+
+
+def stacktrace():
+ """
+ Returns the current stack trace.
+ @since: 0.96
+
+ @return: stack trace
+ """
+
+ return traceback.format_exc()
+
--- /dev/null
+import os
+import time
+
+NAME = "USSD Pad"
+OSSO_NAME = "de.pycage.ussdpad"
+VERSION = "2010.01.05"
+
+AUTHORS = ["Martin Grimme <martin.grimme@lintegra.de>"]
+COPYRIGHT = "\xc2\xa9 2010 Martin Grimme"
+
+USER_DIR = os.path.expanduser("~/.ussdpad")
+APP_DIR = os.path.dirname(__file__)
+
+START_TIME = time.time()