~ubuntu-branches/ubuntu/lucid/meld/lucid

« back to all changes in this revision

Viewing changes to historyentry.py

  • Committer: Bazaar Package Importer
  • Author(s): Ross Burton
  • Date: 2009-06-02 10:00:20 UTC
  • mfrom: (1.2.9 upstream) (2.1.5 squeeze)
  • Revision ID: james.westby@ubuntu.com-20090602100020-jb5wlixbu8xws29a
Tags: 1.3.0-1
* New upstream release (Closes: #528327)
  - Copy/paste behaviour fixes (Closes: #523576)
  - Dotfiles and removals handled in darcs (Closes: #509069)
  - Can handle Mercurial repos (Closes: #428843)
  - Up/Down patch used to skip changes (Closes: #511027)
  - Handle last line when highlighting properly (Closes: #465804)
* Update message and depends for new python-gtksourceview2 package
* Resync makefile.patch
* Remove python-gnome dependency

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008-2009 Kai Willadsen <kai.willadsen@gmail.com>
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
import os
 
18
 
 
19
import gtk
 
20
import gobject
 
21
import atk
 
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 _
 
25
 
 
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
 
30
 
 
31
MIN_ITEM_LEN = 3
 
32
HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT = 10
 
33
 
 
34
def _remove_item(store, text):
 
35
    if text is None:
 
36
        return False
 
37
 
 
38
    for row in store:
 
39
        if row[0] == text:
 
40
            store.remove(row.iter)
 
41
            return True
 
42
    return False
 
43
 
 
44
def _clamp_list_store(liststore, max_items):
 
45
    try:
 
46
        it = liststore.get_iter(max_items - 1) # -1 because TreePath counts from 0
 
47
    except ValueError:
 
48
        return
 
49
    valid = True
 
50
    while valid:
 
51
        valid = liststore.remove(it)
 
52
 
 
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)
 
57
 
 
58
 
 
59
class HistoryEntry(gtk.ComboBoxEntry):
 
60
 
 
61
    def __init__(self, history_id=None, enable_completion=False, **kwargs):
 
62
        super(HistoryEntry, self).__init__(**kwargs)
 
63
 
 
64
        self.__history_id = history_id
 
65
        self.__history_length = HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT
 
66
        self.__completion = None
 
67
        self._get_gconf_client()
 
68
 
 
69
        self.set_model(gtk.ListStore(str))
 
70
        self.props.text_column = 0
 
71
 
 
72
        self._load_history()
 
73
        self.set_enable_completion(enable_completion)
 
74
 
 
75
    def _get_gconf_client(self):
 
76
        self.__gconf_client = gconf.client_get_default()
 
77
 
 
78
    def __get_history_key(self):
 
79
        # We store data under /apps/gnome-settings/ like GnomeEntry did.
 
80
        if not self.__history_id:
 
81
            return None
 
82
        key = ''.join(["/apps/gnome-settings/","meld","/history-",
 
83
                          gconf.escape_key(self.__history_id, -1)])
 
84
        return key
 
85
 
 
86
    def _save_history(self):
 
87
        key = self.__get_history_key()
 
88
        if key is None:
 
89
            return
 
90
        gconf_items = [row[0] for row in self.get_model()]
 
91
        self.__gconf_client.set_list(key, gconf.VALUE_STRING, gconf_items)
 
92
 
 
93
    def __insert_history_item(self, text, prepend):
 
94
        if len(text) <= MIN_ITEM_LEN:
 
95
            return
 
96
 
 
97
        store = self.get_model()
 
98
        if not _remove_item(store, text):
 
99
            _clamp_list_store(store, self.__history_length - 1)
 
100
 
 
101
        if (prepend):
 
102
            store.insert(0, (text,))
 
103
        else:
 
104
            store.append((text,))
 
105
        self._save_history()
 
106
 
 
107
    def prepend_text(self, text):
 
108
        if not text:
 
109
            return
 
110
        self.__insert_history_item(text, True)
 
111
 
 
112
    def append_text(self, text):
 
113
        if not text:
 
114
            return
 
115
        self.__insert_history_item(text, False)
 
116
 
 
117
    def _load_history(self):
 
118
        key = self.__get_history_key()
 
119
        if key is None:
 
120
            return
 
121
        gconf_items = self.__gconf_client.get_list(key, gconf.VALUE_STRING)
 
122
 
 
123
        store = self.get_model()
 
124
        store.clear()
 
125
 
 
126
        for item in gconf_items[:self.__history_length - 1]:
 
127
            store.append((item,))
 
128
 
 
129
    def clear(self):
 
130
        store = self.get_model()
 
131
        store.clear()
 
132
        self._save_history()
 
133
 
 
134
    def set_history_length(self, max_saved):
 
135
        if max_saved <= 0:
 
136
            return
 
137
        self.__history_length = max_saved
 
138
        if len(self.get_model()) > max_saved:
 
139
            self._load_history()
 
140
 
 
141
    def get_history_length(self):
 
142
        return self.__history_length
 
143
 
 
144
    def set_enable_completion(self, enable):
 
145
        if enable:
 
146
            if self.__completion is not None:
 
147
                return
 
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)
 
155
        else:
 
156
            if self.__completion is None:
 
157
                return
 
158
            self.get_entry().set_completion(None)
 
159
            self.__completion = None
 
160
 
 
161
    def get_enable_completion(self):
 
162
        return self.__completion is not None
 
163
 
 
164
    def get_entry(self):
 
165
        return self.child
 
166
 
 
167
    def focus_entry(self):
 
168
        self.child.grab_focus()
 
169
 
 
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:
 
174
            return
 
175
 
 
176
        if escape_func is not None:
 
177
            self.set_cell_data_func(cells[0], _escape_cell_data_func, escape_func)
 
178
        else:
 
179
            self.set_cell_data_func(cells[0], None, None)
 
180
 
 
181
try:
 
182
    import gconf
 
183
except ImportError:
 
184
    do_nothing = lambda *args: None
 
185
    for m in ('_save_history', '_load_history', '_get_gconf_client'):
 
186
        setattr(HistoryEntry, m, do_nothing)
 
187
 
 
188
 
 
189
 
 
190
def _expand_filename(filename, default_dir):
 
191
    if not filename:
 
192
        return ""
 
193
    if os.path.isabs(filename):
 
194
        return filename
 
195
    expanded = os.path.expanduser(filename)
 
196
    if expanded != filename:
 
197
        return expanded
 
198
    elif default_dir:
 
199
        return os.path.expanduser(os.path.join(default_dir, filename))
 
200
    else:
 
201
        return os.path.join(os.getcwd(), filename)
 
202
 
 
203
 
 
204
class HistoryFileEntry(gtk.HBox, gtk.Editable):
 
205
    __gsignals__ = {
 
206
        "browse_clicked" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
 
207
        "activate" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
 
208
    }
 
209
 
 
210
    __gproperties__ = {
 
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),
 
223
    }
 
224
 
 
225
 
 
226
    def __init__(self, history_id=None, browse_dialog_title=None, **kwargs):
 
227
        super(HistoryFileEntry, self).__init__(**kwargs)
 
228
 
 
229
        self.fsw = None
 
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
 
234
        self.__modal = False
 
235
 
 
236
        self.set_spacing(3)
 
237
 
 
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"))
 
243
        self._setup_dnd()
 
244
        self.pack_start(self.__gentry, True, True, 0)
 
245
        self.__gentry.show()
 
246
 
 
247
        button = gtk.Button(_("_Browse..."))
 
248
        button.connect("clicked", self.__browse_clicked)
 
249
        self.pack_start(button, False, False, 0)
 
250
        button.show()
 
251
 
 
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)
 
260
 
 
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":
 
269
            return self.__modal
 
270
        else:
 
271
            raise AttributeError("Unknown property: %s" % pspec.name)
 
272
 
 
273
    def do_set_property(self, pspec, value):
 
274
        if pspec.name == "default-path":
 
275
            if value:
 
276
                self.__default_path = os.path.abspath(value)
 
277
            else:
 
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":
 
284
            self.__modal = value
 
285
        else:
 
286
            raise AttributeError("Unknown property: %s" % pspec.name)
 
287
 
 
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)
 
297
 
 
298
    def append_history(self, text):
 
299
        self.__gentry.append_text(text)
 
300
 
 
301
    def prepend_history(self, text):
 
302
        self.__gentry.prepend_text(text)
 
303
 
 
304
    def focus_entry(self):
 
305
        self.__gentry.focus_entry()
 
306
 
 
307
    def set_default_path(self, path):
 
308
        if path:
 
309
            self.__default_path = os.path.abspath(path)
 
310
        else:
 
311
            self.__default_path = None
 
312
 
 
313
    def set_directory_entry(self, is_directory_entry):
 
314
        self.directory_entry = is_directory_entry
 
315
 
 
316
    def get_directory_entry(self):
 
317
        return self.directory_entry
 
318
 
 
319
    def get_full_path(self):
 
320
        text = self.__gentry.get_entry().get_text()
 
321
        if not text:
 
322
            return None
 
323
        sys_text = gobject.filename_from_utf8(text)
 
324
        filename = _expand_filename(sys_text, self.__default_path)
 
325
        if not filename:
 
326
            return None
 
327
        return filename
 
328
 
 
329
    def set_filename(self, filename):
 
330
        self.__gentry.get_entry().set_text(filename)
 
331
 
 
332
    def __browse_dialog_ok(self, filewidget):
 
333
        locale_filename = filewidget.get_filename()
 
334
        if not locale_filename:
 
335
            return
 
336
 
 
337
        encoding = os.getenv("G_FILENAME_ENCODING")
 
338
        if 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)
 
