/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to olive/__init__.py

  • Committer: Mateusz Korniak
  • Date: 2007-09-11 10:17:33 UTC
  • mto: This revision was merged to the branch mainline in revision 275.
  • Revision ID: matkor@laptop-hp-20070911101733-k4ua1t0itz725o96
Add default_branch_path to MergeDialog and use it from olive-gtk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006 by Szilveszter Farkas (Phanatic) <szilveszter.farkas@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
import sys
 
19
import time
 
20
 
 
21
# gettext support
 
22
import gettext
 
23
gettext.install('olive-gtk')
 
24
 
 
25
try:
 
26
    import pygtk
 
27
    pygtk.require("2.0")
 
28
except:
 
29
    pass
 
30
 
 
31
import gobject
 
32
import gtk
 
33
import gtk.gdk
 
34
import gtk.glade
 
35
 
 
36
from bzrlib.branch import Branch
 
37
import bzrlib.errors as bzrerrors
 
38
from bzrlib.lazy_import import lazy_import
 
39
from bzrlib.ui import ui_factory
 
40
from bzrlib.workingtree import WorkingTree
 
41
 
 
42
from bzrlib.plugins.gtk.dialog import error_dialog, info_dialog, warning_dialog
 
43
from bzrlib.plugins.gtk.errors import show_bzr_error
 
44
from guifiles import GLADEFILENAME
 
45
 
 
46
from bzrlib.plugins.gtk.diff import DiffWindow
 
47
lazy_import(globals(), """
 
48
from bzrlib.plugins.gtk.viz import branchwin
 
49
""")
 
50
from bzrlib.plugins.gtk.annotate.gannotate import GAnnotateWindow
 
51
from bzrlib.plugins.gtk.annotate.config import GAnnotateConfig
 
52
from bzrlib.plugins.gtk.commit import CommitDialog
 
53
from bzrlib.plugins.gtk.conflicts import ConflictsDialog
 
54
from bzrlib.plugins.gtk.initialize import InitDialog
 
55
from bzrlib.plugins.gtk.push import PushDialog
 
56
from bzrlib.plugins.gtk.revbrowser import RevisionBrowser
 
57
 
 
58
def about():
 
59
    """ Display the AboutDialog. """
 
60
    from bzrlib.plugins.gtk import __version__
 
61
    from bzrlib.plugins.gtk.olive.guifiles import GLADEFILENAME
 
62
 
 
63
    # Load AboutDialog description
 
64
    dglade = gtk.glade.XML(GLADEFILENAME, 'aboutdialog')
 
65
    dialog = dglade.get_widget('aboutdialog')
 
66
 
 
67
    # Set version
 
68
    dialog.set_version(__version__)
 
69
    dialog.set_authors([ _("Lead Developer:"),
 
70
                         "Szilveszter Farkas <szilveszter.farkas@gmail.com>",
 
71
                         _("Contributors:"),
 
72
                         "Jelmer Vernooij <jelmer@samba.org>",
 
73
                         "Mateusz Korniak <mateusz.korniak@ant.gliwice.pl>",
 
74
                         "Gary van der Merwe <garyvdm@gmail.com>" ])
 
75
    dialog.set_artists([ "Simon Pascal Klein <klepas@klepas.org>",
 
76
                         "Jakub Steiner <jimmac@novell.com>" ])
 
77
 
 
78
    dialog.run()
 
79
    # Destroy the dialog
 
80
    dialog.destroy()
 
81
 
 
82
class OliveGtk:
 
83
    """ The main Olive GTK frontend class. This is called when launching the
 
84
    program. """
 
85
    
 
86
    def __init__(self):
 
87
        self.toplevel = gtk.glade.XML(GLADEFILENAME, 'window_main', 'olive-gtk')
 
88
        self.window = self.toplevel.get_widget('window_main')
 
89
        self.pref = Preferences()
 
90
        self.path = None
 
91
 
 
92
        # Initialize the statusbar
 
93
        self.statusbar = self.toplevel.get_widget('statusbar')
 
94
        self.context_id = self.statusbar.get_context_id('olive')
 
95
        
 
96
        # Get the main window
 
97
        self.window_main = self.toplevel.get_widget('window_main')
 
98
        # Get the HPaned
 
99
        self.hpaned_main = self.toplevel.get_widget('hpaned_main')
 
100
        # Get the TreeViews
 
101
        self.treeview_left = self.toplevel.get_widget('treeview_left')
 
102
        self.treeview_right = self.toplevel.get_widget('treeview_right')
 
103
        # Get some important menu items
 
104
        self.menuitem_add_files = self.toplevel.get_widget('menuitem_add_files')
 
105
        self.menuitem_remove_files = self.toplevel.get_widget('menuitem_remove_file')
 
106
        self.menuitem_file_make_directory = self.toplevel.get_widget('menuitem_file_make_directory')
 
107
        self.menuitem_file_rename = self.toplevel.get_widget('menuitem_file_rename')
 
108
        self.menuitem_file_move = self.toplevel.get_widget('menuitem_file_move')
 
109
        self.menuitem_file_annotate = self.toplevel.get_widget('menuitem_file_annotate')
 
110
        self.menuitem_view_show_hidden_files = self.toplevel.get_widget('menuitem_view_show_hidden_files')
 
111
        self.menuitem_view_show_ignored_files = self.toplevel.get_widget('menuitem_view_show_ignored_files')
 
112
        self.menuitem_branch = self.toplevel.get_widget('menuitem_branch')
 
113
        self.menuitem_branch_init = self.toplevel.get_widget('menuitem_branch_initialize')
 
114
        self.menuitem_branch_get = self.toplevel.get_widget('menuitem_branch_get')
 
115
        self.menuitem_branch_checkout = self.toplevel.get_widget('menuitem_branch_checkout')
 
116
        self.menuitem_branch_pull = self.toplevel.get_widget('menuitem_branch_pull')
 
117
        self.menuitem_branch_push = self.toplevel.get_widget('menuitem_branch_push')
 
118
        self.menuitem_branch_update = self.toplevel.get_widget('menuitem_branch_update')
 
119
        self.menuitem_branch_revert = self.toplevel.get_widget('menuitem_branch_revert')
 
120
        self.menuitem_branch_merge = self.toplevel.get_widget('menuitem_branch_merge')
 
121
        self.menuitem_branch_commit = self.toplevel.get_widget('menuitem_branch_commit')
 
122
        self.menuitem_branch_tags = self.toplevel.get_widget('menuitem_branch_tags')
 
123
        self.menuitem_branch_status = self.toplevel.get_widget('menuitem_branch_status')
 
124
        self.menuitem_branch_missing = self.toplevel.get_widget('menuitem_branch_missing_revisions')
 
125
        self.menuitem_branch_conflicts = self.toplevel.get_widget('menuitem_branch_conflicts')
 
126
        self.menuitem_stats = self.toplevel.get_widget('menuitem_stats')
 
127
        self.menuitem_stats_diff = self.toplevel.get_widget('menuitem_stats_diff')
 
128
        self.menuitem_stats_log = self.toplevel.get_widget('menuitem_stats_log')
 
129
        # Get some toolbuttons
 
130
        #self.menutoolbutton_diff = self.toplevel.get_widget('menutoolbutton_diff')
 
131
        self.toolbutton_diff = self.toplevel.get_widget('toolbutton_diff')
 
132
        self.toolbutton_log = self.toplevel.get_widget('toolbutton_log')
 
133
        self.toolbutton_commit = self.toplevel.get_widget('toolbutton_commit')
 
134
        self.toolbutton_pull = self.toplevel.get_widget('toolbutton_pull')
 
135
        self.toolbutton_push = self.toplevel.get_widget('toolbutton_push')
 
136
        self.toolbutton_update = self.toplevel.get_widget('toolbutton_update')
 
137
        # Get the drive selector
 
138
        self.combobox_drive = gtk.combo_box_new_text()
 
139
        self.combobox_drive.connect("changed", self._refresh_drives)
 
140
        
 
141
        # Get the navigation widgets
 
142
        self.hbox_location = self.toplevel.get_widget('hbox_location')
 
143
        self.button_location_up = self.toplevel.get_widget('button_location_up')
 
144
        self.button_location_jump = self.toplevel.get_widget('button_location_jump')
 
145
        self.entry_location = self.toplevel.get_widget('entry_location')
 
146
        self.image_location_error = self.toplevel.get_widget('image_location_error')
 
147
        
 
148
        # Get the History widgets
 
149
        self.check_history = self.toplevel.get_widget('checkbutton_history')
 
150
        self.entry_history = self.toplevel.get_widget('entry_history_revno')
 
151
        self.button_history = self.toplevel.get_widget('button_history_browse')
 
152
        
 
153
        self.vbox_main_right = self.toplevel.get_widget('vbox_main_right')
 
154
        
 
155
        # Dictionary for signal_autoconnect
 
