/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: Szilveszter Farkas
  • Date: 2009-05-26 13:58:24 UTC
  • mto: (635.2.6 trunk)
  • mto: This revision was merged to the branch mainline in revision 639.
  • Revision ID: szilveszter.farkas@gmail.com-20090526135824-fwvt03uqtl2p6vk7
Updated the README entry about GtkSourceView.

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
        self.refresh_left()
 
137
 
 
138
        # Apply menu state
 
139
        self.window.mb_view_showhidden.set_active(self.pref.get_preference('dotted_files', 'bool'))
 
140
        self.window.mb_view_showignored.set_active(self.pref.get_preference('ignored_files', 'bool'))
 
141
 
 
142
        # We're starting local
 
143
        self.remote = False
 
144
        self.remote_branch = None
 
145
        self.remote_path = None
 
146
        self.remote_revision = None
 
147
        
 
148
        self.set_path(os.getcwd())
 
149
        self.refresh_right()
 
150
        
 
151
        self._just_started = False
 
152
 
 
153
    def set_path(self, path, force_remote=False):
 
154
        self.window.location_status.destroy()
 
155
        self.notbranch = False
 
156
        
 
157
        if force_remote:
 
158
            # Forcing remote mode (reading data from inventory)
 
159
            self.window.set_location_status(gtk.STOCK_DISCONNECT)
 
160
            try:
 
161
                br = Branch.open_containing(path)[0]
 
162
            except bzrerrors.NotBranchError:
 
163
                self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
 
164
                self.check_history.set_active(False)
 
165
                self.check_history.set_sensitive(False)
 
166
                return False
 
167
            except bzrerrors.UnsupportedProtocol:
 
168
                self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
 
169
                self.check_history.set_active(False)
 
170
                self.check_history.set_sensitive(False)
 
171
                return False
 
172
            
 
173
            self.window.set_location_status(gtk.STOCK_CONNECT)
 
174
            
 
175
            self.remote = True
 
176
           
 
177
            # We're remote
 
178
            self.remote_branch, self.remote_path = Branch.open_containing(path)
 
179
            
 
180
            if self.remote_revision is None:
 
181
                self.remote_revision = self.remote_branch.last_revision()
 
182
            
 
183
            self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
 
184
            
 
185
            if len(self.remote_path) == 0:
 
186
                self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
 
187
            else:
 
188
                for (name, type) in self.remote_entries:
 
189
                    if name == self.remote_path:
 
190
                        self.remote_parent = type.file_id
 
191
                        break
 
192
            
 
193
            if not path.endswith('/'):
 
194
                path += '/'
 
195
            
 
196
            if self.remote_branch.base == path:
 
197
                self.button_location_up.set_sensitive(False)
 
198
            else:
 
199
                self.button_location_up.set_sensitive(True)
 
200
        else:
 
201
            if os.path.isdir(path):
 
202
                self.remote = False
 
203
                
 
204
                # We're local
 
205
                try:
 
206
                    self.wt, self.wtpath = WorkingTree.open_containing(os.path.realpath(path))
 
207
                except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
 
208
                    self.notbranch = True
 
209
                except bzrerrors.PermissionDenied:
 
210
                    self.window.set_location_status(gtk.STOCK_DIALOG_WARNING, allowPopup=True)
 
211
                    self.window.location_status.connect_object('clicked', warning_dialog, 
 
212
                                       *(_i18n('Branch information unreadable'),
 
213
                                                _i18n('The current folder is a branch but the .bzr folder is not readable')))
 
214
                    self.notbranch = True
 
215
                
 
216
                # If we're in the root, we cannot go up anymore
 
217
                if sys.platform == 'win32':
 
218
                    drive, tail = os.path.splitdrive(path)
 
219
                    if tail in ('', '/', '\\'):
 
220
                        self.button_location_up.set_sensitive(False)
 
221
                    else:
 
222
                        self.button_location_up.set_sensitive(True)
 
223
                else:
 
224
                    if self.path == '/':
 
225
                        self.button_location_up.set_sensitive(False)
 
226
                    else:
 
227
                        self.button_location_up.set_sensitive(True)
 
228
            elif not os.path.isfile(path):
 
229
                # Doesn't seem to be a file nor a directory, trying to open a
 
230
                # remote location
 
231
                self.window.set_location_status(gtk.STOCK_DISCONNECT)
 
232
                try:
 
233
                    br = Branch.open_containing(path)[0]
 
234
                except bzrerrors.NotBranchError:
 
235
                    self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
 
236
                    self.check_history.set_active(False)
 
237
                    self.check_history.set_sensitive(False)
 
238
                    return False
 
239
                except bzrerrors.UnsupportedProtocol:
 
240
                    self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
 
241
                    self.check_history.set_active(False)
 
242
                    self.check_history.set_sensitive(False)
 
243
                    return False
 
244
                
 
245
                self.window.set_location_status(gtk.STOCK_CONNECT)
 
246
                
 
247
                self.remote = True
 
248
               
 
249
                # We're remote
 
250
                self.remote_branch, self.remote_path = Branch.open_containing(path)
 
251
                
 
252
                if self.remote_revision is None:
 
253
                    self.remote_revision = self.remote_branch.last_revision()
 
254
                
 
255
                self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
 
256
                
 
257
                if len(self.remote_path) == 0:
 
258
                    self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
 
259
                else:
 
260
                    for (name, type) in self.remote_entries:
 
261
                        if name == self.remote_path:
 
262
                            self.remote_parent = type.file_id
 
263
                            break
 
264
                
 
265
                if not path.endswith('/'):
 
266
                    path += '/'
 
267
                
 
268
                if self.remote_branch.base == path:
 
269
                    self.button_location_up.set_sensitive(False)
 
270
                else:
 
271
                    self.button_location_up.set_sensitive(True)
 
