/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: Jasper Groenewegen
  • Date: 2008-07-20 13:23:29 UTC
  • mto: This revision was merged to the branch mainline in revision 560.
  • Revision ID: colbrac@xs4all.nl-20080720132329-srxnl5lf1k3z43jz
Better way to return response

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