/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-02 15:42:18 UTC
  • mto: This revision was merged to the branch mainline in revision 274.
  • Revision ID: matkor@laptop-hp-20070902154218-nba0woaqjsn20f9n
Ignoring eric3 project files.

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
            merge = MergeDialog(self.wt, self.wtpath)
 
531
            merge.display()
 
532
 
 
533
    @show_bzr_error
 
534
    def on_menuitem_branch_missing_revisions_activate(self, widget):
 
535
        """ Branch/Missing revisions menu handler. """
 
536
        local_branch = self.wt.branch
 
537
        
 
538
        other_branch = local_branch.get_parent()
 
539
        if other_branch is None:
 
540
            error_dialog(_('Parent location is unknown'),
 
541
                         _('Cannot determine missing revisions if no parent location is known.'))
 
542
            return
 
543
        
 
544
        remote_branch = Branch.open(other_branch)
 
545
        
 
546
        if remote_branch.base == local_branch.base:
 
547
            remote_branch = local_branch
 
548
 
 
549
        ret = len(local_branch.missing_revisions(remote_branch))
 
550
 
 
551
        if ret > 0:
 
552
            info_dialog(_('There are missing revisions'),
 
553
                        _('%d revision(s) missing.') % ret)
 
554
        else:
 
555
            info_dialog(_('Local branch up to date'),
 
556
                        _('There are no missing revisions.'))
 
557
 
 
558
    @show_bzr_error
 
559
    def on_menuitem_branch_pull_activate(self, widget):
 
560
        """ Branch/Pull menu handler. """
 
561
        branch_to = self.wt.branch
 
562
 
 
563
        location = branch_to.get_parent()
 
564
        if location is None:
 
565
            error_dialog(_('Parent location is unknown'),
 
566
                                     _('Pulling is not possible until there is a parent location.'))
 
567
            return
 
568
 
 
569
        branch_from = Branch.open(location)
 
570
 
 
571
        if branch_to.get_parent() is None:
 
572
            branch_to.set_parent(branch_from.base)
 
573
 
 
574
        ret = branch_to.pull(branch_from)
 
575
        
 
576
        info_dialog(_('Pull successful'), _('%d revision(s) pulled.') % ret)
 
577
        
 
578
    @show_bzr_error
 
579
    def on_menuitem_branch_update_activate(self, widget):
 
580
        """ Brranch/checkout update menu handler. """
 
581
        
 
582
        ret = self.wt.update()
 
583
        conflicts = self.wt.conflicts()
 
584
        if conflicts:
 
585
            info_dialog(_('Update successful but conflicts generated'), _('Number of conflicts generated: %d.') % (len(conflicts),) )
 
586
        else:
 
587
            info_dialog(_('Update successful'), _('No conflicts generated.') )
 
588
    
 
589
    def on_menuitem_branch_push_activate(self, widget):
 
590
        """ Branch/Push... menu handler. """
 
591
        push = PushDialog(repository=None,revid=None,branch=self.wt.branch, parent=self.window)
 
592
        response = push.run()
 
593
        if response != gtk.RESPONSE_NONE:
 
594
            push.destroy()
 
595
    
 
596
    @show_bzr_error
 
597
    def on_menuitem_branch_revert_activate(self, widget):
 
598
        """ Branch/Revert all changes menu handler. """
 
599
        ret = self.wt.revert([])
 
600
        if ret:
 
601
            warning_dialog(_('Conflicts detected'),
 
602
                           _('Please have a look at the working tree before continuing.'))
 
603
        else:
 
604
            info_dialog(_('Revert successful'),
 
605
                        _('All files reverted to last revision.'))
 
606
        self.refresh_right()
 
607
    
 
608
    def on_menuitem_branch_status_activate(self, widget):
 
609
        """ Branch/Status... menu handler. """
 
610
        from bzrlib.plugins.gtk.status import StatusDialog
 
611
        status = StatusDialog(self.wt, self.wtpath)
 
612
        response = status.run()
 
613
        if response != gtk.RESPONSE_NONE:
 
614
            status.destroy()
 
615
    
 
616
    def on_menuitem_branch_initialize_activate(self, widget):
 
617
        """ Initialize current directory. """
 
618
        init = InitDialog(self.path, self.window)
 
619
        response = init.run()
 
620
        if response != gtk.RESPONSE_NONE:
 
621
            init.hide()
 
622
        
 
623
            if response == gtk.RESPONSE_OK:
 
624
                self.refresh_right()
 
625
            
 
626
            init.destroy()
 
627
        
 
628
    def on_menuitem_branch_tags_activate(self, widget):
 
629
        """ Branch/Tags... menu handler. """
 
630
        from bzrlib.plugins.gtk.tags import TagsWindow
 
631
        if not self.remote:
 
632
            window = TagsWindow(self.wt.branch, self.window)
 
633
        else:
 
634
            window = TagsWindow(self.remote_branch, self.window)
 
635
        window.show()
 
636
    
 
637
    def on_menuitem_file_annotate_activate(self, widget):
 
638
        """ File/Annotate... menu handler. """
 
639
        if self.get_selected_right() is None:
 
640
            error_dialog(_('No file was selected'),
 
641
                         _('Please select a file from the list.'))
 
642
            return
 
643
        
 
644
        branch = self.wt.branch
 
645
        file_id = self.wt.path2id(self.wt.relpath(os.path.join(self.path, self.get_selected_right())))
 
646
        
 
647
        window = GAnnotateWindow(all=False, plain=False)
 
648
        window.set_title(os.path.join(self.path, self.get_selected_right()) + " - Annotate")
 
649
        config = GAnnotateConfig(window)
 
650
        window.show()
 
651
        branch.lock_read()
 
652
        try:
 
653
            window.annotate(self.wt, branch, file_id)
 
654
        finally:
 
655
            branch.unlock()
 
656
    
 
657
    def on_menuitem_file_make_directory_activate(self, widget):
 
658
        """ File/Make directory... menu handler. """
 
659
        from mkdir import OliveMkdir
 
660
        mkdir = OliveMkdir(self.wt, self.wtpath)
 