272
        
 
273
        if self.notbranch:
 
274
            self.check_history.set_active(False)
 
275
            self.check_history.set_sensitive(False)
 
276
        else:
 
277
            self.check_history.set_sensitive(True)
 
278
        
 
279
        self.window.statusbar.push(self.context_id, path)
 
280
        self.entry_location.set_text(path)
 
281
        self.path = path
 
282
        return True
 
283
 
 
284
    def get_path(self):
 
285
        if not self.remote:
 
286
            return self.path
 
287
        else:
 
288
            # Remote mode
 
289
            if len(self.remote_path) > 0:
 
290
                return self.remote_branch.base + self.remote_path + '/'
 
291
            else:
 
292
                return self.remote_branch.base
 
293
   
 
294
    def on_about_activate(self, widget):
 
295
        about()
 
296
    
 
297
    def on_button_history_browse_clicked(self, widget):
 
298
        """ Browse for revision button handler. """
 
299
        if self.remote:
 
300
            br = self.remote_branch
 
301
        else:
 
302
            br = self.wt.branch
 
303
            
 
304
        revb = RevisionBrowser(br, self.window)
 
305
        response = revb.run()
 
306
        if response != gtk.RESPONSE_NONE:
 
307
            revb.hide()
 
308
        
 
309
            if response == gtk.RESPONSE_OK:
 
310
                if revb.selected_revno is not None:
 
311
                    self.entry_history.set_text(revb.selected_revno)
 
312
                    self.on_entry_history_revno_activate()
 
313
            
 
314
            revb.destroy()
 
315
    
 
316
    def on_button_location_jump_clicked(self, widget):
 
317
        """ Location Jump button handler. """
 
318
        location = self.entry_location.get_text()
 
319
        
 
320
        if self.set_path(location):
 
321
            self.refresh_right()
 
322
    
 
323
    def on_button_location_up_clicked(self, widget):
 
324
        """ Location Up button handler. """
 
325
        if not self.remote:
 
326
            # Local mode
 
327
            self.set_path(os.path.split(self.get_path())[0])
 
328
        else:
 
329
            # Remote mode
 
330
            delim = '/'
 
331
            newpath = delim.join(self.get_path().split(delim)[:-2])
 
332
            newpath += '/'
 
333
            self.set_path(newpath)
 
334
 
 
335
        self.refresh_right()
 
336
    
 
337
    def on_checkbutton_history_toggled(self, widget):
 
338
        """ History Mode toggle handler. """
 
339
        if self.check_history.get_active():
 
340
            # History Mode activated
 
341
            self.entry_history.set_sensitive(True)
 
342
            self.button_history.set_sensitive(True)
 
343
            if self.entry_history.get_text() != "":
 
344
                self.on_entry_history_revno_activate()
 
345
        else:
 
346
            # History Mode deactivated
 
347
            self.entry_history.set_sensitive(False)
 
348
            self.button_history.set_sensitive(False)
 
349
            
 
350
            # Return right window to normal view by acting like we jump to it
 
351
            self.on_button_location_jump_clicked(widget)
 
352
    
 
353
    @show_bzr_error
 
354
    def on_entry_history_revno_activate(self, widget=None):
 
355
        """ Key pressed handler for the history entry. """
 
356
        path = self.get_path()
 
357
        if not self.remote:
 
358
            self.remote = True
 
359
            self.remote_branch = self.wt.branch
 
360
        
 
361
        revno = int(self.entry_history.get_text())
 
362
        self.remote_revision = self.remote_branch.get_rev_id(revno)
 
363
        if self.set_path(path, True):
 
364
            self.refresh_right()
 
365
 
 
366
    def on_menuitem_add_files_activate(self, widget):
 
367
        """ Add file(s)... menu handler. """
 
368
        from bzrlib.plugins.gtk.olive.add import AddDialog
 
369
        add = AddDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
 
370
        response = add.run()
 
371
        add.destroy()
 
372
        if response == gtk.RESPONSE_OK:
 
373
            self.refresh_right()
 
374
 
 
375
    def on_menuitem_branch_get_activate(self, widget):
 
376
        """ Branch/Get... menu handler. """
 
377
        from bzrlib.plugins.gtk.branch import BranchDialog
 
378
        
 
379
        if self.remote:
 
380
            branch = BranchDialog(os.getcwd(), self.window, self.remote_branch.base)
 
381
        else:
 
382
            branch = BranchDialog(self.get_path(), self.window)
 
383
        response = branch.run()
 
384
        if response != gtk.RESPONSE_NONE:
 
385
            branch.hide()
 
386
            
 
387
            if response == gtk.RESPONSE_OK:
 
388
                self.refresh_right()
 
389
            
 
390
            branch.destroy()
 
391
    
 
392
    def on_menuitem_branch_checkout_activate(self, widget):
 
393
        """ Branch/Checkout... menu handler. """
 
394
        from bzrlib.plugins.gtk.checkout import CheckoutDialog
 
395
        
 
396
        if self.remote:
 
397
            checkout = CheckoutDialog(os.getcwd(), self.window, self.remote_branch.base)
 
398
        else:
 
399
            checkout = CheckoutDialog(self.get_path(), self.window)
 
400
        response = checkout.run()
 
401
        if response != gtk.RESPONSE_NONE:
 
402
            checkout.hide()
 
403
        
 
404
            if response == gtk.RESPONSE_OK:
 
405
                self.refresh_right()
 
406
            
 
407
            checkout.destroy()
 
408
    
 
409
    @show_bzr_error
 
410
    def on_menuitem_branch_commit_activate(self, widget):
 
411
        """ Branch/Commit... menu handler. """
 
412
        selected = self.get_selected_right()
 
413
        if selected:
 
414
            selected = os.path.join(self.wtpath, selected)
 