156
        dic = { "on_window_main_destroy": gtk.main_quit,
 
157
                "on_window_main_delete_event": self.on_window_main_delete_event,
 
158
                "on_quit_activate": self.on_window_main_delete_event,
 
159
                "on_about_activate": self.on_about_activate,
 
160
                "on_menuitem_add_files_activate": self.on_menuitem_add_files_activate,
 
161
                "on_menuitem_remove_file_activate": self.on_menuitem_remove_file_activate,
 
162
                "on_menuitem_file_make_directory_activate": self.on_menuitem_file_make_directory_activate,
 
163
                "on_menuitem_file_move_activate": self.on_menuitem_file_move_activate,
 
164
                "on_menuitem_file_rename_activate": self.on_menuitem_file_rename_activate,
 
165
                "on_menuitem_file_annotate_activate": self.on_menuitem_file_annotate_activate,
 
166
                "on_menuitem_view_show_hidden_files_activate": self.on_menuitem_view_show_hidden_files_activate,
 
167
                "on_menuitem_view_show_ignored_files_activate": self.on_menuitem_view_show_ignored_files_activate,
 
168
                "on_menuitem_view_refresh_activate": self.on_menuitem_view_refresh_activate,
 
169
                "on_menuitem_branch_initialize_activate": self.on_menuitem_branch_initialize_activate,
 
170
                "on_menuitem_branch_get_activate": self.on_menuitem_branch_get_activate,
 
171
                "on_menuitem_branch_checkout_activate": self.on_menuitem_branch_checkout_activate,
 
172
                "on_menuitem_branch_revert_activate": self.on_menuitem_branch_revert_activate,
 
173
                "on_menuitem_branch_merge_activate": self.on_menuitem_branch_merge_activate,
 
174
                "on_menuitem_branch_commit_activate": self.on_menuitem_branch_commit_activate,
 
175
                "on_menuitem_branch_push_activate": self.on_menuitem_branch_push_activate,
 
176
                "on_menuitem_branch_pull_activate": self.on_menuitem_branch_pull_activate,
 
177
                "on_menuitem_branch_update_activate": self.on_menuitem_branch_update_activate,                
 
178
                "on_menuitem_branch_tags_activate": self.on_menuitem_branch_tags_activate,
 
179
                "on_menuitem_branch_status_activate": self.on_menuitem_branch_status_activate,
 
180
                "on_menuitem_branch_missing_revisions_activate": self.on_menuitem_branch_missing_revisions_activate,
 
181
                "on_menuitem_branch_conflicts_activate": self.on_menuitem_branch_conflicts_activate,
 
182
                "on_menuitem_stats_diff_activate": self.on_menuitem_stats_diff_activate,
 
183
                "on_menuitem_stats_log_activate": self.on_menuitem_stats_log_activate,
 
184
                "on_menuitem_stats_infos_activate": self.on_menuitem_stats_infos_activate,
 
185
                "on_toolbutton_refresh_clicked": self.on_menuitem_view_refresh_activate,
 
186
                "on_toolbutton_log_clicked": self.on_menuitem_stats_log_activate,
 
187
                #"on_menutoolbutton_diff_clicked": self.on_menuitem_stats_diff_activate,
 
188
                "on_toolbutton_diff_clicked": self.on_menuitem_stats_diff_activate,
 
189
                "on_toolbutton_commit_clicked": self.on_menuitem_branch_commit_activate,
 
190
                "on_toolbutton_pull_clicked": self.on_menuitem_branch_pull_activate,
 
191
                "on_toolbutton_push_clicked": self.on_menuitem_branch_push_activate,
 
192
                "on_toolbutton_update_clicked": self.on_menuitem_branch_update_activate,
 
193
                "on_treeview_right_button_press_event": self.on_treeview_right_button_press_event,
 
194
                "on_treeview_right_row_activated": self.on_treeview_right_row_activated,
 
195
                "on_treeview_left_button_press_event": self.on_treeview_left_button_press_event,
 
196
                "on_treeview_left_row_activated": self.on_treeview_left_row_activated,
 
197
                "on_button_location_up_clicked": self.on_button_location_up_clicked,
 
198
                "on_button_location_jump_clicked": self.on_button_location_jump_clicked,
 
199
                "on_entry_location_key_press_event": self.on_entry_location_key_press_event,
 
200
                "on_checkbutton_history_toggled": self.on_checkbutton_history_toggled,
 
201
                "on_entry_history_revno_key_press_event": self.on_entry_history_revno_key_press_event,
 
202
                "on_button_history_browse_clicked": self.on_button_history_browse_clicked
 
203
            }
 
204
        
 
205
        # Connect the signals to the handlers
 
206
        self.toplevel.signal_autoconnect(dic)
 
207
        
 
208
        self._just_started = True
 
209
        
 
210
        # Apply window size and position
 
211
        width = self.pref.get_preference('window_width', 'int')
 
212
        height = self.pref.get_preference('window_height', 'int')
 
213
        self.window.resize(width, height)
 
214
        x = self.pref.get_preference('window_x', 'int')
 
215
        y = self.pref.get_preference('window_y', 'int')
 
216
        self.window.move(x, y)
 
217
        # Apply paned position
 
218
        pos = self.pref.get_preference('paned_position', 'int')
 
219
        self.hpaned_main.set_position(pos)
 
220
        
 
221
        # Apply menu to the toolbutton
 
222
        #menubutton = self.toplevel.get_widget('menutoolbutton_diff')
 
223
        #menubutton.set_menu(handler.menu.toolbar_diff)
 
224
        
 
225
        # Now we can show the window
 
226
        self.window.show()
 
227
        
 
228
        # Show drive selector if under Win32
 
229
        if sys.platform == 'win32':
 
230
            self.hbox_location.pack_start(self.combobox_drive, False, False, 0)
 
231
            self.hbox_location.reorder_child(self.combobox_drive, 1)
 
232
            self.combobox_drive.show()
 
233
            self.gen_hard_selector()
 
234
        
 
235
        self._load_left()
 
236
 
 
237
        # Apply menu state
 
238
        self.menuitem_view_show_hidden_files.set_active(self.pref.get_preference('dotted_files', 'bool'))
 
239
        self.menuitem_view_show_ignored_files.set_active(self.pref.get_preference('ignored_files', 'bool'))
 
240
 
 
241
        # We're starting local
 
242
        self.remote = False
 
243
        self.remote_branch = None
 
244
        self.remote_path = None
 
245
        self.remote_revision = None
 
246
        
 
247
        self.set_path(os.getcwd())
 
248
        self._load_right()
 
249
        
 
250
        self._just_started = False
 
251
 
 
252
    def set_path(self, path, force_remote=False):
 
253
        self.notbranch = False
 
254
        
 
255
        if force_remote:
 
256
            # Forcing remote mode (reading data from inventory)
 
257
            self._show_stock_image(gtk.STOCK_DISCONNECT)
 
258
            try:
 
259
                br = Branch.open_containing(path)[0]
 
260
            except bzrerrors.NotBranchError:
 
261
                self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
 
262
                self.check_history.set_active(False)
 
263
                self.check_history.set_sensitive(False)
 
264
                return False
 
265
            except bzrerrors.UnsupportedProtocol:
 
266
                self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
 
267
                self.check_history.set_active(False)
 
268
                self.check_history.set_sensitive(False)
 
269
                return False
 
270
            
 
271
            self._show_stock_image(gtk.STOCK_CONNECT)
 
272
            
 
273
            self.remote = True
 
274
           
 
275
            # We're remote
 
276
            self.remote_branch, self.remote_path = Branch.open_containing(path)
 
277
            
 
278
            if self.remote_revision is None:
 
279
                self.remote_revision = self.remote_branch.last_revision()
 
280
            
 
281
            self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
 
282
            
 
283
            if len(self.remote_path) == 0:
 
284
                self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
 
285
            else:
 
286
                for (name, type) in self.remote_entries:
 
287
                    if name == self.remote_path:
 
288
                        self.remote_parent = type.file_id
 
289
                        break
 
290
            
 
291
            if not path.endswith('/'):
 
292
                path += '/'
 
293
            
 
294
            if self.remote_branch.base == path:
 
295
                self.button_location_up.set_sensitive(False)
 
296
            else:
 
297
                self.button_location_up.set_sensitive(True)
 
298
        else:
 
299
            if os.path.isdir(path):
 
300
                self.image_location_error.destroy()
 
301
                self.remote = False
 
302
                
 
303
                # We're local
 
304
                try:
 
305
                    self.wt, self.wtpath = WorkingTree.open_containing(path)
 
306
                except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
 
307
                    self.notbranch = True
 
308
                
 
309
                # If we're in the root, we cannot go up anymore
 
310
                if sys.platform == 'win32':
 
311
                    drive, tail = os.path.splitdrive(path)
 
312
                    if tail in ('', '/', '\\'):
 
313
                        self.button_location_up.set_sensitive(False)
 
314
                    else:
 
315
                        self.button_location_up.set_sensitive(True)
 
316
                else:
 
317
                    if self.path == '/':
 
318
                        self.button_location_up.set_sensitive(False)
 
319
                    else:
 
320
                        self.button_location_up.set_sensitive(True)
 
321
            elif not os.path.isfile(path):
 
322
                # Doesn't seem to be a file nor a directory, trying to open a
 
323
                # remote location
 
324
                self._show_stock_image(gtk.STOCK_DISCONNECT)
 
325
                try:
 
326
                    br = Branch.open_containing(path)[0]
 
327
                except bzrerrors.NotBranchError:
 
328
                    self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
 
329
                    self.check_history.set_active(False)
 
330
                    self.check_history.set_sensitive(False)
 
331
                    return False
 
332
                except bzrerrors.UnsupportedProtocol:
 
333
                    self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
 
334
                    self.check_history.set_active(False)
 
335
                    self.check_history.set_sensitive(False)
 
336
                    return False
 
337
                
 
338
                self._show_stock_image(gtk.STOCK_CONNECT)
 
339
                
 
340
                self.remote = True
 
341
               
 
342
                # We're remote
 
343
                self.remote_branch, self.remote_path = Branch.open_containing(path)
 
344
                
 
345
                if self.remote_revision is None:
 
346
                    self.remote_revision = self.remote_branch.last_revision()
 
