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 again

erikg / Revelation

Revelation is a password manager for the GNOME desktop, released under the GNU GPL license. It stores all your accounts and passwords in a single, secure place, and gives you access to it through a user-friendly graphical interface.

Clone this repository (size: 2.1 MB): HTTPS / SSH
hg clone https://bitbucket.org/erikg/revelation
hg clone ssh://hg@bitbucket.org/erikg/revelation

Revelation / 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