415
        commit = CommitDialog(wt=self.wt,
 
416
                              parent=self.window,
 
417
                              selected=selected,
 
418
                             )
 
419
        response = commit.run()
 
420
        if response != gtk.RESPONSE_NONE:
 
421
            commit.hide()
 
422
        
 
423
            if response == gtk.RESPONSE_OK:
 
424
                self.refresh_right()
 
425
            
 
426
            commit.destroy()
 
427
    
 
428
    def on_menuitem_branch_conflicts_activate(self, widget):
 
429
        """ Branch/Conflicts... menu handler. """
 
430
        conflicts = ConflictsDialog(self.wt, self.window)
 
431
        response = conflicts.run()
 
432
        if response != gtk.RESPONSE_NONE:
 
433
            conflicts.destroy()
 
434
    
 
435
    def on_menuitem_branch_merge_activate(self, widget):
 
436
        """ Branch/Merge... menu handler. """
 
437
        from bzrlib.plugins.gtk.merge import MergeDialog
 
438
        
 
439
        if self.check_for_changes():
 
440
            error_dialog(_i18n('There are local changes in the branch'),
 
441
                         _i18n('Please commit or revert the changes before merging.'))
 
442
        else:
 
443
            parent_branch_path = self.wt.branch.get_parent()
 
444
            merge = MergeDialog(self.wt, self.wtpath, parent_branch_path, self.window)
 
445
            response = merge.run()
 
446
            merge.destroy()
 
447
            if response == gtk.RESPONSE_OK:
 
448
                self.refresh_right()
 
449
 
 
450
    @show_bzr_error
 
451
    def on_menuitem_branch_missing_revisions_activate(self, widget):
 
452
        """ Branch/Missing revisions menu handler. """
 
453
        
 
454
        from bzrlib.missing import find_unmerged, iter_log_revisions
 
455
        
 
456
        local_branch = self.wt.branch
 
457
        parent_branch_path = local_branch.get_parent()
 
458
        if parent_branch_path is None:
 
459
            error_dialog(_i18n('Parent location is unknown'),
 
460
                         _i18n('Cannot determine missing revisions if no parent location is known.'))
 
461
            return
 
462
        
 
463
        parent_branch = Branch.open(parent_branch_path)
 
464
        
 
465
        if parent_branch.base == local_branch.base:
 
466
            parent_branch = local_branch
 
467
        
 
468
        local_extra, remote_extra = find_unmerged(local_branch,parent_branch)
 
469
 
 
470
        if local_extra or remote_extra:
 
471
            
 
472
            ## def log_revision_one_line_text(log_revision):
 
473
            ##    """ Generates one line description of log_revison ended with end of line."""
 
474
            ##    revision = log_revision.rev
 
475
            ##    txt =  "- %s (%s)\n" % (revision.get_summary(), revision.committer, )
 
476
            ##    txt = txt.replace("<"," ") # Seems < > chars are expected to be xml tags ...
 
477
            ##    txt = txt.replace(">"," ")
 
478
            ##    return txt
 
479
            
 
480
            dlg_txt = ""
 
481
            if local_extra:
 
482
                dlg_txt += _i18n('%d local extra revision(s). \n') % (len(local_extra),) 
 
483
                ## NOTE: We do not want such ugly info about missing revisions
 
484
                ##       Revision Browser should be used there
 
485
                ## max_revisions = 10
 
486
                ## for log_revision in iter_log_revisions(local_extra, local_branch.repository, verbose=1):
 
487
                ##    dlg_txt += log_revision_one_line_text(log_revision)
 
488
                ##    if max_revisions <= 0:
 
489
                ##        dlg_txt += _i18n("more ... \n")
 
490
                ##        break
 
491
                ## max_revisions -= 1
 
492
            ## dlg_txt += "\n"
 
493
            if remote_extra:
 
494
                dlg_txt += _i18n('%d local missing revision(s).\n') % (len(remote_extra),) 
 
495
                ## max_revisions = 10
 
496
                ## for log_revision in iter_log_revisions(remote_extra, parent_branch.repository, verbose=1):
 
497
                ##    dlg_txt += log_revision_one_line_text(log_revision)
 
498
                ##    if max_revisions <= 0:
 
499
                ##        dlg_txt += _i18n("more ... \n")
 
500
                ##        break
 
501
                ##    max_revisions -= 1
 
502
                
 
503
            info_dialog(_i18n('There are missing revisions'),
 
504
                        dlg_txt)
 
505
        else:
 
506
            info_dialog(_i18n('Local branch up to date'),
 
507
                        _i18n('There are no missing revisions.'))
 
508
 
 
509
    @show_bzr_error
 
510
    def on_menuitem_branch_pull_activate(self, widget):
 
511
        """ Branch/Pull menu handler. """
 
512
        branch_to = self.wt.branch
 
513
 
 
514
        location = branch_to.get_parent()
 
515
        if location is None:
 
516
            error_dialog(_i18n('Parent location is unknown'),
 
517
                                     _i18n('Pulling is not possible until there is a parent location.'))
 
518
            return
 
519
 
 
520
        branch_from = Branch.open(location)
 
521
 
 
522
        if branch_to.get_parent() is None:
 
523
            branch_to.set_parent(branch_from.base)
 
524
 
 
525
        ret = branch_to.pull(branch_from)
 
526
        
 
527
        info_dialog(_i18n('Pull successful'), _i18n('%d revision(s) pulled.') % ret)
 
528
        
 
529
    @show_bzr_error
 
530
    def on_menuitem_branch_update_activate(self, widget):
 
531
        """ Branch/checkout update menu handler. """
 
532
        
 
533
        ret = self.wt.update()
 
534
        conflicts = self.wt.conflicts()
 
535
        if conflicts:
 
536
            info_dialog(_i18n('Update successful but conflicts generated'), _i18n('Number of conflicts generated: %d.') % (len(conflicts),) )
 
