Package flumotion :: Package admin :: Package gtk :: Module message
[hide private]

Source Code for Module flumotion.admin.gtk.message

  1  # -*- Mode: Python -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3  # 
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007,2008 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 21   
 22  """a view display messages containing warnings, errors and information.""" 
 23   
 24  import gettext 
 25  import os 
 26  import time 
 27   
 28  import pango 
 29  import gtk 
 30   
 31  from flumotion.common import log 
 32  from flumotion.common.documentation import getMessageWebLink 
 33  from flumotion.common.i18n import Translator 
 34  from flumotion.common.messages import ERROR, WARNING, INFO 
 35  from flumotion.configure import configure 
 36   
 37  _ = gettext.gettext 
 38  __version__ = "$Rev: 7973 $" 
 39  _stock_icons = { 
 40      ERROR: gtk.STOCK_DIALOG_ERROR, 
 41      WARNING: gtk.STOCK_DIALOG_WARNING, 
 42      INFO: gtk.STOCK_DIALOG_INFO, 
 43      } 
 44  _headings = { 
 45      ERROR: _('Error'), 
 46      WARNING: _('Warning'), 
 47      INFO: _('Note'), 
 48      } 
 49   
 50   
51 -class MessageButton(gtk.ToggleButton):
52 """ 53 I am a button at the top right of the message view, representing a message. 54 """ 55
56 - def __init__(self, message):
57 gtk.ToggleButton.__init__(self) 58 59 self.message = message 60 61 i = gtk.Image() 62 i.set_from_stock(_stock_icons.get(message.level, 63 gtk.STOCK_MISSING_IMAGE), 64 gtk.ICON_SIZE_BUTTON) 65 i.show() 66 self.add(i) 67 self.set_focus_on_click(False) 68 self.set_relief(gtk.RELIEF_NONE)
69
70 - def __repr__(self):
71 return '<MessageButton for %s at %d>' % (self.message, id(self))
72 73 74 # instantiated through create_function in glade files 75 76
77 -class MessagesView(gtk.VBox):
78 """ 79 I am a widget that can show messages. 80 """ 81 # I am a vbox with first row the label and icons, 82 # second row a separator 83 # and third row a text view 84
85 - def __init__(self):
86 gtk.VBox.__init__(self) 87 88 self._disableTimestamps = False 89 self.active_button = None 90 91 self._createUI() 92 self.clear() 93 94 self._translator = Translator() 95 localedir = os.path.join(configure.localedatadir, 'locale') 96 # FIXME: add locales as messages from domains come in 97 self._translator.addLocaleDir(configure.PACKAGE, localedir)
98
99 - def _createUI(self):
100 h1 = gtk.HBox() 101 self.pack_start(h1, False, False, 0) 102 self.label = gtk.Label() 103 self.label.show() 104 h1.pack_start(self.label, False, False, 6) 105 106 # button box holding the message icons at the top right 107 h2 = gtk.HBox() 108 h1.pack_end(h2, False, False, 0) 109 self.buttonbox = h2 110 111 s = gtk.HSeparator() 112 self.pack_start(s, False, False, 6) 113 sw = gtk.ScrolledWindow() 114 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) 115 sw.set_shadow_type(gtk.SHADOW_ETCHED_IN) 116 self.pack_start(sw, True, True, 0) 117 118 # text view shows the messages, plus debug information 119 # FIXME: needs to be hyperlinkable in the future 120 tv = gtk.TextView() 121 tv.set_wrap_mode(gtk.WRAP_WORD) 122 tv.set_left_margin(6) 123 tv.set_right_margin(6) 124 tv.set_accepts_tab(False) 125 tv.set_cursor_visible(False) 126 tv.set_editable(False) 127 #tv.set_sensitive(False) 128 # connect signals to act on the hyperlink 129 tv.connect('event-after', self._after_textview__event) 130 tv.connect('motion-notify-event', 131 self._on_textview___motion_notify_event) 132 sw.add(tv) 133 self.textview = tv 134 135 self.show_all()
136
137 - def clear(self):
138 """ 139 Remove all messages and hide myself. 140 """ 141 for child in self.buttonbox.get_children(): 142 self.clearMessage(child.message.id) 143 self.hide()
144
145 - def addMessage(self, m):
146 """ 147 Add a message to me. 148 @type m: L{flumotion.common.messages.Message} 149 """ 150 # clear all previously added messages with the same id. This allows 151 # us to replace for example a "probing" message with the 152 # result message 153 self.clearMessage(m.id) 154 155 # add a message button to show this message 156 b = MessageButton(m) 157 b.sigid = b.connect('toggled', self._on_message_button__toggled, m) 158 b.show() 159 self.buttonbox.pack_start(b, False, False, 0) 160 161 firstButton = self._sortMessages() 162 163 if not self.active_button: 164 b.set_active(True) 165 elif b == firstButton: 166 b.set_active(True) 167 self.show()
168
169 - def clearMessage(self, id):
170 """ 171 Clear all messages with the given id. 172 Will bring the remaining most important message to the front, 173 or hide the view completely if no messages are left. 174 """ 175 for button in self.buttonbox.get_children(): 176 if button.message.id != id: 177 continue 178 179 self.buttonbox.remove(button) 180 button.disconnect(button.sigid) 181 button.sigid = 0 182 if not self.buttonbox.get_children(): 183 self.active_button = None 184 self.hide() 185 elif self.active_button == button: 186 self.active_button = self.buttonbox.get_children()[0] 187 self.active_button.set_active(True) 188 break
189
190 - def disableTimestamps(self):
191 """Disable timestamps for this MessageView, 192 it will make it easier to understand the error messages and 193 make it suitable for end users. 194 """ 195 self._disableTimestamps = True
196 197 # Private 198
199 - def _addMessageToBuffer(self, message):
200 # FIXME: it would be good to have a "Debug" button when 201 # applicable, instead of always showing the text 202 text = self._translator.translate(message) 203 204 textbuffer = gtk.TextBuffer() 205 textbuffer.set_text(text) 206 self.textview.set_buffer(textbuffer) 207 self.label.set_markup('<b>%s</b>' % 208 _headings.get(message.level, _('Message'))) 209 210 # if we have help information, add it to the end of the text view 211 description = message.getDescription() 212 if description: 213 textbuffer.insert(textbuffer.get_end_iter(), ' ') 214 titer = textbuffer.get_end_iter() 215 # we set the 'link' data field on tags to identify them 216 translated = self._translator.translateTranslatable(description) 217 tag = textbuffer.create_tag(translated) 218 tag.set_property('underline', pango.UNDERLINE_SINGLE) 219 tag.set_property('foreground', 'blue') 220 tag.set_data('link', getMessageWebLink(message)) 221 textbuffer.insert_with_tags_by_name(titer, translated, 222 tag.get_property('name')) 223 224 timestamp = message.getTimeStamp() 225 if timestamp and not self._disableTimestamps: 226 text = _("\nPosted on %s.\n") % time.strftime( 227 "%c", time.localtime(timestamp)) 228 textbuffer.insert(textbuffer.get_end_iter(), text) 229 230 if message.debug: 231 text = "\n\n" + _("Debug information:\n") + message.debug + '\n' 232 textbuffer.insert(textbuffer.get_end_iter(), text)
233
234 - def _sortMessages(self):
235 # Sort all messages first by (reverse of) level, then priority 236 children = [(-w.message.level, w.message.priority, w) 237 for w in self.buttonbox.get_children()] 238 children.sort() 239 children.reverse() 240 children = [(i, children[i][2]) for i in range(len(children))] 241 for child in children: 242 self.buttonbox.reorder_child(child[1], child[0]) 243 244 # the first button, e.g. highest priority 245 return children[0][1]
246 247 # Callbacks 248
249 - def _on_message_button__toggled(self, button, message):
250 # on toggling the button, show the message 251 if not button.get_active(): 252 if self.active_button == button: 253 button.set_active(True) 254 return 255 old_active = self.active_button 256 self.active_button = button 257 if old_active and old_active != button: 258 old_active.set_active(False) 259 260 self._addMessageToBuffer(message)
261 262 # when the mouse cursor moves, set the cursor image accordingly 263
264 - def _on_textview___motion_notify_event(self, textview, event):
265 x, y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, 266 int(event.x), int(event.y)) 267 tags = textview.get_iter_at_location(x, y).get_tags() 268 # without this call, further motion notify events don't get 269 # triggered 270 textview.window.get_pointer() 271 272 # if any of the tags is a link, show a hand 273 cursor = None 274 for tag in tags: 275 if tag.get_data('link'): 276 cursor = gtk.gdk.Cursor(gtk.gdk.HAND2) 277 break 278 textview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(cursor) 279 return False
280
281 - def _after_textview__event(self, textview, event):
282 if event.type != gtk.gdk.BUTTON_RELEASE: 283 return False 284 if event.button != 1: 285 return False 286 287 textbuffer = textview.get_buffer() 288 # we shouldn't follow a link if the user has selected something 289 bounds = textbuffer.get_selection_bounds() 290 if bounds: 291 [start, end] = bounds 292 if start.get_offset() != end.get_offset(): 293 return False 294 295 x, y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, 296 int(event.x), int(event.y)) 297 iter = textview.get_iter_at_location(x, y) 298 299 for tag in iter.get_tags(): 300 link = tag.get_data('link') 301 if link: 302 import webbrowser 303 log.debug('messageview', 'opening %s' % link) 304 webbrowser.open(link) 305 break 306 307 return False
308