347
                
 
348
                self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
 
349
                
 
350
                if len(self.remote_path) == 0:
 
351
                    self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
 
352
                else:
 
353
                    for (name, type) in self.remote_entries:
 
354
                        if name == self.remote_path:
 
355
                            self.remote_parent = type.file_id
 
356
                            break
 
357
                
 
358
                if not path.endswith('/'):
 
359
                    path += '/'
 
360
                
 
361
                if self.remote_branch.base == path:
 
362
                    self.button_location_up.set_sensitive(False)
 
363
                else:
 
364
                    self.button_location_up.set_sensitive(True)
 
365
        
 
366
        if self.notbranch:
 
367
            self.check_history.set_active(False)
 
368
            self.check_history.set_sensitive(False)
 
369
        else:
 
370
            self.check_history.set_sensitive(True)
 
371
        
 
372
        self.statusbar.push(self.context_id, path)
 
373
        self.entry_location.set_text(path)
 
374
        self.path = path
 
375
        return True
 
376
 
 
377
    def get_path(self):
 
378
        if not self.remote:
 
379
            return self.path
 
380
        else:
 
381
            # Remote mode
 
382
            if len(self.remote_path) > 0:
 
383
                return self.remote_branch.base + self.remote_path + '/'
 
384
            else:
 
385
                return self.remote_branch.base
 
386
   
 
387
    def on_about_activate(self, widget):
 
388
        about()
 
389
    
 
390
    def on_button_history_browse_clicked(self, widget):
 
391
        """ Browse for revision button handler. """
 
392
        if self.remote:
 
393
            br = self.remote_branch
 
394
        else:
 
395
            br = self.wt.branch
 
396
            
 
397
        revb = RevisionBrowser(br, self.window)
 
398
        response = revb.run()
 
399
        if response != gtk.RESPONSE_NONE:
 
400
            revb.hide()
 
401
        
 
402
            if response == gtk.RESPONSE_OK:
 
403
                if revb.selected_revno is not None:
 
404
                    self.entry_history.set_text(revb.selected_revno)
 
405
            
 
406
            revb.destroy()
 
407
    
 
408
    def on_button_location_jump_clicked(self, widget):
 
409
        """ Location Jump button handler. """
 
410
        location = self.entry_location.get_text()
 
411
        
 
412
        if self.set_path(location):
 
413
            self.refresh_right()
 
414
    
 
415
    def on_button_location_up_clicked(self, widget):
 
416
        """ Location Up button handler. """
 
417
        if not self.remote:
 
418
            # Local mode
 
419
            self.set_path(os.path.split(self.get_path())[0])
 
420
        else:
 
421
            # Remote mode
 
422
            delim = '/'
 
423
            newpath = delim.join(self.get_path().split(delim)[:-2])
 
424
            newpath += '/'
 
425
            self.set_path(newpath)
 
426
 
 
427
        self.refresh_right()
 
428
    
 
429
    def on_checkbutton_history_toggled(self, widget):
 
430
        """ History Mode toggle handler. """
 
431
        if self.check_history.get_active():
 
432
            # History Mode activated
 
433
            self.entry_history.set_sensitive(True)
 
434
            self.button_history.set_sensitive(True)
 
435
        else:
 
436
            # History Mode deactivated
 
437
            self.entry_history.set_sensitive(False)
 
438
            self.button_history.set_sensitive(False)
 
439
    
 
440
    @show_bzr_error
 
441
    def on_entry_history_revno_key_press_event(self, widget, event):
 
442
        """ Key pressed handler for the history entry. """
 
443
        if event.keyval == 65293:
 
444
            # Return was hit, so we have to load that specific revision
 
445
            # Emulate being remote, so inventory should be used
 
446
            path = self.get_path()
 
447
            if not self.remote:
 
448
                self.remote = True
 
449
                self.remote_branch = self.wt.branch
 
450
            
 
451
            revno = int(self.entry_history.get_text())
 
452
            self.remote_revision = self.remote_branch.get_rev_id(revno)
 
453
            if self.set_path(path, True):
 
454
                self.refresh_right()
 
455
    
 
456
    def on_entry_location_key_press_event(self, widget, event):
 
457
        """ Key pressed handler for the location entry. """
 
458
        if event.keyval == 65293:
 
459
            # Return was hit, so we have to jump
 
460
            self.on_button_location_jump_clicked(widget)
 
461
    
 
462
    def on_menuitem_add_files_activate(self, widget):
 
463
        """ Add file(s)... menu handler. """
 
464
        from add import OliveAdd
 
465
        add = OliveAdd(self.wt, self.wtpath, self.get_selected_right())
 
466
        add.display()
 
467
    
 
468
    def on_menuitem_branch_get_activate(self, widget):
 
469
        """ Branch/Get... menu handler. """
 
470
        from bzrlib.plugins.gtk.branch import BranchDialog
 
471
        
 
472
        if self.remote:
 
473
            branch = BranchDialog(os.getcwd(), self.window, self.remote_branch.base)
 
474
        else:
 
475
            branch = BranchDialog(self.get_path(), self.window)
 
476
        response = branch.run()
 
477
        if response != gtk.RESPONSE_NONE:
 
478
            branch.hide()
 
479
            
 
480
            if response == gtk.RESPONSE_OK:
 
481
                self.refresh_right()
 
482
            
 
483
            branch.destroy()
 
484
    
 
485
    def on_menuitem_branch_checkout_activate(self, widget):
 
486
        """ Branch/Checkout... menu handler. """
 
487
        from bzrlib.plugins.gtk.checkout import CheckoutDialog
 
488
        
 
489
        if self.remote:
 
490
            checkout = CheckoutDialog(os.getcwd(), self.window, self.remote_branch.base)
 
491
        else:
 
492
            checkout = CheckoutDialog(self.get_path(), self.window)
 
493
        response = checkout.run()
 
494
        if response != gtk.RESPONSE_NONE:
 
495
            checkout.hide()
 
496
        
 
497
            if response == gtk.RESPONSE_OK:
 
498
                self.refresh_right()
 
499
            
 
500
            checkout.destroy()
 
501
    
 
502
    @show_bzr_error
 
503
    def on_menuitem_branch_commit_activate(self, widget):
 
504
        """ Branch/Commit... menu handler. """
 
505
        commit = CommitDialog(self.wt, self.wtpath, self.notbranch, self.get_selected_right(), self.window)
 
506
        response = commit.run()
 
507
        if response != gtk.RESPONSE_NONE:
 
508
            commit.hide()
 
509
        
 
510
            if response == gtk.RESPONSE_OK:
 
511
                self.refresh_right()
 
512
            
 
513
            commit.destroy()
 
514
    
 
515
    def on_menuitem_branch_conflicts_activate(self, widget):
 
516
        """ Branch/Conflicts... menu handler. """
 
517
        conflicts = ConflictsDialog(self.wt, self.window)
 
518
        response = conflicts.run()
 
519
        if response != gtk.RESPONSE_NONE:
 
520
            conflicts.destroy()
 
521
    
 
522
    def on_menuitem_branch_merge_activate(self, widget):
 
523
        """ Branch/Merge... menu handler. """
 
524
        from bzrlib.plugins.gtk.merge import MergeDialog
 
525
        
 
526
        if self.check_for_changes():
 
527
            error_dialog(_('There are local changes in the branch'),
 
528
                         _('Please commit or revert the changes before merging.'))
 
529
        else:
 
530
            parent_branch_path = self.wt.branch.get_parent()
 
531
            merge = MergeDialog(self.wt, self.wtpath,default_branch_path=parent_branch_path )
 
532
            merge.display()
 
533
 
 
534
    @show_bzr_error
 
535
    def on_menuitem_branch_missing_revisions_activate(self, widget):
 
536
        """ Branch/Missing revisions menu handler. """
 
537
        
 
538
        from bzrlib.missing import find_unmerged, iter_log_revisions
 
539
        
 
540
        local_branch = self.wt.branch
 
541
        parent_branch_path = local_branch.get_parent()
 
542
        if parent_branch_path is None:
 
543
            error_dialog(_('Parent location is unknown'),
 
544
                         _('Cannot determine missing revisions if no parent location is known.'))
 
545
            return
 
546
        
 
547
        parent_branch = Branch.open(parent_branch_path)
 
548
        
 
549
        if parent_branch.base == local_branch.base:
 
550
            parent_branch = local_branch
 
551
        
 
552
        local_extra, remote_extra = find_unmerged(local_branch,parent_branch)
 
553
 
 
554
        if local_extra or remote_extra:
 
555
            def log_revision_one_line_text(log_revision):
 
556
                """ Generates one line description of log_revison ended with end of line."""
 
557
                revision = log_revision.rev
 
558
                txt =  "- %s (%s)\n" % (revision.get_summary(), revision.committer, )
 
559
                txt = txt.replace("<"," ") # Seems < > chars are expected to be xml tags ...
 
560
                txt = txt.replace(">"," ")
 
561
                return txt
 
562
            
 
563
            dlg_txt = ""
 
564
            if local_extra:
 
565
                dlg_txt += _('%d local extra revision(s):\n') % (len(local_extra),) 
 
566
                max_revisions = 10
 
567
                for log_revision in iter_log_revisions(local_extra, local_branch.repository, verbose=1):
 
568
                    dlg_txt += log_revision_one_line_text(log_revision)
 
569
                    if max_revisions <= 0:
 
570
                        dlg_txt += _("more ... \n")
 
571
                        break
 
572
                max_revisions -= 1
 
573
            dlg_txt += "\n"
 