537
        else:
 
538
            info_dialog(_i18n('Update successful'), _i18n('No conflicts generated.') )
 
539
        self.refresh_right()
 
540
    
 
541
    def on_menuitem_branch_push_activate(self, widget):
 
542
        """ Branch/Push... menu handler. """
 
543
        push = PushDialog(repository=None,revid=None,branch=self.wt.branch, parent=self.window)
 
544
        response = push.run()
 
545
        if response != gtk.RESPONSE_NONE:
 
546
            push.destroy()
 
547
    
 
548
    @show_bzr_error
 
549
    def on_menuitem_branch_revert_activate(self, widget):
 
550
        """ Branch/Revert all changes menu handler. """
 
551
        ret = self.wt.revert(None)
 
552
        if ret:
 
553
            warning_dialog(_i18n('Conflicts detected'),
 
554
                           _i18n('Please have a look at the working tree before continuing.'))
 
555
        else:
 
556
            info_dialog(_i18n('Revert successful'),
 
557
                        _i18n('All files reverted to last revision.'))
 
558
        self.refresh_right()
 
559
    
 
560
    def on_menuitem_branch_status_activate(self, widget):
 
561
        """ Branch/Status... menu handler. """
 
562
        from bzrlib.plugins.gtk.status import StatusDialog
 
563
        status = StatusDialog(self.wt, self.wtpath)
 
564
        response = status.run()
 
565
        if response != gtk.RESPONSE_NONE:
 
566
            status.destroy()
 
567
    
 
568
    def on_menuitem_branch_initialize_activate(self, widget):
 
569
        """ Initialize current directory. """
 
570
        init = InitDialog(self.path, self.window)
 
571
        response = init.run()
 
572
        if response != gtk.RESPONSE_NONE:
 
573
            init.hide()
 
574
        
 
575
            if response == gtk.RESPONSE_OK:
 
576
                self.refresh_right()
 
577
            
 
578
            init.destroy()
 
579
        
 
580
    def on_menuitem_branch_tags_activate(self, widget):
 
581
        """ Branch/Tags... menu handler. """
 
582
        from bzrlib.plugins.gtk.tags import TagsWindow
 
583
        if not self.remote:
 
584
            window = TagsWindow(self.wt.branch, self.window)
 
585
        else:
 
586
            window = TagsWindow(self.remote_branch, self.window)
 
587
        window.show()
 
588
    
 
589
    def on_menuitem_file_annotate_activate(self, widget):
 
590
        """ File/Annotate... menu handler. """
 
591
        if self.get_selected_right() is None:
 
592
            error_dialog(_i18n('No file was selected'),
 
593
                         _i18n('Please select a file from the list.'))
 
594
            return
 
595
        
 
596
        branch = self.wt.branch
 
597
        file_id = self.wt.path2id(self.wt.relpath(os.path.join(self.path, self.get_selected_right())))
 
598
        
 
599
        window = GAnnotateWindow(all=False, plain=False, parent=self.window)
 
600
        window.set_title(os.path.join(self.path, self.get_selected_right()) + " - Annotate")
 
601
        config = GAnnotateConfig(window)
 
602
        window.show()
 
603
        branch.lock_read()
 
604
        try:
 
605
            window.annotate(self.wt, branch, file_id)
 
606
        finally:
 
607
            branch.unlock()
 
608
    
 
609
    def on_menuitem_file_bookmark_activate(self, widget):
 
610
        """ File/Bookmark current directory menu handler. """
 
611
        if self.pref.add_bookmark(self.path):
 
612
            info_dialog(_i18n('Bookmark successfully added'),
 
613
                        _i18n('The current directory was bookmarked. You can reach\nit by selecting it from the left panel.'))
 
614
            self.pref.write()
 
615
        else:
 
616
            warning_dialog(_i18n('Location already bookmarked'),
 
617
                           _i18n('The current directory is already bookmarked.\nSee the left panel for reference.'))
 
618
        
 
619
        self.refresh_left()
 
620
    
 
621
    def on_menuitem_file_make_directory_activate(self, widget):
 
622
        """ File/Make directory... menu handler. """
 
623
        from bzrlib.plugins.gtk.olive.mkdir import MkdirDialog
 
624
        mkdir = MkdirDialog(self.wt, self.wtpath, self.window)
 
625
        response = mkdir.run()
 
626
        mkdir.destroy()
 
627
        if response == gtk.RESPONSE_OK:
 
628
            self.refresh_right()
 
629
    
 
630
    def on_menuitem_file_move_activate(self, widget):
 
631
        """ File/Move... menu handler. """
 
632
        from bzrlib.plugins.gtk.olive.move import MoveDialog
 
633
        move = MoveDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
 
634
        response = move.run()
 
635
        move.destroy()
 
636
        if response == gtk.RESPONSE_OK:
 
637
            self.refresh_right()
 
638
    
 
639
    def on_menuitem_file_rename_activate(self, widget):
 
640
        """ File/Rename... menu handler. """
 
641
        from bzrlib.plugins.gtk.olive.rename import RenameDialog
 
642
        rename = RenameDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
 
643
        response = rename.run()
 
644
        rename.destroy()
 
645
        if response == gtk.RESPONSE_OK:
 
646
            self.refresh_right()
 
647
 
 
648
    def on_menuitem_remove_file_activate(self, widget):
 
649
        """ Remove (unversion) selected file. """
 
650
        from bzrlib.plugins.gtk.olive.remove import RemoveDialog
 
651
        remove = RemoveDialog(self.wt, self.wtpath,
 
652
                                   selected=self.get_selected_right(),
 
653
                                   parent=self.window)
 
654
        response = remove.run()
 
655
        
 
656
        if response != gtk.RESPONSE_NONE:
 
657
            remove.hide()
 
658
        
 
659
            if response == gtk.RESPONSE_OK:
 
