37
by Jason Conti
Reorganizing to make it easier to build a package. |
1 |
"""
|
2 |
MessageWindow.py
|
|
3 |
by Jason Conti
|
|
4 |
February 19, 2010
|
|
5 |
||
6 |
Popup window for viewing notifications.
|
|
7 |
"""
|
|
8 |
||
9 |
import gnomeapplet |
|
56
by Jason Conti
Working on a TreeView with a custom cellrenderer to display the notifications. Works, but still needs lots of work. |
10 |
import gobject |
37
by Jason Conti
Reorganizing to make it easier to build a package. |
11 |
import gtk |
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
12 |
import logging |
37
by Jason Conti
Reorganizing to make it easier to build a package. |
13 |
|
59
by Jason Conti
Think I finally found a decent layout for the messages in the treeview. Still need to fix the time format (I don't like a leading 0 in single digit hours), and the scrolling (should stay on top unless scrolled down manually). |
14 |
from string import Template |
15 |
||
93
by Jason Conti
Pulling the treeview code out of MessageWindow and putting it in MessageList. |
16 |
from MessageList import MessageList |
81
by Jason Conti
Adding support for gettext translations and a pointless en_US translation as an example. Using distutils-extra to automatically build the translations and the bonobo server. Replaced the build_servers target with update_prefix. This performs basically the same as build_servers except that it will do the substitution on any .in file, and it just writes the output file without the .in, instead of adding it to the data file install list. |
17 |
from Translations import _ |
79
by Jason Conti
Selecting new messages when they arrive. The selection is cleared when the message window is closed. Removed icon column and packed the icon cell renderer into the message column. |
18 |
|
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
19 |
logger = logging.getLogger("MessageWindow") |
20 |
||
102
by Jason Conti
Replacing the statusbar with a combobox that shows the number of messages per application, as well as the total number of messages, and allows filtering messages based on app. |
21 |
class Column(object): |
22 |
"""Maps column names to column numbers."""
|
|
105
by Jason Conti
Replaced the new message selections with an unread column in the model. This column is marked False when the message window is closed. Currently just displays NEW next to the message summary if the message is unread, but may add more if I get any ideas. Updated the translation template and added a script to simplify updating the template. |
23 |
APP_NAME = 0 |
102
by Jason Conti
Replacing the statusbar with a combobox that shows the number of messages per application, as well as the total number of messages, and allows filtering messages based on app. |
24 |
SEPARATOR = 1 |
105
by Jason Conti
Replaced the new message selections with an unread column in the model. This column is marked False when the message window is closed. Currently just displays NEW next to the message summary if the message is unread, but may add more if I get any ideas. Updated the translation template and added a script to simplify updating the template. |
25 |
COUNT = 2 |
102
by Jason Conti
Replacing the statusbar with a combobox that shows the number of messages per application, as well as the total number of messages, and allows filtering messages based on app. |
26 |
|
37
by Jason Conti
Reorganizing to make it easier to build a package. |
27 |
class MessageWindow(gtk.Window): |
28 |
"""Shows the log of notification messages."""
|
|
56
by Jason Conti
Working on a TreeView with a custom cellrenderer to display the notifications. Works, but still needs lots of work. |
29 |
def __init__(self, parent): |
37
by Jason Conti
Reorganizing to make it easier to build a package. |
30 |
gtk.Window.__init__(self) |
31 |
||
32 |
self._parent = parent |
|
33 |
||
157
by Jason Conti
Revert hide window when focus is lost. |
34 |
self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DOCK) |
37
by Jason Conti
Reorganizing to make it easier to build a package. |
35 |
self.set_decorated(False) |
36 |
self.set_resizable(False) |
|
156
by Jason Conti
Fixed a bunch of bugs with the new applet display code. |
37 |
self.set_property("skip-taskbar-hint", True) |
37
by Jason Conti
Reorganizing to make it easier to build a package. |
38 |
self.stick() |
81
by Jason Conti
Adding support for gettext translations and a pointless en_US translation as an example. Using distutils-extra to automatically build the translations and the bonobo server. Replaced the build_servers target with update_prefix. This performs basically the same as build_servers except that it will do the substitution on any .in file, and it just writes the output file without the .in, instead of adding it to the data file install list. |
39 |
self.set_title(_("Message Window")) |
62
by Jason Conti
Added a frame around the message window. |
40 |
self.set_border_width(0) |
56
by Jason Conti
Working on a TreeView with a custom cellrenderer to display the notifications. Works, but still needs lots of work. |
41 |
self.set_size_request(400, 400) |
42 |
||
107
by Jason Conti
Adding the frame back to the message window, changed the shadow type. |
43 |
self._frame = gtk.Frame() |
44 |
self._frame.set_border_width(0) |
|
45 |
self._frame.set_shadow_type(gtk.SHADOW_OUT) |
|
46 |
self.add(self._frame) |
|
47 |
||
60
by Jason Conti
Fixed the scrollbar and added alignment/padding to the cells. |
48 |
self._vadjust = gtk.Adjustment() |
49 |
self._vadjust.connect("changed", self.on_vadjust_changed) |
|
50 |
||
86
by Jason Conti
Adding a statusbar to list the number of messages. |
51 |
self._vbox = gtk.VBox() |
107
by Jason Conti
Adding the frame back to the message window, changed the shadow type. |
52 |
self._frame.add(self._vbox) |
86
by Jason Conti
Adding a statusbar to list the number of messages. |
53 |
|
60
by Jason Conti
Fixed the scrollbar and added alignment/padding to the cells. |
54 |
self._scroll = gtk.ScrolledWindow(vadjustment=self._vadjust) |
62
by Jason Conti
Added a frame around the message window. |
55 |
self._scroll.set_border_width(5) |
56
by Jason Conti
Working on a TreeView with a custom cellrenderer to display the notifications. Works, but still needs lots of work. |
56 |
self._scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) |
106
by Jason Conti
Adding code to adjust the wrap width of the message list based on the size of the scrolled window that contains it. Removed the frame from the message window. Adding a script to send test notifications. |
57 |
self._scroll.connect("size-allocate", self.on_scroll_resize) |
86
by Jason Conti
Adding a statusbar to list the number of messages. |
58 |
self._vbox.pack_start(self._scroll, True) |
59 |
||
148
by Jason Conti
Forgot to update the message counts as messages are removed. |
60 |
self._message_list = MessageList(self) |
93
by Jason Conti
Pulling the treeview code out of MessageWindow and putting it in MessageList. |
61 |
self._scroll.add(self._message_list) |
37
by Jason Conti
Reorganizing to make it easier to build a package. |
62 |
|
102
by Jason Conti
Replacing the statusbar with a combobox that shows the number of messages per application, as well as the total number of messages, and allows filtering messages based on app. |
63 |
# app_name, is_separator, count
|
64 |
self._combolist = gtk.ListStore(str, bool, int) |
|
65 |
self._combobox = gtk.ComboBox(self._combolist) |
|
66 |
self._combo_app_cell = gtk.CellRendererText() |
|
67 |
self._combobox.pack_start(self._combo_app_cell, True) |
|
68 |
self._combobox.add_attribute(self._combo_app_cell, "text", Column.APP_NAME) |
|
69 |
self._combo_count_cell = gtk.CellRendererText() |
|
70 |
self._combobox.pack_start(self._combo_count_cell, False) |
|
71 |
self._combobox.add_attribute(self._combo_count_cell, "text", Column.COUNT) |
|
72 |
self._combobox.set_row_separator_func(lambda m, i: m.get_value(i, Column.SEPARATOR)) |
|
73 |
self._combobox.connect("changed", self.on_combobox_changed) |
|
74 |
self._vbox.pack_start(self._combobox, False) |
|
75 |
||
76 |
self.initialize_combobox() |
|
77 |
||
78 |
def on_combobox_changed(self, combobox, *args): |
|
79 |
"""Set the tree filters to the selected app."""
|
|
80 |
active = self._combobox.get_active() |
|
104
by Jason Conti
Switch to TreeModelFilter broke selecting new messages. Updated the MessageList to select the row if it is currently visible in the filter. This was causing inconsistent behavior though. When the filter is changed, and the selected item is still visible, it will still be selected, which makes sense. But if an item that should be selected wasn't visible before the change and becomes visible, it won't be selected. To be consistent, either everything needs to be deselected when the filter is changed, or things that should be selected need to be selected (by perhaps adding a boolean row to the model). The first option is easier, so I'm adding that for now. |
81 |
self._message_list.unselect_all() |
102
by Jason Conti
Replacing the statusbar with a combobox that shows the number of messages per application, as well as the total number of messages, and allows filtering messages based on app. |
82 |
if active == 0: |
83 |
self._message_list.set_filter_app(None) |
|
84 |
else: |
|
85 |
iter = self._combobox.get_active_iter() |
|
86 |
app_name = self._combolist.get_value(iter, Column.APP_NAME) |
|
87 |
self._message_list.set_filter_app(app_name) |
|
88 |
||
106
by Jason Conti
Adding code to adjust the wrap width of the message list based on the size of the scrolled window that contains it. Removed the frame from the message window. Adding a script to send test notifications. |
89 |
def on_scroll_resize(self, scroll, allocation, *args): |
90 |
"""Updates the message list wrap width when the scrolled window resizes."""
|
|
91 |
self._message_list.set_wrap_width(allocation.width) |
|
92 |
||
60
by Jason Conti
Fixed the scrollbar and added alignment/padding to the cells. |
93 |
def on_vadjust_changed(self, adjustment, *args): |
94 |
"""Reset the adjustment position when the adjustment is changed (resized), but
|
|
95 |
not moved, so the user can scroll around freely."""
|
|
96 |
self._vadjust.set_value(0) |
|
97 |
||
37
by Jason Conti
Reorganizing to make it easier to build a package. |
98 |
def add_message(self, message): |
99 |
"""Adds a message to the message window."""
|
|
93
by Jason Conti
Pulling the treeview code out of MessageWindow and putting it in MessageList. |
100 |
self._message_list.add_message(message) |
37
by Jason Conti
Reorganizing to make it easier to build a package. |
101 |
|
75
by Jason Conti
Adding an option to the context menu to clear the message window. Adding more docstrings to Main.py. |
102 |
def clear_messages(self): |
103 |
"""Clears all messages from the message window."""
|
|
93
by Jason Conti
Pulling the treeview code out of MessageWindow and putting it in MessageList. |
104 |
self._message_list.clear() |
102
by Jason Conti
Replacing the statusbar with a combobox that shows the number of messages per application, as well as the total number of messages, and allows filtering messages based on app. |
105 |
self.initialize_combobox() |
75
by Jason Conti
Adding an option to the context menu to clear the message window. Adding more docstrings to Main.py. |
106 |
|
79
by Jason Conti
Selecting new messages when they arrive. The selection is cleared when the message window is closed. Removed icon column and packed the icon cell renderer into the message column. |
107 |
def clear_selections(self): |
105
by Jason Conti
Replaced the new message selections with an unread column in the model. This column is marked False when the message window is closed. Currently just displays NEW next to the message summary if the message is unread, but may add more if I get any ideas. Updated the translation template and added a script to simplify updating the template. |
108 |
"""Unselects every row in the treeview and marks the messages as read."""
|
109 |
self._message_list.mark_as_read() |
|
93
by Jason Conti
Pulling the treeview code out of MessageWindow and putting it in MessageList. |
110 |
self._message_list.unselect_all() |
79
by Jason Conti
Selecting new messages when they arrive. The selection is cleared when the message window is closed. Removed icon column and packed the icon cell renderer into the message column. |
111 |
|
153
by Jason Conti
Experiment with hiding the message window when it loses focus. |
112 |
def show_dropdown(self): |
113 |
"""Shows the dropdown window"""
|
|
114 |
self.show_all() |
|
115 |
self.align_with_parent() |
|
116 |
||
117 |
def hide_dropdown(self): |
|
118 |
"""Hides the dropdown window"""
|
|
119 |
self.hide_all() |
|
120 |
self.clear_selections() |
|
121 |
||
102
by Jason Conti
Replacing the statusbar with a combobox that shows the number of messages per application, as well as the total number of messages, and allows filtering messages based on app. |
122 |
def increment_count(self, iter): |
123 |
"""Increments the message count at the given row."""
|
|
124 |
count = self._combolist.get_value(iter, Column.COUNT) |
|
125 |
self._combolist.set_value(iter, Column.COUNT, count + 1) |
|
126 |
||
148
by Jason Conti
Forgot to update the message counts as messages are removed. |
127 |
def decrement_count(self, iter): |
128 |
"""Decrements the message count at the given row."""
|
|
129 |
count = self._combolist.get_value(iter, Column.COUNT) |
|
130 |
self._combolist.set_value(iter, Column.COUNT, count - 1) |
|
131 |
||
102
by Jason Conti
Replacing the statusbar with a combobox that shows the number of messages per application, as well as the total number of messages, and allows filtering messages based on app. |
132 |
def initialize_combobox(self): |
133 |
"""Clear the combobox and set the initial entry."""
|
|
134 |
self._combolist.clear() |
|
135 |
self._combolist.append([_("All Applications"), False, 0]) |
|
136 |
self._comboapps = {} |
|
137 |
self._combobox.set_active(0) |
|
138 |
||
145
by Jason Conti
Implemented the message limit preference. This required a change to marking the messages as read. Previously, iters to unread items were saved, and then just those iters were marked as unread. I was always a bit wary of this, because I wasn't sure what would happen if I got a stale iter. Well, I found out. The app segfaults. This seems like a bug in GTK, but either way, I decided to stop saving iters. Under the assumption that only the first n messages will be unread, and there are no gaps (which should be correct), the app is now looping through the iters until it finds one that is already read (or runs out). |
139 |
def set_message_limit(self, n): |
140 |
"""Sets the message limit of the MessageList."""
|
|
141 |
self._message_list.set_message_limit(n) |
|
142 |
||
148
by Jason Conti
Forgot to update the message counts as messages are removed. |
143 |
def increment_combobox(self, app_name): |
102
by Jason Conti
Replacing the statusbar with a combobox that shows the number of messages per application, as well as the total number of messages, and allows filtering messages based on app. |
144 |
"""Updates the combobox app names and message counts."""
|
145 |
# Add a separator if there isn't one yet
|
|
146 |
if len(self._combolist) == 1: |
|
147 |
self._combolist.append(["Separator", True, 0]) |
|
148 |
||
149 |
self.increment_count(self._combolist.get_iter_first()) |
|
150 |
||
151 |
if app_name not in self._comboapps: |
|
152 |
iter = self._combolist.append([app_name, False, 1]) |
|
153 |
self._comboapps[app_name] = iter |
|
86
by Jason Conti
Adding a statusbar to list the number of messages. |
154 |
else: |
102
by Jason Conti
Replacing the statusbar with a combobox that shows the number of messages per application, as well as the total number of messages, and allows filtering messages based on app. |
155 |
self.increment_count(self._comboapps[app_name]) |
86
by Jason Conti
Adding a statusbar to list the number of messages. |
156 |
|
148
by Jason Conti
Forgot to update the message counts as messages are removed. |
157 |
def decrement_combobox(self, app_name): |
158 |
"""Updates the combobox app names and message counts."""
|
|
159 |
self.decrement_count(self._combolist.get_iter_first()) |
|
160 |
||
161 |
if app_name in self._comboapps: |
|
162 |
self.decrement_count(self._comboapps[app_name]) |
|
163 |
||
37
by Jason Conti
Reorganizing to make it easier to build a package. |
164 |
def align_with_parent(self): |
165 |
"""Adapted from position_calendar_popup in the GNOME clock applet."""
|
|
166 |
# Get the size of the applet button and the size of this window
|
|
150
by Jason Conti
Adding debug messages for align_with_parent to try to uncover strange positioning issues. |
167 |
applet_x, applet_y = x, y = self._parent.get_origin() |
56
by Jason Conti
Working on a TreeView with a custom cellrenderer to display the notifications. Works, but still needs lots of work. |
168 |
w, h = self.get_size() |
37
by Jason Conti
Reorganizing to make it easier to build a package. |
169 |
button_w, button_h = self._parent.get_size() |
170 |
||
171 |
screen = self.get_screen() |
|
172 |
monitor = None |
|
150
by Jason Conti
Adding debug messages for align_with_parent to try to uncover strange positioning issues. |
173 |
monitor_index = -1 |
37
by Jason Conti
Reorganizing to make it easier to build a package. |
174 |
|
175 |
# Figure out which monitor we are on
|
|
176 |
for i in range(screen.get_n_monitors()): |
|
177 |
m = screen.get_monitor_geometry(i) |
|
178 |
||
179 |
if x >= m.x and x <= m.x + m.width and y >= m.y and y <= m.y + m.height: |
|
180 |
monitor = m |
|
150
by Jason Conti
Adding debug messages for align_with_parent to try to uncover strange positioning issues. |
181 |
monitor_index = i |
37
by Jason Conti
Reorganizing to make it easier to build a package. |
182 |
break
|
183 |
||
184 |
# Probably xinerama
|
|
185 |
if monitor == None: |
|
186 |
monitor = gtk.gdk.Rectangle(0, 0, screen.get_width(), screen.get_height()) |
|
187 |
||
188 |
orient = self._parent.get_orient() |
|
189 |
||
190 |
# Figure out the x, y coordinates of the window and the gravity depending
|
|
191 |
# on the orientation of the panel
|
|
192 |
if orient == gnomeapplet.ORIENT_RIGHT: # Panel on left |
|
193 |
x += button_w |
|
194 |
if (y + h) > monitor.y + monitor.height: |
|
195 |
y -= (y + h) - (monitor.y + monitor.height) |
|
196 |
||
197 |
elif orient == gnomeapplet.ORIENT_LEFT: # Panel on right |
|
198 |
x -= w |
|
199 |
if (y + h) > monitor.y + monitor.height: |
|
200 |
y -= (y + h) - (monitor.y + monitor.height) |
|
201 |
||
202 |
elif orient == gnomeapplet.ORIENT_DOWN: # Panel on top |
|
203 |
y += button_h |
|
204 |
if (x + w) > monitor.x + monitor.width: |
|
205 |
x -= (x + w) - (monitor.x + monitor.width) |
|
206 |
||
207 |
else: # Panel on bottom |
|
208 |
y -= h |
|
209 |
if (x + w) > monitor.x + monitor.width: |
|
210 |
x -= (x + w) - (monitor.x + monitor.width) |
|
211 |
||
165
by Jason Conti
It seems my broken window manager ignores gravity settings, so I never noticed that they were incorrect. This seems to result in incorrect positioning on people with working window managers (that don't ignore the gravity). Removed the gravity calculations from align_with_parent, and assuming everything is calculated from GRAVITY_NORTH_WEST. Still works here, hope it fixes the issue. |
212 |
gravity = gtk.gdk.GRAVITY_NORTH_WEST |
37
by Jason Conti
Reorganizing to make it easier to build a package. |
213 |
|
164
by Jason Conti
According to the pygtk docs, apparently the gravity should be set before move is called. Hoping this might fix the positioning bug. No idea. |
214 |
self.set_gravity(gravity) |
37
by Jason Conti
Reorganizing to make it easier to build a package. |
215 |
self.move(x, y) |
56
by Jason Conti
Working on a TreeView with a custom cellrenderer to display the notifications. Works, but still needs lots of work. |
216 |
|
150
by Jason Conti
Adding debug messages for align_with_parent to try to uncover strange positioning issues. |
217 |
logger.debug(""" |
218 |
--------------------------------------------------
|
|
219 |
align_with_parent:
|
|
220 |
applet:
|
|
221 |
x: {0} |
|
222 |
y: {1} |
|
223 |
w: {2} |
|
224 |
h: {3} |
|
225 |
orient: {4} |
|
226 |
window:
|
|
227 |
x: {5} |
|
228 |
y: {6} |
|
229 |
w: {7} |
|
230 |
h: {8} |
|
231 |
gravity: {9} |
|
232 |
monitor:
|
|
233 |
x: {10} |
|
234 |
y: {11} |
|
235 |
w: {12} |
|
236 |
h: {13} |
|
237 |
index: {14} |
|
238 |
--------------------------------------------------
|
|
239 |
""".format(applet_x, applet_y, button_w, button_h, orient, |
|
240 |
x, y, w, h, gravity, |
|
241 |
monitor.x, monitor.y, monitor.width, monitor.height, monitor_index)) |
|
242 |