343
        entry.activate()
 
344
 
 
345
    def __browse_dialog_response(self, widget, response):
 
346
        if response == gtk.RESPONSE_ACCEPT:
 
347
            self.__browse_dialog_ok(widget)
 
348
        widget.hide()
 
349
 
 
350
    def __build_filename(self):
 
351
        text = self.__gentry.get_entry().get_text()
 
352
        if not text:
 
353
            return self.__default_path + os.sep
 
354
 
 
355
        locale_text = gobject.filename_from_utf8(text)
 
356
        if not locale_text:
 
357
            return self.__default_path + os.sep
 
358
 
 
359
        filename = _expand_filename(locale_text, self.__default_path)
 
360
        if not filename:
 
361
            return self.__default_path + os.sep
 
362
 
 
363
        if not filename.endswith(os.sep) and (self.__directory_entry or os.path.isdir(filename)):
 
364
            filename += os.sep
 
365
        return filename
 
366
 
 
367
    def __browse_clicked(self, *args):
 
368
        if self.fsw:
 
369
            self.fsw.show()
 
370
            if self.fsw.window:
 
371
                self.fsw.window.raise_()
 
372
            return
 
373
 
 
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")
 
379
        else:
 
380
            action = self.__filechooser_action
 
381
            filefilter = None
 