660
                self.refresh_right()
 
661
            
 
662
            remove.destroy()
 
663
    
 
664
    def on_menuitem_stats_diff_activate(self, widget):
 
665
        """ Statistics/Differences... menu handler. """
 
666
        window = DiffWindow(parent=self.window)
 
667
        parent_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
 
668
        window.set_diff(self.wt.branch._get_nick(local=True), self.wt, 
 
669
                        parent_tree)
 
670
        window.show()
 
671
    
 
672
    def on_menuitem_stats_infos_activate(self, widget):
 
673
        """ Statistics/Informations... menu handler. """
 
674
        from bzrlib.plugins.gtk.olive.info import InfoDialog
 
675
        if self.remote:
 
676
            info = InfoDialog(self.remote_branch)
 
677
        else:
 
678
            info = InfoDialog(self.wt.branch)
 
679
        info.display()
 
680
    
 
681
    def on_menuitem_stats_log_activate(self, widget):
 
682
        """ Statistics/Log... menu handler. """
 
683
 
 
684
        if not self.remote:
 
685
            branch = self.wt.branch
 
686
        else:
 
687
            branch = self.remote_branch
 
688
 
 
689
        window = branchwin.BranchWindow(branch, [branch.last_revision()], None, 
 
690
                                        parent=self.window)
 
691
        window.show()
 
692
    
 
693
    def on_menuitem_view_refresh_activate(self, widget):
 
694
        """ View/Refresh menu handler. """
 
695
        # Refresh the left pane
 
696
        self.refresh_left()
 
697
        # Refresh the right pane
 
698
        self.refresh_right()
 
699
   
 
700
    def on_menuitem_view_show_hidden_files_activate(self, widget):
 
701
        """ View/Show hidden files menu handler. """
 
702
        self.pref.set_preference('dotted_files', widget.get_active())
 
703
        self.pref.write()
 
704
        if self.path is not None:
 
705
            self.refresh_right()
 
706
 
 
707
    def on_menuitem_view_show_ignored_files_activate(self, widget):
 
708
        """ Hide/Show ignored files menu handler. """
 
709
        self.pref.set_preference('ignored_files', widget.get_active())
 
710
        self.pref.write()
 
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 get_selected_fileid(self):
 
879
        """ Get the file_id of the selected file. """
 
880
        treeselection = self.window.treeview_right.get_selection()
 
881
        (model, iter) = treeselection.get_selected()
 
882
        
 
883
        if iter is None:
 
884
            return None
 
885
        else:
 
886
            return model.get_value(iter, 9)
 
887
    
 
888
    def get_selected_right(self):
 
889
        """ Get the selected filename. """
 
890
        treeselection = self.window.treeview_right.get_selection()
 
891
        (model, iter) = treeselection.get_selected()
 
892
        
 
893
        if iter is None:
 
894
            return None
 
895
        else:
 
896
            return model.get_value(iter, 2)
 
897
    
 
898
    def get_selected_left(self):
 
899
        """ Get the selected bookmark. """
 
900
        treeselection = self.window.treeview_left.get_selection()
 
901
        (model, iter) = treeselection.get_selected()
 
902
        
 
903
        if iter is None:
 
904
            return None
 
905
        else:
 
906
            return model.get_value(iter, 1)
 
907
 
 
908
    def set_statusbar(self, message):
 
909
        """ Set the statusbar message. """
 
910
        self.window.statusbar.push(self.context_id, message)
 
911
    
 
912
    def clear_statusbar(self):
 
913
        """ Clean the last message from the statusbar. """
 
914
        self.window.statusbar.pop(self.context_id)
 
915
    
 
916
    def set_sensitivity(self):
 
917
        """ Set menu and toolbar sensitivity. """
 
918
        if not self.remote:
 
919
            self.window.set_view_to_localbranch(self.notbranch)
 
920
        else:
 
921
            self.window.set_view_to_remotebranch()
 
922
    
 
923
    def refresh_left(self):
 
924
        """ Refresh the bookmark list. """
 
925
        
 
926
        # Get ListStore and clear it
 
927
        liststore = self.window.bookmarklist
 
928
        liststore.clear()
 
929
 
 
930
        # Re-read preferences
 
931
        self.pref.read()
 
932
        
 
933
        # Get bookmarks
 
934
        bookmarks = self.pref.get_bookmarks()
 
935
 
 
936
        # Get titles and sort by title
 
937
        bookmarks = [[self.pref.get_bookmark_title(item), item, gtk.STOCK_DIRECTORY] for item in bookmarks]
 
938
        bookmarks.sort()
 
939
        for title_item in bookmarks:
 
940
            liststore.append(title_item)
 
941
        
 
942
        # Add the ListStore to the TreeView and refresh column width
 
943
        self.window.treeview_left.set_model(liststore)
 
944
        self.window.treeview_left.columns_autosize()
 
945
 
 
946
    def refresh_right(self):
 
947
        """ Refresh the file list. """
 
948
        if not self.remote:
 
949
            # We're local
 
950
            from bzrlib.workingtree import WorkingTree
 
951
            
 
952
            path = self.get_path()
 
953
            
 
954
            # Get ListStore and clear it
 
955
            liststore = self.window.filelist
 
956
            liststore.clear()
 
957
            
 
958
            dirs = []
 
959
            files = []
 
960
            
 
961
            # Fill the appropriate lists
 
962
            dotted_files = self.pref.get_preference('dotted_files', 'bool')
 
963
            ignored_files = self.pref.get_preference('ignored_files', 'bool')
 
964
            
 
965
            for item in os.listdir(path):
 
966
                if not dotted_files and item[0] == '.':
 
967
                    continue
 
968
                if os.path.isdir(os.path.join(path, item)):
 
969
                    dirs.append(item)
 
970
                else:
 
