1'''
2Defines all plugin-related messaging elements.
3
4@author: Eitan Isaacson
5@organization: Mozilla Foundation
6@copyright: Copyright (c) 2006, 2007 Mozilla Foundation
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 gi.repository import GObject
18from gi.repository import Pango
19
20from accerciser.i18n import _
21
22class MessageManager(GObject.GObject):
23  '''
24  Centralizes all plugin message handling. If the plugin is a visible widget,
25  it displays the message within the plugin. If not it displays the message in
26  a dedicated message tab.
27
28  This manager also could emit module and plugin reload requests from user
29  responses to messages.
30  '''
31  __gsignals__ = {'plugin-reload-request' :
32                    (GObject.SignalFlags.RUN_FIRST,
33                     None,
34                     (GObject.TYPE_PYOBJECT,
35                      GObject.TYPE_PYOBJECT)),
36                  'module-reload-request' :
37                    (GObject.SignalFlags.RUN_FIRST,
38                     None,
39                     (GObject.TYPE_PYOBJECT,
40                      GObject.TYPE_STRING,
41                      GObject.TYPE_STRING))}
42  def __init__(self):
43    '''
44    Initialize the manager.
45    '''
46    GObject.GObject.__init__(self)
47    self.message_tab = None
48
49  def getMessageTab(self):
50    '''
51    Get the manager's dedicated message tab. Initialize a message tab if
52    one does not already exist.
53
54    @return: The message tab.
55    @rtype: L{MessageManager.MessageTab}
56    '''
57    if not self.message_tab:
58      self.message_tab = self.MessageTab()
59    return self.message_tab
60
61  def newPluginError(self, plugin_instance, plugin_class,
62                     error_message, details):
63    '''
64    Create a new plugin error message, and display it eithe in the plugin
65    itself or in the error tab.
66
67    @param plugin_instance: Plugin instance that had the error.
68    @type plugin_instance: L{Plugin}
69    @param plugin_class: Plugin class.
70    @type plugin_class: type
71    @param error_message: Principal error message.
72    @type error_message: string
73    @param details: Detailed error message.
74    @type details: string
75
76    @return: The newly created error message.
77    @rtype: L{PluginErrorMessage}
78    '''
79    message = PluginErrorMessage(error_message, details)
80    message.connect('response', self._onPluginResponseRefresh, plugin_class)
81    if getattr(plugin_instance, 'parent', None):
82      plugin_instance.message_area.pack_start(message, True, True, 0)
83      message.show_all()
84    else:
85      self.message_tab.addMessage(message)
86    return message
87
88  def _onPluginResponseRefresh(self, message, response_id, plugin_class):
89    '''
90    Callback for gtk.RESPONSE_APPLY of a plugin error message, emits a plugin
91    reload request signal.
92
93    @param message: Error message that emitted response signal.
94    @type message: L{PluginErrorMessage}
95    @param response_id: The response ID.
96    @type response_id: integer
97    @param plugin_class: The plugin class of the failed plugin.
98    @type plugin_class: type
99    '''
100    if response_id == gtk.ResponseType.APPLY:
101      self.emit('plugin-reload-request', message, plugin_class)
102
103  def newModuleError(self, module, path, error_message, details):
104    '''
105    Create a new module error dialog. Usually because of a syntax error
106    in a module. Put error message in message tab.
107
108    @param module: Failed module name.
109    @type module: string
110    @param path: Failed module's path.
111    @type path: string
112    @param error_message: Principal error message.
113    @type error_message: string
114    @param details: Detailed error message.
115    @type details: string
116
117    @return: The newly created error message.
118    @rtype: L{PluginErrorMessage}
119    '''
120    message = PluginErrorMessage(error_message, details)
121    message.connect('response', self._onModuleResponseRefresh, module, path)
122    self.message_tab.addMessage(message)
123    return message
124
125  def _onModuleResponseRefresh(self, message, response_id, module, path):
126    '''
127    Callback for gtk.RESPONSE_APPLY of a module error message, emits a module
128    reload request signal.
129
130
131    @param message: Error message that emitted response signal.
132    @type message: L{PluginErrorMessage}
133    @param response_id: The response ID.
134    @type response_id: integer
135    @param module: Failed module name.
136    @type module: string
137    @param path: Failed module's path.
138    @type path: string
139    '''
140    if response_id == gtk.ResponseType.APPLY:
141      self.emit('module-reload-request', message, module, path)
142
143  class MessageTab(gtk.ScrolledWindow):
144    '''
145    Implements a scrolled window with a vbox for messages that cannot be
146    displayed in their plugins
147    '''
148    def __init__(self):
149      '''
150      Initialize tab.
151      '''
152      gtk.ScrolledWindow.__init__(self)
153      self.set_name(_('Plugin Errors'))
154      self._vbox = gtk.VBox()
155      self._vbox.connect('remove', self._onMessageRemove)
156      self.add_with_viewport(self._vbox)
157      self.set_no_show_all(True)
158
159    def addMessage(self, message):
160      '''
161      Add a message to the tab.
162
163      @param message: The message to be added.
164      @type message: L{PluginMessage}
165      '''
166      self._vbox.pack_start(message, False, True, 0)
167      self.show()
168      self._vbox.show_all()
169
170    def removeMessage(self, message):
171      '''
172      Remove a message from the tab. Destroys it.
173
174      @param message: The message to be removed.
175      @type message: L{PluginMessage}
176      '''
177      message.destroy()
178
179    def _onMessageRemove(self, vbox, message):
180      '''
181      Callback for removal of children. If there are no messages displayed,
182      hide this widget.
183
184      @param vbox: Vbox that had a child removed.
185      @type vbox: gtk.VBox
186      @param message: The message that was removed.
187      @type message: L{PluginMessage}
188      '''
189      if len(vbox.get_children()) == 0:
190        self.hide()
191
192class PluginMessage(gtk.Frame):
193  '''
194  Pretty plugin message area that appears either above the plugin if the plugin
195  is realized or in a seperate view.
196
197  @ivar vbox: Main contents container.
198  @type vbox: gtk.VBox
199  @ivar action_area: Area used mainly for response buttons.
200  @type action_area: gtk.VBox
201  @ivar message_style: Tooltip style used for mesages.
202  @type message_style: gtk.Style
203  '''
204  __gsignals__ = {'response' :
205                  (GObject.SignalFlags.RUN_FIRST,
206                   None,
207                   (GObject.TYPE_INT,))}
208  def __init__(self):
209    '''
210    Initialize the message object.
211    '''
212    gtk.Frame.__init__(self)
213    self.vbox = gtk.VBox()
214    self.vbox.set_spacing(3)
215    self.action_area = gtk.VBox()
216    self.action_area.set_homogeneous(True)
217
218    # Get the tooltip style, for use with the message background color.
219    w = gtk.Window()
220    w.set_name('gtk-tooltip')
221    w.ensure_style()
222    #self.message_style = w.rc_get_style()
223    self.message_style = gtk.rc_get_style(w)
224
225    event_box = gtk.EventBox()
226    event_box.set_style(self.message_style)
227    self.add(event_box)
228    hbox = gtk.HBox()
229    event_box.add(hbox)
230    hbox.pack_start(self.vbox, True, True, 3)
231    hbox.pack_start(self.action_area, False, False, 3)
232
233  def add_button(self, button_text, response_id):
234    '''
235    Add a button to the action area that emits a response when clicked.
236
237    @param button_text: The button text, or a stock ID.
238    @type button_text: string
239    @param response_id: The response emitted when the button is pressed.
240    @type response_id: integer
241
242    @return: Return the created button.
243    @rtype: gtk.Button
244    '''
245    button = gtk.Button()
246    button.set_use_stock(True)
247    button.set_label(button_text)
248    button.connect('clicked', self._onActionActivated, response_id)
249    self.action_area.pack_start(button, False, False, 0)
250    return button
251
252  def _onActionActivated(self, button, response_id):
253    '''
254    Callback for button presses that emit the correct response.
255
256    @param button: The button that was clicked.
257    @type button: gtk.Button
258    @param response_id: The response ID to emit a response with.
259    @type response_id: integer
260    '''
261    self.emit('response', response_id)
262
263class PluginErrorMessage(PluginMessage):
264  '''
265  Standard error message.
266  '''
267  def __init__(self, error_message, details):
268    '''
269    Plugin error message.
270
271    @param error_message: The error message.
272    @type error_message: string
273    @param details: Further details about the error.
274    @type details: string
275    '''
276    PluginMessage.__init__(self)
277    hbox = gtk.HBox()
278    hbox.set_spacing(6)
279    self.vbox.pack_start(hbox, False, False, 0)
280    image = gtk.Image()
281    image.set_from_stock(gtk.STOCK_DIALOG_WARNING,
282                         gtk.IconSize.SMALL_TOOLBAR)
283    hbox.pack_start(image, False, False, 0)
284    label = gtk.Label()
285    label.set_ellipsize(Pango.EllipsizeMode.END)
286    label.set_selectable(True)
287    label.set_markup('<b>%s</b>' % error_message)
288    hbox.pack_start(label, True, True, 0)
289    label = gtk.Label(details)
290    label.set_ellipsize(Pango.EllipsizeMode.END)
291    label.set_selectable(True)
292    self.vbox.add(label)
293    self.add_button(gtk.STOCK_CLEAR, gtk.ResponseType.CLOSE)
294    self.add_button(gtk.STOCK_REFRESH, gtk.ResponseType.APPLY)
295    self.connect('response', self._onResponse)
296
297  def _onResponse(self, plugin_message, response_id):
298    '''
299    Destroy the message when the "clear" button is clicked.
300
301    @param plugin_message: Message that emitted this signal.
302    @type plugin_message: L{PluginErrorMessage}
303    @param response_id: The response ID
304    @type response_id: integer
305    '''
306    if response_id == gtk.ResponseType.CLOSE:
307      plugin_message.destroy()
308