/b-gtk/fix-viz

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

« back to all changes in this revision

Viewing changes to olive/__init__.py

  • Committer: Jasper Groenewegen
  • Date: 2008-07-21 13:50:35 UTC
  • mto: This revision was merged to the branch mainline in revision 561.
  • Revision ID: colbrac@xs4all.nl-20080721135035-x9etvb8envqg2kvj
Fix plugin description in gpreferences

Show diffs side-by-side

added added

removed removed

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