/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to olive/__init__.py

  • Committer: Jelmer Vernooij
  • Date: 2008-08-04 18:51:44 UTC
  • Revision ID: jelmer@samba.org-20080804185144-c1v0efzz63zhf790
Remove check for glade.

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