971
                    files.append(item)
 
972
            
 
973
            self.window.col_status.set_visible(False)
 
974
            if not self.notbranch:
 
975
                try:
 
976
                    tree1 = WorkingTree.open_containing(os.path.realpath(path))[0]
 
977
                    branch = tree1.branch
 
978
                    tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
 
979
                    
 
980
                    delta = tree1.changes_from(tree2, want_unchanged=True)
 
981
                    
 
982
                    # Show Status column
 
983
                    self.window.col_status.set_visible(True)
 
984
                except bzrerrors.LockContention:
 
985
                    self.window.set_location_status(gtk.STOCK_DIALOG_ERROR, allowPopup=True)
 
986
                    self.window.location_status.connect_object('clicked', error_dialog, 
 
987
                                       *(_i18n('Branch is locked'),
 
988
                                                _i18n('The branch in the current folder is locked by another Bazaar program')))
 
989
                    self.notbranch = True
 
990
                    self.window.set_view_to_localbranch(False) 
 
991
            
 
992
            # Add'em to the ListStore
 
993
            for item in dirs:
 
994
                status = ''
 
995
                st = ''
 
996
                fileid = ''
 
997
                if not self.notbranch:
 
998
                    filename = tree1.relpath(os.path.join(os.path.realpath(path), item))
 
999
                    
 
1000
                    st, status = self.statusmapper(filename, delta)
 
1001
                    if not ignored_files and status == 'ignored':
 
1002
                        continue
 
1003
                
 
1004
                statinfo = os.lstat(os.path.join(self.path, item))
 
1005
                liststore.append([ gtk.STOCK_DIRECTORY,
 
1006
                                   True,
 
1007
                                   item,
 
1008
                                   st,
 
1009
                                   status,
 
1010
                                   "<DIR>",
 
1011
                                   "<DIR>",
 
1012
                                   statinfo.st_mtime,
 
1013
                                   self._format_date(statinfo.st_mtime),
 
1014
                                   ''])
 
1015
            for item in files:
 
1016
                status = ''
 
1017
                st = ''
 
1018
                fileid = ''
 
1019
                if not self.notbranch:
 
1020
                    filename = tree1.relpath(os.path.join(os.path.realpath(path), item))
 
1021
                    
 
1022
                    st, status = self.statusmapper(filename, delta)
 
1023
                    if not ignored_files and status == 'ignored':
 
1024
                        continue
 
1025
                
 
1026
                statinfo = os.lstat(os.path.join(self.path, item))
 
1027
                liststore.append([gtk.STOCK_FILE,
 
1028
                                  False,
 
1029
                                  item,
 
1030
                                  st,
 
1031
                                  status,
 
1032
                                  str(statinfo.st_size),
 
1033
                                  self._format_size(statinfo.st_size),
 
1034
                                  statinfo.st_mtime,
 
1035
                                  self._format_date(statinfo.st_mtime),
 
1036
                                  fileid])
 
1037
        else:
 
1038
            # We're remote
 
1039
            
 
1040
            # Get ListStore and clear it
 
1041
            liststore = self.window.filelist
 
1042
            liststore.clear()
 
1043
            
 
1044
            # Hide Status column
 
1045
            self.window.col_status.set_visible(False)
 
1046
            
 
1047
            dirs = []
 
1048
            files = []
 
1049
            
 
1050
            self.window.set_location_status(gtk.STOCK_REFRESH)
 
1051
            
 
1052
            for (name, type) in self.remote_entries:
 
1053
                if type.kind == 'directory':
 
1054
                    dirs.append(type)
 
1055
                elif type.kind == 'file':
 
1056
                    files.append(type)
 
1057
            
 
1058
            class HistoryCache:
 
1059
                """ Cache based on revision history. """
 
1060
                def __init__(self, history):
 
1061
                    self._history = history
 
1062
                
 
1063
                def _lookup_revision(self, revid):
 
1064
                    for r in self._history:
 
1065
                        if r.revision_id == revid:
 
1066
                            return r
 
1067
                    rev = repo.get_revision(revid)
 
1068
                    self._history.append(rev)
 
1069
                    return rev
 
1070
            
 
1071
            repo = self.remote_branch.repository
 
1072
            
 
1073
            revhistory = self.remote_branch.revision_history()
 
1074
            try:
 
1075
                revs = repo.get_revisions(revhistory)
 
1076
                cache = HistoryCache(revs)
 
1077
            except bzrerrors.InvalidHttpResponse:
 
1078
                # Fallback to dummy algorithm, because of LP: #115209
 
1079
                cache = HistoryCache([])
 
1080
            
 
1081
            for item in dirs:
 
1082
                if item.parent_id == self.remote_parent:
 
1083
                    rev = cache._lookup_revision(item.revision)
 
1084
                    liststore.append([ gtk.STOCK_DIRECTORY,
 
1085
                                       True,
 
1086
                                       item.name,
 
1087
                                       '',
 
1088
                                       '',
 
1089
                                       "<DIR>",
 
1090
                                       "<DIR>",
 
1091
                                       rev.timestamp,
 
1092
                                       self._format_date(rev.timestamp),
 
1093
                                       ''
 
1094
                                   ])
 
1095
                while gtk.events_pending():
 
1096
                    gtk.main_iteration()
 
1097
            
 
1098
            for item in files:
 
1099
                if item.parent_id == self.remote_parent:
 
1100
                    rev = cache._lookup_revision(item.revision)
 
1101
                    liststore.append([ gtk.STOCK_FILE,
 
1102
                                       False,
 
1103
                                       item.name,
 
1104
                                       '',
 
1105
                                       '',
 
1106
                                       str(item.text_size),
 
1107
                                       self._format_size(item.text_size),
 
1108
                                       rev.timestamp,
 
1109
                                       self._format_date(rev.timestamp),
 
1110
                                       item.file_id
 
1111
                                   ])
 