661
        mkdir.display()
 
662
    
 
663
    def on_menuitem_file_move_activate(self, widget):
 
664
        """ File/Move... menu handler. """
 
665
        from move import OliveMove
 
666
        move = OliveMove(self.wt, self.wtpath, self.get_selected_right())
 
667
        move.display()
 
668
    
 
669
    def on_menuitem_file_rename_activate(self, widget):
 
670
        """ File/Rename... menu handler. """
 
671
        from rename import OliveRename
 
672
        rename = OliveRename(self.wt, self.wtpath, self.get_selected_right())
 
673
        rename.display()
 
674
 
 
675
    def on_menuitem_remove_file_activate(self, widget):
 
676
        """ Remove (unversion) selected file. """
 
677
        from remove import OliveRemoveDialog
 
678
        remove = OliveRemoveDialog(self.wt, self.wtpath,
 
679
                                   selected=self.get_selected_right(),
 
680
                                   parent=self.window)
 
681
        response = remove.run()
 
682
        
 
683
        if response != gtk.RESPONSE_NONE:
 
684
            remove.hide()
 
685
        
 
686
            if response == gtk.RESPONSE_OK:
 
687
                self.set_path(self.path)
 
688
                self.refresh_right()
 
689
            
 
690
            remove.destroy()
 
691
    
 
692
    def on_menuitem_stats_diff_activate(self, widget):
 
693
        """ Statistics/Differences... menu handler. """
 
694
        window = DiffWindow()
 
695
        parent_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
 
696
        window.set_diff(self.wt.branch.nick, self.wt, parent_tree)
 
697
        window.show()
 
698
    
 
699
    def on_menuitem_stats_infos_activate(self, widget):
 
700
        """ Statistics/Informations... menu handler. """
 
701
        from info import OliveInfo
 
702
        if self.remote:
 
703
            info = OliveInfo(self.remote_branch)
 
704
        else:
 
705
            info = OliveInfo(self.wt.branch)
 
706
        info.display()
 
707
    
 
708
    def on_menuitem_stats_log_activate(self, widget):
 
709
        """ Statistics/Log... menu handler. """
 
710
        window = branchwin.BranchWindow()
 
711
        if not self.remote:
 
712
            window.set_branch(self.wt.branch, self.wt.branch.last_revision(), None)
 
713
        else:
 
714
            window.set_branch(self.remote_branch, self.remote_branch.last_revision(), None)
 
715
        window.show()
 
716
    
 
717
    def on_menuitem_view_refresh_activate(self, widget):
 
718
        """ View/Refresh menu handler. """
 
719
        # Refresh the left pane
 
720
        self.refresh_left()
 
721
        # Refresh the right pane
 
722
        self.refresh_right()
 
723
   
 
724
    def on_menuitem_view_show_hidden_files_activate(self, widget):
 
725
        """ View/Show hidden files menu handler. """
 
726
        self.pref.set_preference('dotted_files', widget.get_active())
 
727
        if self.path is not None:
 
728
            self.refresh_right()
 
729
 
 
730
    def on_menuitem_view_show_ignored_files_activate(self, widget):
 
731
        """ Hide/Show ignored files menu handler. """
 
732
        self.pref.set_preference('ignored_files', widget.get_active())
 
733
        if self.path is not None:
 
734
            self.refresh_right()
 
735
            
 
736
    def on_treeview_left_button_press_event(self, widget, event):
 
737
        """ Occurs when somebody right-clicks in the bookmark list. """
 
738
        if event.button == 3:
 
739
            # Don't show context with nothing selected
 
740
            if self.get_selected_left() == None:
 
741
                return
 
742
 
 
743
            # Create a menu
 
744
            from menu import OliveMenu
 
745
            menu = OliveMenu(path=self.get_path(),
 
746
                             selected=self.get_selected_left(),
 
747
                             app=self)
 
748
            
 
749
            menu.left_context_menu().popup(None, None, None, 0,
 
750
                                           event.time)
 
751
 
 
752
    def on_treeview_left_row_activated(self, treeview, path, view_column):
 
753
        """ Occurs when somebody double-clicks or enters an item in the
 
754
        bookmark list. """
 
755
 
 
756
        newdir = self.get_selected_left()
 
757
        if newdir == None:
 
758
            return
 
759
 
 
760
        if self.set_path(newdir):
 
761
            self.refresh_right()
 
762
 
 
763
    def on_treeview_right_button_press_event(self, widget, event):
 
764
        """ Occurs when somebody right-clicks in the file list. """
 
765
        if event.button == 3:
 
766
            # Create a menu
 
767
            from menu import OliveMenu
 
768
            menu = OliveMenu(path=self.get_path(),
 
769
                             selected=self.get_selected_right(),
 
770
                             app=self)
 
771
            # get the menu items
 
772
            m_open = menu.ui.get_widget('/context_right/open')
 
773
            m_add = menu.ui.get_widget('/context_right/add')
 
774
            m_remove = menu.ui.get_widget('/context_right/remove')
 
775
            m_rename = menu.ui.get_widget('/context_right/rename')
 
776
            m_revert = menu.ui.get_widget('/context_right/revert')
 
777
            m_commit = menu.ui.get_widget('/context_right/commit')
 
778
            m_annotate = menu.ui.get_widget('/context_right/annotate')
 
779
            m_diff = menu.ui.get_widget('/context_right/diff')
 
780
            # check if we're in a branch
 
781
            try:
 
782
                from bzrlib.branch import Branch
 
783
                Branch.open_containing(self.get_path())
 
784
                if self.remote:
 
785
                    m_open.set_sensitive(False)
 
786
                    m_add.set_sensitive(False)
 
787
                    m_remove.set_sensitive(False)
 
788
                    m_rename.set_sensitive(False)
 
789
                    m_revert.set_sensitive(False)
 
790
                    m_commit.set_sensitive(False)
 
791
                    m_annotate.set_sensitive(False)
 
792
                    m_diff.set_sensitive(False)
 
793
                else:
 
794
                    m_open.set_sensitive(True)
 
795
                    m_add.set_sensitive(True)
 
