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