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