796
                    m_remove.set_sensitive(True)
 
797
                    m_rename.set_sensitive(True)
 
798
                    m_revert.set_sensitive(True)
 
799
                    m_commit.set_sensitive(True)
 
800
                    m_annotate.set_sensitive(True)
 
801
                    m_diff.set_sensitive(True)
 
802
            except bzrerrors.NotBranchError:
 
803
                m_open.set_sensitive(True)
 
804
                m_add.set_sensitive(False)
 
805
                m_remove.set_sensitive(False)
 
806
                m_rename.set_sensitive(False)
 
807
                m_revert.set_sensitive(False)
 
808
                m_commit.set_sensitive(False)
 
809
                m_annotate.set_sensitive(False)
 
810
                m_diff.set_sensitive(False)
 
811
 
 
812
            if not self.remote:
 
813
                menu.right_context_menu().popup(None, None, None, 0,
 
814
                                                event.time)
 
815
            else:
 
816
                menu.remote_context_menu().popup(None, None, None, 0,
 
817
                                                 event.time)
 
818
        
 
819
    def on_treeview_right_row_activated(self, treeview, path, view_column):
 
820
        """ Occurs when somebody double-clicks or enters an item in the
 
821
        file list. """
 
822
        from launch import launch
 
823
        
 
824
        newdir = self.get_selected_right()
 
825
        
 
826
        if not self.remote:
 
827
            # We're local
 
828
            if newdir == '..':
 
829
                self.set_path(os.path.split(self.get_path())[0])
 
830
            else:
 
831
                fullpath = os.path.join(self.get_path(), newdir)
 
832
                if os.path.isdir(fullpath):
 
833
                    # selected item is an existant directory
 
834
                    self.set_path(fullpath)
 
835
                else:
 
836
                    launch(fullpath)
 
837
        else:
 
838
            # We're remote
 
839
            if self._is_remote_dir(self.get_path() + newdir):
 
840
                self.set_path(self.get_path() + newdir)
 
841
        
 
842
        self.refresh_right()
 
843
    
 
844
    def on_window_main_delete_event(self, widget, event=None):
 
845
        """ Do some stuff before exiting. """
 
846
        width, height = self.window_main.get_size()
 
847
        self.pref.set_preference('window_width', width)
 
848
        self.pref.set_preference('window_height', height)
 
849
        x, y = self.window_main.get_position()
 
850
        self.pref.set_preference('window_x', x)
 
851
        self.pref.set_preference('window_y', y)
 
852
        self.pref.set_preference('paned_position',
 
853
                                 self.hpaned_main.get_position())
 
854
        
 
855
        self.pref.write()
 
856
        self.window_main.destroy()
 
857
        
 
858
    def _load_left(self):
 
859
        """ Load data into the left panel. (Bookmarks) """
 
860
        # Create TreeStore
 
861
        treestore = gtk.TreeStore(str, str)
 
862
        
 
863
        # Get bookmarks
 
864
        bookmarks = self.pref.get_bookmarks()
 
865
        
 
866
        # Add them to the TreeStore
 
867
        titer = treestore.append(None, [_('Bookmarks'), None])
 
868
        for item in bookmarks:
 
869
            title = self.pref.get_bookmark_title(item)
 
870
            treestore.append(titer, [title, item])
 
871
        
 
872
        # Create the column and add it to the TreeView
 
873
        self.treeview_left.set_model(treestore)
 
874
        tvcolumn_bookmark = gtk.TreeViewColumn(_('Bookmark'))
 
875
        self.treeview_left.append_column(tvcolumn_bookmark)
 
876
        
 
877
        # Set up the cells
 
878
        cell = gtk.CellRendererText()
 
879
        tvcolumn_bookmark.pack_start(cell, True)
 
880
        tvcolumn_bookmark.add_attribute(cell, 'text', 0)
 
881
        
 
882
        # Expand the tree
 
883
        self.treeview_left.expand_all()
 
884
 
 
885
    def _load_right(self):
 
886
        """ Load data into the right panel. (Filelist) """
 
887
        # Create ListStore
 
888
        # Model: [ icon, dir, name, status text, status, size (int), size (human), mtime (int), mtime (local), fileid ]
 
889
        liststore = gtk.ListStore(gobject.TYPE_STRING,
 
890
                                  gobject.TYPE_BOOLEAN,
 
891
                                  gobject.TYPE_STRING,
 
892
                                  gobject.TYPE_STRING,
 
893
                                  gobject.TYPE_STRING,
 
894
                                  gobject.TYPE_STRING,
 
895
                                  gobject.TYPE_STRING,
 
896
                                  gobject.TYPE_INT,
 
897
                                  gobject.TYPE_STRING,
 
898
                                  gobject.TYPE_STRING)
 
899
        
 
900
        dirs = []
 
901
        files = []
 
902
        
 
903
        # Fill the appropriate lists
 
904
        dotted_files = self.pref.get_preference('dotted_files', 'bool')
 
905
        for item in os.listdir(self.path):
 
906
            if not dotted_files and item[0] == '.':
 
907
                continue
 
908
            if os.path.isdir(self.path + os.sep + item):
 
909
                dirs.append(item)
 
910
            else:
 
911
                files.append(item)
 
912
        
 
913
        if not self.notbranch:
 
914
            branch = self.wt.branch
 
915
            tree2 = self.wt.branch.repository.revision_tree(branch.last_revision())
 
916
        
 
917
            delta = self.wt.changes_from(tree2, want_unchanged=True)
 
918
        
 
919
        # Add'em to the ListStore
 
920
        for item in dirs:
 
921
            statinfo = os.stat(self.path + os.sep + item)
 
922
            liststore.append([ gtk.STOCK_DIRECTORY,
 
923
                               True,
 
924
                               item,
 
925
                               '',
 
926
                               '',
 
927
                               "<DIR>",
 
928
                               "<DIR>",
 
929
                               statinfo.st_mtime,
 
930
                               self._format_date(statinfo.st_mtime),
 
931
                               ''])
 
932
        for item in files:
 
933
            status = 'unknown'
 
934
            fileid = ''
 