1112
                while gtk.events_pending():
 
1113
                    gtk.main_iteration()
 
1114
            
 
1115
            self.window.location_status.destroy()
 
1116
 
 
1117
        # Columns should auto-size
 
1118
        self.window.treeview_right.columns_autosize()
 
1119
        
 
1120
        # Set sensitivity
 
1121
        self.set_sensitivity()
 
1122
    
 
1123
    def statusmapper(self, filename, delta):
 
1124
        status = 'unknown'
 
1125
        try:
 
1126
            self.wt.lock_read()
 
1127
            
 
1128
            for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
 
1129
                if rpathnew == filename:
 
1130
                    status = 'renamed'
 
1131
                    fileid = id
 
1132
            for rpath, id, kind in delta.added:
 
1133
                if rpath == filename:
 
1134
                    status = 'added'
 
1135
                    fileid = id
 
1136
            for rpath, id, kind in delta.removed:
 
1137
                if rpath == filename:
 
1138
                    status = 'removed'
 
1139
                    fileid = id
 
1140
            for rpath, id, kind, text_modified, meta_modified in delta.modified:
 
1141
                if rpath == filename:
 
1142
                    status = 'modified'
 
1143
                    fileid = id
 
1144
            for rpath, id, kind in delta.unchanged:
 
1145
                if rpath == filename:
 
1146
                    status = 'unchanged'
 
1147
                    fileid = id
 
1148
            for rpath, file_class, kind, id, entry in self.wt.list_files():
 
1149
                if rpath == filename and file_class == 'I':
 
1150
                    status = 'ignored'
 
1151
        finally:
 
1152
            self.wt.unlock()
 
1153
    
 
1154
        if status == 'renamed':
 
1155
            st = _i18n('renamed')
 
1156
        elif status == 'removed':
 
1157
            st = _i18n('removed')
 
1158
        elif status == 'added':
 
1159
            st = _i18n('added')
 
1160
        elif status == 'modified':
 
1161
            st = _i18n('modified')
 
1162
        elif status == 'unchanged':
 
1163
            st = _i18n('unchanged')
 
1164
        elif status == 'ignored':
 
1165
            st = _i18n('ignored')
 
1166
        else:
 
1167
            st = _i18n('unknown')
 
1168
        return st, status
 
1169
 
 
1170
    def _harddisks(self):
 
1171
        """ Returns hard drive letters under Win32. """
 
1172
        try:
 
1173
            import win32file
 
1174
            import string
 
1175
        except ImportError:
 
1176
            if sys.platform == 'win32':
 
1177
                print "pyWin32 modules needed to run Olive on Win32."
 
1178
                sys.exit(1)
 
1179
        
 
1180
        driveletters = []
 
1181
        for drive in string.ascii_uppercase:
 
1182
            if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED or\
 
1183
                win32file.GetDriveType(drive+':') == win32file.DRIVE_REMOTE or\
 
1184
                win32file.GetDriveType(drive+':') == win32file.DRIVE_REMOVABLE:
 
1185
                driveletters.append(drive+':')
 
1186
        return driveletters
 
1187
    
 
1188
    def gen_hard_selector(self):
 
1189
        """ Generate the hard drive selector under Win32. """
 
1190
        drives = self._harddisks()
 
1191
        for drive in drives:
 
1192
            self.combobox_drive.append_text(drive)
 
1193
        self.combobox_drive.set_active(drives.index(os.getcwd()[0:2]))
 
1194
    
 
1195
    def _refresh_drives(self, combobox):
 
1196
        if self._just_started:
 
1197
            return
 
1198
        model = combobox.get_model()
 
1199
        active = combobox.get_active()
 
1200
        if active >= 0:
 
1201
            drive = model[active][0]
 
1202
            self.set_path(drive + '\\')
 
1203
            self.refresh_right()
 
1204
    
 
1205
    def check_for_changes(self):
 
1206
        """ Check whether there were changes in the current working tree. """
 
1207
        old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
 
1208
        delta = self.wt.changes_from(old_tree)
 
1209
 
 
1210
        changes = False
 
1211
        
 
1212
        if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
 
1213
            changes = True
 
1214
        
 
1215
        return changes
 
1216
    
 
1217
    def _sort_filelist_callback(self, model, iter1, iter2, data):
 
1218
        """ The sort callback for the file list, return values:
 
1219
        -1: iter1 < iter2
 
1220
        0: iter1 = iter2
 
1221
        1: iter1 > iter2
 
1222
        """
 
1223
        name1 = model.get_value(iter1, 2)
 
1224
        name2 = model.get_value(iter2, 2)
 
1225
        
 
1226
        if model.get_value(iter1, 1):
 
1227
            # item1 is a directory
 
1228
            if not model.get_value(iter2, 1):
 
1229
                # item2 isn't
 
1230
                return -1
 
1231
            else:
 
1232
                # both of them are directories, we compare their names
 
1233
                if name1 < name2:
 
1234
                    return -1
 
1235
                elif name1 == name2:
 
1236
                    return 0
 
1237
                else:
 
1238
                    return 1
 
1239
        else:
 
1240
            # item1 is not a directory
 
1241
            if model.get_value(iter2, 1):
 
1242
                # item2 is
 
1243
                return 1
 
1244
            else:
 
1245
                # both of them are files, compare them
 
1246
                if name1 < name2:
 
1247
                    return -1
 
1248
                elif name1 == name2:
 
1249
                    return 0
 
1250
                else:
 
1251
                    return 1
 
1252
    
 
1253
    def _format_size(self, size):
 
1254
        """ Format size to a human readable format. """
 
1255
        if size < 1000:
 
1256
            return "%d[B]" % (size,)
 
1257
        size = size / 1000.0
 
1258
        
 
1259
        for metric in ["kB","MB","GB","TB"]:
 
