1''' 2Defines the base classes for all plugins. 3 4@author: Eitan Isaacson 5@organization: IBM Corporation 6@copyright: Copyright (c) 2006, 2007 IBM Corporation 7@license: BSD 8 9All rights reserved. This program and the accompanying materials are made 10available under the terms of the BSD which accompanies this distribution, and 11is available at U{http://www.opensource.org/licenses/bsd-license.php} 12''' 13 14import gi 15 16from gi.repository import Gtk as gtk 17from accerciser.tools import ToolsAccessor 18import traceback 19 20class Plugin(ToolsAccessor): 21 ''' 22 Base class for all plugins. It contains abstract methods for initializing 23 and finalizing a plugin. It also holds a reference to the main L{Node} and 24 listens for 'accessible_changed' events on it. 25 26 @cvar plugin_name: Plugin name. 27 @type plugin_name: string 28 @cvar plugin_name_localized: Translated plugin name. 29 @type plugin_name_localized: string 30 @cvar plugin_description: Plugin description. 31 @type plugin_description: string 32 @cvar plugin_desc_localized: Translated plugin description. 33 @type plugin_desc_localized: string 34 35 @ivar global_hotkeys: A list of tuples containing hotkeys and callbacks. 36 @type global_hotkeys: list 37 @ivar node: An object with a reference to the currently selected 38 accessible in the main treeview. 39 @type node: L{Node} 40 @ivar acc: The curently selected accessible in the main treeview. 41 @type acc: L{Accessibility.Accessible} 42 @ivar _handler: The handler id for the L{Node}'s 'accessible_changed' signal 43 @type _handler: integer 44 ''' 45 plugin_name = None 46 plugin_name_localized = None 47 plugin_description = None 48 plugin_desc_localized = None 49 def __init__(self, node, message_manager): 50 ''' 51 Connects the L{Node}'s 'accessible_changed' signal to a handler. 52 53 @param node: The applications main L{Node} 54 @type node: L{Node} 55 @note: L{Plugin} writers should override L{init} to do initialization, not 56 this method. 57 ''' 58 self._message_manager = message_manager 59 self.global_hotkeys = [] 60 self.node = node 61 self._handler = self.node.connect('accessible_changed', self._onAccChanged) 62 self.acc = self.node.acc 63 64 def init(self): 65 ''' 66 An abstract initialization method. Should be overridden by 67 L{Plugin} authors. 68 ''' 69 pass 70 71 def _close(self): 72 ''' 73 Called by the L{PluginManager} when a plugin needs to be finalized. This 74 method disconnects all signal handlers, and calls L{close} for 75 plugin-specific cleanup 76 ''' 77 self.node.disconnect(self._handler) 78 self.close() 79 80 def close(self): 81 ''' 82 An abstract initialization method. Should be overridden by 83 L{Plugin} authors. 84 ''' 85 pass 86 87 def _onAccChanged(self, node, acc): 88 ''' 89 A signal handler for L{Node}'s 'accessible_changed'. It assigns the 90 currently selected accessible to L{acc}, and calls {onAccChanged} for 91 plugin-specific event handling. 92 93 @param node: Node that emitted the signal. 94 @type node: L{Node} 95 @param acc: The new accessibility object. 96 @type acc: Accessibility.Accessible 97 ''' 98 if not self.isMyApp(acc): 99 self.acc = acc 100 self.onAccChanged(acc) 101 102 def onAccChanged(self, acc): 103 ''' 104 An abstract event handler method that is called when the selected 105 accessible in the main app changes. Should be overridden by 106 L{Plugin} authors. 107 108 @param acc: The new accessibility object. 109 @type acc: Accessibility.Accessible 110 ''' 111 pass 112 113 class _PluginMethodWrapper(object): 114 ''' 115 Wraps all callable plugin attributes so that a nice message is displayed 116 if an exception is raised. 117 ''' 118 def __init__(self, func): 119 ''' 120 Initialize wrapper. 121 122 @param func: Callable to wrap. 123 @type func: callable 124 ''' 125 self.func = func 126 def __call__(self, *args, **kwargs): 127 ''' 128 Involed when instance is called. Mimics the wrapped function. 129 130 @param args: Arguments in call. 131 @type args: list 132 @param kwargs: Key word arguments in call. 133 @type kwargs: dictionary 134 135 @return: Any value that is expected from the method 136 @rtype: object 137 ''' 138 try: 139 return self.func(*args, **kwargs) 140 except Exception as e: 141 if hasattr(self.func, 'im_self') and hasattr(self.func, 'im_class'): 142 message_manager = getattr(self.func.__self__, '_message_manager', None) 143 if not message_manager: 144 raise e 145 message_manager.newPluginError( 146 self.func.__self__, self.func.__self__.__class__, 147 traceback.format_exception_only(e.__class__, e)[0].strip(), 148 traceback.format_exc()) 149 150 def __eq__(self, other): 151 ''' 152 Compare the held function and instance with that held by another wrapper. 153 154 @param other: Another wrapper object 155 @type other: L{_PluginMethodWrapper} 156 157 @return: Whether this func/inst pair is equal to the one in the other 158 wrapper object or not 159 @rtype: boolean 160 ''' 161 try: 162 return self.func == other.func 163 except Exception: 164 return False 165 166 def __hash__(self): 167 return hash(self.func) 168 169 170 171 172class ViewportPlugin(Plugin, gtk.ScrolledWindow): 173 ''' 174 A base class for plugins that need to represent a GUI to the user. 175 176 @ivar viewport: The top viewport of this plugin. 177 @type viewport: gtk.Viewport 178 @ivar message_area: Area for plugin messages, mostly errors. 179 @type message_area: gtk.VBox 180 @ivar plugin_area: Main frame where plugin resides. 181 @type plugin_area: gtk.Frame 182 ''' 183 def __init__(self, node, message_manager): 184 ''' 185 Initialize object. 186 187 @param node: Main application selected accessible node. 188 @type node: L{Node} 189 ''' 190 Plugin.__init__(self, node, message_manager) 191 gtk.ScrolledWindow.__init__(self) 192 193 self.set_policy(gtk.PolicyType.AUTOMATIC, 194 gtk.PolicyType.AUTOMATIC) 195 self.set_border_width(3) 196 self.set_shadow_type(gtk.ShadowType.NONE) 197 self.viewport = gtk.Viewport() 198 vbox = gtk.VBox() 199 self.viewport.add(vbox) 200 self.viewport.connect('set-focus-child', self._onScrollToFocus) 201 self.add(self.viewport) 202 # Message area 203 self.message_area = gtk.VBox() 204 vbox.pack_start(self.message_area, False, False, 0) 205 206 # Plugin area 207 self.plugin_area = gtk.Frame() 208 self.plugin_area.set_shadow_type(gtk.ShadowType.NONE) 209 vbox.pack_start(self.plugin_area, True, True, 0) 210 211 def _onScrollToFocus(self, container, widget): 212 ''' 213 Scrolls a focused child widget in viewport into view. 214 215 @param container: Viewport with child focus change. 216 @type container: gtk.Viewport 217 @param widget: Child widget of container that had a focus change. 218 @type widget: gtk.Widget 219 ''' 220 if widget is None: return 221 child = widget 222 while isinstance(child, gtk.Container) and \ 223 child.get_focus_child() is not None: 224 child = child.get_focus_child() 225 226 x, y = child.translate_coordinates(self.viewport, 0, 0) 227 w, h = child.get_allocation().width, child.get_allocation().height 228 vw, vh = self.viewport.get_allocation().width, self.viewport.get_allocation().height 229 230 adj = self.viewport.get_vadjustment() 231 if y+h > vh: 232 value = adj.get_value() + min((y+h) - vh + 2, y) 233 adj.set_value(value) 234 elif y < 0: 235 adj.set_value(max(adj.get_value() + y - 2, adj.get_lower())) 236 237 def _onMessageResponse(self, error_message, response_id): 238 ''' 239 Standard response callback for error messages. 240 241 @param error_message: Message that emitted this response. 242 @type error_message: L{PluginErrorMessage} 243 @param response_id: response ID 244 @type response_id: integer 245 ''' 246 if response_id == gtk.ResponseType.APPLY: 247 pass 248 elif response_id == gtk.ResponseType.CLOSE: 249 error_message.destroy() 250 251class ConsolePlugin(ViewportPlugin): 252 ''' 253 A base class for plugins that provides a simple console view where textual 254 information could be displayed to the user. 255 ''' 256 257 def __init__(self, node, message_manager): 258 ''' 259 Sets a few predefined settings for the derivative L{gtk.TextView}. 260 261 @param node: Application's main accessibility selection. 262 @type node: L{Node} 263 ''' 264 ViewportPlugin.__init__(self, node, message_manager) 265 self.text_view = gtk.TextView() 266 self.text_view.set_editable(False) 267 self.text_view.set_cursor_visible(False) 268 self.plugin_area.add(self.text_view) 269 text_buffer = self.text_view.get_buffer() 270 self.mark = text_buffer.create_mark('scroll_mark', 271 text_buffer.get_end_iter(), 272 False) 273 274 def appendText(self, text): 275 ''' 276 Appends the given text to the L{gtk.TextView} which in turn displays the 277 text in the plugins's console. 278 279 @param text: Text to append. 280 @type text: string 281 ''' 282 text_buffer = self.text_view.get_buffer() 283 text_buffer.insert(text_buffer.get_end_iter(), text) 284 self.text_view.scroll_mark_onscreen(self.mark) 285 286