935
            if not self.notbranch:
 
936
                filename = self.wt.relpath(self.path + os.sep + item)
 
937
                
 
938
                try:
 
939
                    self.wt.lock_read()
 
940
                    
 
941
                    for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
 
942
                        if rpathnew == filename:
 
943
                            status = 'renamed'
 
944
                            fileid = id
 
945
                    for rpath, id, kind in delta.added:
 
946
                        if rpath == filename:
 
947
                            status = 'added'
 
948
                            fileid = id
 
949
                    for rpath, id, kind in delta.removed:
 
950
                        if rpath == filename:
 
951
                            status = 'removed'
 
952
                            fileid = id
 
953
                    for rpath, id, kind, text_modified, meta_modified in delta.modified:
 
954
                        if rpath == filename:
 
955
                            status = 'modified'
 
956
                            fileid = id
 
957
                    for rpath, id, kind in delta.unchanged:
 
958
                        if rpath == filename:
 
959
                            status = 'unchanged'
 
960
                            fileid = id
 
961
                    for rpath, file_class, kind, id, entry in self.wt.list_files():
 
962
                        if rpath == filename and file_class == 'I':
 
963
                            status = 'ignored'
 
964
                finally:
 
965
                    self.wt.unlock()
 
966
            
 
967
            if status == 'renamed':
 
968
                st = _('renamed')
 
969
            elif status == 'removed':
 
970
                st = _('removed')
 
971
            elif status == 'added':
 
972
                st = _('added')
 
973
            elif status == 'modified':
 
974
                st = _('modified')
 
975
            elif status == 'unchanged':
 
976
                st = _('unchanged')
 
977
            elif status == 'ignored':
 
978
                st = _('ignored')
 
979
            else:
 
980
                st = _('unknown')
 
981
            
 
982
            statinfo = os.stat(self.path + os.sep + item)
 
983
            liststore.append([gtk.STOCK_FILE,
 
984
                              False,
 
985
                              item,
 
986
                              st,
 
987
                              status,
 
988
                              str(statinfo.st_size), # NOTE: if int used there it will fail for large files (size expressed as long int)
 
989
                              self._format_size(statinfo.st_size),
 
990
                              statinfo.st_mtime,
 
991
                              self._format_date(statinfo.st_mtime),
 
992
                              fileid])
 
993
        
 
994
        # Create the columns and add them to the TreeView
 
995
        self.treeview_right.set_model(liststore)
 
996
        self._tvcolumn_filename = gtk.TreeViewColumn(_('Filename'))
 
997
        self._tvcolumn_status = gtk.TreeViewColumn(_('Status'))
 
998
        self._tvcolumn_size = gtk.TreeViewColumn(_('Size'))
 
999
        self._tvcolumn_mtime = gtk.TreeViewColumn(_('Last modified'))
 
1000
        self.treeview_right.append_column(self._tvcolumn_filename)
 
1001
        self.treeview_right.append_column(self._tvcolumn_status)
 
1002
        self.treeview_right.append_column(self._tvcolumn_size)
 
1003
        self.treeview_right.append_column(self._tvcolumn_mtime)
 
1004
        
 
1005
        # Set up the cells
 
1006
        cellpb = gtk.CellRendererPixbuf()
 
1007
        cell = gtk.CellRendererText()
 
1008
        self._tvcolumn_filename.pack_start(cellpb, False)
 
1009
        self._tvcolumn_filename.pack_start(cell, True)
 
1010
        self._tvcolumn_filename.set_attributes(cellpb, stock_id=0)
 
1011
        self._tvcolumn_filename.add_attribute(cell, 'text', 2)
 
1012
        self._tvcolumn_status.pack_start(cell, True)
 
1013
        self._tvcolumn_status.add_attribute(cell, 'text', 3)
 
1014
        self._tvcolumn_size.pack_start(cell, True)
 
1015
        self._tvcolumn_size.add_attribute(cell, 'text', 6)
 
1016
        self._tvcolumn_mtime.pack_start(cell, True)
 
1017
        self._tvcolumn_mtime.add_attribute(cell, 'text', 8)
 
1018
        
 
1019
        # Set up the properties of the TreeView
 
1020
        self.treeview_right.set_headers_visible(True)
 
1021
        self.treeview_right.set_headers_clickable(True)
 
1022
        self.treeview_right.set_search_column(1)
 
1023
        self._tvcolumn_filename.set_resizable(True)
 
1024
        self._tvcolumn_status.set_resizable(True)
 
1025
        self._tvcolumn_size.set_resizable(True)
 
1026
        self._tvcolumn_mtime.set_resizable(True)
 
1027
        # Set up sorting
 
1028
        liststore.set_sort_func(13, self._sort_filelist_callback, None)
 
1029
        liststore.set_sort_column_id(13, gtk.SORT_ASCENDING)
 
1030
        self._tvcolumn_filename.set_sort_column_id(13)
 
1031
        self._tvcolumn_status.set_sort_column_id(3)
 
1032
        self._tvcolumn_size.set_sort_column_id(5)
 
1033
        self._tvcolumn_mtime.set_sort_column_id(7)
 
1034
        
 
1035
        # Set sensitivity
 
1036
        self.set_sensitivity()
 
1037
        
 
1038
    def get_selected_fileid(self):
 
1039
        """ Get the file_id of the selected file. """
 
1040
        treeselection = self.treeview_right.get_selection()
 
1041
        (model, iter) = treeselection.get_selected()
 
1042
        
 
1043
        if iter is None:
 
1044
            return None
 
1045
        else:
 
1046
            return model.get_value(iter, 9)
 
1047
    
 
1048
    def get_selected_right(self):
 
1049
        """ Get the selected filename. """
 
1050
        treeselection = self.treeview_right.get_selection()
 
1051
        (model, iter) = treeselection.get_selected()
 
1052
        
 
1053
        if iter is None:
 
1054
            return None
 
1055
        else:
 
1056
            return model.get_value(iter, 2)
 
1057
    
 
1058
    def get_selected_left(self):
 
1059
        """ Get the selected bookmark. """
 
1060
        treeselection = self.treeview_left.get_selection()
 
