#! /usr/bin/env python # -*- encoding: utf-8 -*- #sbpython.py #This file is part of sugarbot. #sugarbot is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by #the Free Software Foundation, either version 3 of the License, or #(at your option) any later version. #sugarbot is distributed in the hope that it will be useful, #but WITHOUT ANY WARRANTY; without even the implied warranty of #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #GNU General Public License for more details. #You should have received a copy of the GNU General Public License #along with sugarbot. If not, see . import sys import time import gtk import logging from gtk import gdk import gobject from sbdecorators import * import sugar from sugar import graphics from sugar.graphics.toolcombobox import ToolComboBox class NotSupportedError(NotImplementedError): def __init__(self, widget=None, command=None): self.widget = widget self.command = command def __str__(self): return repr('The widget %s [%s] does not support the command(s) %s' \ % (self.widget, self.widget.__class__, self.command)) class WidgetDoesNotExist(ValueError): def __init__(self, widget=None): self.widget = widget def __str__(self): return repr('The widget %s could not be identified.' % self.widget) class wrappedWidget(object): raiseExceptions = True def __init__(self, widget, name): self.widget = widget self.name = name self.log = logging.getLogger('w(%s)' % name) def __getitem__(self, index): """ Returns the indexed item. For ComboBox and similar items, this will effectively be the n'th object. Uses a helper function for each class type. """ indexMethods = \ { gtk.ComboBox: self.getList_GtkComboBox, ToolComboBox: self.getList_SugarGraphicsCombo, # gtk.Container: self.getList_Container } for classType in indexMethods: if isinstance(self.widget, classType): return indexMethods[classType]()[index] def notSupportedError(self, args): """ Wrapper for raising NotSupportedError exceptions. """ if self.raiseExceptions: raise NotSupportedError, (self.widget, args) return None def supportsSignal(self, signalName): """ Checks to see if a GTK object supports a signal. Checks to see if a GTK object supports a signal, via the gobject.signal_lookup method. Returns True if it is supported, false otherwise. """ return gobject.signal_lookup(signalName, self.widget.__class__) != 0 def click(self): """ Simulates a user click. Simulates a user click by: 1) Emitting a 'clicked' signal 2) Calling the 'activate' method """ self.log.info("Clicking %s" % self.name) widgetClass = self.widget.__class__ clickedSignal = 'clicked' activateMethod = 'activate' # Attempt to emit the 'clicked' signal. if self.supportsSignal(clickedSignal): self.widget.emit(clickedSignal) return True # Attempt to simply 'activate' the widget. elif hasattr(self.widget, activateMethod): getattr(self.widget, activateMethod)() return True # Fail gracefully return self.notSupportedError((clickedSignal, activateMethod)) def getText(self): """ Returns the text in the widget. Returns whatever text is being stored by the widget. This function gives priority to Widget functions in the following order: 1) get_text method 2) label (same as getLabel) 3) title (same as getTitle) """ self.log.info("Getting text for %s" % self.name) getText = "get_text" # Get the text from the widget # try: return self.__simpleGetter(getText) # except NotSupportedError: pass if hasattr(self.widget, getText): return getattr(self.widget, getText)() # Special handling for sugar ToolComboBox if isinstance(self.widget, ToolComboBox): logging.fatal("YES!!!") return self.widget.combo.get_active_item()[0] # Try the label try: return self.label except NotSupportedError: pass # Try the title try: return self.title except NotSupportedError: pass # Fail return self.notSupportedError((getText,"label","title")) def typeText(self, val): """ Simulates user text entry. Simulates a user typing into a widget. Inserts the text at the current insertion location, via: 1) Emits the 'insert-at-cursor' signal. """ self.log.info("Adding text for %s to %s" % (self.name, val)) terminalMethod = "feed_child" insertAtCursor = 'insert-at-cursor' if self.supportsSignal(insertAtCursor): self.widget.emit(insertAtCursor, val) return True # --- TERMINAL.ACTIVITY HACK --- # vte.Terminal-specific typing if hasattr(self.widget, terminalMethod): self.widget.feed_child(val) return True return self.notSupportedError(insertAtCursor, terminalMethod) def setText(self, val): """ Sets the text for the widget. Sets the text for the widget to the user-provided string. Uses the following procedures to attempt to set the text: 1) Calls 'set_text' method 2) Set the 'label' property 3) Set the 'title' property 4) A Terminal-specific method, 'feed_child' 5) Set the text via 'typeText' """ self.log.info("Setting text for %s to %s" % (self.name, val)) setText = "set_text" # --- GENERAL APPROACH --- # Try simply setting the text... try: self.__simpleSetter(setText, val) return True except NotSupportedError: pass # if hasattr(self.widget, setText): # getattr(self.widget, setText)(val) # return True # Try setting the label try: self.label = val return True except NotSupportedError: pass # Try setting the title try: self.title = val return True except NotSupportedError: pass # --- TEXT APPENDING --- # Try to just insert the text at the given point... try: self.typeText(val) return True except NotSupportedError: pass # Fail return self.notSupportedError((setText,"label","title")) def delete(self, numberOfTimes, deleteType): """ Simulates user pressing the 'delete' key. Simulates user pressing the 'delete' key a given number of times, with a flexible deletion type (e.g. characters, words, lines) """ # Note that we must negate numberOfTimes here, because it is # re-negated inside of the 'backspace' call. return backspace(-numberOfTimes, deleteType) def backspace(self, numberOfTimes=1, deleteType=gtk.DELETE_CHARS): """ Simulates the backspace keypress. Simulates the backspace keypress. numberOfTimes times. If numberOfTimes is negative, simulate the 'delete' keypress. """ backspace = 'backspace' deleteFromCursor = 'delete-from-cursor' # Try the 'deleteFromCursor' approach, as it is very flexible in the # number of ways to delete content. if self.supportsSignal(deleteFromCursor): self.widget.emit(deleteFromCursor, deleteType, -numberOfTimes) # Try the standard 'backspace' command. Note that this will not work # for negative quantities. elif self.supportsSignal(backspace) and numberOfTimes >= 0: for i in range(0, numberOfTimes): self.widget.emit(backspace) return True else: return self.notSupportedError((backspace, deleteFromCursor)) def doFocus(self, setFocus=None): """ Either sets or gets the focus for the widget. """ if setFocus is None: return self.widget.flags() & gtk.HAS_FOCUS elif setFocus: self.widget.grab_focus() return True def getInfo(self): """ Returns a bunch of information about the widget. """ def getClasses(object): """ Returns a list containing the class that the object is an instance of, and all of the classes that the instance inherits from. For example: >>> class A: pass >>> class B(A): pass >>> class C(B): pass >>> x = C() >>> list = getClasses(x) >>> list [, , ] >>> for i in list: ... print "Is instance of " + str(i) + "? " + str(isinstance(x,i)) ... Is instance of __main__.C? True Is instance of __main__.B? True Is instance of __main__.A? True """ # If the passed object is an instance of a class, it will have a # __class__ attribute. if hasattr(object,"__class__"): recursionData = [object.__class__] # Include the lowest-level class in the heirarchy tree # Iterate through all of the base classes for _class in object.__class__.__bases__: if not _class is object: recursionData.append(_class) recursionData.extend(getClasses(_class)) # Otherwise, the object -is- a class, and it will have a __bases__ # attribute. elif hasattr(object,"__bases__"): # Iterate through all of the base classes. Note that we do NOT add # the class itself here, as that is only done above, on the first # call. for _class in object.__bases__: recursionData.append(_class) recursionData.extend(getClasses(_class)) return recursionData # ------ BEGIN INFO() METHOD ------- infoStr = "\n%s" % getClasses(self.widget) l = dir(self.widget) for i in l: infoStr+= "\n> %s" % i return infoStr def __simpleGetter(self, method): """ Wrapper for simple 'get' methods Wrapper for simple 'get' methods (like get_label), that fails gracefully if the method is not available for the widget object. """ self.log.info("Getting %s for %s" % (method, self.name)) if hasattr(self.widget, method): return getattr(self.widget, method) return self.notSupportedError(method) def __simpleSetter(self, method, val): """ Wrapper for simple 'set' methods Wrapper for simple 'set' methods (like get_label), that fails gracefully if the method is not available for the widget object. """ self.log.info("Setting %s to %s for %s" % (method, val, self.name)) if hasattr(self.widget, method): getattr(self.widget, method)(val) return True return self.notSupportedError(method) def getTitle(self): """ Returns the widget's title. """ return self.__simpleGetter('get_title') def setTitle(self, val): """ Sets the widget's title """ return self.__simpleSetter('set_title', val) def getLabel(self): """ Returns the widget's label. """ return self.__simpleGetter('get_label') def setLabel(self, val): """ Sets the widget's label """ return self.__simpleSetter('set_label', val) def getListFormat(self, item): """ Converts a ComboBox or ToolComboBox's entries into a list Converts a ComboBox or ToolComboBox's entries into a list. This is useful in enumerating the entries in a ComboBox for selection. """ retVal = [] if isinstance(item, ToolComboBox): retVal = self.getList_GtkComboBox(item) elif isinstance(item, gtk.ComboBox): retVal = self.getList_SugarGraphicsCombo() return retVal def getList_GtkComboBox(self, combo): """ getListFormat handler for gtk.ComboBo objects. """ index = combo.get_active() if index == -1: index = 0 model = combo.get_model() row = model.iter_nth_child(None, index) if not row: return None listOfValues = [] for i in range(0, len(model)): try: item = model[i] listOfValues.append(item) except IndexError: break return listOfValues def getList_SugarGraphicsCombo(self): """ getListFormat handler for sugar.graphics.ComboBox objects. """ tempList = self.getList_GtkComboBox(self.widget.combo) returnList = [] for entry in tempList: returnList.append(entry[1]) return returnList def getSelected_SugarGraphicsCombo(self): """ getSelected handler for sugar.graphics.combo* objects. """ active = self.getSelected_ComboBox(self.widget.combo) if len(active) > 1: return active[1] elif len(active) == 1: return active[0] return active def getSelected_ComboBox(self, combo=None): """ getSelected handler for gtk.ComboBox objects. """ if combo is None: combo = self.widget index = combo.get_active() if index == -1: index = 0 row = combo.get_model().iter_nth_child(None, index) if not row: return None return combo.get_model()[row] def getSelected(self): """ Returns the item selected in a gtk.ComboBox or sugar.graphics.ToolComboBox """ self.log.info("Getting selected entry") # Handle for the ComboBox... if isinstance(self.widget, gtk.ComboBox): return self.getSelected_ComboBox(self.widget) # Special handling for sugar ToolComboBox if isinstance(self.widget, ToolComboBox): return self.getSelected_SugarGraphicsCombo() try: return self.__simpleGetter('get_active_text') except NotSupportedError: pass try: i = self.widget.get_active() mod = self.widget.get_model() return mod[i] except: pass return self.notSupportedError('get_active_text', 'get_model') def setSelected(self, val): """ Sets the item selected in a gtk.ComboBox or sugar.graphics.ToolComboBox """ self.log.info("Setting selected entry") # If textual: Enumerate all of the selections # If numeric: set selected combo = self.widget # Special handling for ToolComboBox if isinstance(self.widget, ToolComboBox): combo = self.widget.combo if isinstance(combo, gtk.ComboBox): # If we were provided with a string, get the numerical offset of # the provided entry. if isinstance(val, str): l = self.getListFormat(combo) if val in l: val = l.index(val) # Use the index to set the active combobox item if isinstance(val, int): combo.set_active(val) return return self.notSupportedError('set_active') text = property(getText, setText) title = property(getTitle, setTitle) label = property(getLabel, setLabel) focus = property(doFocus, doFocus) selected = property(getSelected, setSelected) info = property(getInfo) class widgetRegistry(): gui = None def __init__(self): self.widgets = {} self.log = logging.getLogger('wrappedWidget') self.log.info("Instantiated widgetRegistry") def __getitem__(self, which): """ Allows us to select widgets like a dictionary. """ # If we do not already have the Widget wrapped in a wrappedWidget, # do so now. if which not in self.widgets: widget = self.gui.getWidgetByName(which) self.log.info("Fetched widget %s [%s]" % (which, which.__class__)) if widget is None: raise WidgetDoesNotExist, which wrapped = wrappedWidget(widget, which) self.setWidget(which, wrapped) # Return the wrappedWidget object. return self.widgets[which] def setWidget(self, which, value): self.widgets[which]=value # Create the 'sbwidgets' instance. if not globals().has_key("sbwidgets"): sbwidgets = widgetRegistry()