~jconti/recent-notifications/trunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
"""
MessageItem.py
March 28, 2011

A widget to represent a notification.
"""

import logging

from gi.repository import GLib, GObject, Gtk, Pango

import Icon
import Timestamp

from RoundedWidgets import RoundedBox, RoundedIcon
from Theme import Color
from Translations import _

logger = logging.getLogger("MessageItem")

def create_label():
  label = Gtk.Label()
  label.set_use_markup(True)
  label.set_selectable(True)
  label.set_line_wrap(True)
  label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR)
  label.set_alignment(0, 0)
  label.set_size_request(300, -1)
  return label

def escape_text(text):
  return GLib.markup_escape_text(text, len(text))

class MessageItem(RoundedBox):
  """A widget to represent a notification"""
  def __init__(self, message):
    RoundedBox.__init__(self)

    self.set_border_width(2)
    self.set_name("message_item")

    self._unread = True

    self._message = message
    self._message_icon_size = 48

    self._hbox = Gtk.HBox()
    self._hbox.set_border_width(10)
    self.add(self._hbox)

    self._image = RoundedIcon(radius = 5)
    self._hbox.pack_start(self._image, False, False, 10)
    self.update_image()

    self._vbox = Gtk.VBox()
    self._hbox.pack_start(self._vbox, True, True, 0)

    self._summary_hbox = Gtk.HBox()
    self._vbox.pack_start(self._summary_hbox, False, False, 1)

    self._image_unread = Gtk.Image()
    self._summary_hbox.pack_start(self._image_unread, False, False, 5)

    try:
      pixbuf = Icon.load("notification-new", 16)
    except:
      logger.exception("Failed to load notification-new icon.")
    else:
      self._image_unread.set_from_pixbuf(pixbuf)

    self._label_summary = create_label()
    self._label_summary.set_name("message_label")
    self._summary_hbox.pack_start(self._label_summary, True, True, 0)

    self._label_body = create_label()
    self._label_body.set_name("message_label")
    logger.debug("FG: {0}".format(self._label_body.style.fg))
    self._vbox.pack_start(self._label_body, True, True, 1)

    self._label_info = create_label()
    self._label_info.set_name("message_label")
    self._vbox.pack_start(self._label_info, False, False, 0)
    self.update_labels()

  def get_app_name(self):
    """Returns the app_name of this item"""
    return self._message.app_name

  def escape_body(self, text):
    """Text needs to be escaped in case it contains characters that will cause errors
    parsing the markup, but some clients send text that is already escaped. This causes
    messages to have &, <, and > strings instead of the appropriate characters.
    This method replaces those with the appropriate characters and then escapes the text.
    TODO: Create a better HTML/entity parser. 
    """
    result = text.replace("&", "&")
    result = result.replace("'", "'")
    result = result.replace(">", ">")
    result = result.replace("&lt;", "<")
    result = result.replace("&quot;", '"')
    return self.markup_links(escape_text(result))

  def is_unread(self):
    """Get the unread status of this message"""
    return self._unread

  def set_unread(self, unread):
    """Sets the unread status of this messages and the visibility of the icon"""
    if unread and not self._unread:
      self._unread = True
      self._image_unread.show()
      self._summary_hbox.pack_start(self._image_unread, False, False, 5)
      self._summary_hbox.reorder_child(self._image_unread, 0)

    elif not unread and self._unread:
      self._unread = False
      self._image_unread.hide()
      self._summary_hbox.remove(self._image_unread)

  def set_width(self, width):
    """Updates the wrap width of the labels in this message item, minus the icon
    size and various padding."""
    new_width = width - self._message_icon_size - 60
    self._label_summary.set_size_request(new_width, -1)
    self._label_body.set_size_request(new_width, -1)
    self._label_info.set_size_request(new_width, -1)

  def markup_links(self, text):
    """Filters escaped text for web links and makes them clickable"""
    result = []
    for s in text.split():
      if s.startswith("http://"):
        name = s
        url = s
        result.append('<a href="{0}">{1}</a>'.format(url, name))
      elif s.startswith("www."):
        name = s
        url = "http://" + s
        result.append('<a href="{0}">{1}</a>'.format(url, name))
      else:
        result.append(s)

    return " ".join(result)

  def update_image(self):
    """Updates the image"""
    try:
      pixbuf = self._message.get_icon(self._message_icon_size)
    except:
      logger.exception("Failed to get icon: {0}".format(self._message.app_icon))
    else:
      self._image.set_from_pixbuf(pixbuf)

  def update_labels(self):
    """Updates the label markup"""
    summary = escape_text(self._message.summary.encode("utf8"))
    body = self.escape_body(self._message.body.encode("utf8"))
    timestamp = escape_text(Timestamp.locale_datetime(self._message.timestamp))
    app_name = escape_text(self._message.app_name)

    self._label_summary.set_markup("<b>{0}</b>".format(summary))
    self._label_body.set_markup(body)
    self._label_info.set_markup("<small><i>{0} {1} <b>{2}</b></i></small>".format(timestamp,
      _("from"), app_name))