1061
        (model, iter) = treeselection.get_selected()
 
1062
        
 
1063
        if iter is None:
 
1064
            return None
 
1065
        else:
 
1066
            return model.get_value(iter, 1)
 
1067
 
 
1068
    def set_statusbar(self, message):
 
1069
        """ Set the statusbar message. """
 
1070
        self.statusbar.push(self.context_id, message)
 
1071
    
 
1072
    def clear_statusbar(self):
 
1073
        """ Clean the last message from the statusbar. """
 
1074
        self.statusbar.pop(self.context_id)
 
1075
    
 
1076
    def set_sensitivity(self):
 
1077
        """ Set menu and toolbar sensitivity. """
 
1078
        if not self.remote:
 
1079
            # We're local
 
1080
            self.menuitem_branch_init.set_sensitive(self.notbranch)
 
1081
            self.menuitem_branch_get.set_sensitive(self.notbranch)
 
1082
            self.menuitem_branch_checkout.set_sensitive(self.notbranch)
 
1083
            self.menuitem_branch_pull.set_sensitive(not self.notbranch)
 
1084
            self.menuitem_branch_push.set_sensitive(not self.notbranch)
 
1085
            self.menuitem_branch_update.set_sensitive(not self.notbranch)
 
1086
            self.menuitem_branch_revert.set_sensitive(not self.notbranch)
 
1087
            self.menuitem_branch_merge.set_sensitive(not self.notbranch)
 
1088
            self.menuitem_branch_commit.set_sensitive(not self.notbranch)
 
1089
            self.menuitem_branch_tags.set_sensitive(not self.notbranch)
 
1090
            self.menuitem_branch_status.set_sensitive(not self.notbranch)
 
1091
            self.menuitem_branch_missing.set_sensitive(not self.notbranch)
 
1092
            self.menuitem_branch_conflicts.set_sensitive(not self.notbranch)
 
1093
            self.menuitem_stats.set_sensitive(not self.notbranch)
 
1094
            self.menuitem_stats_diff.set_sensitive(not self.notbranch)
 
1095
            self.menuitem_add_files.set_sensitive(not self.notbranch)
 
1096
            self.menuitem_remove_files.set_sensitive(not self.notbranch)
 
1097
            self.menuitem_file_make_directory.set_sensitive(not self.notbranch)
 
1098
            self.menuitem_file_rename.set_sensitive(not self.notbranch)
 
1099
            self.menuitem_file_move.set_sensitive(not self.notbranch)
 
1100
            self.menuitem_file_annotate.set_sensitive(not self.notbranch)
 
1101
            #self.menutoolbutton_diff.set_sensitive(True)
 
1102
            self.toolbutton_diff.set_sensitive(not self.notbranch)
 
1103
            self.toolbutton_log.set_sensitive(not self.notbranch)
 
1104
            self.toolbutton_commit.set_sensitive(not self.notbranch)
 
1105
            self.toolbutton_pull.set_sensitive(not self.notbranch)
 
1106
            self.toolbutton_push.set_sensitive(not self.notbranch)
 
1107
            self.toolbutton_update.set_sensitive(not self.notbranch)
 
1108
        else:
 
1109
            # We're remote
 
1110
            self.menuitem_branch_init.set_sensitive(False)
 
1111
            self.menuitem_branch_get.set_sensitive(True)
 
1112
            self.menuitem_branch_checkout.set_sensitive(True)
 
1113
            self.menuitem_branch_pull.set_sensitive(False)
 
1114
            self.menuitem_branch_push.set_sensitive(False)
 
1115
            self.menuitem_branch_update.set_sensitive(False)
 
1116
            self.menuitem_branch_revert.set_sensitive(False)
 
1117
            self.menuitem_branch_merge.set_sensitive(False)
 
1118
            self.menuitem_branch_commit.set_sensitive(False)
 
1119
            self.menuitem_branch_tags.set_sensitive(True)
 
1120
            self.menuitem_branch_status.set_sensitive(False)
 
1121
            self.menuitem_branch_missing.set_sensitive(False)
 
1122
            self.menuitem_branch_conflicts.set_sensitive(False)
 
1123
            self.menuitem_stats.set_sensitive(True)
 
1124
            self.menuitem_stats_diff.set_sensitive(False)
 
1125
            self.menuitem_add_files.set_sensitive(False)
 
1126
            self.menuitem_remove_files.set_sensitive(False)
 
1127
            self.menuitem_file_make_directory.set_sensitive(False)
 
1128
            self.menuitem_file_rename.set_sensitive(False)
 
1129
            self.menuitem_file_move.set_sensitive(False)
 
1130
            self.menuitem_file_annotate.set_sensitive(False)
 
1131
            #self.menutoolbutton_diff.set_sensitive(True)
 
1132
            self.toolbutton_diff.set_sensitive(False)
 
1133
            self.toolbutton_log.set_sensitive(True)
 
1134
            self.toolbutton_commit.set_sensitive(False)
 
1135
            self.toolbutton_pull.set_sensitive(False)
 
1136
            self.toolbutton_push.set_sensitive(False)
 
1137
            self.toolbutton_update.set_sensitive(False)
 
1138
    
 
1139
    def refresh_left(self):
 
1140
        """ Refresh the bookmark list. """
 
1141
        
 
1142
        # Get TreeStore and clear it
 
1143
        treestore = self.treeview_left.get_model()
 
1144
        treestore.clear()
 
1145
 
 
1146
        # Re-read preferences
 
1147
        self.pref.read()
 
1148
        
 
1149
        # Get bookmarks
 
1150
        bookmarks = self.pref.get_bookmarks()
 
1151
 
 
1152
        # Add them to the TreeStore
 
1153
        titer = treestore.append(None, [_('Bookmarks'), None])
 
1154
        for item in bookmarks:
 
1155
            title = self.pref.get_bookmark_title(item)
 
1156
            treestore.append(titer, [title, item])
 
1157
 
 
1158
        # Add the TreeStore to the TreeView
 
1159
        self.treeview_left.set_model(treestore)
 
1160
 
 
1161
        # Expand the tree
 