1260
            if size < 1000:
 
1261
                break
 
1262
            size = size / 1000.0
 
1263
        return "%.1f[%s]" % (size,metric) 
 
1264
    
 
1265
    def _format_date(self, timestamp):
 
1266
        """ Format the time (given in secs) to a human readable format. """
 
1267
        return time.ctime(timestamp)
 
1268
    
 
1269
    def _is_remote_dir(self, location):
 
1270
        """ Determine whether the given location is a directory or not. """
 
1271
        if not self.remote:
 
1272
            # We're in local mode
 
1273
            return False
 
1274
        else:
 
1275
            branch, path = Branch.open_containing(location)
 
1276
            for (name, type) in self.remote_entries:
 
1277
                if name == path and type.kind == 'directory':
 
1278
                    # We got it
 
1279
                    return True
 
1280
            # Either it's not a directory or not in the inventory
 
1281
            return False
 
1282
 
 
1283
import ConfigParser
 
1284
 
 
1285
class Preferences:
 
1286
    """ A class which handles Olive's preferences. """
 
1287
    def __init__(self, path=None):
 
1288
        """ Initialize the Preferences class. """
 
1289
        # Some default options
 
1290
        self.defaults = { 'strict_commit' : False,
 
1291
                          'dotted_files'  : False,
 
1292
                          'ignored_files' : True,
 
1293
                          'window_width'  : 700,
 
1294
                          'window_height' : 400,
 
1295
                          'window_x'      : 40,
 
1296
                          'window_y'      : 40,
 
1297
                          'paned_position': 200 }
 
1298
 
 
1299
        # Create a config parser object
 
1300
        self.config = ConfigParser.RawConfigParser()
 
1301
 
 
1302
        # Set filename
 
1303
        if path is None:
 
1304
            if sys.platform == 'win32':
 
1305
                # Windows - no dotted files
 
1306
                self._filename = os.path.expanduser('~/olive.conf')
 
1307
            else:
 
1308
                self._filename = os.path.expanduser('~/.olive.conf')
 
1309
        else:
 
1310
            self._filename = path
 
1311
        
 
1312
        # Load the configuration
 
1313
        self.read()
 
1314
        
 
1315
    def _get_default(self, option):
 
1316
        """ Get the default option for a preference. """
 
1317
        try:
 
1318
            ret = self.defaults[option]
 
1319
        except KeyError:
 
1320
            return None
 
1321
        else:
 
1322
            return ret
 
1323
 
 
1324
    def refresh(self):
 
1325
        """ Refresh the configuration. """
 
1326
        # First write out the changes
 
1327
        self.write()
 
1328
        # Then load the configuration again
 
1329
        self.read()
 
1330
 
 
1331
    def read(self):
 
1332
        """ Just read the configuration. """
 
1333
        # Re-initialize the config parser object to avoid some bugs
 
1334
        self.config = ConfigParser.RawConfigParser()
 
1335
        self.config.read([self._filename])
 
1336
    
 
1337
    def write(self):
 
1338
        """ Write the configuration to the appropriate files. """
 
1339
        fp = open(self._filename, 'w')
 
1340
        self.config.write(fp)
 
1341
        fp.close()
 
1342
 
 
1343
    def get_bookmarks(self):
 
1344
        """ Return the list of bookmarks. """
 
1345
        bookmarks = self.config.sections()
 
1346
        if self.config.has_section('preferences'):
 
1347
            bookmarks.remove('preferences')
 
1348
        return bookmarks
 
1349
 
 
1350
    def add_bookmark(self, path):
 
1351
        """ Add bookmark. """
 
1352
        try:
 
1353
            self.config.add_section(path)
 
1354
        except ConfigParser.DuplicateSectionError:
 
1355
            return False
 
1356
        else:
 
1357
            return True
 
1358
 
 
1359
    def get_bookmark_title(self, path):
 
1360
        """ Get bookmark title. """
 
1361
        try:
 
1362
            ret = self.config.get(path, 'title')
 
1363
        except ConfigParser.NoOptionError:
 
1364
            ret = path
 
1365
        
 
1366
        return ret
 
1367
    
 
1368
    def set_bookmark_title(self, path, title):
 
1369
        """ Set bookmark title. """
 
1370
        # FIXME: What if path isn't listed yet?
 
1371
        # FIXME: Canonicalize paths first?
 
1372
        self.config.set(path, 'title', title)
 
1373
    
 
1374
    def remove_bookmark(self, path):
 
1375
        """ Remove bookmark. """
 
1376
        return self.config.remove_section(path)
 
1377
 
 
1378
    def set_preference(self, option, value):
 
1379
        """ Set the value of the given option. """
 
1380
        if value is True:
 
1381
            value = 'yes'
 
1382
        elif value is False:
 
1383
            value = 'no'
 
1384
        
 
1385
        if self.config.has_section('preferences'):
 
1386
            self.config.set('preferences', option, value)
 
1387
        else:
 
1388
            self.config.add_section('preferences')
 
1389
            self.config.set('preferences', option, value)
 
1390
 
 
1391
    def get_preference(self, option, kind='str'):
 
1392
        """ Get the value of the given option.
 
1393
        
 
1394
        :param kind: str/bool/int/float. default: str
 
1395
        """
 
1396
        if self.config.has_option('preferences', option):
 
1397
            if kind == 'bool':
 
1398
                return self.config.getboolean('preferences', option)
 
1399
            elif kind == 'int':
 
1400
                return self.config.getint('preferences', option)
 
1401
            elif kind == 'float':
 
1402
                return self.config.getfloat('preferences', option)
 
1403
            else:
 
1404
                return self.config.get('preferences', option)
 
1405
        else:
 
1406
            try:
 
1407
                return self._get_default(option)
 
1408
            except KeyError:
 
1409
                return None