/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-27 07:59:23 UTC
  • mto: This revision was merged to the branch mainline in revision 577.
  • Revision ID: colbrac@xs4all.nl-20080727075923-89sjwarwxezojre5
Add parent setting to dialogs and implement in gcommit

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
    iconpath = icon_path() + os.sep
 
64
    
 
65
    dialog = gtk.AboutDialog()
 
66
    dialog.set_name("Olive")
 
67
    dialog.set_version(__version__)
 
68
    dialog.set_copyright("Copyright (C) 2006 Szilveszter Farkas (Phanatic)")
 
69
    dialog.set_website("https://launchpad.net/products/olive")
 
70
    dialog.set_website_label("https://launchpad.net/products/olive")
 
71
    dialog.set_icon_from_file(iconpath+"oliveicon2.png")
 
72
    dialog.set_logo(gtk.gdk.pixbuf_new_from_file(iconpath+"oliveicon2.png"))
 
73
    dialog.set_authors([ _i18n("Lead Developer:"),
 
74
                         "Szilveszter Farkas <szilveszter.farkas@gmail.com>",
 
75
                         _i18n("Contributors:"),
 
76
                         "Jelmer Vernooij <jelmer@samba.org>",
 
77
                         "Mateusz Korniak <mateusz.korniak@ant.gliwice.pl>",
 
78
                         "Gary van der Merwe <garyvdm@gmail.com>" ])
 
79
    dialog.set_artists([ "Simon Pascal Klein <klepas@klepas.org>",
 
80
                         "Jakub Steiner <jimmac@novell.com>" ])
 
81
 
 
82
    dialog.run()
 
83
    # Destroy the dialog
 
84
    dialog.destroy()
 
85
 
 
86
class OliveGtk:
 
87
    """ The main Olive GTK frontend class. This is called when launching the
 
88
    program. """
 
89
    
 
90
    def __init__(self):
 
91
        self.window = OliveGui(calling_app = self)
 
92
        
 
93
        self.pref = Preferences()
 
94
        self.path = None
 
95
 
 
96
        # Initialize the statusbar
 
97
        self.context_id = self.window.statusbar.get_context_id('olive')
 
98
        
 
99
                # Get the TreeViews
 
100
        self.treeview_left = self.window.treeview_left
 
101
        self.treeview_right = self.window.treeview_right
 
102
        
 
103
        # Get the drive selector
 
104
        self.combobox_drive = gtk.combo_box_new_text()
 
105
        self.combobox_drive.connect("changed", self._refresh_drives)
 
106
        
 
107
        # Get the navigation widgets
 
108
        self.hbox_location = self.window.locationbar
 
109
        self.button_location_up = self.window.button_location_up
 
110
        self.button_location_jump = self.window.button_location_jump
 
111
        self.entry_location = self.window.entry_location
 
112
        self.image_location_error = self.window.image_location_error
 
113
        
 
114
        # Get the History widgets
 
115
        self.check_history = self.window.checkbutton_history
 
116
        self.entry_history = self.window.entry_history_revno
 
117
        self.button_history = self.window.button_history_browse
 
118
        
 
119
        self._just_started = True
 
120
        
 
121
        # Apply window size and position
 
122
        width = self.pref.get_preference('window_width', 'int')
 
123
        height = self.pref.get_preference('window_height', 'int')
 
124
        self.window.resize(width, height)
 
125
        x = self.pref.get_preference('window_x', 'int')
 
126
        y = self.pref.get_preference('window_y', 'int')
 
127
        self.window.move(x, y)
 
128
        # Apply paned position
 
129
        pos = self.pref.get_preference('paned_position', 'int')
 
130
        self.window.hpaned_main.set_position(pos)
 
131
        
 
132
        # Now we can show the window
 
133
        self.window.show()
 
134
        
 
135
        # Show drive selector if under Win32
 
136
        if sys.platform == 'win32':
 
137
            self.hbox_location.pack_start(self.combobox_drive, False, False, 0)
 
138
            self.hbox_location.reorder_child(self.combobox_drive, 1)
 
139
            self.combobox_drive.show()
 
140
            self.gen_hard_selector()
 
141
        
 
142
        # Acceptable errors when loading files/folders in the treeviews
 
143
        self.acceptable_errors = (errno.ENOENT, errno.ELOOP)
 
144
        
 
145
        self._load_left()
 
146
 
 
147
        # Apply menu state
 
148
        self.window.mb_view_showhidden.set_active(self.pref.get_preference('dotted_files', 'bool'))
 
149
        self.window.mb_view_showignored.set_active(self.pref.get_preference('ignored_files', 'bool'))
 
150
 
 
151
        # We're starting local
 
152
        self.remote = False
 
153
        self.remote_branch = None
 
154
        self.remote_path = None
 
155
        self.remote_revision = None
 
156
        
 
157
        self.set_path(os.getcwd())
 
158
        self._load_right()
 
159
        
 
160
        self._just_started = False
 
161
 
 
162
    def set_path(self, path, force_remote=False):
 
163
        self.notbranch = False
 
164
        
 
165
        if force_remote:
 
166
            # Forcing remote mode (reading data from inventory)
 
167
            self._show_stock_image(gtk.STOCK_DISCONNECT)
 
168
            try:
 
169
                br = Branch.open_containing(path)[0]
 
170
            except bzrerrors.NotBranchError:
 
171
                self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
 
172
                self.check_history.set_active(False)
 
173
                self.check_history.set_sensitive(False)
 
174
                return False
 
175
            except bzrerrors.UnsupportedProtocol:
 
176
                self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
 
177
                self.check_history.set_active(False)
 
178
                self.check_history.set_sensitive(False)
 
179
                return False
 
180
            
 
181
            self._show_stock_image(gtk.STOCK_CONNECT)
 
182
            
 
183
            self.remote = True
 
184
           
 
185
            # We're remote
 
186
            self.remote_branch, self.remote_path = Branch.open_containing(path)
 
187
            
 
188
            if self.remote_revision is None:
 
189
                self.remote_revision = self.remote_branch.last_revision()
 
190
            
 
191
            self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
 
192
            
 
193
            if len(self.remote_path) == 0:
 
194
                self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
 
195
            else:
 
196
                for (name, type) in self.remote_entries:
 
197
                    if name == self.remote_path:
 
198
                        self.remote_parent = type.file_id
 
199
                        break
 
200
            
 
201
            if not path.endswith('/'):
 
202
                path += '/'
 
203
            
 
204
            if self.remote_branch.base == path:
 