1162
        self.treeview_left.expand_all()
 
1163
 
 
1164
    def refresh_right(self, path=None):
 
1165
        """ Refresh the file list. """
 
1166
        if not self.remote:
 
1167
            # We're local
 
1168
            from bzrlib.workingtree import WorkingTree
 
1169
    
 
1170
            if path is None:
 
1171
                path = self.get_path()
 
1172
    
 
1173
            # A workaround for double-clicking Bookmarks
 
1174
            if not os.path.exists(path):
 
1175
                return
 
1176
    
 
1177
            # Get ListStore and clear it
 
1178
            liststore = self.treeview_right.get_model()
 
1179
            liststore.clear()
 
1180
            
 
1181
            # Show Status column
 
1182
            self._tvcolumn_status.set_visible(True)
 
1183
    
 
1184
            dirs = []
 
1185
            files = []
 
1186
    
 
1187
            # Fill the appropriate lists
 
1188
            dotted_files = self.pref.get_preference('dotted_files', 'bool')
 
1189
            ignored_files = self.pref.get_preference('ignored_files', 'bool')
 
1190
 
 
1191
            for item in os.listdir(path):
 
1192
                if not dotted_files and item[0] == '.':
 
1193
                    continue
 
1194
                if os.path.isdir(path + os.sep + item):
 
1195
                    dirs.append(item)
 
1196
                else:
 
1197
                    files.append(item)
 
1198
            
 
1199
            # Try to open the working tree
 
1200
            notbranch = False
 
1201
            try:
 
1202
                tree1 = WorkingTree.open_containing(path)[0]
 
1203
            except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
 
1204
                notbranch = True
 
1205
            
 
1206
            if not notbranch:
 
1207
                branch = tree1.branch
 
1208
                tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
 
1209
            
 
1210
                delta = tree1.changes_from(tree2, want_unchanged=True)
 
1211
                
 
1212
            # Add'em to the ListStore
 
1213
            for item in dirs:
 
1214
                statinfo = os.stat(self.path + os.sep + item)
 
1215
                liststore.append([gtk.STOCK_DIRECTORY,
 
1216
                                  True,
 
1217
                                  item,
 
1218
                                  '',
 
1219
                                  '',
 
1220
                                  "<DIR>",
 
1221
                                  "<DIR>",
 
1222
                                  statinfo.st_mtime,
 
1223
                                  self._format_date(statinfo.st_mtime),
 
1224
                                  ''])
 
1225
            for item in files:
 
1226
                status = 'unknown'
 
1227
                fileid = ''
 
1228
                if not notbranch:
 
1229
                    filename = tree1.relpath(path + os.sep + item)
 
1230
                    
 
1231
                    try:
 
1232
                        self.wt.lock_read()
 
1233
                        
 
1234
                        for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
 
1235
                            if rpathnew == filename:
 
1236
                                status = 'renamed'
 
1237
                                fileid = id
 
1238
                        for rpath, id, kind in delta.added:
 
1239
                            if rpath == filename:
 
1240
                                status = 'added'
 
1241
                                fileid = id
 
1242
                        for rpath, id, kind in delta.removed:
 
1243
                            if rpath == filename:
 
1244
                                status = 'removed'
 
1245
                                fileid = id
 
1246
                        for rpath, id, kind, text_modified, meta_modified in delta.modified:
 
1247
                            if rpath == filename:
 
1248
                                status = 'modified'
 
1249
                                fileid = id
 
1250
                        for rpath, id, kind in delta.unchanged:
 
1251
                            if rpath == filename:
 
1252
                                status = 'unchanged'
 
1253
                                fileid = id
 
1254
                        for rpath, file_class, kind, id, entry in self.wt.list_files():
 
1255
                            if rpath == filename and file_class == 'I':
 
1256
                                status = 'ignored'
 
1257
                    finally:
 
1258
                        self.wt.unlock()
 
1259
                
 
1260
                if status == 'renamed':
 
1261
                    st = _('renamed')
 
1262
                elif status == 'removed':
 
1263
                    st = _('removed')
 
1264
                elif status == 'added':
 
1265
                    st = _('added')
 
1266
                elif status == 'modified':
 
1267
                    st = _('modified')
 
1268
                elif status == 'unchanged':
 
1269
                    st = _('unchanged')
 
1270
                elif status == 'ignored':
 
1271
                    st = _('ignored')
 
1272
                    if not ignored_files:
 
1273
                        continue
 
1274
                else:
 
1275
                    st = _('unknown')
 
1276
                
 
1277
                statinfo = os.stat(self.path + os.sep + item)
 
1278
                liststore.append([gtk.STOCK_FILE,
 
1279
                                  False,
 
1280
                                  item,
 
1281
                                  st,
 
1282
                                  status,
 
1283
                                  str(statinfo.st_size),
 
1284
                                  self._format_size(statinfo.st_size),
 
1285
                                  statinfo.st_mtime,
 
1286
                                  self._format_date(statinfo.st_mtime),
 
1287
                                  fileid])
 
1288
        else:
 
1289
            # We're remote
 
1290
            
 
1291
            # Get ListStore and clear it
 
1292
            liststore = self.treeview_right.get_model()
 
1293
            liststore.clear()
 
1294
            
 
1295
            # Hide Status column
 
1296
            self._tvcolumn_status.set_visible(False)
 
1297
            
 
1298
            dirs = []
 
1299
            files = []
 
1300
            
 
1301
            self._show_stock_image(gtk.STOCK_REFRESH)
 
1302
            
 
1303
            for (name, type) in self.remote_entries:
 
1304
                if type.kind == 'directory':
 
1305
                    dirs.append(type)
 
1306
                elif type.kind == 'file':
 
1307
                    files.append(type)
 
1308
            
 
1309
            class HistoryCache:
 
1310
                """ Cache based on revision history. """
 
1311
                def __init__(self, history):
 
1312
                    self._history = history
 
1313
                
 
1314
                def _lookup_revision(self, revid):
 
1315
                    for r in self._history:
 
1316
                        if r.revision_id == revid:
 
1317
                            return r
 
