initial import of ussd-pad
authorpycage <martin.grimme@gmail.com>
Thu, 7 Jan 2010 17:58:40 +0000 (17:58 +0000)
committerpycage <martin.grimme@gmail.com>
Thu, 7 Jan 2010 17:58:40 +0000 (17:58 +0000)
git-svn-id: file:///svnroot/ussd-widget/trunk@11 d197f4d6-dc93-42ad-8354-0da1f58e353f

55 files changed:
ussd-pad/src/opt/ussd-pad/USSDPad.py [new file with mode: 0755]
ussd-pad/src/opt/ussd-pad/com/Component.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/com/Container.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/com/Mediator.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/com/MessageBus.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/com/__init__.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/com/exc.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/com/msgs.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/components/core/Initialiser.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/components/core/__init__.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/components/core/__messages__.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/components/gui/MainWindow.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/components/gui/__init__.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/components/ussd/USSDService.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/components/ussd/__init__.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/components/ussd/__messages__.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/platforms/__init__.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/platforms/computer.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/platforms/htpc.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/platforms/maemo4.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/platforms/maemo5.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/platforms/mer.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/theme/Color.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/theme/Font.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/theme/Pixbuf.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/theme/__init__.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/theme/default/PREVIEW.png [new file with mode: 0755]
ussd-pad/src/opt/ussd-pad/theme/default/info [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/theme/default/theme.def [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/theme/default/ui_button_1.png [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/theme/default/ui_button_2.png [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/Button.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/ImageButton.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/Label.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/Pixmap.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/Widget.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/Window.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/__init__.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/dialog/InputDialog.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/dialog/ListDialog.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/dialog/OptionDialog.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/dialog/__init__.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/layout/Arrangement.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/layout/Box.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/layout/HBox.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/layout/VBox.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/layout/__init__.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/ui/pixbuftools.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/utils/Config.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/utils/EventEmitter.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/utils/MiniXML.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/utils/__init__.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/utils/gconftool.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/utils/logging.py [new file with mode: 0644]
ussd-pad/src/opt/ussd-pad/values.py [new file with mode: 0644]

diff --git a/ussd-pad/src/opt/ussd-pad/USSDPad.py b/ussd-pad/src/opt/ussd-pad/USSDPad.py
new file mode 100755 (executable)
index 0000000..04cd42e
--- /dev/null
@@ -0,0 +1,48 @@
+#! /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()
+
diff --git a/ussd-pad/src/opt/ussd-pad/com/Component.py b/ussd-pad/src/opt/ussd-pad/com/Component.py
new file mode 100644 (file)
index 0000000..b30df0c
--- /dev/null
@@ -0,0 +1,82 @@
+"""
+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)
+
diff --git a/ussd-pad/src/opt/ussd-pad/com/Container.py b/ussd-pad/src/opt/ussd-pad/com/Container.py
new file mode 100644 (file)
index 0000000..07e8ce2
--- /dev/null
@@ -0,0 +1,178 @@
+"""
+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)
+
diff --git a/ussd-pad/src/opt/ussd-pad/com/Mediator.py b/ussd-pad/src/opt/ussd-pad/com/Mediator.py
new file mode 100644 (file)
index 0000000..3bcfac8
--- /dev/null
@@ -0,0 +1,113 @@
+"""
+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)
+        
diff --git a/ussd-pad/src/opt/ussd-pad/com/MessageBus.py b/ussd-pad/src/opt/ussd-pad/com/MessageBus.py
new file mode 100644 (file)
index 0000000..036f1e5
--- /dev/null
@@ -0,0 +1,144 @@
+"""
+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
diff --git a/ussd-pad/src/opt/ussd-pad/com/__init__.py b/ussd-pad/src/opt/ussd-pad/com/__init__.py
new file mode 100644 (file)
index 0000000..9ec0ab0
--- /dev/null
@@ -0,0 +1,12 @@
+"""
+Component Subsystem
+===================
+
+Component subsystem for extending the application.
+@since: 0.96
+"""
+
+import msgs
+from exc import *
+from Component import Component
+from Container import Container
diff --git a/ussd-pad/src/opt/ussd-pad/com/exc.py b/ussd-pad/src/opt/ussd-pad/com/exc.py
new file mode 100644 (file)
index 0000000..1627706
--- /dev/null
@@ -0,0 +1,6 @@
+"""
+Exceptions used by the component subsystem.
+"""
+
+class ServiceNotAvailableError(StandardError): pass
+"""The requested service is not available."""
diff --git a/ussd-pad/src/opt/ussd-pad/com/msgs.py b/ussd-pad/src/opt/ussd-pad/com/msgs.py
new file mode 100644 (file)
index 0000000..cac081a
--- /dev/null
@@ -0,0 +1,79 @@
+"""
+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
+
diff --git a/ussd-pad/src/opt/ussd-pad/components/core/Initialiser.py b/ussd-pad/src/opt/ussd-pad/components/core/Initialiser.py
new file mode 100644 (file)
index 0000000..0086352
--- /dev/null
@@ -0,0 +1,18 @@
+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)
+        
diff --git a/ussd-pad/src/opt/ussd-pad/components/core/__init__.py b/ussd-pad/src/opt/ussd-pad/components/core/__init__.py
new file mode 100644 (file)
index 0000000..03cc781
--- /dev/null
@@ -0,0 +1,11 @@
+def get_classes():
+
+    from Initialiser import Initialiser
+
+    return [Initialiser]
+
+
+
+import __messages__
+messages = [ m for m in dir(__messages__) if not m.startswith("__") ]
+
diff --git a/ussd-pad/src/opt/ussd-pad/components/core/__messages__.py b/ussd-pad/src/opt/ussd-pad/components/core/__messages__.py
new file mode 100644 (file)
index 0000000..dd80fe6
--- /dev/null
@@ -0,0 +1,172 @@
+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
+"""
+
diff --git a/ussd-pad/src/opt/ussd-pad/components/gui/MainWindow.py b/ussd-pad/src/opt/ussd-pad/components/gui/MainWindow.py
new file mode 100644 (file)
index 0000000..93fadc4
--- /dev/null
@@ -0,0 +1,122 @@
+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 " ")
+
diff --git a/ussd-pad/src/opt/ussd-pad/components/gui/__init__.py b/ussd-pad/src/opt/ussd-pad/components/gui/__init__.py
new file mode 100644 (file)
index 0000000..c713e62
--- /dev/null
@@ -0,0 +1,5 @@
+def get_classes():
+
+    from MainWindow import MainWindow
+    return [MainWindow]
+
diff --git a/ussd-pad/src/opt/ussd-pad/components/ussd/USSDService.py b/ussd-pad/src/opt/ussd-pad/components/ussd/USSDService.py
new file mode 100644 (file)
index 0000000..bab9b4e
--- /dev/null
@@ -0,0 +1,51 @@
+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"
+
diff --git a/ussd-pad/src/opt/ussd-pad/components/ussd/__init__.py b/ussd-pad/src/opt/ussd-pad/components/ussd/__init__.py
new file mode 100644 (file)
index 0000000..7bd91eb
--- /dev/null
@@ -0,0 +1,9 @@
+def get_classes():
+
+    from USSDService import USSDService
+    return [USSDService]
+    
+    
+import __messages__
+messages = [ m for m in dir(__messages__) if not m.startswith("__") ]
+
diff --git a/ussd-pad/src/opt/ussd-pad/components/ussd/__messages__.py b/ussd-pad/src/opt/ussd-pad/components/ussd/__messages__.py
new file mode 100644 (file)
index 0000000..e667c22
--- /dev/null
@@ -0,0 +1,2 @@
+def USSD_SVC_SEND(ussd_code): pass
+
diff --git a/ussd-pad/src/opt/ussd-pad/platforms/__init__.py b/ussd-pad/src/opt/ussd-pad/platforms/__init__.py
new file mode 100644 (file)
index 0000000..e751ea7
--- /dev/null
@@ -0,0 +1,57 @@
+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)
+
diff --git a/ussd-pad/src/opt/ussd-pad/platforms/computer.py b/ussd-pad/src/opt/ussd-pad/platforms/computer.py
new file mode 100644 (file)
index 0000000..35718a6
--- /dev/null
@@ -0,0 +1,6 @@
+PLATFORM = "computer"
+
+
+def get_product_code():
+
+    return "?"
diff --git a/ussd-pad/src/opt/ussd-pad/platforms/htpc.py b/ussd-pad/src/opt/ussd-pad/platforms/htpc.py
new file mode 100644 (file)
index 0000000..31fb530
--- /dev/null
@@ -0,0 +1 @@
+PLATFORM = "htpc"
diff --git a/ussd-pad/src/opt/ussd-pad/platforms/maemo4.py b/ussd-pad/src/opt/ussd-pad/platforms/maemo4.py
new file mode 100644 (file)
index 0000000..0ba4d93
--- /dev/null
@@ -0,0 +1,150 @@
+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()
diff --git a/ussd-pad/src/opt/ussd-pad/platforms/maemo5.py b/ussd-pad/src/opt/ussd-pad/platforms/maemo5.py
new file mode 100644 (file)
index 0000000..6cef369
--- /dev/null
@@ -0,0 +1,15 @@
+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)
+
diff --git a/ussd-pad/src/opt/ussd-pad/platforms/mer.py b/ussd-pad/src/opt/ussd-pad/platforms/mer.py
new file mode 100644 (file)
index 0000000..83c3f77
--- /dev/null
@@ -0,0 +1 @@
+PLATFORM = "mer"
diff --git a/ussd-pad/src/opt/ussd-pad/theme/Color.py b/ussd-pad/src/opt/ussd-pad/theme/Color.py
new file mode 100644 (file)
index 0000000..db8c380
--- /dev/null
@@ -0,0 +1,31 @@
+"""
+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
+
diff --git a/ussd-pad/src/opt/ussd-pad/theme/Font.py b/ussd-pad/src/opt/ussd-pad/theme/Font.py
new file mode 100644 (file)
index 0000000..a23a4e8
--- /dev/null
@@ -0,0 +1,33 @@
+"""
+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
diff --git a/ussd-pad/src/opt/ussd-pad/theme/Pixbuf.py b/ussd-pad/src/opt/ussd-pad/theme/Pixbuf.py
new file mode 100644 (file)
index 0000000..5894669
--- /dev/null
@@ -0,0 +1,52 @@
+"""
+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
diff --git a/ussd-pad/src/opt/ussd-pad/theme/__init__.py b/ussd-pad/src/opt/ussd-pad/theme/__init__.py
new file mode 100644 (file)
index 0000000..d31cee2
--- /dev/null
@@ -0,0 +1,237 @@
+"""
+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")
+
diff --git a/ussd-pad/src/opt/ussd-pad/theme/default/PREVIEW.png b/ussd-pad/src/opt/ussd-pad/theme/default/PREVIEW.png
new file mode 100755 (executable)
index 0000000..abd5c9c
Binary files /dev/null and b/ussd-pad/src/opt/ussd-pad/theme/default/PREVIEW.png differ
diff --git a/ussd-pad/src/opt/ussd-pad/theme/default/info b/ussd-pad/src/opt/ussd-pad/theme/default/info
new file mode 100644 (file)
index 0000000..357f416
--- /dev/null
@@ -0,0 +1,4 @@
+name:        Default
+description: Black theme for Maemo5
+author:      Martin Grimme
+
diff --git a/ussd-pad/src/opt/ussd-pad/theme/default/theme.def b/ussd-pad/src/opt/ussd-pad/theme/default/theme.def
new file mode 100644 (file)
index 0000000..9784bc6
--- /dev/null
@@ -0,0 +1,5 @@
+color_ui_background: #141414
+color_ui_text: #efeff1
+
+font_ui_plain:    Nokia Sans 32
+
diff --git a/ussd-pad/src/opt/ussd-pad/theme/default/ui_button_1.png b/ussd-pad/src/opt/ussd-pad/theme/default/ui_button_1.png
new file mode 100644 (file)
index 0000000..c131059
Binary files /dev/null and b/ussd-pad/src/opt/ussd-pad/theme/default/ui_button_1.png differ
diff --git a/ussd-pad/src/opt/ussd-pad/theme/default/ui_button_2.png b/ussd-pad/src/opt/ussd-pad/theme/default/ui_button_2.png
new file mode 100644 (file)
index 0000000..892cfb2
Binary files /dev/null and b/ussd-pad/src/opt/ussd-pad/theme/default/ui_button_2.png differ
diff --git a/ussd-pad/src/opt/ussd-pad/ui/Button.py b/ussd-pad/src/opt/ussd-pad/ui/Button.py
new file mode 100644 (file)
index 0000000..71dd2f7
--- /dev/null
@@ -0,0 +1,32 @@
+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")
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/ImageButton.py b/ussd-pad/src/opt/ussd-pad/ui/ImageButton.py
new file mode 100644 (file)
index 0000000..e96aac9
--- /dev/null
@@ -0,0 +1,123 @@
+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()
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/Label.py b/ussd-pad/src/opt/ussd-pad/ui/Label.py
new file mode 100644 (file)
index 0000000..9699a0c
--- /dev/null
@@ -0,0 +1,232 @@
+"""
+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
+        
diff --git a/ussd-pad/src/opt/ussd-pad/ui/Pixmap.py b/ussd-pad/src/opt/ussd-pad/ui/Pixmap.py
new file mode 100644 (file)
index 0000000..d87e72e
--- /dev/null
@@ -0,0 +1,786 @@
+"""
+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.
+"""
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/Widget.py b/ussd-pad/src/opt/ussd-pad/ui/Widget.py
new file mode 100644 (file)
index 0000000..290315b
--- /dev/null
@@ -0,0 +1,1077 @@
+"""
+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])
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/Window.py b/ussd-pad/src/opt/ussd-pad/ui/Window.py
new file mode 100644 (file)
index 0000000..99a2f73
--- /dev/null
@@ -0,0 +1,397 @@
+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)
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/__init__.py b/ussd-pad/src/opt/ussd-pad/ui/__init__.py
new file mode 100644 (file)
index 0000000..745d1a2
--- /dev/null
@@ -0,0 +1,30 @@
+"""
+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)
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/dialog/InputDialog.py b/ussd-pad/src/opt/ussd-pad/ui/dialog/InputDialog.py
new file mode 100644 (file)
index 0000000..f0d9f67
--- /dev/null
@@ -0,0 +1,57 @@
+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
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/dialog/ListDialog.py b/ussd-pad/src/opt/ussd-pad/ui/dialog/ListDialog.py
new file mode 100644 (file)
index 0000000..2097c1b
--- /dev/null
@@ -0,0 +1,66 @@
+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
diff --git a/ussd-pad/src/opt/ussd-pad/ui/dialog/OptionDialog.py b/ussd-pad/src/opt/ussd-pad/ui/dialog/OptionDialog.py
new file mode 100644 (file)
index 0000000..111061e
--- /dev/null
@@ -0,0 +1,63 @@
+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
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/dialog/__init__.py b/ussd-pad/src/opt/ussd-pad/ui/dialog/__init__.py
new file mode 100644 (file)
index 0000000..f1d5122
--- /dev/null
@@ -0,0 +1,4 @@
+from InputDialog import InputDialog
+from ListDialog import ListDialog
+from OptionDialog import OptionDialog
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/layout/Arrangement.py b/ussd-pad/src/opt/ussd-pad/ui/layout/Arrangement.py
new file mode 100644 (file)
index 0000000..4e76a4c
--- /dev/null
@@ -0,0 +1,234 @@
+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
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/layout/Box.py b/ussd-pad/src/opt/ussd-pad/ui/layout/Box.py
new file mode 100644 (file)
index 0000000..b992ce4
--- /dev/null
@@ -0,0 +1,160 @@
+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")
+        """
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/layout/HBox.py b/ussd-pad/src/opt/ussd-pad/ui/layout/HBox.py
new file mode 100644 (file)
index 0000000..4452fdb
--- /dev/null
@@ -0,0 +1,10 @@
+from Box import Box
+
+
+class HBox(Box):
+
+    def __init__(self):
+    
+        Box.__init__(self)
+        self.set_orientation(self.HORIZONTAL)
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/layout/VBox.py b/ussd-pad/src/opt/ussd-pad/ui/layout/VBox.py
new file mode 100644 (file)
index 0000000..c86b16e
--- /dev/null
@@ -0,0 +1,10 @@
+from Box import Box
+
+
+class VBox(Box):
+
+    def __init__(self):
+    
+        Box.__init__(self)
+        self.set_orientation(self.VERTICAL)
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/layout/__init__.py b/ussd-pad/src/opt/ussd-pad/ui/layout/__init__.py
new file mode 100644 (file)
index 0000000..ab6a09a
--- /dev/null
@@ -0,0 +1,5 @@
+from Box import Box
+from HBox import HBox
+from VBox import VBox
+from Arrangement import Arrangement
+
diff --git a/ussd-pad/src/opt/ussd-pad/ui/pixbuftools.py b/ussd-pad/src/opt/ussd-pad/ui/pixbuftools.py
new file mode 100644 (file)
index 0000000..8c3d854
--- /dev/null
@@ -0,0 +1,149 @@
+"""
+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
+
diff --git a/ussd-pad/src/opt/ussd-pad/utils/Config.py b/ussd-pad/src/opt/ussd-pad/utils/Config.py
new file mode 100644 (file)
index 0000000..5108dd5
--- /dev/null
@@ -0,0 +1,127 @@
+"""
+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)
+
diff --git a/ussd-pad/src/opt/ussd-pad/utils/EventEmitter.py b/ussd-pad/src/opt/ussd-pad/utils/EventEmitter.py
new file mode 100644 (file)
index 0000000..a651eef
--- /dev/null
@@ -0,0 +1,60 @@
+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
+
diff --git a/ussd-pad/src/opt/ussd-pad/utils/MiniXML.py b/ussd-pad/src/opt/ussd-pad/utils/MiniXML.py
new file mode 100644 (file)
index 0000000..9997638
--- /dev/null
@@ -0,0 +1,443 @@
+"""
+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("&lt;", "<") \
+                   .replace("&gt;", ">") \
+                   .replace("&quot;", "\"") \
+                   .replace("&apos;", "'") \
+                   .replace("&amp;", "&")
+        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()
+    
diff --git a/ussd-pad/src/opt/ussd-pad/utils/__init__.py b/ussd-pad/src/opt/ussd-pad/utils/__init__.py
new file mode 100644 (file)
index 0000000..7194527
--- /dev/null
@@ -0,0 +1,3 @@
+"""
+Various utility classes and modules.
+"""
diff --git a/ussd-pad/src/opt/ussd-pad/utils/gconftool.py b/ussd-pad/src/opt/ussd-pad/utils/gconftool.py
new file mode 100644 (file)
index 0000000..60239a5
--- /dev/null
@@ -0,0 +1,137 @@
+"""
+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
+
diff --git a/ussd-pad/src/opt/ussd-pad/utils/logging.py b/ussd-pad/src/opt/ussd-pad/utils/logging.py
new file mode 100644 (file)
index 0000000..b736f02
--- /dev/null
@@ -0,0 +1,134 @@
+"""
+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()
+
diff --git a/ussd-pad/src/opt/ussd-pad/values.py b/ussd-pad/src/opt/ussd-pad/values.py
new file mode 100644 (file)
index 0000000..51b35f8
--- /dev/null
@@ -0,0 +1,14 @@
+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()