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