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  from flumotion.common.pygobject import gsignal 
 37   
 38  _ = gettext.gettext 
 39  __version__ = "$Rev$" 
 40  _stock_icons = { 
 41      ERROR: gtk.STOCK_DIALOG_ERROR, 
 42      WARNING: gtk.STOCK_DIALOG_WARNING, 
 43      INFO: gtk.STOCK_DIALOG_INFO, 
 44      } 
 45  _headings = { 
 46      ERROR: _('Error'), 
 47      WARNING: _('Warning'), 
 48      INFO: _('Note'), 
 49      } 
 50   
 51   
52 -class MessageButton(gtk.ToggleButton):
53 """ 54 I am a button at the top right of the message view, representing a message. 55 """ 56
57 - def __init__(self, message):
58 gtk.ToggleButton.__init__(self) 59 60 self.message = message 61 62 i = gtk.Image() 63 i.set_from_stock(_stock_icons.get(message.level, 64 gtk.STOCK_MISSING_IMAGE), 65 gtk.ICON_SIZE_MENU) 66 i.show() 67 self.add(i) 68 self.set_focus_on_click(False) 69 self.set_relief(gtk.RELIEF_NONE)
70
71 - def __repr__(self):
72 return '<MessageButton for %s at %d>' % (self.message, id(self))
73 74 75 # instantiated through create_function in glade files 76 77
78 -class MessagesView(gtk.VBox):
79 """ 80 I am a widget that can show messages. 81 """ 82 # I am a vbox with first row the label and icons, 83 # second row a separator 84 # and third row a text view 85 gsignal('resize-event', bool) 86
87 - def __init__(self):
88 gtk.VBox.__init__(self) 89 90 self._disableTimestamps = False 91 self.active_button = None 92 93 self._createUI() 94 self.clear() 95 96 self._translator = Translator() 97 localedir = os.path.join(configure.localedatadir, 'locale') 98 # FIXME: add locales as messages from domains come in 99 self._translator.addLocaleDir(configure.PACKAGE, localedir)
100
101 - def _createUI(self):
102 h1 = gtk.HBox() 103 self.pack_start(h1, False, False, 0) 104 105 self.hline = gtk.HSeparator() 106 h1.pack_start(self.hline, True, True, 3) 107 # button box holding the message icons at the top right 108 h2 = gtk.HBox() 109 h1.pack_end(h2, False, False, 0) 110 self.buttonbox = h2 111 112 sw = gtk.ScrolledWindow() 113 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) 114 sw.set_shadow_type(gtk.SHADOW_ETCHED_IN) 115 self.pack_start(sw, True, True, 0) 116 self.sw = sw 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 self.show() 164 if not self.active_button: 165 b.set_active(True) 166 elif b == firstButton: 167 b.set_active(True)
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 208 # if we have help information, add it to the end of the text view 209 description = message.getDescription() 210 if description: 211 textbuffer.insert(textbuffer.get_end_iter(), ' ') 212 titer = textbuffer.get_end_iter() 213 # we set the 'link' data field on tags to identify them 214 translated = self._translator.translateTranslatable(description) 215 tag = textbuffer.create_tag(translated) 216 tag.set_property('underline', pango.UNDERLINE_SINGLE) 217 tag.set_property('foreground', 'blue') 218 tag.set_data('link', getMessageWebLink(message)) 219 textbuffer.insert_with_tags_by_name(titer, translated, 220 tag.get_property('name')) 221 222 timestamp = message.getTimeStamp() 223 if timestamp and not self._disableTimestamps: 224 text = _("\nPosted on %s.\n") % time.strftime( 225 "%c", time.localtime(timestamp)) 226 textbuffer.insert(textbuffer.get_end_iter(), text) 227 228 if message.debug: 229 text = "\n\n" + _("Debug information:\n") + message.debug + '\n' 230 textbuffer.insert(textbuffer.get_end_iter(), text)
231
232 - def _sortMessages(self):
233 # Sort all messages first by (reverse of) level, then priority 234 children = [(-w.message.level, w.message.priority, w) 235 for w in self.buttonbox.get_children()] 236 children.sort() 237 children.reverse() 238 children = [(i, children[i][2]) for i in range(len(children))] 239 for child in children: 240 self.buttonbox.reorder_child(child[1], child[0]) 241 242 # the first button, e.g. highest priority 243 return children[0][1]
244 245 # Callbacks 246
247 - def _on_message_button__toggled(self, button, message):
248 # on toggling the button, show the message 249 if not button.get_active(): 250 if self.active_button == button: 251 self.sw.hide() 252 self.hline.hide() 253 button.set_active(False) 254 self.active_button = None 255 self.emit('resize-event', True) 256 return 257 old_active = self.active_button 258 self.active_button = button 259 if old_active and old_active != button: 260 old_active.set_active(False) 261 262 self._addMessageToBuffer(message) 263 self.show_all() 264 self.emit('resize-event', False)
265 266 # when the mouse cursor moves, set the cursor image accordingly 267
268 - def _on_textview___motion_notify_event(self, textview, event):
269 x, y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, 270 int(event.x), int(event.y)) 271 tags = textview.get_iter_at_location(x, y).get_tags() 272 # without this call, further motion notify events don't get 273 # triggered 274 textview.window.get_pointer() 275 276 # if any of the tags is a link, show a hand 277 cursor = None 278 for tag in tags: 279 if tag.get_data('link'): 280 cursor = gtk.gdk.Cursor(gtk.gdk.HAND2) 281 break 282 textview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(cursor) 283 return False
284
285 - def _after_textview__event(self, textview, event):
286 if event.type != gtk.gdk.BUTTON_RELEASE: 287 return False 288 if event.button != 1: 289 return False 290 291 textbuffer = textview.get_buffer() 292 # we shouldn't follow a link if the user has selected something 293 bounds = textbuffer.get_selection_bounds() 294 if bounds: 295 [start, end] = bounds 296 if start.get_offset() != end.get_offset(): 297 return False 298 299 x, y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, 300 int(event.x), int(event.y)) 301 iter = textview.get_iter_at_location(x, y) 302 303 for tag in iter.get_tags(): 304 link = tag.get_data('link') 305 if link: 306 import webbrowser 307 log.debug('messageview', 'opening %s' % link) 308 webbrowser.open(link) 309 break 310 311 return False
312