574
            if remote_extra:
 
575
                dlg_txt += _('%d local missing revision(s):\n') % (len(remote_extra),) 
 
576
                max_revisions = 10
 
577
                for log_revision in iter_log_revisions(local_extra, local_branch.repository, verbose=1):
 
578
                    dlg_txt += log_revision_one_line_text(log_revision)
 
579
                    if max_revisions <= 0:
 
580
                        dlg_txt += _("more ... \n")
 
581
                        break
 
582
                    max_revisions -= 1
 
583
                
 
584
            info_dialog(_('There are missing revisions'),
 
585
                        dlg_txt)
 
586
        else:
 
587
            info_dialog(_('Local branch up to date'),
 
588
                        _('There are no missing revisions.'))
 
589
 
 
590
    @show_bzr_error
 
591
    def on_menuitem_branch_pull_activate(self, widget):
 
592
        """ Branch/Pull menu handler. """
 
593
        branch_to = self.wt.branch
 
594
 
 
595
        location = branch_to.get_parent()
 
596
        if location is None:
 
597
            error_dialog(_('Parent location is unknown'),
 
598
                                     _('Pulling is not possible until there is a parent location.'))
 
599
            return
 
600
 
 
601
        branch_from = Branch.open(location)
 
602
 
 
603
        if branch_to.get_parent() is None:
 
604
            branch_to.set_parent(branch_from.base)
 
605
 
 
606
        ret = branch_to.pull(branch_from)
 
607
        
 
608
        info_dialog(_('Pull successful'), _('%d revision(s) pulled.') % ret)
 
609
        
 
610
    @show_bzr_error
 
611
    def on_menuitem_branch_update_activate(self, widget):
 
612
        """ Brranch/checkout update menu handler. """
 
613
        
 
614
        ret = self.wt.update()
 
615
        conflicts = self.wt.conflicts()
 
616
        if conflicts:
 
617
            info_dialog(_('Update successful but conflicts generated'), _('Number of conflicts generated: %d.') % (len(conflicts),) )
 
618
        else:
 
619
            info_dialog(_('Update successful'), _('No conflicts generated.') )
 
620
    
 
621
    def on_menuitem_branch_push_activate(self, widget):
 
622
        """ Branch/Push... menu handler. """
 
623
        push = PushDialog(repository=None,revid=None,branch=self.wt.branch, parent=self.window)
 
624
        response = push.run()
 
625
        if response != gtk.RESPONSE_NONE:
 
626
            push.destroy()
 
627
    
 
628
    @show_bzr_error
 
629
    def on_menuitem_branch_revert_activate(self, widget):
 
630
        """ Branch/Revert all changes menu handler. """
 
631
        ret = self.wt.revert([])
 
632
        if ret:
 
633
            warning_dialog(_('Conflicts detected'),
 
634
                           _('Please have a look at the working tree before continuing.'))
 
635
        else:
 
636
            info_dialog(_('Revert successful'),
 
637
                        _('All files reverted to last revision.'))
 
638
        self.refresh_right()
 
639
    
 
640
    def on_menuitem_branch_status_activate(self, widget):
 
641
        """ Branch/Status... menu handler. """
 
642
        from bzrlib.plugins.gtk.status import StatusDialog
 
643
        status = StatusDialog(self.wt, self.wtpath)
 
644
        response = status.run()
 
645
        if response != gtk.RESPONSE_NONE:
 
646
            status.destroy()
 
647
    
 
648
    def on_menuitem_branch_initialize_activate(self, widget):
 
649
        """ Initialize current directory. """
 
650
        init = InitDialog(self.path, self.window)
 
651
        response = init.run()
 
652
        if response != gtk.RESPONSE_NONE:
 
653
            init.hide()
 
654
        
 
655
            if response == gtk.RESPONSE_OK:
 
656
                self.refresh_right()
 
657
            
 
658
            init.destroy()
 
659
        
 
660
    def on_menuitem_branch_tags_activate(self, widget):
 
661
        """ Branch/Tags... menu handler. """
 
662
        from bzrlib.plugins.gtk.tags import TagsWindow
 
663
        if not self.remote:
 
664
            window = TagsWindow(self.wt.branch, self.window)
 
665
        else:
 
666
            window = TagsWindow(self.remote_branch, self.window)
 
667
        window.show()
 
668
    
 
669
    def on_menuitem_file_annotate_activate(self, widget):
 
670
        """ File/Annotate... menu handler. """
 
671
        if self.get_selected_right() is None:
 
672
            error_dialog(_('No file was selected'),
 
673
                         _('Please select a file from the list.'))
 
674
            return
 
675
        
 
676
        branch = self.wt.branch
 
677
        file_id = self.wt.path2id(self.wt.relpath(os.path.join(self.path, self.get_selected_right())))
 
678
        
 
679
        window = GAnnotateWindow(all=False, plain=False)
 
680
        window.set_title(os.path.join(self.path, self.get_selected_right()) + " - Annotate")
 
681
        config = GAnnotateConfig(window)
 
682
        window.show()
 
683
        branch.lock_read()
 
684
        try:
 
685
            window.annotate(self.wt, branch, file_id)
 
686
        finally:
 
687
            branch.unlock()
 
688
    
 
689
    def on_menuitem_file_make_directory_activate(self, widget):
 
690
        """ File/Make directory... menu handler. """
 
691
        from mkdir import OliveMkdir
 
692
        mkdir = OliveMkdir(self.wt, self.wtpath)
 
693
        mkdir.display()
 
694
    
 
695
    def on_menuitem_file_move_activate(self, widget):
 
696
        """ File/Move... menu handler. """
 
697
        from move import OliveMove
 
698
        move = OliveMove(self.wt, self.wtpath, self.get_selected_right())
 
699
        move.display()
 
700
    
 
701
    def on_menuitem_file_rename_activate(self, widget):
 
702
        """ File/Rename... menu handler. """
 
703
        from rename import OliveRename
 
704
        rename = OliveRename(self.wt, self.wtpath, self.get_selected_right())
 
705
        rename.display()
 
706
 
 
707
    def on_menuitem_remove_file_activate(self, widget):
 
708
        """ Remove (unversion) selected file. """
 
709
        from remove import OliveRemoveDialog
 
710
        remove = OliveRemoveDialog(self.wt, self.wtpath,
 
711
                                   selected=self.get_selected_right(),
 
712
                                   parent=self.window)
 
713
        response = remove.run()
 
714
        
 
715
        if response != gtk.RESPONSE_NONE:
 
716
            remove.hide()
 
717
        
 
718
            if response == gtk.RESPONSE_OK:
 
719
                self.set_path(self.path)
 
720
                self.refresh_right()
 
721
            
 
722
            remove.destroy()
 
723
    
 
724
    def on_menuitem_stats_diff_activate(self, widget):
 
725
        """ Statistics/Differences... menu handler. """
 
726
        window = DiffWindow()
 
727
        parent_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
 
728
        window.set_diff(self.wt.branch.nick, self.wt, parent_tree)
 
729
        window.show()
 
730
    
 
731
    def on_menuitem_stats_infos_activate(self, widget):
 
732
        """ Statistics/Informations... menu handler. """
 
733
        from info import OliveInfo
 
734
        if self.remote:
 
735
            info = OliveInfo(self.remote_branch)
 
736
        else:
 
737
            info = OliveInfo(self.wt.branch)
 
738
        info.display()
 
739
    
 
740
    def on_menuitem_stats_log_activate(self, widget):
 
741
        """ Statistics/Log... menu handler. """
 
742
        window = branchwin.BranchWindow()
 
743
        if not self.remote:
 
744
            window.set_branch(self.wt.branch, self.wt.branch.last_revision(), None)
 
745
        else:
 
746
            window.set_branch(self.remote_branch, self.remote_branch.last_revision(), None)
 
747
        window.show()
 
748
    
 
749
    def on_menuitem_view_refresh_activate(self, widget):
 
750
        """ View/Refresh menu handler. """
 
751
        # Refresh the left pane
 
752
        self.refresh_left()
 
753
        # Refresh the right pane
 
754
        self.refresh_right()
 
755
   
 
756
    def on_menuitem_view_show_hidden_files_activate(self, widget):
 
757
        """ View/Show hidden files menu handler. """
 
758
        self.pref.set_preference('dotted_files', widget.get_active())
 
759
        if self.path is not None:
 
760
            self.refresh_right()
 
761
 
 
762
    def on_menuitem_view_show_ignored_files_activate(self, widget):
 
763
        """ Hide/Show ignored files menu handler. """
 
764
        self.pref.set_preference('ignored_files', widget.get_active())
 
765
        if self.path is not None:
 
766
            self.refresh_right()
 
767
            
 
768
    def on_treeview_left_button_press_event(self, widget, event):
 
769
        """ Occurs when somebody right-clicks in the bookmark list. """
 
770
        if event.button == 3:
 
771
            # Don't show context with nothing selected
 
772
            if self.get_selected_left() == None:
 
773
                return
 
774
 
 
775
            # Create a menu
 
776
            from menu import OliveMenu
 
777
            menu = OliveMenu(path=self.get_path(),
 
778
                             selected=self.get_selected_left(),
 
779
                             app=self)
 
780
            
 
781
            menu.left_context_menu().popup(None, None, None, 0,
 
782
                                           event.time)
 
783
 
 
784
    def on_treeview_left_row_activated(self, treeview, path, view_column):
 
785
        """ Occurs when somebody double-clicks or enters an item in the
 
786
        bookmark list. """
 
787
 
 
788
        newdir = self.get_selected_left()
 
