~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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
"""
MessageWindow.py
by Jason Conti
February 19, 2010

Popup window for viewing notifications.
"""

import gnomeapplet
import gobject
import gtk
import logging

from string import Template

from MessageList import MessageList
from Translations import _

logger = logging.getLogger("MessageWindow")

class Column(object):
  """Maps column names to column numbers."""
  APP_NAME  = 0
  SEPARATOR = 1
  COUNT     = 2

class MessageWindow(gtk.Window):
  """Shows the log of notification messages."""
  def __init__(self, parent):
    gtk.Window.__init__(self)

    self._parent = parent

    self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DOCK)
    self.set_decorated(False)
    self.set_resizable(False)
    self.set_property("skip-taskbar-hint", True)
    self.stick()
    self.set_title(_("Message Window"))
    self.set_border_width(0)
    self.set_size_request(400, 400)

    self._frame = gtk.Frame()
    self._frame.set_border_width(0)
    self._frame.set_shadow_type(gtk.SHADOW_OUT)
    self.add(self._frame)

    self._vadjust = gtk.Adjustment()
    self._vadjust.connect("changed", self.on_vadjust_changed)

    self._vbox = gtk.VBox()
    self._frame.add(self._vbox)

    self._scroll = gtk.ScrolledWindow(vadjustment=self._vadjust)
    self._scroll.set_border_width(5)
    self._scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
    self._scroll.connect("size-allocate", self.on_scroll_resize)
    self._vbox.pack_start(self._scroll, True)

    self._message_list = MessageList(self)
    self._scroll.add(self._message_list)

    # app_name, is_separator, count
    self._combolist = gtk.ListStore(str, bool, int)
    self._combobox = gtk.ComboBox(self._combolist)
    self._combo_app_cell = gtk.CellRendererText()
    self._combobox.pack_start(self._combo_app_cell, True)
    self._combobox.add_attribute(self._combo_app_cell, "text", Column.APP_NAME)
    self._combo_count_cell = gtk.CellRendererText()
    self._combobox.pack_start(self._combo_count_cell, False)
    self._combobox.add_attribute(self._combo_count_cell, "text", Column.COUNT)
    self._combobox.set_row_separator_func(lambda m, i: m.get_value(i, Column.SEPARATOR))
    self._combobox.connect("changed", self.on_combobox_changed)
    self._vbox.pack_start(self._combobox, False)

    self.initialize_combobox()

  def on_combobox_changed(self, combobox, *args):
    """Set the tree filters to the selected app."""
    active = self._combobox.get_active()
    self._message_list.unselect_all()
    if active == 0:
      self._message_list.set_filter_app(None)
    else:
      iter = self._combobox.get_active_iter()
      app_name = self._combolist.get_value(iter, Column.APP_NAME)
      self._message_list.set_filter_app(app_name)

  def on_scroll_resize(self, scroll, allocation, *args):
    """Updates the message list wrap width when the scrolled window resizes."""
    self._message_list.set_wrap_width(allocation.width)

  def on_vadjust_changed(self, adjustment, *args):
    """Reset the adjustment position when the adjustment is changed (resized), but
    not moved, so the user can scroll around freely."""
    self._vadjust.set_value(0)

  def add_message(self, message):
    """Adds a message to the message window."""
    self._message_list.add_message(message)

  def clear_messages(self):
    """Clears all messages from the message window."""
    self._message_list.clear()
    self.initialize_combobox()

  def clear_selections(self):
    """Unselects every row in the treeview and marks the messages as read."""
    self._message_list.mark_as_read()
    self._message_list.unselect_all()

  def show_dropdown(self):
    """Shows the dropdown window"""
    self.show_all()
    self.align_with_parent()

  def hide_dropdown(self):
    """Hides the dropdown window"""
    self.hide_all()
    self.clear_selections()

  def increment_count(self, iter):
    """Increments the message count at the given row."""
    count = self._combolist.get_value(iter, Column.COUNT)
    self._combolist.set_value(iter, Column.COUNT, count + 1)

  def decrement_count(self, iter):
    """Decrements the message count at the given row."""
    count = self._combolist.get_value(iter, Column.COUNT)
    self._combolist.set_value(iter, Column.COUNT, count - 1)

  def initialize_combobox(self):
    """Clear the combobox and set the initial entry."""
    self._combolist.clear()
    self._combolist.append([_("All Applications"), False, 0])
    self._comboapps = {}
    self._combobox.set_active(0)

  def set_message_limit(self, n):
    """Sets the message limit of the MessageList."""
    self._message_list.set_message_limit(n)

  def increment_combobox(self, app_name):
    """Updates the combobox app names and message counts."""
    # Add a separator if there isn't one yet
    if len(self._combolist) == 1:
      self._combolist.append(["Separator", True, 0])

    self.increment_count(self._combolist.get_iter_first())

    if app_name not in self._comboapps:
      iter = self._combolist.append([app_name, False, 1])
      self._comboapps[app_name] = iter
    else:
      self.increment_count(self._comboapps[app_name])

  def decrement_combobox(self, app_name):
    """Updates the combobox app names and message counts."""
    self.decrement_count(self._combolist.get_iter_first())

    if app_name in self._comboapps:
      self.decrement_count(self._comboapps[app_name])

  def align_with_parent(self):
    """Adapted from position_calendar_popup in the GNOME clock applet."""
    # Get the size of the applet button and the size of this window
    applet_x, applet_y = x, y = self._parent.get_origin()
    w, h = self.get_size()
    button_w, button_h = self._parent.get_size()

    screen = self.get_screen()
    monitor = None
    monitor_index = -1

    # Figure out which monitor we are on
    for i in range(screen.get_n_monitors()):
      m = screen.get_monitor_geometry(i)

      if x >= m.x and x <= m.x + m.width and y >= m.y and y <= m.y + m.height:
        monitor = m
        monitor_index = i
        break

    # Probably xinerama
    if monitor == None:
      monitor = gtk.gdk.Rectangle(0, 0, screen.get_width(), screen.get_height())

    orient = self._parent.get_orient()

    # Figure out the x, y coordinates of the window and the gravity depending
    # on the orientation of the panel
    if orient == gnomeapplet.ORIENT_RIGHT:  # Panel on left
      x += button_w
      if (y + h) > monitor.y + monitor.height:
        y -= (y + h) - (monitor.y + monitor.height)

    elif orient == gnomeapplet.ORIENT_LEFT: # Panel on right
      x -= w
      if (y + h) > monitor.y + monitor.height:
        y -= (y + h) - (monitor.y + monitor.height)

    elif orient == gnomeapplet.ORIENT_DOWN: # Panel on top
      y += button_h
      if (x + w) > monitor.x + monitor.width:
        x -= (x + w) - (monitor.x + monitor.width)

    else: # Panel on bottom
      y -= h
      if (x + w) > monitor.x + monitor.width:
        x -= (x + w) - (monitor.x + monitor.width)

    gravity = gtk.gdk.GRAVITY_NORTH_WEST

    self.set_gravity(gravity)
    self.move(x, y)

    logger.debug("""
--------------------------------------------------
align_with_parent:
  applet:
    x: {0}
    y: {1}
    w: {2}
    h: {3}
    orient: {4}
  window:
    x: {5}
    y: {6}
    w: {7}
    h: {8}
    gravity: {9}
  monitor:
    x: {10}
    y: {11}
    w: {12}
    h: {13}
    index: {14}
--------------------------------------------------
    """.format(applet_x, applet_y, button_w, button_h, orient,
      x, y, w, h, gravity,
      monitor.x, monitor.y, monitor.width, monitor.height, monitor_index))