/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: Jelmer Vernooij
  • Date: 2008-07-18 20:19:45 UTC
  • mto: This revision was merged to the branch mainline in revision 555.
  • Revision ID: jelmer@samba.org-20080718201945-85lowjdgx1d9ds82
UseĀ absoluteĀ imports.

Show diffs side-by-side

added added

removed removed

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