789
        if newdir == None:
 
790
            return
 
791
 
 
792
        if self.set_path(newdir):
 
793
            self.refresh_right()
 
794
 
 
795
    def on_treeview_right_button_press_event(self, widget, event):
 
796
        """ Occurs when somebody right-clicks in the file list. """
 
797
        if event.button == 3:
 
798
            # Create a menu
 
799
            from menu import OliveMenu
 
800
            menu = OliveMenu(path=self.get_path(),
 
801
                             selected=self.get_selected_right(),
 
802
                             app=self)
 
803
            # get the menu items
 
804
            m_open = menu.ui.get_widget('/context_right/open')
 
805
            m_add = menu.ui.get_widget('/context_right/add')
 
806
            m_remove = menu.ui.get_widget('/context_right/remove')
 
807
            m_rename = menu.ui.get_widget('/context_right/rename')
 
808
            m_revert = menu.ui.get_widget('/context_right/revert')
 
809
            m_commit = menu.ui.get_widget('/context_right/commit')
 
810
            m_annotate = menu.ui.get_widget('/context_right/annotate')
 
811
            m_diff = menu.ui.get_widget('/context_right/diff')
 
812
            # check if we're in a branch
 
813
            try:
 
814
                from bzrlib.branch import Branch
 
815
                Branch.open_containing(self.get_path())
 
816
                if self.remote:
 
817
                    m_open.set_sensitive(False)
 
818
                    m_add.set_sensitive(False)
 
819
                    m_remove.set_sensitive(False)
 
820
                    m_rename.set_sensitive(False)
 
821
                    m_revert.set_sensitive(False)
 
822
                    m_commit.set_sensitive(False)
 
823
                    m_annotate.set_sensitive(False)
 
824
                    m_diff.set_sensitive(False)
 
825
                else:
 
826
                    m_open.set_sensitive(True)
 
827
                    m_add.set_sensitive(True)
 
828
                    m_remove.set_sensitive(True)
 
829
                    m_rename.set_sensitive(True)
 
830
                    m_revert.set_sensitive(True)
 
831
                    m_commit.set_sensitive(True)
 
832
                    m_annotate.set_sensitive(True)
 
833
                    m_diff.set_sensitive(True)
 
834
            except bzrerrors.NotBranchError:
 
835
                m_open.set_sensitive(True)
 
836
                m_add.set_sensitive(False)
 
837
                m_remove.set_sensitive(False)
 
838
                m_rename.set_sensitive(False)
 
839
                m_revert.set_sensitive(False)
 
840
                m_commit.set_sensitive(False)
 
841
                m_annotate.set_sensitive(False)
 
842
                m_diff.set_sensitive(False)
 
843
 
 
844
            if not self.remote:
 
845
                menu.right_context_menu().popup(None, None, None, 0,
 
846
                                                event.time)
 
847
            else:
 
848
                menu.remote_context_menu().popup(None, None, None, 0,
 
849
                                                 event.time)
 
850
        
 
851
    def on_treeview_right_row_activated(self, treeview, path, view_column):
 
852
        """ Occurs when somebody double-clicks or enters an item in the
 
853
        file list. """
 
854
        from launch import launch
 
855
        
 
856
        newdir = self.get_selected_right()
 
857
        
 
858
        if not self.remote:
 
859
            # We're local
 
860
            if newdir == '..':
 
861
                self.set_path(os.path.split(self.get_path())[0])
 
862
            else:
 
863
                fullpath = os.path.join(self.get_path(), newdir)
 
864
                if os.path.isdir(fullpath):
 
865
                    # selected item is an existant directory
 
866
                    self.set_path(fullpath)
 
867
                else:
 
868
                    launch(fullpath)
 
869
        else:
 
870
            # We're remote
 
871
            if self._is_remote_dir(self.get_path() + newdir):
 
872
                self.set_path(self.get_path() + newdir)
 
873
        
 
874
        self.refresh_right()
 
875
    
 
876
    def on_window_main_delete_event(self, widget, event=None):
 
877
        """ Do some stuff before exiting. """
 
878
        width, height = self.window_main.get_size()
 
879
        self.pref.set_preference('window_width', width)
 
880
        self.pref.set_preference('window_height', height)
 
881
        x, y = self.window_main.get_position()
 
882
        self.pref.set_preference('window_x', x)
 
883
        self.pref.set_preference('window_y', y)
 
884
        self.pref.set_preference('paned_position',
 
885
                                 self.hpaned_main.get_position())
 
886
        
 
887
        self.pref.write()
 
888
        self.window_main.destroy()
 
889
        
 
890
    def _load_left(self):
 
891
        """ Load data into the left panel. (Bookmarks) """
 
892
        # Create TreeStore
 
893
        treestore = gtk.TreeStore(str, str)
 
894
        
 
895
        # Get bookmarks
 
896
        bookmarks = self.pref.get_bookmarks()
 
897
        
 
898
        # Add them to the TreeStore
 
899
        titer = treestore.append(None, [_('Bookmarks'), None])
 
900
        for item in bookmarks:
 
901
            title = self.pref.get_bookmark_title(item)
 
902
            treestore.append(titer, [title, item])
 
903
        
 
904
        # Create the column and add it to the TreeView
 
905
        self.treeview_left.set_model(treestore)
 
906
        tvcolumn_bookmark = gtk.TreeViewColumn(_('Bookmark'))
 
907
        self.treeview_left.append_column(tvcolumn_bookmark)
 
908
        
 
909
        # Set up the cells
 
910
        cell = gtk.CellRendererText()
 
911
        tvcolumn_bookmark.pack_start(cell, True)
 
912
        tvcolumn_bookmark.add_attribute(cell, 'text', 0)
 
913
        
 
914
        # Expand the tree
 
915
        self.treeview_left.expand_all()
 
916
 
 
917
    def _load_right(self):
 
918
        """ Load data into the right panel. (Filelist) """
 
919
        # Create ListStore
 
920
        # Model: [ icon, dir, name, status text, status, size (int), size (human), mtime (int), mtime (local), fileid ]
 
921
        liststore = gtk.ListStore(gobject.TYPE_STRING,
 
922
                                  gobject.TYPE_BOOLEAN,
 
923
                                  gobject.TYPE_STRING,
 
924
                                  gobject.TYPE_STRING,
 
925
                                  gobject.TYPE_STRING,
 
926
                                  gobject.TYPE_STRING,
 
927
                                  gobject.TYPE_STRING,
 
928
                                  gobject.TYPE_INT,
 
929
                                  gobject.TYPE_STRING,
 
930
                                  gobject.TYPE_STRING)
 
931
        
 
932
        dirs = []
 
933
        files = []
 
934
        
 
935
        # Fill the appropriate lists
 
936
        dotted_files = self.pref.get_preference('dotted_files', 'bool')
 
937
        for item in os.listdir(self.path):
 
938
            if not dotted_files and item[0] == '.':
 
939
                continue
 
940
            if os.path.isdir(self.path + os.sep + item):
 
941
                dirs.append(item)
 
942
            else:
 
943
                files.append(item)
 
944
        
 
945
        if not self.notbranch:
 
946
            branch = self.wt.branch
 
947
            tree2 = self.wt.branch.repository.revision_tree(branch.last_revision())
 
948
        
 
949
            delta = self.wt.changes_from(tree2, want_unchanged=True)
 
950
        
 
951
        # Add'em to the ListStore
 
952
        for item in dirs:
 
953
            statinfo = os.stat(self.path + os.sep + item)
 
954
            liststore.append([ gtk.STOCK_DIRECTORY,
 
955
                               True,
 
956
                               item,
 
957
                               '',
 
958
                               '',
 
959
                               "<DIR>",
 
960
                               "<DIR>",
 
961
                               statinfo.st_mtime,
 
962
                               self._format_date(statinfo.st_mtime),
 
963
                               ''])
 
964
        for item in files:
 
965
            status = 'unknown'
 
966
            fileid = ''
 
967
            if not self.notbranch:
 
968
                filename = self.wt.relpath(self.path + os.sep + item)
 
969
                
 
970
                try:
 
971
                    self.wt.lock_read()
 
972
                    
 
973
                    for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
 
974
                        if rpathnew == filename:
 
975
                            status = 'renamed'
 
976
                            fileid = id
 
977
                    for rpath, id, kind in delta.added:
 
978
                        if rpath == filename:
 
979
                            status = 'added'
 
980
                            fileid = id
 
981
                    for rpath, id, kind in delta.removed:
 
982
                        if rpath == filename:
 
983
                            status = 'removed'
 
984
                            fileid = id
 
985
                    for rpath, id, kind, text_modified, meta_modified in delta.modified:
 
986
                        if rpath == filename:
 
987
                            status = 'modified'
 
988
                            fileid = id
 
989
                    for rpath, id, kind in delta.unchanged:
 
990
                        if rpath == filename:
 
991
                            status = 'unchanged'
 
992
                            fileid = id
 
993
                    for rpath, file_class, kind, id, entry in self.wt.list_files():
 
994
                        if rpath == filename and file_class == 'I':
 
995
                            status = 'ignored'
 
996
                finally:
 
997
                    self.wt.unlock()
 
998
            
 
999
            if status == 'renamed':
 
1000
                st = _('renamed')
 
1001
            elif status == 'removed':
 
1002
                st = _('removed')
 
1003
            elif status == 'added':
 
1004
                st = _('added')
 
1005
            elif status == 'modified':
 
1006
                st = _('modified')
 
1007
            elif status == 'unchanged':
 
1008
                st = _('unchanged')
 
