1
# Copyright (C) 2008-2009 Kai Willadsen <kai.willadsen@gmail.com>
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
# gconf is also imported; see end of HistoryEntry class for details
23
# gnomevfs is also imported; see end of HistoryFileEntry class for details
24
from gettext import gettext as _
26
# This file is a Python translation of:
27
# * gedit/gedit/gedit-history-entry.c
28
# * libgnomeui/libgnomeui/gnome-file-entry.c
29
# roughly based on Colin Walters' Python translation of msgarea.py from Hotwire
32
HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT = 10
34
def _remove_item(store, text):
40
store.remove(row.iter)
44
def _clamp_list_store(liststore, max_items):
46
it = liststore.get_iter(max_items - 1) # -1 because TreePath counts from 0
51
valid = liststore.remove(it)
53
def _escape_cell_data_func(col, renderer, model, it, escape_func):
54
string = model.get(it, 0)
55
escaped = escape_func(string)
56
renderer.set("text", escaped)
59
class HistoryEntry(gtk.ComboBoxEntry):
61
def __init__(self, history_id=None, enable_completion=False, **kwargs):
62
super(HistoryEntry, self).__init__(**kwargs)
64
self.__history_id = history_id
65
self.__history_length = HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT
66
self.__completion = None
67
self._get_gconf_client()
69
self.set_model(gtk.ListStore(str))
70
self.props.text_column = 0
73
self.set_enable_completion(enable_completion)
75
def _get_gconf_client(self):
76
self.__gconf_client = gconf.client_get_default()
78
def __get_history_key(self):
79
# We store data under /apps/gnome-settings/ like GnomeEntry did.
80
if not self.__history_id:
82
key = ''.join(["/apps/gnome-settings/","meld","/history-",
83
gconf.escape_key(self.__history_id, -1)])
86
def _save_history(self):
87
key = self.__get_history_key()
90
gconf_items = [row[0] for row in self.get_model()]
91
self.__gconf_client.set_list(key, gconf.VALUE_STRING, gconf_items)
93
def __insert_history_item(self, text, prepend):
94
if len(text) <= MIN_ITEM_LEN:
97
store = self.get_model()
98
if not _remove_item(store, text):
99
_clamp_list_store(store, self.__history_length - 1)
102
store.insert(0, (text,))
104
store.append((text,))
107
def prepend_text(self, text):
110
self.__insert_history_item(text, True)
112
def append_text(self, text):
115
self.__insert_history_item(text, False)
117
def _load_history(self):
118
key = self.__get_history_key()
121
gconf_items = self.__gconf_client.get_list(key, gconf.VALUE_STRING)
123
store = self.get_model()
126
for item in gconf_items[:self.__history_length - 1]:
127
store.append((item,))
130
store = self.get_model()
134
def set_history_length(self, max_saved):
137
self.__history_length = max_saved
138
if len(self.get_model()) > max_saved:
141
def get_history_length(self):
142
return self.__history_length
144
def set_enable_completion(self, enable):
146
if self.__completion is not None:
148
self.__completion = gtk.EntryCompletion()
149
self.__completion.set_model(self.get_model())
150
self.__completion.set_text_column(0)
151
self.__completion.set_minimum_key_length(MIN_ITEM_LEN)
152
self.__completion.set_popup_completion(False)
153
self.__completion.set_inline_completion(True)
154
self.child.set_completion(self.__completion)
156
if self.__completion is None:
158
self.get_entry().set_completion(None)
159
self.__completion = None
161
def get_enable_completion(self):
162
return self.__completion is not None
167
def focus_entry(self):
168
self.child.grab_focus()
170
def set_escape_func(self, escape_func):
171
cells = self.get_cells()
172
# We only have one cell renderer
173
if len(cells) == 0 or len(cells) > 1:
176
if escape_func is not None:
177
self.set_cell_data_func(cells[0], _escape_cell_data_func, escape_func)
179
self.set_cell_data_func(cells[0], None, None)
184
do_nothing = lambda *args: None
185
for m in ('_save_history', '_load_history', '_get_gconf_client'):
186
setattr(HistoryEntry, m, do_nothing)
190
def _expand_filename(filename, default_dir):
193
if os.path.isabs(filename):
195
expanded = os.path.expanduser(filename)
196
if expanded != filename:
199
return os.path.expanduser(os.path.join(default_dir, filename))
201
return os.path.join(os.getcwd(), filename)
204
class HistoryFileEntry(gtk.HBox, gtk.Editable):
206
"browse_clicked" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
207
"activate" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
211
"default-path": (str, "Default path",
212
"Default path for file chooser",
213
"~", gobject.PARAM_READWRITE),
214
"directory-entry": (bool, "File or directory entry",
215
"Whether the created file chooser should select directories instead of files",
216
False, gobject.PARAM_READWRITE),
217
"filename": (str, "Filename",
218
"Filename of the selected file",
219
"", gobject.PARAM_READWRITE),
220
"modal": (bool, "File chooser modality",
221
"Whether the created file chooser is modal",
222
False, gobject.PARAM_READWRITE),
226
def __init__(self, history_id=None, browse_dialog_title=None, **kwargs):
227
super(HistoryFileEntry, self).__init__(**kwargs)
230
self.browse_dialog_title = browse_dialog_title
231
self.__filechooser_action = gtk.FILE_CHOOSER_ACTION_OPEN
232
self.__default_path = "~"
233
self.__directory_entry = False
238
# TODO: completion would be nice, but some quirks make it currently too irritating to turn on by default
239
self.__gentry = HistoryEntry(history_id, False)
240
entry = self.__gentry.get_entry()
241
entry.connect("changed", lambda *args: self.emit("changed"))
242
entry.connect("activate", lambda *args: self.emit("activate"))
244
self.pack_start(self.__gentry, True, True, 0)
247
button = gtk.Button(_("_Browse..."))
248
button.connect("clicked", self.__browse_clicked)
249
self.pack_start(button, False, False, 0)
252
access_entry = self.__gentry.get_accessible()
253
access_button = button.get_accessible()
254
if access_entry and access_button:
255
access_entry.set_name(_("Path"))
256
access_entry.set_description(_("Path to file"))
257
access_button.set_description(_("Pop up a file selector to choose a file"))
258
access_button.add_relationship(atk.RELATION_CONTROLLER_FOR, access_entry)
259
access_entry.add_relationship(atk.RELATION_CONTROLLED_BY, access_button)
261
def do_get_property(self, pspec):
262
if pspec.name == "default-path":
263
return self.__default_path
264
elif pspec.name == "directory-entry":
265
return self.__directory_entry
266
elif pspec.name == "filename":
267
return self.get_full_path()
268
elif pspec.name == "modal":
271
raise AttributeError("Unknown property: %s" % pspec.name)
273
def do_set_property(self, pspec, value):
274
if pspec.name == "default-path":
276
self.__default_path = os.path.abspath(value)
278
self.__default_path = None
279
elif pspec.name == "directory-entry":
280
self.__directory_entry = value
281
elif pspec.name == "filename":
282
self.set_filename(value)
283
elif pspec.name == "modal":
286
raise AttributeError("Unknown property: %s" % pspec.name)
288
def _setup_dnd(self):
289
# we must get rid of gtk's drop site on the entry else weird stuff can happen
290
self.__gentry.get_entry().drag_dest_unset()
291
self.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
292
gtk.DEST_DEFAULT_HIGHLIGHT |
293
gtk.DEST_DEFAULT_DROP,
294
[], gtk.gdk.ACTION_COPY)
295
self.drag_dest_add_uri_targets()
296
self.connect("drag_data_received", self.history_entry_drag_data_received)
298
def append_history(self, text):
299
self.__gentry.append_text(text)
301
def prepend_history(self, text):
302
self.__gentry.prepend_text(text)
304
def focus_entry(self):
305
self.__gentry.focus_entry()
307
def set_default_path(self, path):
309
self.__default_path = os.path.abspath(path)
311
self.__default_path = None
313
def set_directory_entry(self, is_directory_entry):
314
self.directory_entry = is_directory_entry
316
def get_directory_entry(self):
317
return self.directory_entry
319
def get_full_path(self):
320
text = self.__gentry.get_entry().get_text()
323
sys_text = gobject.filename_from_utf8(text)
324
filename = _expand_filename(sys_text, self.__default_path)
329
def set_filename(self, filename):
330
self.__gentry.get_entry().set_text(filename)
332
def __browse_dialog_ok(self, filewidget):
333
locale_filename = filewidget.get_filename()
334
if not locale_filename:
337
encoding = os.getenv("G_FILENAME_ENCODING")
339
# FIXME: This isn't tested.
340
locale_filename = unicode(locale_filename, encoding)
341
entry = self.__gentry.get_entry()
342
entry.set_text(locale_filename)
345
def __browse_dialog_response(self, widget, response):
346
if response == gtk.RESPONSE_ACCEPT:
347
self.__browse_dialog_ok(widget)
350
def __build_filename(self):
351
text = self.__gentry.get_entry().get_text()
353
return self.__default_path + os.sep
355
locale_text = gobject.filename_from_utf8(text)
357
return self.__default_path + os.sep
359
filename = _expand_filename(locale_text, self.__default_path)
361
return self.__default_path + os.sep
363
if not filename.endswith(os.sep) and (self.__directory_entry or os.path.isdir(filename)):
367
def __browse_clicked(self, *args):
371
self.fsw.window.raise_()
374
if self.__directory_entry:
375
action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
376
filefilter = gtk.FileFilter()
377
filefilter.add_mime_type("x-directory/normal")
378
title = self.browse_dialog_title or _("Select directory")
380
action = self.__filechooser_action
382
title = self.browse_dialog_title or _("Select file")
384
if action == gtk.FILE_CHOOSER_ACTION_SAVE:
385
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)
387
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT)
389
self.fsw = gtk.FileChooserDialog(title, None, action, buttons, None)
390
self.fsw.props.filter = filefilter
391
self.fsw.set_default_response(gtk.RESPONSE_ACCEPT)
392
self.fsw.set_filename(self.__build_filename())
393
self.fsw.connect("response", self.__browse_dialog_response)
395
toplevel = self.get_toplevel()
397
if toplevel.flags() & gtk.TOPLEVEL:
398
self.fsw.set_transient_for(toplevel)
399
modal_fentry = toplevel.get_modal()
400
if self.__modal or modal_fentry:
401
self.fsw.set_modal(True)
405
def history_entry_drag_data_received(self, widget, context, x, y, selection_data, info, time):
406
uris = selection_data.data.split()
408
context.finish(False, False, time)
412
path = gnomevfs.get_local_path_from_uri(uri)
416
context.finish(False, False, time)
419
entry = self.__gentry.get_entry()
421
context.finish(True, False, time)
427
do_nothing = lambda *args: None
428
setattr(HistoryFileEntry, '_setup_dnd', do_nothing)
430
def create_fileentry( history_id, dialog_title, is_directory_entry, int2):
431
w = HistoryFileEntry(history_id, dialog_title)
432
w.props.directory_entry = is_directory_entry
435
def create_entry( history_id, str2, int1, int2):
436
w = HistoryEntry(history_id)