1318
                    rev = repo.get_revision(revid)
 
1319
                    self._history.append(rev)
 
1320
                    return rev
 
1321
            
 
1322
            repo = self.remote_branch.repository
 
1323
            
 
1324
            revhistory = self.remote_branch.revision_history()
 
1325
            try:
 
1326
                revs = repo.get_revisions(revhistory)
 
1327
                cache = HistoryCache(revs)
 
1328
            except bzrerrors.InvalidHttpResponse:
 
1329
                # Fallback to dummy algorithm, because of LP: #115209
 
1330
                cache = HistoryCache([])
 
1331
            
 
1332
            for item in dirs:
 
1333
                if item.parent_id == self.remote_parent:
 
1334
                    rev = cache._lookup_revision(item.revision)
 
1335
                    liststore.append([ gtk.STOCK_DIRECTORY,
 
1336
                                       True,
 
1337
                                       item.name,
 
1338
                                       '',
 
1339
                                       '',
 
1340
                                       "<DIR>",
 
1341
                                       "<DIR>",
 
1342
                                       rev.timestamp,
 
1343
                                       self._format_date(rev.timestamp),
 
1344
                                       ''
 
1345
                                   ])
 
1346
                while gtk.events_pending():
 
1347
                    gtk.main_iteration()
 
1348
            
 
1349
            for item in files:
 
1350
                if item.parent_id == self.remote_parent:
 
1351
                    rev = cache._lookup_revision(item.revision)
 
1352
                    liststore.append([ gtk.STOCK_FILE,
 
1353
                                       False,
 
1354
                                       item.name,
 
1355
                                       '',
 
1356
                                       '',
 
1357
                                       str(item.text_size),
 
1358
                                       self._format_size(item.text_size),
 
1359
                                       rev.timestamp,
 
1360
                                       self._format_date(rev.timestamp),
 
1361
                                       item.file_id
 
1362
                                   ])
 
1363
                while gtk.events_pending():
 
1364
                    gtk.main_iteration()
 
1365
            
 
1366
            self.image_location_error.destroy()
 
1367
 
 
1368
        # Columns should auto-size
 
1369
        self.treeview_right.columns_autosize()
 
1370
        
 
1371
        # Set sensitivity
 
1372
        self.set_sensitivity()
 
1373
 
 
1374
    def _harddisks(self):
 
1375
        """ Returns hard drive letters under Win32. """
 
1376
        try:
 
1377
            import win32file
 
1378
            import string
 
1379
        except ImportError:
 
1380
            if sys.platform == 'win32':
 
1381
                print "pyWin32 modules needed to run Olive on Win32."
 
1382
                sys.exit(1)
 
1383
            else:
 
1384
                pass
 
1385
        
 
1386
        driveletters = []
 
1387
        for drive in string.ascii_uppercase:
 
1388
            if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED:
 
1389
                driveletters.append(drive+':')
 
1390
        return driveletters
 
1391
    
 
1392
    def gen_hard_selector(self):
 
1393
        """ Generate the hard drive selector under Win32. """
 
1394
        drives = self._harddisks()
 
1395
        for drive in drives:
 
1396
            self.combobox_drive.append_text(drive)
 
1397
        self.combobox_drive.set_active(drives.index(os.getcwd()[0:2]))
 
1398
    
 
1399
    def _refresh_drives(self, combobox):
 
1400
        if self._just_started:
 
1401
            return
 
1402
        model = combobox.get_model()
 
1403
        active = combobox.get_active()
 
1404
        if active >= 0:
 
1405
            drive = model[active][0]
 
1406
            self.set_path(drive + '\\')
 
1407
            self.refresh_right(drive + '\\')
 
1408
    
 
1409
    def check_for_changes(self):
 
1410
        """ Check whether there were changes in the current working tree. """
 
1411
        old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
 
1412
        delta = self.wt.changes_from(old_tree)
 
1413
 
 
1414
        changes = False
 
1415
        
 
1416
        if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
 
1417
            changes = True
 
1418
        
 
1419
        return changes
 
1420
    
 
1421
    def _sort_filelist_callback(self, model, iter1, iter2, data):
 
1422
        """ The sort callback for the file list, return values:
 
1423
        -1: iter1 < iter2
 
1424
        0: iter1 = iter2
 
1425
        1: iter1 > iter2
 
1426
        """
 
1427
        name1 = model.get_value(iter1, 2)
 
1428
        name2 = model.get_value(iter2, 2)
 
1429
        
 
1430
        if model.get_value(iter1, 1):
 
1431
            # item1 is a directory
 
1432
            if not model.get_value(iter2, 1):
 
1433
                # item2 isn't
 
1434
                return -1
 
1435
            else:
 
1436
                # both of them are directories, we compare their names
 
1437
                if name1 < name2:
 
1438
                    return -1
 
1439
                elif name1 == name2:
 
1440
                    return 0
 
1441
                else:
 
1442
                    return 1
 
1443
        else:
 
1444
            # item1 is not a directory
 
1445
            if model.get_value(iter2, 1):
 
1446
                # item2 is
 
1447
                return 1
 
1448
            else:
 
1449
                # both of them are files, compare them
 
1450
                if name1 < name2:
 
1451
                    return -1
 
1452
                elif name1 == name2:
 
1453
                    return 0
 
1454
                else:
 
1455
                    return 1
 
1456
    
 
1457
    def _format_size(self, size):
 
1458
        """ Format size to a human readable format. """
 
1459
        if size < 1000:
 
1460
            return "%d[B]" % (size,)
 
1461
        size = size / 1000.0
 
1462
        
 
1463
        for metric in ["kB","MB","GB","TB"]:
 
1464
            if size < 1000:
 
1465
                break
 
1466
            size = size / 1000.0
 
1467
        return "%.1f[%s]" % (size,metric) 
 
1468
    
 
1469
    def _format_date(self, timestamp):
 
1470
        """ Format the time (given in secs) to a human readable format. """
 
1471
        return time.ctime(timestamp)
 
1472
    
 
1473
    def _is_remote_dir(self, location):
 
1474
        """ Determine whether the given location is a directory or not. """
 