1009
            elif status == 'ignored':
 
1010
                st = _('ignored')
 
1011
            else:
 
1012
                st = _('unknown')
 
1013
            
 
1014
            statinfo = os.stat(self.path + os.sep + item)
 
1015
            liststore.append([gtk.STOCK_FILE,
 
1016
                              False,
 
1017
                              item,
 
1018
                              st,
 
1019
                              status,
 
1020
                              str(statinfo.st_size), # NOTE: if int used there it will fail for large files (size expressed as long int)
 
1021
                              self._format_size(statinfo.st_size),
 
1022
                              statinfo.st_mtime,
 
1023
                              self._format_date(statinfo.st_mtime),
 
1024
                              fileid])
 
1025
        
 
1026
        # Create the columns and add them to the TreeView
 
1027
        self.treeview_right.set_model(liststore)
 
1028
        self._tvcolumn_filename = gtk.TreeViewColumn(_('Filename'))
 
1029
        self._tvcolumn_status = gtk.TreeViewColumn(_('Status'))
 
1030
        self._tvcolumn_size = gtk.TreeViewColumn(_('Size'))
 
1031
        self._tvcolumn_mtime = gtk.TreeViewColumn(_('Last modified'))
 
1032
        self.treeview_right.append_column(self._tvcolumn_filename)
 
1033
        self.treeview_right.append_column(self._tvcolumn_status)
 
1034
        self.treeview_right.append_column(self._tvcolumn_size)
 
1035
        self.treeview_right.append_column(self._tvcolumn_mtime)
 
1036
        
 
1037
        # Set up the cells
 
1038
        cellpb = gtk.CellRendererPixbuf()
 
1039
        cell = gtk.CellRendererText()
 
1040
        self._tvcolumn_filename.pack_start(cellpb, False)
 
1041
        self._tvcolumn_filename.pack_start(cell, True)
 
1042
        self._tvcolumn_filename.set_attributes(cellpb, stock_id=0)
 
1043
        self._tvcolumn_filename.add_attribute(cell, 'text', 2)
 
1044
        self._tvcolumn_status.pack_start(cell, True)
 
1045
        self._tvcolumn_status.add_attribute(cell, 'text', 3)
 
1046
        self._tvcolumn_size.pack_start(cell, True)
 
1047
        self._tvcolumn_size.add_attribute(cell, 'text', 6)
 
1048
        self._tvcolumn_mtime.pack_start(cell, True)
 
1049
        self._tvcolumn_mtime.add_attribute(cell, 'text', 8)
 
1050
        
 
1051
        # Set up the properties of the TreeView
 
1052
        self.treeview_right.set_headers_visible(True)
 
1053
        self.treeview_right.set_headers_clickable(True)
 
1054
        self.treeview_right.set_search_column(1)
 
1055
        self._tvcolumn_filename.set_resizable(True)
 
1056
        self._tvcolumn_status.set_resizable(True)
 
1057
        self._tvcolumn_size.set_resizable(True)
 
1058
        self._tvcolumn_mtime.set_resizable(True)
 
1059
        # Set up sorting
 
1060
        liststore.set_sort_func(13, self._sort_filelist_callback, None)
 
1061
        liststore.set_sort_column_id(13, gtk.SORT_ASCENDING)
 
1062
        self._tvcolumn_filename.set_sort_column_id(13)
 
1063
        self._tvcolumn_status.set_sort_column_id(3)
 
1064
        self._tvcolumn_size.set_sort_column_id(5)
 
1065
        self._tvcolumn_mtime.set_sort_column_id(7)
 
1066
        
 
1067
        # Set sensitivity
 
1068
        self.set_sensitivity()
 
1069
        
 
1070
    def get_selected_fileid(self):
 
1071
        """ Get the file_id of the selected file. """
 
1072
        treeselection = self.treeview_right.get_selection()
 
1073
        (model, iter) = treeselection.get_selected()
 
1074
        
 
1075
        if iter is None:
 
1076
            return None
 
1077
        else:
 
1078
            return model.get_value(iter, 9)
 
1079
    
 
1080
    def get_selected_right(self):
 
1081
        """ Get the selected filename. """
 
1082
        treeselection = self.treeview_right.get_selection()
 
1083
        (model, iter) = treeselection.get_selected()
 
1084
        
 
1085
        if iter is None:
 
1086
            return None
 
1087
        else:
 
1088
            return model.get_value(iter, 2)
 
1089
    
 
1090
    def get_selected_left(self):
 
1091
        """ Get the selected bookmark. """
 
1092
        treeselection = self.treeview_left.get_selection()
 
1093
        (model, iter) = treeselection.get_selected()
 
1094
        
 
1095
        if iter is None:
 
1096
            return None
 
1097
        else:
 
1098
            return model.get_value(iter, 1)
 
1099
 
 
1100
    def set_statusbar(self, message):
 
1101
        """ Set the statusbar message. """
 
1102
        self.statusbar.push(self.context_id, message)
 
1103
    
 
1104
    def clear_statusbar(self):
 
1105
        """ Clean the last message from the statusbar. """
 
1106
        self.statusbar.pop(self.context_id)
 
1107
    
 
1108
    def set_sensitivity(self):
 
1109
        """ Set menu and toolbar sensitivity. """
 
1110
        if not self.remote:
 
1111
            # We're local
 
1112
            self.menuitem_branch_init.set_sensitive(self.notbranch)
 
1113
            self.menuitem_branch_get.set_sensitive(self.notbranch)
 
1114
            self.menuitem_branch_checkout.set_sensitive(self.notbranch)
 
1115
            self.menuitem_branch_pull.set_sensitive(not self.notbranch)
 
1116
            self.menuitem_branch_push.set_sensitive(not self.notbranch)
 
1117
            self.menuitem_branch_update.set_sensitive(not self.notbranch)
 
1118
            self.menuitem_branch_revert.set_sensitive(not self.notbranch)
 
1119
            self.menuitem_branch_merge.set_sensitive(not self.notbranch)
 
1120
            self.menuitem_branch_commit.set_sensitive(not self.notbranch)
 
1121
            self.menuitem_branch_tags.set_sensitive(not self.notbranch)
 
1122
            self.menuitem_branch_status.set_sensitive(not self.notbranch)
 
1123
            self.menuitem_branch_missing.set_sensitive(not self.notbranch)
 
1124
            self.menuitem_branch_conflicts.set_sensitive(not self.notbranch)
 
1125
            self.menuitem_stats.set_sensitive(not self.notbranch)
 
1126
            self.menuitem_stats_diff.set_sensitive(not self.notbranch)
 
1127
            self.menuitem_add_files.set_sensitive(not self.notbranch)
 
1128
            self.menuitem_remove_files.set_sensitive(not self.notbranch)
 
1129
            self.menuitem_file_make_directory.set_sensitive(not self.notbranch)
 
1130
            self.menuitem_file_rename.set_sensitive(not self.notbranch)
 
1131
            self.menuitem_file_move.set_sensitive(not self.notbranch)
 
1132
            self.menuitem_file_annotate.set_sensitive(not self.notbranch)
 
1133
            #self.menutoolbutton_diff.set_sensitive(True)
 
1134
            self.toolbutton_diff.set_sensitive(not self.notbranch)
 
1135
            self.toolbutton_log.set_sensitive(not self.notbranch)
 
1136
            self.toolbutton_commit.set_sensitive(not self.notbranch)
 
1137
            self.toolbutton_pull.set_sensitive(not self.notbranch)
 
1138
            self.toolbutton_push.set_sensitive(not self.notbranch)
 
1139
            self.toolbutton_update.set_sensitive(not self.notbranch)
 
1140
        else:
 
1141
            # We're remote
 
1142
            self.menuitem_branch_init.set_sensitive(False)
 
1143
            self.menuitem_branch_get.set_sensitive(True)
 
1144
            self.menuitem_branch_checkout.set_sensitive(True)
 
1145
            self.menuitem_branch_pull.set_sensitive(False)
 
1146
            self.menuitem_branch_push.set_sensitive(False)
 
1147
            self.menuitem_branch_update.set_sensitive(False)
 
1148
            self.menuitem_branch_revert.set_sensitive(False)
 
1149
            self.menuitem_branch_merge.set_sensitive(False)
 
1150
            self.menuitem_branch_commit.set_sensitive(False)
 
1151
            self.menuitem_branch_tags.set_sensitive(True)
 
1152
            self.menuitem_branch_status.set_sensitive(False)
 
1153
            self.menuitem_branch_missing.set_sensitive(False)
 
1154
            self.menuitem_branch_conflicts.set_sensitive(False)
 
1155
            self.menuitem_stats.set_sensitive(True)
 
1156
            self.menuitem_stats_diff.set_sensitive(False)
 
1157
            self.menuitem_add_files.set_sensitive(False)
 
1158
            self.menuitem_remove_files.set_sensitive(False)
 
1159
            self.menuitem_file_make_directory.set_sensitive(False)
 
1160
            self.menuitem_file_rename.set_sensitive(False)
 
1161
            self.menuitem_file_move.set_sensitive(False)
 
1162
            self.menuitem_file_annotate.set_sensitive(False)
 
1163
            #self.menutoolbutton_diff.set_sensitive(True)
 
1164
            self.toolbutton_diff.set_sensitive(False)
 
1165
            self.toolbutton_log.set_sensitive(True)
 
1166
            self.toolbutton_commit.set_sensitive(False)
 
1167
            self.toolbutton_pull.set_sensitive(False)
 
1168
            self.toolbutton_push.set_sensitive(False)
 
