New release.
[memo.git] / memos.py
blob5d5382589be8d936f759a7b1e83e6269b5d58d88
1 from __future__ import generators
3 import rox, gobject
4 from rox import g, app_options, options, basedir
6 import time
7 import os
9 from Memo import Memo, memo_from_node
11 max_visible = options.Option('max_visible', 5)
12 max_future = options.Option('max_future', 6)
14 # Columns
15 TIME = 0
16 BRIEF = 1
17 MEMO = 2
18 HIDDEN = 3
20 class MemoList(g.ListStore):
21 def __init__(self):
22 g.ListStore.__init__(self, gobject.TYPE_STRING, # Time
23 gobject.TYPE_STRING, # Brief
24 gobject.TYPE_OBJECT, # Memo
25 gobject.TYPE_BOOLEAN) # Deleted
26 self.watchers = []
28 def __iter__(self):
29 "When used as a python iterator, return a list of TreeIters"
30 iter = self.get_iter_first()
31 while iter:
32 yield iter
33 iter = self.iter_next(iter)
35 def delete(self, memo, update = 1):
36 import dbus_notify
37 dbus_notify.close(memo)
38 for iter in self:
39 m = self.get_value(iter, MEMO)
40 if m is memo:
41 self.remove(iter)
42 if update:
43 self.notify_changed()
44 return
45 # Not found. That's OK.
47 def new_day(self):
48 "Recalculate the time display after midnight."
49 for memo in self:
50 self.set(memo, TIME, self.get_value(memo, MEMO).str_when())
52 def add(self, memo, update = 1):
53 assert isinstance(memo, Memo)
55 for iter in self:
56 m = self.get_value(iter, MEMO)
57 if m.comes_after(memo):
58 break
59 else:
60 iter = None
62 if iter:
63 new = self.insert_before(iter)
64 else:
65 # PyGtk bug
66 new = self.append()
68 self.set(new,
69 TIME, memo.str_when(),
70 BRIEF, memo.brief,
71 MEMO, memo,
72 HIDDEN, memo.hidden)
74 if update:
75 self.notify_changed()
77 def notify_changed(self):
78 "Called after a Memo is added, removed or updated."
79 map(apply, self.watchers)
81 def get_memo_by_path(self, path):
82 iter = self.get_iter(path)
83 return self.get_memo_by_iter(iter)
85 def get_memo_by_iter(self, iter):
86 return self.get_value(iter, MEMO)
88 def catch_up(self):
89 "Returns a list of alarms to go off, and the time until the "
90 "next alarm (in seconds) or None."
92 missed = []
93 now = time.time()
95 for iter in self:
96 m = self.get_value(iter, MEMO)
98 if m.silent or m.hidden or not m.at:
99 continue
101 delay = m.time - now
103 if delay <= 0:
104 missed.append(m)
105 else:
106 return (missed, delay)
107 return (missed, None)
109 class MasterList(MemoList):
110 def __init__(self):
111 MemoList.__init__(self)
113 self.visible = MemoList()
115 path = basedir.load_first_config('rox.sourceforge.net', 'Memo', 'Entries')
116 if path:
117 try:
118 from xml.dom import minidom, Node
119 doc = minidom.parse(path)
120 except:
121 rox.report_exception()
123 errors = 0
124 root = doc.documentElement
125 for node in root.getElementsByTagName('memo'):
126 try:
127 memo = memo_from_node(node)
128 self.add(memo, update = 0)
129 except:
130 if not errors:
131 rox.report_exception()
132 errors = 1
133 self.update_visible()
134 app_options.add_notify(self.update_visible)
136 def new_day(self):
137 MemoList.new_day(self)
138 self.visible.new_day()
140 def toggle_hidden(self, path):
141 if g.pygtk_version == (1, 99, 12):
142 iter = self.get_iter_first()
143 self.get_iter_from_string(iter, path)
144 else:
145 iter = self.get_iter_from_string(path)
147 memo = self.get_memo_by_iter(iter)
148 self.set_hidden(memo, not memo.hidden)
150 def set_hidden(self, memo, hidden):
151 self.delete(memo, update = 0)
152 memo.set_hidden(hidden)
153 self.add(memo)
155 def save(self):
156 save_dir = basedir.save_config_path('rox.sourceforge.net', 'Memo')
157 path = os.path.join(save_dir, 'Entries.new')
158 if not path:
159 sys.stderr.write(
160 "Memo: Saving disabled by CHOICESPATH\n")
161 return
162 try:
163 f = os.open(path, os.O_CREAT | os.O_WRONLY, 0600)
164 self.save_to_stream(os.fdopen(f, 'w'))
166 real_path = os.path.join(save_dir, 'Entries')
167 os.rename(path, real_path)
168 except:
169 rox.report_exception()
171 def save_to_stream(self, stream):
172 from xml.dom import minidom
173 doc = minidom.Document()
175 root = doc.createElement('memos')
176 doc.appendChild(root)
177 for iter in self:
178 m = self.get_value(iter, MEMO)
179 m.save(root)
180 root.appendChild(doc.createTextNode('\n'))
181 doc.writexml(stream)
183 def notify_changed(self):
184 "Called after a Memo is added, removed or updated."
185 MemoList.notify_changed(self)
186 self.update_visible()
187 self.save()
189 def choose_visible(self):
190 "Return a list of Memos which should be made/kept visible."
191 now = time.time()
192 A_DAY = 60 * 60 * 24
194 VISIBLE_REGION = A_DAY * 31 * max_future.int_value
196 out = []
197 for iter in self:
198 memo = self.get_value(iter, MEMO)
200 if memo.hidden:
201 continue
203 if memo.time > now + VISIBLE_REGION:
204 break # Way too far ahead
206 if memo.time > now + A_DAY:
207 # Skip future memos if the list is too long
208 if len(out) >= max_visible.int_value:
209 break
211 out.append(memo)
213 return out
215 def update_visible(self):
216 # Find what was in the visible list
217 old_vis = {}
218 for iter in self.visible:
219 old_vis[self.visible.get_value(iter, MEMO)] = None
221 new_vis = self.choose_visible()
223 # Find what has changed
224 to_hide = []
225 to_show = []
226 for memo in new_vis:
227 if memo in old_vis:
228 del old_vis[memo]
229 else:
230 to_show.append(memo)
232 # Anything remaining was visible but isn't now
233 for memo in old_vis:
234 to_hide.append(memo)
236 if not to_show and not to_hide:
237 return
239 # Apply changes
240 for m in to_hide:
241 self.visible.delete(m, update = 0)
242 for m in to_show:
243 self.visible.add(m, update = 0)
244 self.visible.notify_changed()
246 def warn_if_not_visible(self, memo):
247 if memo.hidden:
248 return
249 for iter in self.visible:
250 m = self.visible.get_value(iter, MEMO)
251 if m is memo:
252 return
253 rox.info("This memo has been added, but is not shown in the main "
254 "window. Use Show All from the popup menu to see it.\n\n"
255 "You can use the Options window to control when memos "
256 "are shown in the main window.")