205
                self.button_location_up.set_sensitive(False)
 
206
            else:
 
207
                self.button_location_up.set_sensitive(True)
 
208
        else:
 
209
            if os.path.isdir(path):
 
210
                self.image_location_error.destroy()
 
211
                self.remote = False
 
212
                
 
213
                # We're local
 
214
                try:
 
215
                    self.wt, self.wtpath = WorkingTree.open_containing(path)
 
216
                except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
 
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._show_stock_image(gtk.STOCK_DISCONNECT)
 
235
                try:
 
236
                    br = Branch.open_containing(path)[0]
 
237
                except bzrerrors.NotBranchError:
 
238
                    self._show_stock_image(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._show_stock_image(gtk.STOCK_DIALOG_ERROR)
 
244
                    self.check_history.set_active(False)
 
245
                    self.check_history.set_sensitive(False)
 
246
                    return False
 
247
                
 
248
                self._show_stock_image(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
        """ Brranch/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
    
 
543
    def on_menuitem_branch_push_activate(self, widget):
 
544
        """ Branch/Push... menu handler. """
 
545
        push = PushDialog(repository=None,revid=None,branch=self.wt.branch, parent=self.window)
 
546
        response = push.run()
 
547
        if response != gtk.RESPONSE_NONE:
 
548
            push.destroy()
 
549
    
 
550
    @show_bzr_error
 
551
    def on_menuitem_branch_revert_activate(self, widget):
 
552
        """ Branch/Revert all changes menu handler. """
 
553
        ret = self.wt.revert(None)
 
554
        if ret:
 
555
            warning_dialog(_i18n('Conflicts detected'),
 
556
                           _i18n('Please have a look at the working tree before continuing.'))
 
557
        else:
 
558
            info_dialog(_i18n('Revert successful'),
 
559
                        _i18n('All files reverted to last revision.'))
 
560
        self.refresh_right()
 
561
    
 
562
    def on_menuitem_branch_status_activate(self, widget):
 
563
        """ Branch/Status... menu handler. """
 
564
        from bzrlib.plugins.gtk.status import StatusDialog
 
565
        status = StatusDialog(self.wt, self.wtpath)
 
566
        response = status.run()
 
567
        if response != gtk.RESPONSE_NONE:
 
568
            status.destroy()
 
569
    
 
570
    def on_menuitem_branch_initialize_activate(self, widget):
 
571
        """ Initialize current directory. """
 
572
        init = InitDialog(self.path, self.window)
 
573
        response = init.run()
 
574
        if response != gtk.RESPONSE_NONE:
 
575
            init.hide()
 
576
        
 
577
            if response == gtk.RESPONSE_OK:
 
578
                self.refresh_right()
 
579
            
 
580
            init.destroy()
 
581
        
 
582
    def on_menuitem_branch_tags_activate(self, widget):
 
583
        """ Branch/Tags... menu handler. """
 
584
        from bzrlib.plugins.gtk.tags import TagsWindow
 
585
        if not self.remote:
 
586
            window = TagsWindow(self.wt.branch, self.window)
 
587
        else:
 
588
            window = TagsWindow(self.remote_branch, self.window)
 
589
        window.show()
 
590
    
 
591
    def on_menuitem_file_annotate_activate(self, widget):
 
592
        """ File/Annotate... menu handler. """
 
593
        if self.get_selected_right() is None:
 
594
            error_dialog(_i18n('No file was selected'),
 
595
                         _i18n('Please select a file from the list.'))
 
596
            return
 
597
        
 
598
        branch = self.wt.branch
 
599
        file_id = self.wt.path2id(self.wt.relpath(os.path.join(self.path, self.get_selected_right())))
 
600
        
 
601
        window = GAnnotateWindow(all=False, plain=False, parent=self.window)
 
602
        window.set_title(os.path.join(self.path, self.get_selected_right()) + " - Annotate")
 
603
        config = GAnnotateConfig(window)
 
604
        window.show()
 
605
        branch.lock_read()
 
606
        try:
 
607
            window.annotate(self.wt, branch, file_id)
 
608
        finally:
 
609
            branch.unlock()
 
610
    
 
611
    def on_menuitem_file_bookmark_activate(self, widget):
 
612
        """ File/Bookmark current directory menu handler. """
 
613
        if self.pref.add_bookmark(self.path):
 
614
            info_dialog(_i18n('Bookmark successfully added'),
 
615
                        _i18n('The current directory was bookmarked. You can reach\nit by selecting it from the left panel.'))
 
616
            self.pref.write()
 
617
        else:
 
618
            warning_dialog(_i18n('Location already bookmarked'),
 
619
                           _i18n('The current directory is already bookmarked.\nSee the left panel for reference.'))
 
620
        
 
621
        self.refresh_left()
 
622
    
 
623
    def on_menuitem_file_make_directory_activate(self, widget):
 
624
        """ File/Make directory... menu handler. """
 
625
        from bzrlib.plugins.gtk.olive.mkdir import MkdirDialog
 
626
        mkdir = MkdirDialog(self.wt, self.wtpath, self.window)
 
627
        response = mkdir.run()
 
628
        mkdir.destroy()
 
629
        if response == gtk.RESPONSE_OK:
 
630
            self.refresh_right()
 
631
    
 
632
    def on_menuitem_file_move_activate(self, widget):
 
633
        """ File/Move... menu handler. """
 
634
        from bzrlib.plugins.gtk.olive.move import MoveDialog
 
635
        move = MoveDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
 
636
        response = move.run()
 
637
        move.destroy()
 
638
        if response == gtk.RESPONSE_OK:
 
639
            self.refresh_right()
 
640
    
 
641
    def on_menuitem_file_rename_activate(self, widget):
 
642
        """ File/Rename... menu handler. """
 
643
        from bzrlib.plugins.gtk.olive.rename import RenameDialog
 
644
        rename = RenameDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
 
645
        response = rename.run()
 
646
        rename.destroy()
 
647
        if response == gtk.RESPONSE_OK:
 
648
            self.refresh_right()
 
649
 
 
650
    def on_menuitem_remove_file_activate(self, widget):
 
651
        """ Remove (unversion) selected file. """
 
652
        from bzrlib.plugins.gtk.olive.remove import RemoveDialog
 
653
        remove = RemoveDialog(self.wt, self.wtpath,
 
654
                                   selected=self.get_selected_right(),
 
655
                                   parent=self.window)
 
656
        response = remove.run()
 
657
        
 
658
        if response != gtk.RESPONSE_NONE:
 
659
            remove.hide()
 
660
        
 
661
            if response == gtk.RESPONSE_OK:
 
662
                self.set_path(self.path)
 
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
        if self.path is not None:
 
706
            self.refresh_right()
 
707
 
 
708
    def on_menuitem_view_show_ignored_files_activate(self, widget):
 
709
        """ Hide/Show ignored files menu handler. """
 
710
        self.pref.set_preference('ignored_files', widget.get_active())
 
711
        if self.path is not None:
 
712
            self.refresh_right()
 
713
            
 
714
    def on_treeview_left_button_press_event(self, widget, event):
 
715
        """ Occurs when somebody clicks in the bookmark list. """
 
716
        treepathpos = widget.get_path_at_pos(int(event.x), int(event.y))
 
717
        treeselection = widget.get_selection()
 
718
        if treepathpos is not None:
 
719
            treeselection.select_path(treepathpos[0])
 
720
            if event.button == 1:
 
721
                newdir = self.get_selected_left()
 
722
                if newdir == None:
 
723
                    return
 
724
 
 
725
                if self.set_path(newdir):
 
726
                    self.refresh_right()
 
727
            elif event.button == 3:
 
728
                # Don't show context with nothing selected
 
729
                if self.get_selected_left() == None:
 
730
                    return
 
731
 
 
732
                # Create a menu
 
733
                from menu import OliveMenu
 
734
                menu = OliveMenu(path=self.get_path(),
 
735
                                 selected=self.get_selected_left(),
 
736
                                 app=self)
 
737
                
 
738
                menu.left_context_menu().popup(None, None, None, 0,
 
739
                                               event.time)
 
740
        else:
 
741
            if treeselection is not None:
 
742
                treeselection.unselect_all()
 
743
 
 
744
    def on_treeview_left_row_activated(self, treeview, path, view_column):
 
745
        """ Occurs when somebody double-clicks or enters an item in the
 
746
        bookmark list. """
 
747
 
 
748
        newdir = self.get_selected_left()
 
749
        if newdir == None:
 
750
            return
 
751
 
 
752
        if self.set_path(newdir):
 
753
            self.refresh_right()
 
754
 
 
755
    def on_treeview_right_button_press_event(self, widget, event):
 
756
        """ Occurs when somebody clicks in the file list. """
 
757
        treepathpos = widget.get_path_at_pos(int(event.x), int(event.y))
 
758
        if event.button == 1:
 
759
            if treepathpos is None and widget.get_selection is not None:
 
760
                treeselection = widget.get_selection()
 
761
                treeselection.unselect_all()
 
762
        elif event.button == 3:
 
763
            treeselection = widget.get_selection()
 
764
            if treepathpos is not None:
 
765
                treeselection.select_path(treepathpos[0])
 
766
            else:
 
767
                if treeselection is not None:
 
768
                    treeselection.unselect_all()
 
769
            # Create a menu
 
770
            from menu import OliveMenu
 
771
            menu = OliveMenu(path=self.get_path(),
 
772
                             selected=self.get_selected_right(),
 
773
                             app=self)
 
774
            # get the menu items
 
775
            m_open = menu.ui.get_widget('/context_right/open')
 
776
            m_add = menu.ui.get_widget('/context_right/add')
 
777
            m_remove = menu.ui.get_widget('/context_right/remove')
 
778
            m_remove_and_delete = menu.ui.get_widget('/context_right/remove_and_delete')
 
779
            m_rename = menu.ui.get_widget('/context_right/rename')
 
780
            m_revert = menu.ui.get_widget('/context_right/revert')
 
781
            m_commit = menu.ui.get_widget('/context_right/commit')
 
782
            m_annotate = menu.ui.get_widget('/context_right/annotate')
 
783
            m_diff = menu.ui.get_widget('/context_right/diff')
 
784
            # check if we're in a branch
 
785
            try:
 
786
                from bzrlib.branch import Branch
 
787
                Branch.open_containing(self.get_path())
 
788
                if self.remote:
 
789
                    m_open.set_sensitive(False)
 
790
                    m_add.set_sensitive(False)
 
791
                    m_remove.set_sensitive(False)
 
792
                    m_remove_and_delete.set_sensitive(False)
 
793
                    m_rename.set_sensitive(False)
 
794
                    m_revert.set_sensitive(False)
 
795
                    m_commit.set_sensitive(False)
 
796
                    m_annotate.set_sensitive(False)
 
797
                    m_diff.set_sensitive(False)
 
798
                else:
 
799
                    if treepathpos is None:
 
800
                        m_open.set_sensitive(False)
 
801
                        m_add.set_sensitive(False)
 
802
                        m_remove.set_sensitive(False)
 
803
                        m_remove_and_delete.set_sensitive(False)
 
804
                        m_rename.set_sensitive(False)
 
805
                        m_annotate.set_sensitive(False)
 
806
                        m_diff.set_sensitive(False)
 
807
                        m_revert.set_sensitive(False)
 
808
                    else:
 
809
                        m_open.set_sensitive(True)
 
810
                        m_add.set_sensitive(True)
 
811
                        m_remove.set_sensitive(True)
 
812
                        m_remove_and_delete.set_sensitive(True)
 
813
                        m_rename.set_sensitive(True)
 
814
                        m_annotate.set_sensitive(True)
 
815
                        m_diff.set_sensitive(True)
 
816
                        m_revert.set_sensitive(True)
 
817
                    m_commit.set_sensitive(True)
 
818
            except bzrerrors.NotBranchError:
 
819
                if treepathpos is None:
 
820
                    m_open.set_sensitive(False)
 
821
                else:
 
822
                    m_open.set_sensitive(True)
 
823
                m_add.set_sensitive(False)
 
824
                m_remove.set_sensitive(False)
 
825
                m_remove_and_delete.set_sensitive(False)
 
826
                m_rename.set_sensitive(False)
 
827
                m_revert.set_sensitive(False)
 
828
                m_commit.set_sensitive(False)
 
829
                m_annotate.set_sensitive(False)
 
830
                m_diff.set_sensitive(False)
 
831
 
 
832
            if not self.remote:
 
833
                menu.right_context_menu().popup(None, None, None, 0,
 
834
                                                event.time)
 
835
            else:
 
836
                menu.remote_context_menu().popup(None, None, None, 0,
 
837
                                                 event.time)
 
838
        
 
839
    def on_treeview_right_row_activated(self, treeview, path, view_column):
 
840
        """ Occurs when somebody double-clicks or enters an item in the
 
841
        file list. """
 
842
        from launch import launch
 
843
        
 
844
        newdir = self.get_selected_right()
 
845
        
 
846
        if not self.remote:
 
847
            # We're local
 
848
            if newdir == '..':
 
849
                self.set_path(os.path.split(self.get_path())[0])
 
850
            else:
 
851
                fullpath = os.path.join(self.get_path(), newdir)
 
852
                if os.path.isdir(fullpath):
 
853
                    # selected item is an existant directory
 
854
                    self.set_path(fullpath)
 
855
                else:
 
856
                    launch(fullpath)
 
857
        else:
 
858
            # We're remote
 
859
            if self._is_remote_dir(self.get_path() + newdir):
 
860
                self.set_path(self.get_path() + newdir)
 
861
        
 
862
        self.refresh_right()
 
863
    
 
864
    def on_window_main_delete_event(self, widget, event=None):
 
865
        """ Do some stuff before exiting. """
 
866
        width, height = self.window.get_size()
 
867
        self.pref.set_preference('window_width', width)
 
868
        self.pref.set_preference('window_height', height)
 
869
        x, y = self.window.get_position()
 
870
        self.pref.set_preference('window_x', x)
 
871
        self.pref.set_preference('window_y', y)
 
872
        self.pref.set_preference('paned_position',
 
873
                                 self.window.hpaned_main.get_position())
 
874
        
 
875
        self.pref.write()
 
876
        self.window.destroy()
 
877
        
 
878
    def _load_left(self):
 
879
        """ Load data into the left panel. (Bookmarks) """
 
880
        # Create TreeStore
 
881
        treestore = gtk.TreeStore(str, str)
 
882
        
 
883
        # Get bookmarks
 
884
        bookmarks = self.pref.get_bookmarks()
 
885
        
 
886
        # Add them to the TreeStore
 
887
        titer = treestore.append(None, [_i18n('Bookmarks'), None])
 
888
 
 
889
        # Get titles and sort by title
 
890
        bookmarks = [[self.pref.get_bookmark_title(item), item] for item in bookmarks]
 
891
        bookmarks.sort()
 
892
        for title_item in bookmarks:
 
893
            treestore.append(titer, title_item)
 
894
        
 
895
        # Create the column and add it to the TreeView
 
896
        self.treeview_left.set_model(treestore)
 
897
        tvcolumn_bookmark = gtk.TreeViewColumn(_i18n('Bookmark'))
 
898
        self.treeview_left.append_column(tvcolumn_bookmark)
 
899
        
 
900
        # Set up the cells
 
901
        cell = gtk.CellRendererText()
 
902
        tvcolumn_bookmark.pack_start(cell, True)
 
903
        tvcolumn_bookmark.add_attribute(cell, 'text', 0)
 
904
        
 
905
        # Expand the tree
 
906
        self.treeview_left.expand_all()
 
907
 
 
908
    def _load_right(self):
 
909
        """ Load data into the right panel. (Filelist) """
 
910
        # Create ListStore
 
911
        # Model: [ icon, dir, name, status text, status, size (int), size (human), mtime (int), mtime (local), fileid ]
 
912
        liststore = gtk.ListStore(gobject.TYPE_STRING,
 
913
                                  gobject.TYPE_BOOLEAN,
 
914
                                  gobject.TYPE_STRING,
 
915
                                  gobject.TYPE_STRING,
 
916
                                  gobject.TYPE_STRING,
 
917
                                  gobject.TYPE_STRING,
 
918
                                  gobject.TYPE_STRING,
 
919
                                  gobject.TYPE_INT,
 
920
                                  gobject.TYPE_STRING,
 
921
                                  gobject.TYPE_STRING)
 
922
        
 
923
        dirs = []
 
924
        files = []
 
925
        
 
926
        # Fill the appropriate lists
 
927
        dotted_files = self.pref.get_preference('dotted_files', 'bool')
 
928
        for item in os.listdir(self.path):
 
929
            if not dotted_files and item[0] == '.':
 
930
                continue
 
931
            if os.path.isdir(self.path + os.sep + item):
 
932
                dirs.append(item)
 
933
            else:
 
934
                files.append(item)
 
935
        
 
936
        if not self.notbranch:
 
937
            branch = self.wt.branch
 
938
            tree2 = self.wt.branch.repository.revision_tree(branch.last_revision())
 
939
        
 
940
            delta = self.wt.changes_from(tree2, want_unchanged=True)
 
941
        
 
942
        # Add'em to the ListStore
 
943
        for item in dirs:
 
944
            try:
 
945
                statinfo = os.stat(self.path + os.sep + item)
 
946
            except OSError, e:
 
947
                if e.errno in self.acceptable_errors:
 
948
                    continue
 
949
                else:
 
950
                    raise
 
951
            liststore.append([ gtk.STOCK_DIRECTORY,
 
952
                               True,
 
953
                               item,
 
954
                               '',
 
955
                               '',
 
956
                               "<DIR>",
 
957
                               "<DIR>",
 
958
                               statinfo.st_mtime,
 
959
                               self._format_date(statinfo.st_mtime),
 
960
                               ''])
 
961
        for item in files:
 
962
            status = 'unknown'
 
963
            fileid = ''
 
964
            if not self.notbranch:
 
965
                filename = self.wt.relpath(self.path + os.sep + item)
 
966
                
 
967
                try:
 
968
                    self.wt.lock_read()
 
969
                    
 
970
                    for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
 
971
                        if rpathnew == filename:
 
972
                            status = 'renamed'
 
973
                            fileid = id
 
974
                    for rpath, id, kind in delta.added:
 
975
                        if rpath == filename:
 
976
                            status = 'added'
 
977
                            fileid = id
 
978
                    for rpath, id, kind in delta.removed:
 
979
                        if rpath == filename:
 
980
                            status = 'removed'
 
981
                            fileid = id
 
982
                    for rpath, id, kind, text_modified, meta_modified in delta.modified:
 
983
                        if rpath == filename:
 
984
                            status = 'modified'
 
985
                            fileid = id
 
986
                    for rpath, id, kind in delta.unchanged:
 
987
                        if rpath == filename:
 
988
                            status = 'unchanged'
 
989
                            fileid = id
 
990
                    for rpath, file_class, kind, id, entry in self.wt.list_files():
 
991
                        if rpath == filename and file_class == 'I':
 
992
                            status = 'ignored'
 
993
                finally:
 
994
                    self.wt.unlock()
 
995
            
 
996
            if status == 'renamed':
 
997
                st = _i18n('renamed')
 
998
            elif status == 'removed':
 
999
                st = _i18n('removed')
 
1000
            elif status == 'added':
 
1001
                st = _i18n('added')
 
1002
            elif status == 'modified':
 
1003
                st = _i18n('modified')
 
1004
            elif status == 'unchanged':
 
1005
                st = _i18n('unchanged')
 
1006
            elif status == 'ignored':
 
1007
                st = _i18n('ignored')
 
1008
            else:
 
1009
                st = _i18n('unknown')
 
1010
            
 
1011
            try:
 
1012
                statinfo = os.stat(self.path + os.sep + item)
 
1013
            except OSError, e:
 
1014
                if e.errno in self.acceptable_errors:
 
1015
                    continue
 
1016
                else:
 
1017
                    raise
 
1018
            liststore.append([gtk.STOCK_FILE,
 
1019
                              False,
 
1020
                              item,
 
1021
                              st,
 
1022
                              status,
 
1023
                              str(statinfo.st_size), # NOTE: if int used there it will fail for large files (size expressed as long int)
 
1024
                              self._format_size(statinfo.st_size),
 
1025
                              statinfo.st_mtime,
 
1026
                              self._format_date(statinfo.st_mtime),
 
1027
                              fileid])
 
1028
        
 
1029
        # Create the columns and add them to the TreeView
 
1030
        self.treeview_right.set_model(liststore)
 
1031
        self._tvcolumn_filename = gtk.TreeViewColumn(_i18n('Filename'))
 
1032
        self._tvcolumn_status = gtk.TreeViewColumn(_i18n('Status'))
 
1033
        self._tvcolumn_size = gtk.TreeViewColumn(_i18n('Size'))
 
1034
        self._tvcolumn_mtime = gtk.TreeViewColumn(_i18n('Last modified'))
 
1035
        self.treeview_right.append_column(self._tvcolumn_filename)
 
1036
        self.treeview_right.append_column(self._tvcolumn_status)
 
1037
        self.treeview_right.append_column(self._tvcolumn_size)
 
1038
        self.treeview_right.append_column(self._tvcolumn_mtime)
 
1039
        
 
1040
        # Set up the cells
 
1041
        cellpb = gtk.CellRendererPixbuf()
 
1042
        cell = gtk.CellRendererText()
 
1043
        self._tvcolumn_filename.pack_start(cellpb, False)
 
1044
        self._tvcolumn_filename.pack_start(cell, True)
 
1045
        self._tvcolumn_filename.set_attributes(cellpb, stock_id=0)
 
1046
        self._tvcolumn_filename.add_attribute(cell, 'text', 2)
 
1047
        self._tvcolumn_status.pack_start(cell, True)
 
1048
        self._tvcolumn_status.add_attribute(cell, 'text', 3)
 
1049
        self._tvcolumn_size.pack_start(cell, True)
 
1050
        self._tvcolumn_size.add_attribute(cell, 'text', 6)
 
1051
        self._tvcolumn_mtime.pack_start(cell, True)
 
1052
        self._tvcolumn_mtime.add_attribute(cell, 'text', 8)
 
1053
        
 
1054
        # Set up the properties of the TreeView
 
1055
        self.treeview_right.set_headers_visible(True)
 
1056
        self.treeview_right.set_headers_clickable(True)
 
1057
        self.treeview_right.set_search_column(1)
 
1058
        self._tvcolumn_filename.set_resizable(True)
 
1059
        self._tvcolumn_status.set_resizable(True)
 
1060
        self._tvcolumn_size.set_resizable(True)
 
1061
        self._tvcolumn_mtime.set_resizable(True)
 
1062
        # Set up sorting
 
1063
        liststore.set_sort_func(13, self._sort_filelist_callback, None)
 
1064
        liststore.set_sort_column_id(13, gtk.SORT_ASCENDING)
 
1065
        self._tvcolumn_filename.set_sort_column_id(13)
 
1066
        self._tvcolumn_status.set_sort_column_id(3)
 
1067
        self._tvcolumn_size.set_sort_column_id(5)
 
1068
        self._tvcolumn_mtime.set_sort_column_id(7)
 
1069
        
 
1070
        # Set sensitivity
 
1071
        self.set_sensitivity()
 
1072
        
 
1073
    def get_selected_fileid(self):
 
1074
        """ Get the file_id of the selected file. """
 
1075
        treeselection = self.treeview_right.get_selection()
 
1076
        (model, iter) = treeselection.get_selected()
 
1077
        
 
1078
        if iter is None:
 
1079
            return None
 
1080
        else:
 
1081
            return model.get_value(iter, 9)
 
1082
    
 
1083
    def get_selected_right(self):
 
1084
        """ Get the selected filename. """
 
1085
        treeselection = self.treeview_right.get_selection()
 
1086
        (model, iter) = treeselection.get_selected()
 
1087
        
 
1088
        if iter is None:
 
1089
            return None
 
1090
        else:
 
1091
            return model.get_value(iter, 2)
 
1092
    
 
1093
    def get_selected_left(self):
 
1094
        """ Get the selected bookmark. """
 
1095
        treeselection = self.treeview_left.get_selection()
 
1096
        (model, iter) = treeselection.get_selected()
 
1097
        
 
1098
        if iter is None:
 
1099
            return None
 
1100
        else:
 
1101
            return model.get_value(iter, 1)
 
1102
 
 
1103
    def set_statusbar(self, message):
 
1104
        """ Set the statusbar message. """
 
1105
        self.window.statusbar.push(self.context_id, message)
 
1106
    
 
1107
    def clear_statusbar(self):
 
1108
        """ Clean the last message from the statusbar. """
 
1109
        self.window.statusbar.pop(self.context_id)
 
1110
    
 
1111
    def set_sensitivity(self):
 
1112
        """ Set menu and toolbar sensitivity. """
 
1113
        if not self.remote:
 
1114
            self.window.set_view_to_localbranch(self.notbranch)
 
1115
        else:
 
1116
            self.window.set_view_to_remotebranch()
 
1117
    
 
1118
    def refresh_left(self):
 
1119
        """ Refresh the bookmark list. """
 
1120
        
 
1121
        # Get TreeStore and clear it
 
1122
        treestore = self.treeview_left.get_model()
 
1123
        treestore.clear()
 
1124
 
 
1125
        # Re-read preferences
 
1126
        self.pref.read()
 
1127
        
 
1128
        # Get bookmarks
 
1129
        bookmarks = self.pref.get_bookmarks()
 
1130
 
 
1131
        # Add them to the TreeStore
 
1132
        titer = treestore.append(None, [_i18n('Bookmarks'), None])
 
1133
 
 
1134
        # Get titles and sort by title
 
1135
        bookmarks = [[self.pref.get_bookmark_title(item), item] for item in bookmarks]
 
1136
        bookmarks.sort()
 
1137
        for title_item in bookmarks:
 
1138
            treestore.append(titer, title_item)
 
1139
        
 
1140
        # Add the TreeStore to the TreeView
 
1141
        self.treeview_left.set_model(treestore)
 
1142
 
 
1143
        # Expand the tree
 
1144
        self.treeview_left.expand_all()
 
1145
 
 
1146
    def refresh_right(self, path=None):
 
1147
        """ Refresh the file list. """
 
1148
        if not self.remote:
 
1149
            # We're local
 
1150
            from bzrlib.workingtree import WorkingTree
 
1151
    
 
1152
            if path is None:
 
1153
                path = self.get_path()
 
1154
    
 
1155
            # A workaround for double-clicking Bookmarks
 
1156
            if not os.path.exists(path):
 
1157
                return
 
1158
    
 
1159
            # Get ListStore and clear it
 
1160
            liststore = self.treeview_right.get_model()
 
1161
            liststore.clear()
 
1162
            
 
1163
            # Show Status column
 
1164
            self._tvcolumn_status.set_visible(True)
 
1165
    
 
1166
            dirs = []
 
1167
            files = []
 
1168
    
 
1169
            # Fill the appropriate lists
 
1170
            dotted_files = self.pref.get_preference('dotted_files', 'bool')
 
1171
            ignored_files = self.pref.get_preference('ignored_files', 'bool')
 
1172
 
 
1173
            for item in os.listdir(path):
 
1174
                if not dotted_files and item[0] == '.':
 
1175
                    continue
 
1176
                if os.path.isdir(path + os.sep + item):
 
1177
                    dirs.append(item)
 
1178
                else:
 
1179
                    files.append(item)
 
1180
            
 
1181
            # Try to open the working tree
 
1182
            notbranch = False
 
1183
            try:
 
1184
                tree1 = WorkingTree.open_containing(path)[0]
 
1185
            except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
 
1186
                notbranch = True
 
1187
            
 
1188
            if not notbranch:
 
1189
                branch = tree1.branch
 
1190
                tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
 
1191
            
 
1192
                delta = tree1.changes_from(tree2, want_unchanged=True)
 
1193
                
 
1194
            # Add'em to the ListStore
 
1195
            for item in dirs:
 
1196
                try:
 
1197
                    statinfo = os.stat(self.path + os.sep + item)
 
1198
                except OSError, e:
 
1199
                    if e.errno in self.acceptable_errors:
 
1200
                        continue
 
1201
                    else:
 
1202
                        raise
 
1203
                liststore.append([gtk.STOCK_DIRECTORY,
 
1204
                                  True,
 
1205
                                  item,
 
1206
                                  '',
 
1207
                                  '',
 
1208
                                  "<DIR>",
 
1209
                                  "<DIR>",
 
1210
                                  statinfo.st_mtime,
 
1211
                                  self._format_date(statinfo.st_mtime),
 
1212
                                  ''])
 
1213
            for item in files:
 
1214
                status = 'unknown'
 
1215
                fileid = ''
 
1216
                if not notbranch:
 
1217
                    filename = tree1.relpath(path + os.sep + item)
 
1218
                    
 
1219
                    try:
 
1220
                        self.wt.lock_read()
 
1221
                        
 
1222
                        for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
 
1223
                            if rpathnew == filename:
 
1224
                                status = 'renamed'
 
1225
                                fileid = id
 
1226
                        for rpath, id, kind in delta.added:
 
1227
                            if rpath == filename:
 
1228
                                status = 'added'
 
1229
                                fileid = id
 
1230
                        for rpath, id, kind in delta.removed:
 
1231
                            if rpath == filename:
 
1232
                                status = 'removed'
 
1233
                                fileid = id
 
1234
                        for rpath, id, kind, text_modified, meta_modified in delta.modified:
 
1235
                            if rpath == filename:
 
1236
                                status = 'modified'
 
1237
                                fileid = id
 
1238
                        for rpath, id, kind in delta.unchanged:
 
1239
                            if rpath == filename:
 
1240
                                status = 'unchanged'
 
1241
                                fileid = id
 
1242
                        for rpath, file_class, kind, id, entry in self.wt.list_files():
 
1243
                            if rpath == filename and file_class == 'I':
 
1244
                                status = 'ignored'
 
1245
                    finally:
 
1246
                        self.wt.unlock()
 
1247
                
 
1248
                if status == 'renamed':
 
1249
                    st = _i18n('renamed')
 
1250
                elif status == 'removed':
 
1251
                    st = _i18n('removed')
 
1252
                elif status == 'added':
 
1253
                    st = _i18n('added')
 
1254
                elif status == 'modified':
 
1255
                    st = _i18n('modified')
 
1256
                elif status == 'unchanged':
 
1257
                    st = _i18n('unchanged')
 
1258
                elif status == 'ignored':
 
1259
                    st = _i18n('ignored')
 
1260
                    if not ignored_files:
 
1261
                        continue
 
1262
                else:
 
1263
                    st = _i18n('unknown')
 
1264
                
 
1265
                try:
 
1266
                    statinfo = os.stat(self.path + os.sep + item)
 
1267
                except OSError, e:
 
1268
                    if e.errno in self.acceptable_errors:
 
1269
                        continue
 
1270
                    else:
 
1271
                        raise
 
1272
                liststore.append([gtk.STOCK_FILE,
 
1273
                                  False,
 
1274
                                  item,
 
1275
                                  st,
 
1276
                                  status,
 
1277
                                  str(statinfo.st_size),
 
1278
                                  self._format_size(statinfo.st_size),
 
1279
                                  statinfo.st_mtime,
 
1280
                                  self._format_date(statinfo.st_mtime),
 
1281
                                  fileid])
 
1282
        else:
 
1283
            # We're remote
 
1284
            
 
1285
            # Get ListStore and clear it
 
1286
            liststore = self.treeview_right.get_model()
 
1287
            liststore.clear()
 
1288
            
 
1289
            # Hide Status column
 
1290
            self._tvcolumn_status.set_visible(False)
 
1291
            
 
1292
            dirs = []
 
1293
            files = []
 
1294
            
 
1295
            self._show_stock_image(gtk.STOCK_REFRESH)
 
1296
            
 
1297
            for (name, type) in self.remote_entries:
 
1298
                if type.kind == 'directory':
 
1299
                    dirs.append(type)
 
1300
                elif type.kind == 'file':
 
1301
                    files.append(type)
 
1302
            
 
1303
            class HistoryCache:
 
1304
                """ Cache based on revision history. """
 
1305
                def __init__(self, history):
 
1306
                    self._history = history
 
1307
                
 
1308
                def _lookup_revision(self, revid):
 
1309
                    for r in self._history:
 
1310
                        if r.revision_id == revid:
 
1311
                            return r
 
1312
                    rev = repo.get_revision(revid)
 
1313
                    self._history.append(rev)
 
1314
                    return rev
 
1315
            
 
1316
            repo = self.remote_branch.repository
 
1317
            
 
1318
            revhistory = self.remote_branch.revision_history()
 
1319
            try:
 
1320
                revs = repo.get_revisions(revhistory)
 
1321
                cache = HistoryCache(revs)
 
1322
            except bzrerrors.InvalidHttpResponse:
 
1323
                # Fallback to dummy algorithm, because of LP: #115209
 
1324
                cache = HistoryCache([])
 
1325
            
 
1326
            for item in dirs:
 
1327
                if item.parent_id == self.remote_parent:
 
1328
                    rev = cache._lookup_revision(item.revision)
 
1329
                    liststore.append([ gtk.STOCK_DIRECTORY,
 
1330
                                       True,
 
1331
                                       item.name,
 
1332
                                       '',
 
1333
                                       '',
 
1334
                                       "<DIR>",
 
1335
                                       "<DIR>",
 
1336
                                       rev.timestamp,
 
1337
                                       self._format_date(rev.timestamp),
 
1338
                                       ''
 
1339
                                   ])
 
1340
                while gtk.events_pending():
 
1341
                    gtk.main_iteration()
 
1342
            
 
1343
            for item in files:
 
1344
                if item.parent_id == self.remote_parent:
 
1345
                    rev = cache._lookup_revision(item.revision)
 
1346
                    liststore.append([ gtk.STOCK_FILE,
 
1347
                                       False,
 
1348
                                       item.name,
 
1349
                                       '',
 
1350
                                       '',
 
1351
                                       str(item.text_size),
 
1352
                                       self._format_size(item.text_size),
 
1353
                                       rev.timestamp,
 
1354
                                       self._format_date(rev.timestamp),
 
1355
                                       item.file_id
 
1356
                                   ])
 
1357
                while gtk.events_pending():
 
1358
                    gtk.main_iteration()
 
1359
            
 
1360
            self.image_location_error.destroy()
 
1361
 
 
1362
        # Columns should auto-size
 
1363
        self.treeview_right.columns_autosize()
 
1364
        
 
1365
        # Set sensitivity
 
1366
        self.set_sensitivity()
 
1367
 
 
1368
    def _harddisks(self):
 
1369
        """ Returns hard drive letters under Win32. """
 
1370
        try:
 
1371
            import win32file
 
1372
            import string
 
1373
        except ImportError:
 
1374
            if sys.platform == 'win32':
 
1375
                print "pyWin32 modules needed to run Olive on Win32."
 
1376
                sys.exit(1)
 
1377
        
 
1378
        driveletters = []
 
1379
        for drive in string.ascii_uppercase:
 
1380
            if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED or\
 
1381
                win32file.GetDriveType(drive+':') == win32file.DRIVE_REMOTE:
 
1382
                driveletters.append(drive+':')
 
1383
        return driveletters
 
1384
    
 
1385
    def gen_hard_selector(self):
 
1386
        """ Generate the hard drive selector under Win32. """
 
1387
        drives = self._harddisks()
 
1388
        for drive in drives:
 
1389
            self.combobox_drive.append_text(drive)
 
1390
        self.combobox_drive.set_active(drives.index(os.getcwd()[0:2]))
 
1391
    
 
1392
    def _refresh_drives(self, combobox):
 
1393
        if self._just_started:
 
1394
            return
 
1395
        model = combobox.get_model()
 
1396
        active = combobox.get_active()
 
1397
        if active >= 0:
 
1398
            drive = model[active][0]
 
1399
            self.set_path(drive + '\\')
 
1400
            self.refresh_right(drive + '\\')
 
1401
    
 
1402
    def check_for_changes(self):
 
1403
        """ Check whether there were changes in the current working tree. """
 
1404
        old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
 
1405
        delta = self.wt.changes_from(old_tree)
 
1406
 
 
1407
        changes = False
 
1408
        
 
1409
        if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
 
1410
            changes = True
 
1411
        
 
1412
        return changes
 
1413
    
 
1414
    def _sort_filelist_callback(self, model, iter1, iter2, data):
 
1415
        """ The sort callback for the file list, return values:
 
1416
        -1: iter1 < iter2
 
1417
        0: iter1 = iter2
 
1418
        1: iter1 > iter2
 
1419
        """
 
1420
        name1 = model.get_value(iter1, 2)
 
1421
        name2 = model.get_value(iter2, 2)
 
1422
        
 
1423
        if model.get_value(iter1, 1):
 
1424
            # item1 is a directory
 
1425
            if not model.get_value(iter2, 1):
 
1426
                # item2 isn't
 
1427
                return -1
 
1428
            else:
 
1429
                # both of them are directories, we compare their names
 
1430
                if name1 < name2:
 
1431
                    return -1
 
1432
                elif name1 == name2:
 
1433
                    return 0
 
1434
                else:
 
1435
                    return 1
 
1436
        else:
 
1437
            # item1 is not a directory
 
1438
            if model.get_value(iter2, 1):
 
1439
                # item2 is
 
1440
                return 1
 
1441
            else:
 
1442
                # both of them are files, compare them
 
1443
                if name1 < name2:
 
1444
                    return -1
 
1445
                elif name1 == name2:
 
1446
                    return 0
 
1447
                else:
 
1448
                    return 1
 
1449
    
 
1450
    def _format_size(self, size):
 
1451
        """ Format size to a human readable format. """
 
1452
        if size < 1000:
 
1453
            return "%d[B]" % (size,)
 
1454
        size = size / 1000.0
 
1455
        
 
1456
        for metric in ["kB","MB","GB","TB"]:
 
1457
            if size < 1000:
 
1458
                break
 
1459
            size = size / 1000.0
 
1460
        return "%.1f[%s]" % (size,metric) 
 
1461
    
 
1462
    def _format_date(self, timestamp):
 
1463
        """ Format the time (given in secs) to a human readable format. """
 
1464
        return time.ctime(timestamp)
 
1465
    
 
1466
    def _is_remote_dir(self, location):
 
1467
        """ Determine whether the given location is a directory or not. """
 
1468
        if not self.remote:
 
1469
            # We're in local mode
 
1470
            return False
 
1471
        else:
 
1472
            branch, path = Branch.open_containing(location)
 
1473
            for (name, type) in self.remote_entries:
 
1474
                if name == path and type.kind == 'directory':
 
1475
                    # We got it
 
1476
                    return True
 
1477
            # Either it's not a directory or not in the inventory
 
1478
            return False
 
1479
    
 
1480
    def _show_stock_image(self, stock_id):
 
1481
        """ Show a stock image next to the location entry. """
 
1482
        self.image_location_error.destroy()
 
1483
        self.image_location_error = gtk.image_new_from_stock(stock_id, gtk.ICON_SIZE_BUTTON)
 
1484
        self.hbox_location.pack_start(self.image_location_error, False, False, 0)
 
1485
        if sys.platform == 'win32':
 
1486
            self.hbox_location.reorder_child(self.image_location_error, 2)
 
1487
        else:
 
1488
            self.hbox_location.reorder_child(self.image_location_error, 1)
 
1489
        self.image_location_error.show()
 
1490
        while gtk.events_pending():
 
1491
            gtk.main_iteration()
 
1492
 
 
1493
import ConfigParser
 
1494
 
 
1495
class Preferences:
 
1496
    """ A class which handles Olive's preferences. """
 
1497
    def __init__(self, path=None):
 
1498
        """ Initialize the Preferences class. """
 
1499
        # Some default options
 
1500
        self.defaults = { 'strict_commit' : False,
 
1501
                          'dotted_files'  : False,
 
1502
                          'ignored_files' : True,
 
1503
                          'window_width'  : 700,
 
1504
                          'window_height' : 400,
 
1505
                          'window_x'      : 40,
 
1506
                          'window_y'      : 40,
 
1507
                          'paned_position': 200 }
 
1508
 
 
1509
        # Create a config parser object
 
1510
        self.config = ConfigParser.RawConfigParser()
 
1511
 
 
1512
        # Set filename
 
1513
        if path is None:
 
1514
            if sys.platform == 'win32':
 
1515
                # Windows - no dotted files
 
1516
                self._filename = os.path.expanduser('~/olive.conf')
 
1517
            else:
 
1518
                self._filename = os.path.expanduser('~/.olive.conf')
 
1519
        else:
 
1520
            self._filename = path
 
1521
        
 
1522
        # Load the configuration
 
1523
        self.read()
 
1524
        
 
1525
    def _get_default(self, option):
 
1526
        """ Get the default option for a preference. """
 
1527
        try:
 
1528
            ret = self.defaults[option]
 
1529
        except KeyError:
 
1530
            return None
 
1531
        else:
 
1532
            return ret
 
1533
 
 
1534
    def refresh(self):
 
1535
        """ Refresh the configuration. """
 
1536
        # First write out the changes
 
1537
        self.write()
 
1538
        # Then load the configuration again
 
1539
        self.read()
 
1540
 
 
1541
    def read(self):
 
1542
        """ Just read the configuration. """
 
1543
        # Re-initialize the config parser object to avoid some bugs
 
1544
        self.config = ConfigParser.RawConfigParser()
 
1545
        self.config.read([self._filename])
 
1546
    
 
1547
    def write(self):
 
1548
        """ Write the configuration to the appropriate files. """
 
1549
        fp = open(self._filename, 'w')
 
1550
        self.config.write(fp)
 
1551
        fp.close()
 
1552
 
 
1553
    def get_bookmarks(self):
 
1554
        """ Return the list of bookmarks. """
 
1555
        bookmarks = self.config.sections()
 
1556
        if self.config.has_section('preferences'):
 
1557
            bookmarks.remove('preferences')
 
1558
        return bookmarks
 
1559
 
 
1560
    def add_bookmark(self, path):
 
1561
        """ Add bookmark. """
 
1562
        try:
 
1563
            self.config.add_section(path)
 
1564
        except ConfigParser.DuplicateSectionError:
 
1565
            return False
 
1566
        else:
 
1567
            return True
 
1568
 
 
1569
    def get_bookmark_title(self, path):
 
1570
        """ Get bookmark title. """
 
1571
        try:
 
1572
            ret = self.config.get(path, 'title')
 
1573
        except ConfigParser.NoOptionError:
 
1574
            ret = path
 
1575
        
 
1576
        return ret
 
1577
    
 
1578
    def set_bookmark_title(self, path, title):
 
1579
        """ Set bookmark title. """
 
1580
        # FIXME: What if path isn't listed yet?
 
1581
        # FIXME: Canonicalize paths first?
 
1582
        self.config.set(path, 'title', title)
 
1583
    
 
1584
    def remove_bookmark(self, path):
 
1585
        """ Remove bookmark. """
 
1586
        return self.config.remove_section(path)
 
1587
 
 
1588
    def set_preference(self, option, value):
 
1589
        """ Set the value of the given option. """
 
1590
        if value is True:
 
1591
            value = 'yes'
 
1592
        elif value is False:
 
1593
            value = 'no'
 
1594
        
 
1595
        if self.config.has_section('preferences'):
 
1596
            self.config.set('preferences', option, value)
 
1597
        else:
 
1598
            self.config.add_section('preferences')
 
1599
            self.config.set('preferences', option, value)
 
1600
 
 
1601
    def get_preference(self, option, kind='str'):
 
1602
        """ Get the value of the given option.
 
1603
        
 
1604
        :param kind: str/bool/int/float. default: str
 
1605
        """
 
1606
        if self.config.has_option('preferences', option):
 
1607
            if kind == 'bool':
 
1608
                return self.config.getboolean('preferences', option)
 
1609
            elif kind == 'int':
 
1610
                return self.config.getint('preferences', option)
 
1611
            elif kind == 'float':
 
1612
                return self.config.getfloat('preferences', option)
 
1613
            else:
 
1614
                return self.config.get('preferences', option)
 
1615
        else:
 
1616
            try:
 
1617
                return self._get_default(option)
 
1618
            except KeyError:
 
1619
                return None