Erik Grinaker is sharing code with you
Bitbucket is a code hosting site. Unlimited public and private repositories. Free for small teams.
Don't show this againRevelation / src / lib / data.py
- commit
- dc6f5e1b5dc0
- parent
- 07f580f3bff4
- branch
- default
fixed crash when searching on some systems
1 |
cea4127a11c0
|
# |
2 |
78fb3436ec03
|
# Revelation - a password manager for GNOME 2 |
3 |
ae2e5643ddb8
|
# http://oss.codepoet.no/revelation/ |
4 |
dc69203e823e
|
# $Id$ |
5 |
cea4127a11c0
|
# |
6 |
cea4127a11c0
|
# Module containing data-related functionality |
7 |
cea4127a11c0
|
# |
8 |
cea4127a11c0
|
# |
9 |
d230b54e5239
|
# Copyright (c) 2003-2006 Erik Grinaker |
10 |
cea4127a11c0
|
# |
11 |
cea4127a11c0
|
# This program is free software; you can redistribute it and/or |
12 |
cea4127a11c0
|
# modify it under the terms of the GNU General Public License |
13 |
cea4127a11c0
|
# as published by the Free Software Foundation; either version 2 |
14 |
cea4127a11c0
|
# of the License, or (at your option) any later version. |
15 |
cea4127a11c0
|
# |
16 |
cea4127a11c0
|
# This program is distributed in the hope that it will be useful, |
17 |
cea4127a11c0
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 |
cea4127a11c0
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
19 |
cea4127a11c0
|
# GNU General Public License for more details. |
20 |
cea4127a11c0
|
# |
21 |
cea4127a11c0
|
# You should have received a copy of the GNU General Public License |
22 |
cea4127a11c0
|
# along with this program; if not, write to the Free Software |
23 |
cea4127a11c0
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
24 |
cea4127a11c0
|
# |
25 |
cea4127a11c0
|
|
26 |
09c83811662e
|
import datahandler, entry |
27 |
cea4127a11c0
|
|
28 |
89c3dd775abc
|
import gobject, gtk, gtk.gdk, time |
29 |
c9d89f3ff91d
|
|
30 |
cea4127a11c0
|
|
31 |
cea4127a11c0
|
|
32 |
c188d5e734c8
|
COLUMN_NAME = 0 |
33 |
c188d5e734c8
|
COLUMN_ICON = 1 |
34 |
c188d5e734c8
|
COLUMN_ENTRY = 2 |
35 |
cea4127a11c0
|
|
36 |
c188d5e734c8
|
SEARCH_NEXT = "next" |
37 |
c9d89f3ff91d
|
SEARCH_PREVIOUS = "prev" |
38 |
c9d89f3ff91d
|
|
39 |
c9d89f3ff91d
|
|
40 |
c9d89f3ff91d
|
|
41 |
c9d89f3ff91d
|
class Clipboard(gobject.GObject): |
42 |
c9d89f3ff91d
|
"A normal text-clipboard" |
43 |
c9d89f3ff91d
|
|
44 |
c9d89f3ff91d
|
def __init__(self): |
45 |
c9d89f3ff91d
|
gobject.GObject.__init__(self) |
46 |
c9d89f3ff91d
|
|
47 |
704e07bb5692
|
self.clip_clipboard = gtk.clipboard_get("CLIPBOARD") |
48 |
704e07bb5692
|
self.clip_primary = gtk.clipboard_get("PRIMARY") |
49 |
c9d89f3ff91d
|
|
50 |
8812d70e39d9
|
self.cleartimer = Timer(10) |
51 |
8812d70e39d9
|
self.cleartimeout = 60 |
52 |
8812d70e39d9
|
self.cleartimer.connect("ring", self.__cb_clear_ring) |
53 |
8812d70e39d9
|
|
54 |
b57efdd083f5
|
self.content = None |
55 |
b57efdd083f5
|
self.contentpointer = 0 |
56 |
c9d89f3ff91d
|
|
57 |
e5330daca1cd
|
|
58 |
b57efdd083f5
|
def __cb_clear(self, clipboard, data = None): |
59 |
b57efdd083f5
|
"Clears the clipboard data" |
60 |
e5330daca1cd
|
|
61 |
b57efdd083f5
|
return |
62 |
8812d70e39d9
|
|
63 |
8812d70e39d9
|
|
64 |
8812d70e39d9
|
def __cb_clear_ring(self, widget): |
65 |
8812d70e39d9
|
"Handles cleartimer rings" |
66 |
8812d70e39d9
|
|
67 |
1de7c1999e26
|
self.content = None |
68 |
1de7c1999e26
|
self.contentpointer = 0 |
69 |
e5330daca1cd
|
|
70 |
e5330daca1cd
|
|
71 |
b57efdd083f5
|
def __cb_get(self, clipboard, selectiondata, info, data): |
72 |
b57efdd083f5
|
"Returns text for clipboard requests" |
73 |
b57efdd083f5
|
|
74 |
b57efdd083f5
|
if self.content == None: |
75 |
b57efdd083f5
|
text = "" |
76 |
b57efdd083f5
|
|
77 |
b57efdd083f5
|
elif type(self.content) == list: |
78 |
b57efdd083f5
|
|
79 |
b57efdd083f5
|
if len(self.content) == 0: |
80 |
b57efdd083f5
|
text = "" |
81 |
b57efdd083f5
|
|
82 |
b57efdd083f5
|
else: |
83 |
b57efdd083f5
|
text = self.content[self.contentpointer] |
84 |
b57efdd083f5
|
|
85 |
b57efdd083f5
|
if self.contentpointer < len(self.content) - 1: |
86 |
b57efdd083f5
|
self.contentpointer += 1 |
87 |
b57efdd083f5
|
|
88 |
b57efdd083f5
|
else: |
89 |
b57efdd083f5
|
text = str(self.content) |
90 |
b57efdd083f5
|
|
91 |
b57efdd083f5
|
selectiondata.set_text(text, len(text)) |
92 |
b57efdd083f5
|
|
93 |
b57efdd083f5
|
|
94 |
b57efdd083f5
|
def clear(self): |
95 |
c9d89f3ff91d
|
"Clears the clipboard" |
96 |
c9d89f3ff91d
|
|
97 |
b57efdd083f5
|
self.clip_clipboard.clear() |
98 |
b57efdd083f5
|
self.clip_primary.clear() |
99 |
c9d89f3ff91d
|
|
100 |
c9d89f3ff91d
|
|
101 |
b57efdd083f5
|
def get(self): |
102 |
c9d89f3ff91d
|
"Fetches text from the clipboard" |
103 |
c9d89f3ff91d
|
|
104 |
b57efdd083f5
|
text = self.clip_clipboard.wait_for_text() |
105 |
704e07bb5692
|
|
106 |
704e07bb5692
|
if text is None: |
107 |
704e07bb5692
|
text = "" |
108 |
704e07bb5692
|
|
109 |
704e07bb5692
|
return text |
110 |
c9d89f3ff91d
|
|
111 |
c9d89f3ff91d
|
|
112 |
b57efdd083f5
|
def has_contents(self): |
113 |
c9d89f3ff91d
|
"Checks if the clipboard has any contents" |
114 |
c9d89f3ff91d
|
|
115 |
b6859b8eb257
|
return self.clip_clipboard.wait_is_text_available() |
116 |
c9d89f3ff91d
|
|
117 |
c9d89f3ff91d
|
|
118 |
8812d70e39d9
|
def set(self, content, secret = False): |
119 |
c9d89f3ff91d
|
"Copies text to the clipboard" |
120 |
c9d89f3ff91d
|
|
121 |
b57efdd083f5
|
self.content = content |
122 |
b57efdd083f5
|
self.contentpointer = 0 |
123 |
e5330daca1cd
|
|
124 |
b57efdd083f5
|
targets = ( |
125 |
b57efdd083f5
|
( "text/plain", 0, 0 ), |
126 |
b57efdd083f5
|
( "STRING", 0, 0 ), |
127 |
b6859b8eb257
|
( "TEXT", 0, 0 ), |
128 |
b6859b8eb257
|
( "COMPOUND_TEXT", 0, 0 ), |
129 |
b6859b8eb257
|
( "UTF8_STRING", 0, 0 ) |
130 |
b57efdd083f5
|
) |
131 |
b57efdd083f5
|
|
132 |
b57efdd083f5
|
self.clip_clipboard.set_with_data(targets, self.__cb_get, self.__cb_clear, None) |
133 |
b57efdd083f5
|
self.clip_primary.set_with_data(targets, self.__cb_get, self.__cb_clear, None) |
134 |
c9d89f3ff91d
|
|
135 |
8812d70e39d9
|
if secret == True: |
136 |
8812d70e39d9
|
self.cleartimer.start(self.cleartimeout) |
137 |
8812d70e39d9
|
|
138 |
8812d70e39d9
|
else: |
139 |
8812d70e39d9
|
self.cleartimer.stop() |
140 |
8812d70e39d9
|
|
141 |
c9d89f3ff91d
|
|
142 |
c9d89f3ff91d
|
|
143 |
c9d89f3ff91d
|
class EntryClipboard(gobject.GObject): |
144 |
c9d89f3ff91d
|
"A clipboard for entries" |
145 |
c9d89f3ff91d
|
|
146 |
c9d89f3ff91d
|
def __init__(self): |
147 |
c9d89f3ff91d
|
gobject.GObject.__init__(self) |
148 |
c9d89f3ff91d
|
|
149 |
704e07bb5692
|
self.clipboard = gtk.Clipboard(gtk.gdk.display_get_default(), "_REVELATION_ENTRY") |
150 |
c9d89f3ff91d
|
self.__has_contents = False |
151 |
c9d89f3ff91d
|
|
152 |
b57efdd083f5
|
gobject.timeout_add(500, lambda: self.__check_contents()) |
153 |
c9d89f3ff91d
|
|
154 |
c9d89f3ff91d
|
|
155 |
b57efdd083f5
|
def __check_contents(self): |
156 |
c9d89f3ff91d
|
"Callback which check the clipboard" |
157 |
c9d89f3ff91d
|
|
158 |
c9d89f3ff91d
|
state = self.has_contents() |
159 |
c9d89f3ff91d
|
|
160 |
c9d89f3ff91d
|
if state != self.__has_contents: |
161 |
c9d89f3ff91d
|
self.emit("content-toggled", state) |
162 |
c9d89f3ff91d
|
self.__has_contents = state |
163 |
c9d89f3ff91d
|
|
164 |
c9d89f3ff91d
|
return True |
165 |
c9d89f3ff91d
|
|
166 |
c9d89f3ff91d
|
|
167 |
c9d89f3ff91d
|
def clear(self): |
168 |
c9d89f3ff91d
|
"Clears the clipboard" |
169 |
c9d89f3ff91d
|
|
170 |
704e07bb5692
|
self.clipboard.clear() |
171 |
b57efdd083f5
|
self.__check_contents() |
172 |
c9d89f3ff91d
|
|
173 |
c9d89f3ff91d
|
|
174 |
c9d89f3ff91d
|
def get(self): |
175 |
c9d89f3ff91d
|
"Fetches entries from the clipboard" |
176 |
c9d89f3ff91d
|
|
177 |
704e07bb5692
|
try: |
178 |
704e07bb5692
|
xml = self.clipboard.wait_for_text() |
179 |
c9d89f3ff91d
|
|
180 |
704e07bb5692
|
if xml in ( None, "" ): |
181 |
704e07bb5692
|
return None |
182 |
704e07bb5692
|
|
183 |
704e07bb5692
|
handler = datahandler.RevelationXML() |
184 |
704e07bb5692
|
entrystore = handler.import_data(xml) |
185 |
704e07bb5692
|
|
186 |
704e07bb5692
|
return entrystore |
187 |
704e07bb5692
|
|
188 |
704e07bb5692
|
except datahandler.HandlerError: |
189 |
c9d89f3ff91d
|
return None |
190 |
c9d89f3ff91d
|
|
191 |
c9d89f3ff91d
|
|
192 |
c9d89f3ff91d
|
def has_contents(self): |
193 |
c9d89f3ff91d
|
"Checks if the clipboard has any contents" |
194 |
c9d89f3ff91d
|
|
195 |
704e07bb5692
|
return self.clipboard.wait_for_text() is not None |
196 |
c9d89f3ff91d
|
|
197 |
c9d89f3ff91d
|
|
198 |
c9d89f3ff91d
|
def set(self, entrystore, iters): |
199 |
c9d89f3ff91d
|
"Copies entries from an entrystore to the clipboard" |
200 |
c9d89f3ff91d
|
|
201 |
c9d89f3ff91d
|
copystore = EntryStore() |
202 |
c9d89f3ff91d
|
|
203 |
c9d89f3ff91d
|
for iter in entrystore.filter_parents(iters): |
204 |
c9d89f3ff91d
|
copystore.import_entry(entrystore, iter) |
205 |
c9d89f3ff91d
|
|
206 |
c9d89f3ff91d
|
xml = datahandler.RevelationXML().export_data(copystore) |
207 |
c9d89f3ff91d
|
self.clipboard.set_text(xml) |
208 |
c9d89f3ff91d
|
|
209 |
b57efdd083f5
|
self.__check_contents() |
210 |
c9d89f3ff91d
|
|
211 |
c9d89f3ff91d
|
|
212 |
c9d89f3ff91d
|
gobject.signal_new("content-toggled", EntryClipboard, gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, ( gobject.TYPE_BOOLEAN, )) |
213 |
268984dc47cf
|
|
214 |
268984dc47cf
|
|
215 |
cea4127a11c0
|
|
216 |
704e07bb5692
|
class EntrySearch(gobject.GObject): |
217 |
c188d5e734c8
|
"Handles searching in an EntryStore" |
218 |
cea4127a11c0
|
|
219 |
cea4127a11c0
|
def __init__(self, entrystore): |
220 |
704e07bb5692
|
gobject.GObject.__init__(self) |
221 |
f2f6bb9f7c28
|
self.entrystore = entrystore |
222 |
f2f6bb9f7c28
|
|
223 |
c188d5e734c8
|
self.folders = True |
224 |
c188d5e734c8
|
self.namedesconly = False |
225 |
c188d5e734c8
|
self.casesensitive = False |
226 |
f2f6bb9f7c28
|
|
227 |
cea4127a11c0
|
|
228 |
c188d5e734c8
|
def find(self, string, entrytype = None, offset = None, direction = SEARCH_NEXT): |
229 |
c188d5e734c8
|
"Searches for an entry, starting at the given offset" |
230 |
cea4127a11c0
|
|
231 |
c188d5e734c8
|
iter = offset |
232 |
cea4127a11c0
|
|
233 |
dc936312d815
|
while 1: |
234 |
f2f6bb9f7c28
|
|
235 |
dc936312d815
|
if direction == SEARCH_NEXT: |
236 |
dc936312d815
|
iter = self.entrystore.iter_traverse_next(iter) |
237 |
f2f6bb9f7c28
|
|
238 |
dc936312d815
|
else: |
239 |
dc936312d815
|
iter = self.entrystore.iter_traverse_prev(iter) |
240 |
cea4127a11c0
|
|
241 |
c188d5e734c8
|
# if we've wrapped around, return None |
242 |
c188d5e734c8
|
if self.entrystore.get_path(iter) == self.entrystore.get_path(offset): |
243 |
dc936312d815
|
return None |
244 |
dc936312d815
|
|
245 |
c188d5e734c8
|
if self.match(iter, string, entrytype) == True: |
246 |
cea4127a11c0
|
return iter |
247 |
cea4127a11c0
|
|
248 |
cea4127a11c0
|
|
249 |
e5681e842641
|
def find_all(self, string, entrytype = None): |
250 |
e5681e842641
|
"Searches for all entries matching a term" |
251 |
e5681e842641
|
|
252 |
e5681e842641
|
matches = [] |
253 |
e5681e842641
|
iter = self.entrystore.iter_children(None) |
254 |
e5681e842641
|
|
255 |
e5681e842641
|
while iter != None: |
256 |
e5681e842641
|
|
257 |
e5681e842641
|
if self.match(iter, string, entrytype) == True: |
258 |
e5681e842641
|
matches.append(iter) |
259 |
e5681e842641
|
|
260 |
e5681e842641
|
iter = self.entrystore.iter_traverse_next(iter) |
261 |
e5681e842641
|
|
262 |
e5681e842641
|
return matches |
263 |
e5681e842641
|
|
264 |
e5681e842641
|
|
265 |
c188d5e734c8
|
def match(self, iter, string, entrytype = None): |
266 |
f2f6bb9f7c28
|
"Check if an entry matches the search criteria" |
267 |
f2f6bb9f7c28
|
|
268 |
dc6f5e1b5dc0
|
if iter is None or not string: |
269 |
c188d5e734c8
|
return False |
270 |
cea4127a11c0
|
|
271 |
c188d5e734c8
|
e = self.entrystore.get_entry(iter) |
272 |
cea4127a11c0
|
|
273 |
f2f6bb9f7c28
|
|
274 |
c188d5e734c8
|
# check entry type |
275 |
c188d5e734c8
|
if type(e) == entry.FolderEntry and self.folders == False: |
276 |
c188d5e734c8
|
return False |
277 |
cea4127a11c0
|
|
278 |
c188d5e734c8
|
if entrytype is not None and type(e) not in ( entrytype, entry.FolderEntry ): |
279 |
c188d5e734c8
|
return False |
280 |
cea4127a11c0
|
|
281 |
f2f6bb9f7c28
|
|
282 |
c188d5e734c8
|
# check entry fields |
283 |
c188d5e734c8
|
items = [ e.name, e.description ] |
284 |
f2f6bb9f7c28
|
|
285 |
c188d5e734c8
|
if self.namedesconly == False: |
286 |
c188d5e734c8
|
items.extend([ field.value for field in e.fields if field.value != "" ]) |
287 |
cea4127a11c0
|
|
288 |
f2f6bb9f7c28
|
|
289 |
cea4127a11c0
|
# run the search |
290 |
cea4127a11c0
|
for item in items: |
291 |
6691f6ade6e7
|
if self.casesensitive == True and string in item: |
292 |
c188d5e734c8
|
return True |
293 |
f2f6bb9f7c28
|
|
294 |
6691f6ade6e7
|
elif self.casesensitive == False and string.decode("utf-8").lower() in item.decode("utf-8").lower(): |
295 |
c188d5e734c8
|
return True |
296 |
cea4127a11c0
|
|
297 |
c188d5e734c8
|
return False |
298 |
cea4127a11c0
|
|
299 |
cea4127a11c0
|
|
300 |
cea4127a11c0
|
|
301 |
c188d5e734c8
|
class EntryStore(gtk.TreeStore): |
302 |
c188d5e734c8
|
"A data structure for storing entries" |
303 |
cea4127a11c0
|
|
304 |
cea4127a11c0
|
def __init__(self): |
305 |
c188d5e734c8
|
gtk.TreeStore.__init__( |
306 |
c188d5e734c8
|
self, |
307 |
c188d5e734c8
|
gobject.TYPE_STRING, # name |
308 |
c188d5e734c8
|
gobject.TYPE_STRING, # icon |
309 |
c188d5e734c8
|
gobject.TYPE_PYOBJECT # entry |
310 |
c188d5e734c8
|
) |
311 |
a877d43f0987
|
|
312 |
c188d5e734c8
|
self.changed = False |
313 |
c9d89f3ff91d
|
self.connect("row-has-child-toggled", self.__cb_iter_has_child) |
314 |
c9d89f3ff91d
|
|
315 |
c9d89f3ff91d
|
|
316 |
c9d89f3ff91d
|
def __cb_iter_has_child(self, widget, path, iter): |
317 |
c9d89f3ff91d
|
"Callback for iters having children" |
318 |
c9d89f3ff91d
|
|
319 |
c9d89f3ff91d
|
if self.iter_n_children(iter) == 0: |
320 |
c9d89f3ff91d
|
self.folder_expanded(iter, False) |
321 |
a877d43f0987
|
|
322 |
cea4127a11c0
|
|
323 |
c188d5e734c8
|
def add_entry(self, e, parent = None, sibling = None): |
324 |
4787d15f40a4
|
"Adds an entry" |
325 |
4787d15f40a4
|
|
326 |
c188d5e734c8
|
# place after parent if it's not a folder |
327 |
c188d5e734c8
|
if parent is not None and type(self.get_entry(parent)) != entry.FolderEntry: |
328 |
4787d15f40a4
|
iter = self.insert_after(self.iter_parent(parent), parent) |
329 |
4787d15f40a4
|
|
330 |
4787d15f40a4
|
# place before sibling, if given |
331 |
4787d15f40a4
|
elif sibling is not None: |
332 |
4787d15f40a4
|
iter = self.insert_before(parent, sibling) |
333 |
4787d15f40a4
|
|
334 |
4787d15f40a4
|
# otherwise, append to parent |
335 |
4787d15f40a4
|
else: |
336 |
4787d15f40a4
|
iter = self.append(parent) |
337 |
4787d15f40a4
|
|
338 |
c188d5e734c8
|
self.update_entry(iter, e) |
339 |
c188d5e734c8
|
self.changed = True |
340 |
4787d15f40a4
|
|
341 |
cea4127a11c0
|
return iter |
342 |
cea4127a11c0
|
|
343 |
cea4127a11c0
|
|
344 |
4787d15f40a4
|
def clear(self): |
345 |
c188d5e734c8
|
"Removes all entries" |
346 |
cea4127a11c0
|
|
347 |
c188d5e734c8
|
gtk.TreeStore.clear(self) |
348 |
c188d5e734c8
|
self.changed = False |
349 |
cea4127a11c0
|
|
350 |
cea4127a11c0
|
|
351 |
8077271f872d
|
def copy_entry(self, iter, parent = None, sibling = None): |
352 |
8077271f872d
|
"Copies an entry recursively" |
353 |
8077271f872d
|
|
354 |
8077271f872d
|
newiter = self.add_entry(self.get_entry(iter), parent, sibling) |
355 |
8077271f872d
|
|
356 |
8077271f872d
|
for i in range(self.iter_n_children(iter)): |
357 |
8077271f872d
|
child = self.iter_nth_child(iter, i) |
358 |
8077271f872d
|
self.copy_entry(child, newiter) |
359 |
8077271f872d
|
|
360 |
8077271f872d
|
return newiter |
361 |
8077271f872d
|
|
362 |
8077271f872d
|
|
363 |
c9d89f3ff91d
|
def filter_parents(self, iters): |
364 |
c9d89f3ff91d
|
"Removes all descendants from the list of iters" |
365 |
c9d89f3ff91d
|
|
366 |
c9d89f3ff91d
|
parents = [] |
367 |
c9d89f3ff91d
|
|
368 |
c9d89f3ff91d
|
for child in iters: |
369 |
c9d89f3ff91d
|
for parent in iters: |
370 |
c9d89f3ff91d
|
if self.is_ancestor(parent, child): |
371 |
c9d89f3ff91d
|
break |
372 |
c9d89f3ff91d
|
|
373 |
c9d89f3ff91d
|
else: |
374 |
c9d89f3ff91d
|
parents.append(child) |
375 |
c9d89f3ff91d
|
|
376 |
c9d89f3ff91d
|
return parents |
377 |
c9d89f3ff91d
|
|
378 |
c9d89f3ff91d
|
|
379 |
c9d89f3ff91d
|
def folder_expanded(self, iter, expanded): |
380 |
c9d89f3ff91d
|
"Sets the expanded state of an entry" |
381 |
c9d89f3ff91d
|
|
382 |
09c83811662e
|
e = self.get_entry(iter) |
383 |
09c83811662e
|
|
384 |
09c83811662e
|
if e == None or type(e) != entry.FolderEntry: |
385 |
c9d89f3ff91d
|
return |
386 |
c9d89f3ff91d
|
|
387 |
c9d89f3ff91d
|
elif expanded == True: |
388 |
09c83811662e
|
self.set_value(iter, COLUMN_ICON, e.openicon) |
389 |
c9d89f3ff91d
|
|
390 |
c9d89f3ff91d
|
else: |
391 |
09c83811662e
|
self.set_value(iter, COLUMN_ICON, e.icon) |
392 |
c9d89f3ff91d
|
|
393 |
c9d89f3ff91d
|
|
394 |
cea4127a11c0
|
def get_entry(self, iter): |
395 |
4787d15f40a4
|
"Fetches data for an entry" |
396 |
4787d15f40a4
|
|
397 |
4787d15f40a4
|
if iter is None: |
398 |
cea4127a11c0
|
return None |
399 |
cea4127a11c0
|
|
400 |
5706a9d0a689
|
e = self.get_value(iter, COLUMN_ENTRY) |
401 |
5706a9d0a689
|
|
402 |
5706a9d0a689
|
if e is None: |
403 |
5706a9d0a689
|
return None |
404 |
5706a9d0a689
|
|
405 |
5706a9d0a689
|
else: |
406 |
5706a9d0a689
|
return e.copy() |
407 |
cea4127a11c0
|
|
408 |
dc936312d815
|
|
409 |
c188d5e734c8
|
def get_iter(self, path): |
410 |
c188d5e734c8
|
"Gets an iter from a path" |
411 |
cea4127a11c0
|
|
412 |
c188d5e734c8
|
try: |
413 |
c188d5e734c8
|
if path in ( None, "", (), [] ): |
414 |
c188d5e734c8
|
return None |
415 |
cea4127a11c0
|
|
416 |
8077271f872d
|
if type(path) == list: |
417 |
8077271f872d
|
path = tuple(path) |
418 |
8077271f872d
|
|
419 |
c188d5e734c8
|
return gtk.TreeStore.get_iter(self, path) |
420 |
a877d43f0987
|
|
421 |
c188d5e734c8
|
except ValueError: |
422 |
c188d5e734c8
|
return None |
423 |
a877d43f0987
|
|
424 |
a877d43f0987
|
|
425 |
c188d5e734c8
|
def get_path(self, iter): |
426 |
c188d5e734c8
|
"Gets a path from an iter" |
427 |
a877d43f0987
|
|
428 |
c188d5e734c8
|
return iter is not None and gtk.TreeStore.get_path(self, iter) or None |
429 |
a877d43f0987
|
|
430 |
a32ec604c58f
|
|
431 |
c9d89f3ff91d
|
def get_popular_values(self, fieldtype, threshold = 3): |
432 |
c9d89f3ff91d
|
"Gets popular values for a field type" |
433 |
c9d89f3ff91d
|
|
434 |
c9d89f3ff91d
|
valuecount = {} |
435 |
c9d89f3ff91d
|
iter = self.iter_nth_child(None, 0) |
436 |
c9d89f3ff91d
|
|
437 |
c9d89f3ff91d
|
while iter is not None: |
438 |
c9d89f3ff91d
|
e = self.get_entry(iter) |
439 |
c9d89f3ff91d
|
|
440 |
c9d89f3ff91d
|
if e.has_field(fieldtype) == False: |
441 |
c9d89f3ff91d
|
iter = self.iter_traverse_next(iter) |
442 |
c9d89f3ff91d
|
continue |
443 |
c9d89f3ff91d
|
|
444 |
c9d89f3ff91d
|
value = e[fieldtype].strip() |
445 |
c9d89f3ff91d
|
|
446 |
c9d89f3ff91d
|
if value != "": |
447 |
c9d89f3ff91d
|
if valuecount.has_key(value) == False: |
448 |
c9d89f3ff91d
|
valuecount[value] = 0 |
449 |
c9d89f3ff91d
|
|
450 |
c9d89f3ff91d
|
valuecount[value] += 1 |
451 |
c9d89f3ff91d
|
|
452 |
c9d89f3ff91d
|
iter = self.iter_traverse_next(iter) |
453 |
c9d89f3ff91d
|
|
454 |
c9d89f3ff91d
|
popular = [ value for value, count in valuecount.items() if count >= threshold ] |
455 |
c9d89f3ff91d
|
popular.sort() |
456 |
c9d89f3ff91d
|
|
457 |
c9d89f3ff91d
|
return popular |
458 |
c9d89f3ff91d
|
|
459 |
c9d89f3ff91d
|
|
460 |
c9d89f3ff91d
|
def import_entry(self, source, iter, parent = None, sibling = None): |
461 |
c9d89f3ff91d
|
"Recursively copies an entry from a different entrystore" |
462 |
c9d89f3ff91d
|
|
463 |
c9d89f3ff91d
|
if iter is not None: |
464 |
c9d89f3ff91d
|
copy = self.add_entry(source.get_entry(iter), parent, sibling) |
465 |
c9d89f3ff91d
|
parent, sibling = copy, None |
466 |
c9d89f3ff91d
|
|
467 |
c9d89f3ff91d
|
else: |
468 |
c9d89f3ff91d
|
copy = None |
469 |
c9d89f3ff91d
|
|
470 |
c9d89f3ff91d
|
newiters = [] |
471 |
c9d89f3ff91d
|
for i in range(source.iter_n_children(iter)): |
472 |
c9d89f3ff91d
|
child = source.iter_nth_child(iter, i) |
473 |
c9d89f3ff91d
|
newiter = self.import_entry(source, child, parent, sibling) |
474 |
c9d89f3ff91d
|
newiters.append(newiter) |
475 |
c9d89f3ff91d
|
|
476 |
c9d89f3ff91d
|
return copy is not None and copy or newiters |
477 |
c9d89f3ff91d
|
|
478 |
c9d89f3ff91d
|
|
479 |
c188d5e734c8
|
def iter_traverse_next(self, iter): |
480 |
c188d5e734c8
|
"Gets the 'logically next' iter" |
481 |
a32ec604c58f
|
|
482 |
c188d5e734c8
|
# get the first child, if any |
483 |
c188d5e734c8
|
child = self.iter_nth_child(iter, 0) |
484 |
c188d5e734c8
|
if child is not None: |
485 |
c188d5e734c8
|
return child |
486 |
a32ec604c58f
|
|
487 |
c188d5e734c8
|
# check for a sibling or, if not found, a sibling of any ancestors |
488 |
c188d5e734c8
|
parent = iter |
489 |
c188d5e734c8
|
while parent is not None: |
490 |
c188d5e734c8
|
sibling = parent.copy() |
491 |
c188d5e734c8
|
sibling = self.iter_next(sibling) |
492 |
a32ec604c58f
|
|
493 |
c188d5e734c8
|
if sibling is not None: |
494 |
c188d5e734c8
|
return sibling |
495 |
a32ec604c58f
|
|
496 |
c188d5e734c8
|
parent = self.iter_parent(parent) |
497 |
a32ec604c58f
|
|
498 |
c188d5e734c8
|
return None |
499 |
a32ec604c58f
|
|
500 |
a32ec604c58f
|
|
501 |
c188d5e734c8
|
def iter_traverse_prev(self, iter): |
502 |
c188d5e734c8
|
"Gets the 'logically previous' iter" |
503 |
a32ec604c58f
|
|
504 |
c188d5e734c8
|
# get the previous sibling, or parent, of the iter - if any |
505 |
c188d5e734c8
|
if iter is not None: |
506 |
c188d5e734c8
|
parent = self.iter_parent(iter) |
507 |
c188d5e734c8
|
index = self.get_path(iter)[-1] |
508 |
a32ec604c58f
|
|
509 |
c188d5e734c8
|
# if no sibling is found, return the parent |
510 |
c188d5e734c8
|
if index == 0: |
511 |
c188d5e734c8
|
return parent |
512 |
a32ec604c58f
|
|
513 |
c188d5e734c8
|
# otherwise, get the sibling |
514 |
c188d5e734c8
|
iter = self.iter_nth_child(parent, index - 1) |
515 |
a32ec604c58f
|
|
516 |
c188d5e734c8
|
# get the last, deepest child of the sibling or root, if any |
517 |
c188d5e734c8
|
while self.iter_n_children(iter) > 0: |
518 |
c188d5e734c8
|
iter = self.iter_nth_child(iter, self.iter_n_children(iter) - 1) |
519 |
a32ec604c58f
|
|
520 |
c188d5e734c8
|
return iter |
521 |
cea4127a11c0
|
|
522 |
cea4127a11c0
|
|
523 |
8077271f872d
|
def move_entry(self, iter, parent = None, sibling = None): |
524 |
8077271f872d
|
"Moves an entry" |
525 |
8077271f872d
|
|
526 |
8077271f872d
|
newiter = self.copy_entry(iter, parent, sibling) |
527 |
8077271f872d
|
self.remove_entry(iter) |
528 |
8077271f872d
|
|
529 |
8077271f872d
|
return newiter |
530 |
8077271f872d
|
|
531 |
8077271f872d
|
|
532 |
cea4127a11c0
|
def remove_entry(self, iter): |
533 |
4787d15f40a4
|
"Removes an entry, and its children if any" |
534 |
4787d15f40a4
|
|
535 |
c188d5e734c8
|
if iter is None: |
536 |
c188d5e734c8
|
return None |
537 |
c188d5e734c8
|
|
538 |
cea4127a11c0
|
self.remove(iter) |
539 |
c188d5e734c8
|
self.changed = True |
540 |
cea4127a11c0
|
|
541 |
cea4127a11c0
|
|
542 |
c188d5e734c8
|
def update_entry(self, iter, e): |
543 |
c188d5e734c8
|
"Updates an entry" |
544 |
cea4127a11c0
|
|
545 |
c188d5e734c8
|
if None in ( iter, e): |
546 |
c188d5e734c8
|
return None |
547 |
a877d43f0987
|
|
548 |
c188d5e734c8
|
self.set_value(iter, COLUMN_NAME, e.name) |
549 |
c188d5e734c8
|
self.set_value(iter, COLUMN_ICON, e.icon) |
550 |
c188d5e734c8
|
self.set_value(iter, COLUMN_ENTRY, e.copy()) |
551 |
a877d43f0987
|
|
552 |
c188d5e734c8
|
self.changed = True |
553 |
a877d43f0987
|
|
554 |
4787d15f40a4
|
|
555 |
cea4127a11c0
|
|
556 |
89c3dd775abc
|
class Timer(gobject.GObject): |
557 |
89c3dd775abc
|
"Handles timeouts etc" |
558 |
89c3dd775abc
|
|
559 |
89c3dd775abc
|
def __init__(self, resolution = 1): |
560 |
89c3dd775abc
|
gobject.GObject.__init__(self) |
561 |
89c3dd775abc
|
|
562 |
89c3dd775abc
|
self.offset = None |
563 |
89c3dd775abc
|
self.timeout = None |
564 |
89c3dd775abc
|
|
565 |
89c3dd775abc
|
gobject.timeout_add(resolution * 1000, self.__cb_check) |
566 |
89c3dd775abc
|
|
567 |
89c3dd775abc
|
|
568 |
89c3dd775abc
|
def __cb_check(self): |
569 |
89c3dd775abc
|
"Checks if the timeout has been reached" |
570 |
89c3dd775abc
|
|
571 |
89c3dd775abc
|
if None not in (self.offset, self.timeout) and int(time.time()) >= (self.offset + self.timeout): |
572 |
89c3dd775abc
|
self.stop() |
573 |
89c3dd775abc
|
self.emit("ring") |
574 |
89c3dd775abc
|
|
575 |
89c3dd775abc
|
return True |
576 |
89c3dd775abc
|
|
577 |
89c3dd775abc
|
|
578 |
89c3dd775abc
|
def reset(self): |
579 |
89c3dd775abc
|
"Resets the timer" |
580 |
89c3dd775abc
|
|
581 |
89c3dd775abc
|
if self.offset != None: |
582 |
89c3dd775abc
|
self.offset = int(time.time()) |
583 |
89c3dd775abc
|
|
584 |
89c3dd775abc
|
|
585 |
89c3dd775abc
|
def start(self, timeout): |
586 |
89c3dd775abc
|
"Starts the timer" |
587 |
89c3dd775abc
|
|
588 |
89c3dd775abc
|
if timeout == 0: |
589 |
89c3dd775abc
|
self.stop() |
590 |
89c3dd775abc
|
|
591 |
89c3dd775abc
|
else: |
592 |
89c3dd775abc
|
self.offset = int(time.time()) |
593 |
89c3dd775abc
|
self.timeout = timeout |
594 |
89c3dd775abc
|
|
595 |
89c3dd775abc
|
|
596 |
89c3dd775abc
|
def stop(self): |
597 |
89c3dd775abc
|
"Stops the timer" |
598 |
89c3dd775abc
|
|
599 |
89c3dd775abc
|
self.offset = None |
600 |
89c3dd775abc
|
self.timeout = None |
601 |
89c3dd775abc
|
|
602 |
89c3dd775abc
|
|
603 |
89c3dd775abc
|
gobject.signal_new("ring", Timer, gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, ()) |
604 |
89c3dd775abc
|
|
605 |
89c3dd775abc
|
|
606 |
89c3dd775abc
|
|
607 |
c9d89f3ff91d
|
class UndoQueue(gobject.GObject): |
608 |
c188d5e734c8
|
"Handles undo/redo tracking" |
609 |
cea4127a11c0
|
|
610 |
cea4127a11c0
|
def __init__(self): |
611 |
c9d89f3ff91d
|
gobject.GObject.__init__(self) |
612 |
c9d89f3ff91d
|
|
613 |
c188d5e734c8
|
self.queue = [] |
614 |
c188d5e734c8
|
self.pointer = 0 |
615 |
cea4127a11c0
|
|
616 |
cea4127a11c0
|
|
617 |
c188d5e734c8
|
def add_action(self, name, cb_undo, cb_redo, actiondata): |
618 |
268984dc47cf
|
"Adds an action to the undo queue" |
619 |
268984dc47cf
|
|
620 |
c188d5e734c8
|
del self.queue[self.pointer:] |
621 |
268984dc47cf
|
|
622 |
c188d5e734c8
|
self.queue.append(( name, cb_undo, cb_redo, actiondata )) |
623 |
c188d5e734c8
|
self.pointer = len(self.queue) |
624 |
268984dc47cf
|
|
625 |
c9d89f3ff91d
|
self.emit("changed") |
626 |
c9d89f3ff91d
|
|
627 |
268984dc47cf
|
|
628 |
268984dc47cf
|
def can_redo(self): |
629 |
268984dc47cf
|
"Checks if a redo action is possible" |
630 |
268984dc47cf
|
|
631 |
c188d5e734c8
|
return self.pointer < len(self.queue) |
632 |
268984dc47cf
|
|
633 |
268984dc47cf
|
|
634 |
c188d5e734c8
|
def can_undo(self): |
635 |
268984dc47cf
|
"Checks if an undo action is possible" |
636 |
268984dc47cf
|
|
637 |
c188d5e734c8
|
return self.pointer > 0 |
638 |
268984dc47cf
|
|
639 |
268984dc47cf
|
|
640 |
268984dc47cf
|
def clear(self): |
641 |
c188d5e734c8
|
"Clears the queue" |
642 |
268984dc47cf
|
|
643 |
cea4127a11c0
|
self.queue = [] |
644 |
c188d5e734c8
|
self.pointer = 0 |
645 |
cea4127a11c0
|
|
646 |
c9d89f3ff91d
|
self.emit("changed") |
647 |
c9d89f3ff91d
|
|
648 |
cea4127a11c0
|
|
649 |
c188d5e734c8
|
def get_redo_action(self): |
650 |
c188d5e734c8
|
"Returns data for the next redo operation" |
651 |
cea4127a11c0
|
|
652 |
c188d5e734c8
|
if self.can_redo() == False: |
653 |
c188d5e734c8
|
return None |
654 |
cea4127a11c0
|
|
655 |
c188d5e734c8
|
name, cb_undo, cb_redo, actiondata = self.queue[self.pointer] |
656 |
cea4127a11c0
|
|
657 |
c188d5e734c8
|
return cb_redo, name, actiondata |
658 |
cea4127a11c0
|
|
659 |
cea4127a11c0
|
|
660 |
c188d5e734c8
|
def get_undo_action(self): |
661 |
c188d5e734c8
|
"Returns data for the next undo operation" |
662 |
cea4127a11c0
|
|
663 |
c188d5e734c8
|
if self.can_undo() == False: |
664 |
c188d5e734c8
|
return None |
665 |
cea4127a11c0
|
|
666 |
c188d5e734c8
|
name, cb_undo, cb_redo, actiondata = self.queue[self.pointer - 1] |
667 |
cea4127a11c0
|
|
668 |
c188d5e734c8
|
return cb_undo, name, actiondata |
669 |
cea4127a11c0
|
|
670 |
cea4127a11c0
|
|
671 |
cea4127a11c0
|
def redo(self): |
672 |
268984dc47cf
|
"Executes a redo operation" |
673 |
cea4127a11c0
|
|
674 |
c188d5e734c8
|
if self.can_redo() == False: |
675 |
c188d5e734c8
|
return None |
676 |
268984dc47cf
|
|
677 |
c188d5e734c8
|
cb_redo, name, actiondata = self.get_redo_action() |
678 |
c188d5e734c8
|
self.pointer += 1 |
679 |
268984dc47cf
|
|
680 |
c188d5e734c8
|
cb_redo(name, actiondata) |
681 |
c9d89f3ff91d
|
self.emit("changed") |
682 |
cea4127a11c0
|
|
683 |
cea4127a11c0
|
|
684 |
cea4127a11c0
|
def undo(self): |
685 |
268984dc47cf
|
"Executes an undo operation" |
686 |
cea4127a11c0
|
|
687 |
c188d5e734c8
|
if self.can_undo() == False: |
688 |
c188d5e734c8
|
return None |
689 |
cea4127a11c0
|
|
690 |
c188d5e734c8
|
cb_undo, name, actiondata = self.get_undo_action() |
691 |
c188d5e734c8
|
self.pointer -= 1 |
692 |
cea4127a11c0
|
|
693 |
c188d5e734c8
|
cb_undo(name, actiondata) |
694 |
c9d89f3ff91d
|
self.emit("changed") |
695 |
cea4127a11c0
|
|
696 |
c9d89f3ff91d
|
|
697 |
c9d89f3ff91d
|
gobject.type_register(UndoQueue) |
698 |
c9d89f3ff91d
|
gobject.signal_new("changed", UndoQueue, gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, ()) |
699 |
c9d89f3ff91d
|