1169
            self.toolbutton_update.set_sensitive(False)
 
1170
    
 
1171
    def refresh_left(self):
 
1172
        """ Refresh the bookmark list. """
 
1173
        
 
1174
        # Get TreeStore and clear it
 
1175
        treestore = self.treeview_left.get_model()
 
1176
        treestore.clear()
 
1177
 
 
1178
        # Re-read preferences
 
1179
        self.pref.read()
 
1180
        
 
1181
        # Get bookmarks
 
1182
        bookmarks = self.pref.get_bookmarks()
 
1183
 
 
1184
        # Add them to the TreeStore
 
1185
        titer = treestore.append(None, [_('Bookmarks'), None])
 
1186
        for item in bookmarks:
 
1187
            title = self.pref.get_bookmark_title(item)
 
1188
            treestore.append(titer, [title, item])
 
1189
 
 
1190
        # Add the TreeStore to the TreeView
 
1191
        self.treeview_left.set_model(treestore)
 
1192
 
 
1193
        # Expand the tree
 
1194
        self.treeview_left.expand_all()
 
1195
 
 
1196
    def refresh_right(self, path=None):
 
1197
        """ Refresh the file list. """
 
1198
        if not self.remote:
 
1199
            # We're local
 
1200
            from bzrlib.workingtree import WorkingTree
 
1201
    
 
1202
            if path is None:
 
1203
                path = self.get_path()
 
1204
    
 
1205
            # A workaround for double-clicking Bookmarks
 
1206
            if not os.path.exists(path):
 
1207
                return
 
1208
    
 
1209
            # Get ListStore and clear it
 
1210
            liststore = self.treeview_right.get_model()
 
1211
            liststore.clear()
 
1212
            
 
1213
            # Show Status column
 
1214
            self._tvcolumn_status.set_visible(True)
 
1215
    
 
1216
            dirs = []
 
1217
            files = []
 
1218
    
 
1219
            # Fill the appropriate lists
 
1220
            dotted_files = self.pref.get_preference('dotted_files', 'bool')
 
1221
            ignored_files = self.pref.get_preference('ignored_files', 'bool')
 
1222
 
 
1223
            for item in os.listdir(path):
 
1224
                if not dotted_files and item[0] == '.':
 
1225
                    continue
 
1226
                if os.path.isdir(path + os.sep + item):
 
1227
                    dirs.append(item)
 
1228
                else:
 
1229
                    files.append(item)
 
1230
            
 
1231
            # Try to open the working tree
 
1232
            notbranch = False
 
1233
            try:
 
1234
                tree1 = WorkingTree.open_containing(path)[0]
 
1235
            except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
 
1236
                notbranch = True
 
1237
            
 
1238
            if not notbranch:
 
1239
                branch = tree1.branch
 
1240
                tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
 
1241
            
 
1242
                delta = tree1.changes_from(tree2, want_unchanged=True)
 
1243
                
 
1244
            # Add'em to the ListStore
 
1245
            for item in dirs:
 
1246
                statinfo = os.stat(self.path + os.sep + item)
 
1247
                liststore.append([gtk.STOCK_DIRECTORY,
 
1248
                                  True,
 
1249
                                  item,
 
1250
                                  '',
 
1251
                                  '',
 
1252
                                  "<DIR>",
 
1253
                                  "<DIR>",
 
1254
                                  statinfo.st_mtime,
 
1255
                                  self._format_date(statinfo.st_mtime),
 
1256
                                  ''])
 
1257
            for item in files:
 
1258
                status = 'unknown'
 
1259
                fileid = ''
 
1260
                if not notbranch:
 
1261
                    filename = tree1.relpath(path + os.sep + item)
 
1262
                    
 
1263
                    try:
 
1264
                        self.wt.lock_read()
 
1265
                        
 
1266
                        for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
 
1267
                            if rpathnew == filename:
 
1268
                                status = 'renamed'
 
1269
                                fileid = id
 
1270
                        for rpath, id, kind in delta.added:
 
1271
                            if rpath == filename:
 
1272
                                status = 'added'
 
1273
                                fileid = id
 
1274
                        for rpath, id, kind in delta.removed:
 
1275
                            if rpath == filename:
 
1276
                                status = 'removed'
 
1277
                                fileid = id
 
1278
                        for rpath, id, kind, text_modified, meta_modified in delta.modified:
 
1279
                            if rpath == filename:
 
1280
                                status = 'modified'
 
1281
                                fileid = id
 
1282
                        for rpath, id, kind in delta.unchanged:
 
1283
                            if rpath == filename:
 
1284
                                status = 'unchanged'
 
1285
                                fileid = id
 
1286
                        for rpath, file_class, kind, id, entry in self.wt.list_files():
 
1287
                            if rpath == filename and file_class == 'I':
 
1288
                                status = 'ignored'
 
1289
                    finally:
 
1290
                        self.wt.unlock()
 
1291
                
 
1292
                if status == 'renamed':
 
1293
                    st = _('renamed')
 
1294
                elif status == 'removed':
 
1295
                    st = _('removed')
 
1296
                elif status == 'added':
 
1297
                    st = _('added')
 
1298
                elif status == 'modified':
 
1299
                    st = _('modified')
 
1300
                elif status == 'unchanged':
 
1301
                    st = _('unchanged')
 
1302
                elif status == 'ignored':
 
1303
                    st = _('ignored')
 
1304
                    if not ignored_files:
 
1305
                        continue
 
1306
                else:
 
1307
                    st = _('unknown')
 
1308
                
 
1309
                statinfo = os.stat(self.path + os.sep + item)
 
1310
                liststore.append([gtk.STOCK_FILE,
 
1311
                                  False,
 
1312
                                  item,
 
1313
                                  st,
 
1314
                                  status,
 
1315
                                  str(statinfo.st_size),
 
1316
                                  self._format_size(statinfo.st_size),
 
1317
                                  statinfo.st_mtime,
 
1318
                                  self._format_date(statinfo.st_mtime),
 
1319
                                  fileid])
 
1320
        else:
 
1321
            # We're remote
 
1322
            
 
1323
            # Get ListStore and clear it
 
1324
            liststore = self.treeview_right.get_model()
 
1325
            liststore.clear()
 
1326
            
 
1327
            # Hide Status column
 
1328
            self._tvcolumn_status.set_visible(False)
 
1329
            
 
1330
            dirs = []
 
1331
            files = []
 
1332
            
 
1333
            self._show_stock_image(gtk.STOCK_REFRESH)
 
1334
            
 
1335
            for (name, type) in self.remote_entries:
 
1336
                if type.kind == 'directory':
 
1337
                    dirs.append(type)
 
1338
                elif type.kind == 'file':
 
1339
                    files.append(type)
 
1340
            
 
1341
            class HistoryCache:
 
1342
                """ Cache based on revision history. """
 
1343
                def __init__(self, history):
 
1344
                    self._history = history
 
1345
                
 
1346
                def _lookup_revision(self, revid):
 
1347
                    for r in self._history:
 
1348
                        if r.revision_id == revid:
 
1349
                            return r
 
1350
                    rev = repo.get_revision(revid)
 
1351
                    self._history.append(rev)
 
1352
                    return rev
 
1353
            
 
1354
            repo = self.remote_branch.repository
 
1355
            
 
1356
            revhistory = self.remote_branch.revision_history()
 
1357
            try:
 
1358
                revs = repo.get_revisions(revhistory)
 
1359
                cache = HistoryCache(revs)
 
1360
            except bzrerrors.InvalidHttpResponse:
 
1361
                # Fallback to dummy algorithm, because of LP: #115209
 
1362
                cache = HistoryCache([])
 
1363
            
 
1364
            for item in dirs:
 
1365
                if item.parent_id == self.remote_parent:
 
1366
                    rev = cache._lookup_revision(item.revision)
 
1367
                    liststore.append([ gtk.STOCK_DIRECTORY,
 
1368
                                       True,
 
1369
                                       item.name,
 
1370
                                       '',
 
1371
                                       '',
 
1372
                                       "<DIR>",
 
1373
                                       "<DIR>",
 
1374
                                       rev.timestamp,
 
1375
                                       self._format_date(rev.timestamp),
 
1376
                                       ''
 
1377
                                   ])
 
1378
                while gtk.events_pending():
 
1379
                    gtk.main_iteration()
 
1380
            
 
1381
            for item in files:
 
1382
                if item.parent_id == self.remote_parent:
 
1383
                    rev = cache._lookup_revision(item.revision)
 
1384
                    liststore.append([ gtk.STOCK_FILE,
 
1385
                                       False,
 
1386
                                       item.name,
 
1387
                                       '',
 
1388
                                       '',
 
1389
                                       str(item.text_size),
 
1390
                                       self._format_size(item.text_size),
 
1391
                                       rev.timestamp,
 
1392
                                       self._format_date(rev.timestamp),
 
1393
                                       item.file_id
 
1394
                                   ])
 
1395
                while gtk.events_pending():
 
1396
                    gtk.main_iteration()
 
1397
            
 
1398
            self.image_location_error.destroy()
 
1399
 
 
1400
        # Columns should auto-size
 
1401
        self.treeview_right.columns_autosize()
 
1402
        
 
1403
        # Set sensitivity
 
1404
        self.set_sensitivity()
 
1405
 
 
1406
    def _harddisks(self):
 
1407
        """ Returns hard drive letters under Win32. """
 
1408
        try:
 
1409
            import win32file
 
1410
            import string
 
1411
        except ImportError:
 
1412
            if sys.platform == 'win32':
 