382
            title = self.browse_dialog_title or _("Select file")
 
383
 
 
384
        if action == gtk.FILE_CHOOSER_ACTION_SAVE:
 
385
            buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)
 
386
        else:
 
387
            buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT)
 
388
 
 
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)
 
394
 
 
395
        toplevel = self.get_toplevel()
 
396
        modal_fentry = False
 
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)
 
402
 
 
403
        self.fsw.show()
 
404
 
 
405
    def history_entry_drag_data_received(self, widget, context, x, y, selection_data, info, time):
 
406
        uris = selection_data.data.split()
 
407
        if not uris:
 
408
            context.finish(False, False, time)
 
409
            return
 
410
 
 
411
        for uri in uris:
 
412
            path = gnomevfs.get_local_path_from_uri(uri)
 
413
            if path:
 
414
                break
 
415
        else:
 
416
            context.finish(False, False, time)
 
417
            return
 
418
 
 
419
        entry = self.__gentry.get_entry()
 
420
        entry.set_text(path)
 
421
        context.finish(True, False, time)
 
422
        entry.activate()
 
423
 
 
424
try:
 
425
    import gnomevfs
 
426
except ImportError:
 
427
    do_nothing = lambda *args: None
 
428
    setattr(HistoryFileEntry, '_setup_dnd', do_nothing)
 
429
 
 
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
 
433
    return w
 
434
 
 
435
def create_entry( history_id, str2, int1, int2):
 
436
    w = HistoryEntry(history_id)
 
437
    return w
 
438