/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: Vincent Ladeuil
  • Date: 2008-09-29 16:10:51 UTC
  • Revision ID: v.ladeuil+lp@free.fr-20080929161051-pn1akd7ja5eiviwk
Fix gtk dialogs popping up and asking for input during selftest.

* tests/test_commit.py:
(TestCommitDialog_Commit._set_question_yes,
TestCommitDialog_Commit._set_question_no): Accept keyword
arguments (but ignore them).
(TestCommitDialog_Commit.test_commit_empty_message): Rename the
test to avoid confusion with test_commit_no_messages.

* commit.py:
(CommitDialog.__init__): Restore the ability to override the
dialog used for automated tests.
(CommitDialog._do_commit, CommitDialog._do_commit,
CommitDialog._do_commit): Use the dialog class defined at
construction time.

Show diffs side-by-side

added added

removed removed

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