1413
                print "pyWin32 modules needed to run Olive on Win32."
 
1414
                sys.exit(1)
 
1415
            else:
 
1416
                pass
 
1417
        
 
1418
        driveletters = []
 
1419
        for drive in string.ascii_uppercase:
 
1420
            if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED:
 
1421
                driveletters.append(drive+':')
 
1422
        return driveletters
 
1423
    
 
1424
    def gen_hard_selector(self):
 
1425
        """ Generate the hard drive selector under Win32. """
 
1426
        drives = self._harddisks()
 
1427
        for drive in drives:
 
1428
            self.combobox_drive.append_text(drive)
 
1429
        self.combobox_drive.set_active(drives.index(os.getcwd()[0:2]))
 
1430
    
 
1431
    def _refresh_drives(self, combobox):
 
1432
        if self._just_started:
 
1433
            return
 
1434
        model = combobox.get_model()
 
1435
        active = combobox.get_active()
 
1436
        if active >= 0:
 
1437
            drive = model[active][0]
 
1438
            self.set_path(drive + '\\')
 
1439
            self.refresh_right(drive + '\\')
 
1440
    
 
1441
    def check_for_changes(self):
 
1442
        """ Check whether there were changes in the current working tree. """
 
1443
        old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
 
1444
        delta = self.wt.changes_from(old_tree)
 
1445
 
 
1446
        changes = False
 
1447
        
 
1448
        if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
 
1449
            changes = True
 
1450
        
 
1451
        return changes
 
1452
    
 
1453
    def _sort_filelist_callback(self, model, iter1, iter2, data):
 
1454
        """ The sort callback for the file list, return values:
 
1455
        -1: iter1 < iter2
 
1456
        0: iter1 = iter2
 
1457
        1: iter1 > iter2
 
1458
        """
 
1459
        name1 = model.get_value(iter1, 2)
 
1460
        name2 = model.get_value(iter2, 2)
 
1461
        
 
1462
        if model.get_value(iter1, 1):
 
1463
            # item1 is a directory
 
1464
            if not model.get_value(iter2, 1):
 
1465
                # item2 isn't
 
1466
                return -1
 
1467
            else:
 
1468
                # both of them are directories, we compare their names
 
1469
                if name1 < name2:
 
1470
                    return -1
 
1471
                elif name1 == name2:
 
1472
                    return 0
 
1473
                else:
 
1474
                    return 1
 
1475
        else:
 
1476
            # item1 is not a directory
 
1477
            if model.get_value(iter2, 1):
 
1478
                # item2 is
 
1479
                return 1
 
1480
            else:
 
1481
                # both of them are files, compare them
 
1482
                if name1 < name2:
 
1483
                    return -1
 
1484
                elif name1 == name2:
 
1485
                    return 0
 
1486
                else:
 
1487
                    return 1
 
1488
    
 
1489
    def _format_size(self, size):
 
1490
        """ Format size to a human readable format. """
 
1491
        if size < 1000:
 
1492
            return "%d[B]" % (size,)
 
1493
        size = size / 1000.0
 
1494
        
 
1495
        for metric in ["kB","MB","GB","TB"]:
 
1496
            if size < 1000:
 
1497
                break
 
1498
            size = size / 1000.0
 
1499
        return "%.1f[%s]" % (size,metric) 
 
1500
    
 
1501
    def _format_date(self, timestamp):
 
1502
        """ Format the time (given in secs) to a human readable format. """
 
1503
        return time.ctime(timestamp)
 
1504
    
 
1505
    def _is_remote_dir(self, location):
 
1506
        """ Determine whether the given location is a directory or not. """
 
1507
        if not self.remote:
 
1508
            # We're in local mode
 
1509
            return False
 
1510
        else:
 
1511
            branch, path = Branch.open_containing(location)
 
1512
            for (name, type) in self.remote_entries:
 
1513
                if name == path and type.kind == 'directory':
 
1514
                    # We got it
 
1515
                    return True
 
1516
            # Either it's not a directory or not in the inventory
 
1517
            return False
 
1518
    
 
1519
    def _show_stock_image(self, stock_id):
 
1520
        """ Show a stock image next to the location entry. """
 
1521
        self.image_location_error.destroy()
 
1522
        self.image_location_error = gtk.image_new_from_stock(stock_id, gtk.ICON_SIZE_BUTTON)
 
1523
        self.hbox_location.pack_start(self.image_location_error, False, False, 0)
 
1524
        if sys.platform == 'win32':
 
1525
            self.hbox_location.reorder_child(self.image_location_error, 2)
 
1526
        else:
 
1527
            self.hbox_location.reorder_child(self.image_location_error, 1)
 
1528
        self.image_location_error.show()
 
1529
        while gtk.events_pending():
 
1530
            gtk.main_iteration()
 
1531
 
 
1532
import ConfigParser
 
1533
 
 
1534
class Preferences:
 
1535
    """ A class which handles Olive's preferences. """
 
1536
    def __init__(self, path=None):
 
1537
        """ Initialize the Preferences class. """
 
1538
        # Some default options
 
1539
        self.defaults = { 'strict_commit' : False,
 
1540
                          'dotted_files'  : False,
 
1541
                          'ignored_files' : True,
 
1542
                          'window_width'  : 700,
 
1543
                          'window_height' : 400,
 
1544
                          'window_x'      : 40,
 
1545
                          'window_y'      : 40,
 
1546
                          'paned_position': 200 }
 
1547
 
 
1548
        # Create a config parser object
 
1549
        self.config = ConfigParser.RawConfigParser()
 
1550
 
 
1551
        # Set filename
 
1552
        if path is None:
 
1553
            if sys.platform == 'win32':
 
1554
                # Windows - no dotted files
 
1555
                self._filename = os.path.expanduser('~/olive.conf')
 
1556
            else:
 
1557
                self._filename = os.path.expanduser('~/.olive.conf')
 
1558
        else:
 
1559
            self._filename = path
 
1560
        
 
1561
        # Load the configuration
 
1562
        self.read()
 
1563
        
 
1564
    def _get_default(self, option):
 
1565
        """ Get the default option for a preference. """
 
1566
        try:
 
1567
            ret = self.defaults[option]
 
1568
        except KeyError:
 
1569
            return None
 
1570
        else:
 
1571
            return ret
 
1572
 
 
1573
    def refresh(self):
 
1574
        """ Refresh the configuration. """
 
1575
        # First write out the changes
 
1576
        self.write()
 
1577
        # Then load the configuration again
 
1578
        self.read()
 
1579
 
 
1580
    def read(self):
 
1581
        """ Just read the configuration. """
 
1582
        # Re-initialize the config parser object to avoid some bugs
 
1583
        self.config = ConfigParser.RawConfigParser()
 
1584
        self.config.read([self._filename])
 
1585
    
 
1586
    def write(self):
 
1587
        """ Write the configuration to the appropriate files. """
 
1588
        fp = open(self._filename, 'w')
 
1589
        self.config.write(fp)
 
1590
        fp.close()
 
1591
 
 
1592
    def get_bookmarks(self):
 
1593
        """ Return the list of bookmarks. """
 
1594
        bookmarks = self.config.sections()
 
1595
        if self.config.has_section('preferences'):
 
1596
            bookmarks.remove('preferences')
 
1597
        return bookmarks
 
1598
 
 
1599
    def add_bookmark(self, path):
 
1600
        """ Add bookmark. """
 
1601
        try:
 
1602
            self.config.add_section(path)
 
1603
        except ConfigParser.DuplicateSectionError:
 
1604
            return False
 
1605
        else:
 
1606
            return True
 
1607
 
 
1608
    def get_bookmark_title(self, path):
 
1609
        """ Get bookmark title. """
 
1610
        try:
 
1611
            ret = self.config.get(path, 'title')
 
1612
        except ConfigParser.NoOptionError:
 
1613
            ret = path
 
1614
        
 
1615
        return ret
 
1616
    
 
1617
    def set_bookmark_title(self, path, title):
 
1618
        """ Set bookmark title. """
 
1619
        # FIXME: What if path isn't listed yet?
 
1620
        # FIXME: Canonicalize paths first?
 
1621
        self.config.set(path, 'title', title)
 
1622
    
 
1623
    def remove_bookmark(self, path):
 
1624
        """ Remove bookmark. """
 
1625
        return self.config.remove_section(path)
 
1626
 
 
1627
    def set_preference(self, option, value):
 
1628
        """ Set the value of the given option. """
 
1629
        if value is True:
 
1630
            value = 'yes'
 
1631
        elif value is False:
 
1632
            value = 'no'
 
1633
        
 
1634
        if self.config.has_section('preferences'):
 
1635
            self.config.set('preferences', option, value)
 
1636
        else:
 
1637
            self.config.add_section('preferences')
 
1638
            self.config.set('preferences', option, value)
 
1639
 
 
1640
    def get_preference(self, option, kind='str'):
 
1641
        """ Get the value of the given option.
 
1642
        
 
1643
        :param kind: str/bool/int/float. default: str
 
1644
        """
 
1645
        if self.config.has_option('preferences', option):
 
1646
            if kind == 'bool':
 
1647
                return self.config.getboolean('preferences', option)
 
1648
            elif kind == 'int':
 
1649
                return self.config.getint('preferences', option)
 
1650
            elif kind == 'float':
 
1651
                return self.config.getfloat('preferences', option)
 
1652
            else:
 
1653
                return self.config.get('preferences', option)
 
1654
        else:
 
1655
            try:
 
1656
                return self._get_default(option)
 
1657
            except KeyError:
 
1658
                return None
 
1659