1475
        if not self.remote:
 
1476
            # We're in local mode
 
1477
            return False
 
1478
        else:
 
1479
            branch, path = Branch.open_containing(location)
 
1480
            for (name, type) in self.remote_entries:
 
1481
                if name == path and type.kind == 'directory':
 
1482
                    # We got it
 
1483
                    return True
 
1484
            # Either it's not a directory or not in the inventory
 
1485
            return False
 
1486
    
 
1487
    def _show_stock_image(self, stock_id):
 
1488
        """ Show a stock image next to the location entry. """
 
1489
        self.image_location_error.destroy()
 
1490
        self.image_location_error = gtk.image_new_from_stock(stock_id, gtk.ICON_SIZE_BUTTON)
 
1491
        self.hbox_location.pack_start(self.image_location_error, False, False, 0)
 
1492
        if sys.platform == 'win32':
 
1493
            self.hbox_location.reorder_child(self.image_location_error, 2)
 
1494
        else:
 
1495
            self.hbox_location.reorder_child(self.image_location_error, 1)
 
1496
        self.image_location_error.show()
 
1497
        while gtk.events_pending():
 
1498
            gtk.main_iteration()
 
1499
 
 
1500
import ConfigParser
 
1501
 
 
1502
class Preferences:
 
1503
    """ A class which handles Olive's preferences. """
 
1504
    def __init__(self, path=None):
 
1505
        """ Initialize the Preferences class. """
 
1506
        # Some default options
 
1507
        self.defaults = { 'strict_commit' : False,
 
1508
                          'dotted_files'  : False,
 
1509
                          'ignored_files' : True,
 
1510
                          'window_width'  : 700,
 
1511
                          'window_height' : 400,
 
1512
                          'window_x'      : 40,
 
1513
                          'window_y'      : 40,
 
1514
                          'paned_position': 200 }
 
1515
 
 
1516
        # Create a config parser object
 
1517
        self.config = ConfigParser.RawConfigParser()
 
1518
 
 
1519
        # Set filename
 
1520
        if path is None:
 
1521
            if sys.platform == 'win32':
 
1522
                # Windows - no dotted files
 
1523
                self._filename = os.path.expanduser('~/olive.conf')
 
1524
            else:
 
1525
                self._filename = os.path.expanduser('~/.olive.conf')
 
1526
        else:
 
1527
            self._filename = path
 
1528
        
 
1529
        # Load the configuration
 
1530
        self.read()
 
1531
        
 
1532
    def _get_default(self, option):
 
1533
        """ Get the default option for a preference. """
 
1534
        try:
 
1535
            ret = self.defaults[option]
 
1536
        except KeyError:
 
1537
            return None
 
1538
        else:
 
1539
            return ret
 
1540
 
 
1541
    def refresh(self):
 
1542
        """ Refresh the configuration. """
 
1543
        # First write out the changes
 
1544
        self.write()
 
1545
        # Then load the configuration again
 
1546
        self.read()
 
1547
 
 
1548
    def read(self):
 
1549
        """ Just read the configuration. """
 
1550
        # Re-initialize the config parser object to avoid some bugs
 
1551
        self.config = ConfigParser.RawConfigParser()
 
1552
        self.config.read([self._filename])
 
1553
    
 
1554
    def write(self):
 
1555
        """ Write the configuration to the appropriate files. """
 
1556
        fp = open(self._filename, 'w')
 
1557
        self.config.write(fp)
 
1558
        fp.close()
 
1559
 
 
1560
    def get_bookmarks(self):
 
1561
        """ Return the list of bookmarks. """
 
1562
        bookmarks = self.config.sections()
 
1563
        if self.config.has_section('preferences'):
 
1564
            bookmarks.remove('preferences')
 
1565
        return bookmarks
 
1566
 
 
1567
    def add_bookmark(self, path):
 
1568
        """ Add bookmark. """
 
1569
        try:
 
1570
            self.config.add_section(path)
 
1571
        except ConfigParser.DuplicateSectionError:
 
1572
            return False
 
1573
        else:
 
1574
            return True
 
1575
 
 
1576
    def get_bookmark_title(self, path):
 
1577
        """ Get bookmark title. """
 
1578
        try:
 
1579
            ret = self.config.get(path, 'title')
 
1580
        except ConfigParser.NoOptionError:
 
1581
            ret = path
 
1582
        
 
1583
        return ret
 
1584
    
 
1585
    def set_bookmark_title(self, path, title):
 
1586
        """ Set bookmark title. """
 
1587
        # FIXME: What if path isn't listed yet?
 
1588
        # FIXME: Canonicalize paths first?
 
1589
        self.config.set(path, 'title', title)
 
1590
    
 
1591
    def remove_bookmark(self, path):
 
1592
        """ Remove bookmark. """
 
1593
        return self.config.remove_section(path)
 
1594
 
 
1595
    def set_preference(self, option, value):
 
1596
        """ Set the value of the given option. """
 
1597
        if value is True:
 
1598
            value = 'yes'
 
1599
        elif value is False:
 
1600
            value = 'no'
 
1601
        
 
1602
        if self.config.has_section('preferences'):
 
1603
            self.config.set('preferences', option, value)
 
1604
        else:
 
1605
            self.config.add_section('preferences')
 
1606
            self.config.set('preferences', option, value)
 
1607
 
 
1608
    def get_preference(self, option, kind='str'):
 
1609
        """ Get the value of the given option.
 
1610
        
 
1611
        :param kind: str/bool/int/float. default: str
 
1612
        """
 
1613
        if self.config.has_option('preferences', option):
 
1614
            if kind == 'bool':
 
1615
                return self.config.getboolean('preferences', option)
 
1616
            elif kind == 'int':
 
1617
                return self.config.getint('preferences', option)
 
1618
            elif kind == 'float':
 
1619
                return self.config.getfloat('preferences', option)
 
1620
            else:
 
1621
                return self.config.get('preferences', option)
 
1622
        else:
 
1623
            try:
 
1624
                return self._get_default(option)
 
1625
            except KeyError:
 
1626
                return None
 
1627