forked from oliver/meld
-
Notifications
You must be signed in to change notification settings - Fork 0
/
historyentry.py
439 lines (360 loc) · 14.6 KB
/
historyentry.py
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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
# Copyright (C) 2008-2009 Kai Willadsen <kai.willadsen@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import os
import sys
import gtk
import gobject
import atk
# gconf is also imported; see end of HistoryEntry class for details
# gnomevfs is also imported; see end of HistoryFileEntry class for details
from gettext import gettext as _
# This file is a Python translation of:
# * gedit/gedit/gedit-history-entry.c
# * libgnomeui/libgnomeui/gnome-file-entry.c
# roughly based on Colin Walters' Python translation of msgarea.py from Hotwire
MIN_ITEM_LEN = 3
HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT = 10
def _remove_item(store, text):
if text is None:
return False
for row in store:
if row[0] == text:
store.remove(row.iter)
return True
return False
def _clamp_list_store(liststore, max_items):
try:
it = liststore.get_iter(max_items - 1) # -1 because TreePath counts from 0
except ValueError:
return
valid = True
while valid:
valid = liststore.remove(it)
def _escape_cell_data_func(col, renderer, model, it, escape_func):
string = model.get(it, 0)
escaped = escape_func(string)
renderer.set("text", escaped)
class HistoryEntry(gtk.ComboBoxEntry):
def __init__(self, history_id=None, enable_completion=False, **kwargs):
super(HistoryEntry, self).__init__(**kwargs)
self.__history_id = history_id
self.__history_length = HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT
self.__completion = None
self._get_gconf_client()
self.set_model(gtk.ListStore(str))
self.props.text_column = 0
self._load_history()
self.set_enable_completion(enable_completion)
def _get_gconf_client(self):
self.__gconf_client = gconf.client_get_default()
def __get_history_key(self):
# We store data under /apps/gnome-settings/ like GnomeEntry did.
if not self.__history_id:
return None
key = ''.join(["/apps/gnome-settings/","meld","/history-",
gconf.escape_key(self.__history_id, -1)])
return key
def _save_history(self):
key = self.__get_history_key()
if key is None:
return
gconf_items = [row[0] for row in self.get_model()]
self.__gconf_client.set_list(key, gconf.VALUE_STRING, gconf_items)
def __insert_history_item(self, text, prepend):
if len(text) <= MIN_ITEM_LEN:
return
store = self.get_model()
if not _remove_item(store, text):
_clamp_list_store(store, self.__history_length - 1)
if (prepend):
store.insert(0, (text,))
else:
store.append((text,))
self._save_history()
def prepend_text(self, text):
if not text:
return
self.__insert_history_item(text, True)
def append_text(self, text):
if not text:
return
self.__insert_history_item(text, False)
def _load_history(self):
key = self.__get_history_key()
if key is None:
return
gconf_items = self.__gconf_client.get_list(key, gconf.VALUE_STRING)
store = self.get_model()
store.clear()
for item in gconf_items[:self.__history_length - 1]:
store.append((item,))
def clear(self):
store = self.get_model()
store.clear()
self._save_history()
def set_history_length(self, max_saved):
if max_saved <= 0:
return
self.__history_length = max_saved
if len(self.get_model()) > max_saved:
self._load_history()
def get_history_length(self):
return self.__history_length
def set_enable_completion(self, enable):
if enable:
if self.__completion is not None:
return
self.__completion = gtk.EntryCompletion()
self.__completion.set_model(self.get_model())
self.__completion.set_text_column(0)
self.__completion.set_minimum_key_length(MIN_ITEM_LEN)
self.__completion.set_popup_completion(False)
self.__completion.set_inline_completion(True)
self.child.set_completion(self.__completion)
else:
if self.__completion is None:
return
self.get_entry().set_completion(None)
self.__completion = None
def get_enable_completion(self):
return self.__completion is not None
def get_entry(self):
return self.child
def focus_entry(self):
self.child.grab_focus()
def set_escape_func(self, escape_func):
cells = self.get_cells()
# We only have one cell renderer
if len(cells) == 0 or len(cells) > 1:
return
if escape_func is not None:
self.set_cell_data_func(cells[0], _escape_cell_data_func, escape_func)
else:
self.set_cell_data_func(cells[0], None, None)
try:
import gconf
except ImportError:
do_nothing = lambda *args: None
for m in ('_save_history', '_load_history', '_get_gconf_client'):
setattr(HistoryEntry, m, do_nothing)
def _expand_filename(filename, default_dir):
if not filename:
return ""
if os.path.isabs(filename):
return filename
expanded = os.path.expanduser(filename)
if expanded != filename:
return expanded
elif default_dir:
return os.path.expanduser(os.path.join(default_dir, filename))
else:
return os.path.join(os.getcwd(), filename)
class HistoryFileEntry(gtk.HBox, gtk.Editable):
__gsignals__ = {
"browse_clicked" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
"activate" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
}
__gproperties__ = {
"default-path": (str, "Default path",
"Default path for file chooser",
"~", gobject.PARAM_READWRITE),
"directory-entry": (bool, "File or directory entry",
"Whether the created file chooser should select directories instead of files",
False, gobject.PARAM_READWRITE),
"filename": (str, "Filename",
"Filename of the selected file",
"", gobject.PARAM_READWRITE),
"modal": (bool, "File chooser modality",
"Whether the created file chooser is modal",
False, gobject.PARAM_READWRITE),
}
def __init__(self, history_id=None, browse_dialog_title=None, **kwargs):
super(HistoryFileEntry, self).__init__(**kwargs)
self.fsw = None
self.browse_dialog_title = browse_dialog_title
self.__filechooser_action = gtk.FILE_CHOOSER_ACTION_OPEN
self.__default_path = "~"
self.__directory_entry = False
self.__modal = False
self.set_spacing(3)
# TODO: completion would be nice, but some quirks make it currently too irritating to turn on by default
self.__gentry = HistoryEntry(history_id, False)
entry = self.__gentry.get_entry()
entry.connect("changed", lambda *args: self.emit("changed"))
entry.connect("activate", lambda *args: self.emit("activate"))
self._setup_dnd()
self.pack_start(self.__gentry, True, True, 0)
self.__gentry.show()
button = gtk.Button(_("_Browse..."))
button.connect("clicked", self.__browse_clicked)
self.pack_start(button, False, False, 0)
button.show()
access_entry = self.__gentry.get_accessible()
access_button = button.get_accessible()
if access_entry and access_button:
access_entry.set_name(_("Path"))
access_entry.set_description(_("Path to file"))
access_button.set_description(_("Pop up a file selector to choose a file"))
access_button.add_relationship(atk.RELATION_CONTROLLER_FOR, access_entry)
access_entry.add_relationship(atk.RELATION_CONTROLLED_BY, access_button)
def do_get_property(self, pspec):
if pspec.name == "default-path":
return self.__default_path
elif pspec.name == "directory-entry":
return self.__directory_entry
elif pspec.name == "filename":
return self.get_full_path()
elif pspec.name == "modal":
return self.__modal
else:
raise AttributeError("Unknown property: %s" % pspec.name)
def do_set_property(self, pspec, value):
if pspec.name == "default-path":
if value:
self.__default_path = os.path.abspath(value)
else:
self.__default_path = None
elif pspec.name == "directory-entry":
self.__directory_entry = value
elif pspec.name == "filename":
self.set_filename(value)
elif pspec.name == "modal":
self.__modal = value
else:
raise AttributeError("Unknown property: %s" % pspec.name)
def _setup_dnd(self):
# we must get rid of gtk's drop site on the entry else weird stuff can happen
self.__gentry.get_entry().drag_dest_unset()
self.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
gtk.DEST_DEFAULT_HIGHLIGHT |
gtk.DEST_DEFAULT_DROP,
[], gtk.gdk.ACTION_COPY)
self.drag_dest_add_uri_targets()
self.connect("drag_data_received", self.history_entry_drag_data_received)
def append_history(self, text):
self.__gentry.append_text(text)
def prepend_history(self, text):
self.__gentry.prepend_text(text)
def focus_entry(self):
self.__gentry.focus_entry()
def set_default_path(self, path):
if path:
self.__default_path = os.path.abspath(path)
else:
self.__default_path = None
def set_directory_entry(self, is_directory_entry):
self.directory_entry = is_directory_entry
def get_directory_entry(self):
return self.directory_entry
def get_full_path(self):
text = self.__gentry.get_entry().get_text()
if not text:
return None
sys_text = gobject.filename_from_utf8(text)
filename = _expand_filename(sys_text, self.__default_path)
if not filename:
return None
return filename
def set_filename(self, filename):
self.__gentry.get_entry().set_text(filename)
def __browse_dialog_ok(self, filewidget):
filename = filewidget.get_filename()
if not filename:
return
encoding = sys.getfilesystemencoding()
if encoding:
filename = unicode(filename, encoding)
entry = self.__gentry.get_entry()
entry.set_text(filename)
entry.activate()
def __browse_dialog_response(self, widget, response):
if response == gtk.RESPONSE_ACCEPT:
self.__browse_dialog_ok(widget)
widget.destroy()
self.fsw = None
def __build_filename(self):
text = self.__gentry.get_entry().get_text()
if not text:
return self.__default_path + os.sep
locale_text = gobject.filename_from_utf8(text)
if not locale_text:
return self.__default_path + os.sep
filename = _expand_filename(locale_text, self.__default_path)
if not filename:
return self.__default_path + os.sep
if not filename.endswith(os.sep) and (self.__directory_entry or os.path.isdir(filename)):
filename += os.sep
return filename
def __browse_clicked(self, *args):
if self.fsw:
self.fsw.show()
if self.fsw.window:
self.fsw.window.raise_()
return
if self.__directory_entry:
action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
filefilter = gtk.FileFilter()
filefilter.add_mime_type("x-directory/normal")
title = self.browse_dialog_title or _("Select directory")
else:
action = self.__filechooser_action
filefilter = None
title = self.browse_dialog_title or _("Select file")
if action == gtk.FILE_CHOOSER_ACTION_SAVE:
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)
else:
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT)
self.fsw = gtk.FileChooserDialog(title, None, action, buttons, None)
self.fsw.props.filter = filefilter
self.fsw.set_default_response(gtk.RESPONSE_ACCEPT)
self.fsw.set_filename(self.__build_filename())
self.fsw.connect("response", self.__browse_dialog_response)
toplevel = self.get_toplevel()
modal_fentry = False
if toplevel.flags() & gtk.TOPLEVEL:
self.fsw.set_transient_for(toplevel)
modal_fentry = toplevel.get_modal()
if self.__modal or modal_fentry:
self.fsw.set_modal(True)
self.fsw.show()
def history_entry_drag_data_received(self, widget, context, x, y, selection_data, info, time):
uris = selection_data.data.split()
if not uris:
context.finish(False, False, time)
return
for uri in uris:
path = gnomevfs.get_local_path_from_uri(uri)
if path:
break
else:
context.finish(False, False, time)
return
entry = self.__gentry.get_entry()
entry.set_text(path)
context.finish(True, False, time)
entry.activate()
try:
import gnomevfs
except ImportError:
do_nothing = lambda *args: None
setattr(HistoryFileEntry, '_setup_dnd', do_nothing)
def create_fileentry( history_id, dialog_title, is_directory_entry, int2):
w = HistoryFileEntry(history_id, dialog_title)
w.props.directory_entry = is_directory_entry
return w
def create_entry( history_id, str2, int1, int2):
w = HistoryEntry(history_id)
return w