1
# Copyright (C) 2006 by Szilveszter Farkas (Phanatic) <szilveszter.farkas@gmail.com>
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.
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.
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
24
gettext.install('olive-gtk')
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
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
47
from bzrlib.plugins.gtk.diff import DiffWindow
48
lazy_import(globals(), """
49
from bzrlib.plugins.gtk.viz import branchwin
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
60
""" Display the AboutDialog. """
61
from bzrlib.plugins.gtk import __version__, icon_path
63
iconpath = icon_path() + os.sep
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>" ])
87
""" The main Olive GTK frontend class. This is called when launching the
91
self.window = OliveGui(calling_app = self)
93
self.pref = Preferences()
96
# Initialize the statusbar
97
self.context_id = self.window.statusbar.get_context_id('olive')
100
self.treeview_left = self.window.treeview_left
101
self.treeview_right = self.window.treeview_right
103
# Get the drive selector
104
self.combobox_drive = gtk.combo_box_new_text()
105
self.combobox_drive.connect("changed", self._refresh_drives)
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
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
119
self._just_started = True
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)
132
# Now we can show the window
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()
142
# Acceptable errors when loading files/folders in the treeviews
143
self.acceptable_errors = (errno.ENOENT, errno.ELOOP)
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'))
151
# We're starting local
153
self.remote_branch = None
154
self.remote_path = None
155
self.remote_revision = None
157
self.set_path(os.getcwd())
160
self._just_started = False
162
def set_path(self, path, force_remote=False):
163
self.notbranch = False
166
# Forcing remote mode (reading data from inventory)
167
self._show_stock_image(gtk.STOCK_DISCONNECT)
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)
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)
181
self._show_stock_image(gtk.STOCK_CONNECT)
186
self.remote_branch, self.remote_path = Branch.open_containing(path)
188
if self.remote_revision is None:
189
self.remote_revision = self.remote_branch.last_revision()
191
self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
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
196
for (name, type) in self.remote_entries:
197
if name == self.remote_path:
198
self.remote_parent = type.file_id
201
if not path.endswith('/'):
204
if self.remote_branch.base == path:
205
self.button_location_up.set_sensitive(False)
207
self.button_location_up.set_sensitive(True)
209
if os.path.isdir(path):
210
self.image_location_error.destroy()
215
self.wt, self.wtpath = WorkingTree.open_containing(path)
216
except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
217
self.notbranch = True
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)
225
self.button_location_up.set_sensitive(True)
228
self.button_location_up.set_sensitive(False)
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
234
self._show_stock_image(gtk.STOCK_DISCONNECT)
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)
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)
248
self._show_stock_image(gtk.STOCK_CONNECT)
253
self.remote_branch, self.remote_path = Branch.open_containing(path)
255
if self.remote_revision is None:
256
self.remote_revision = self.remote_branch.last_revision()
258
self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
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
263
for (name, type) in self.remote_entries:
264
if name == self.remote_path:
265
self.remote_parent = type.file_id
268
if not path.endswith('/'):
271
if self.remote_branch.base == path:
272
self.button_location_up.set_sensitive(False)
274
self.button_location_up.set_sensitive(True)
277
self.check_history.set_active(False)
278
self.check_history.set_sensitive(False)
280
self.check_history.set_sensitive(True)
282
self.window.statusbar.push(self.context_id, path)
283
self.entry_location.set_text(path)
292
if len(self.remote_path) > 0:
293
return self.remote_branch.base + self.remote_path + '/'
295
return self.remote_branch.base
297
def on_about_activate(self, widget):
300
def on_button_history_browse_clicked(self, widget):
301
""" Browse for revision button handler. """
303
br = self.remote_branch
307
revb = RevisionBrowser(br, self.window)
308
response = revb.run()
309
if response != gtk.RESPONSE_NONE:
312
if response == gtk.RESPONSE_OK:
313
if revb.selected_revno is not None:
314
self.entry_history.set_text(revb.selected_revno)
318
def on_button_location_jump_clicked(self, widget):
319
""" Location Jump button handler. """
320
location = self.entry_location.get_text()
322
if self.set_path(location):
325
def on_button_location_up_clicked(self, widget):
326
""" Location Up button handler. """
329
self.set_path(os.path.split(self.get_path())[0])
333
newpath = delim.join(self.get_path().split(delim)[:-2])
335
self.set_path(newpath)
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)
346
# History Mode deactivated
347
self.entry_history.set_sensitive(False)
348
self.button_history.set_sensitive(False)
350
# Return right window to normal view by acting like we jump to it
351
self.on_button_location_jump_clicked(widget)
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()
362
self.remote_branch = self.wt.branch
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):
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)
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)
381
if response == gtk.RESPONSE_OK:
384
def on_menuitem_branch_get_activate(self, widget):
385
""" Branch/Get... menu handler. """
386
from bzrlib.plugins.gtk.branch import BranchDialog
389
branch = BranchDialog(os.getcwd(), self.window, self.remote_branch.base)
391
branch = BranchDialog(self.get_path(), self.window)
392
response = branch.run()
393
if response != gtk.RESPONSE_NONE:
396
if response == gtk.RESPONSE_OK:
401
def on_menuitem_branch_checkout_activate(self, widget):
402
""" Branch/Checkout... menu handler. """
403
from bzrlib.plugins.gtk.checkout import CheckoutDialog
406
checkout = CheckoutDialog(os.getcwd(), self.window, self.remote_branch.base)
408
checkout = CheckoutDialog(self.get_path(), self.window)
409
response = checkout.run()
410
if response != gtk.RESPONSE_NONE:
413
if response == gtk.RESPONSE_OK:
419
def on_menuitem_branch_commit_activate(self, widget):
420
""" Branch/Commit... menu handler. """
421
selected = self.get_selected_right()
423
selected = os.path.join(self.wtpath, selected)
424
commit = CommitDialog(wt=self.wt,
428
response = commit.run()
429
if response != gtk.RESPONSE_NONE:
432
if response == gtk.RESPONSE_OK:
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:
444
def on_menuitem_branch_merge_activate(self, widget):
445
""" Branch/Merge... menu handler. """
446
from bzrlib.plugins.gtk.merge import MergeDialog
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.'))
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()
456
if response == gtk.RESPONSE_OK:
460
def on_menuitem_branch_missing_revisions_activate(self, widget):
461
""" Branch/Missing revisions menu handler. """
463
from bzrlib.missing import find_unmerged, iter_log_revisions
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.'))
472
parent_branch = Branch.open(parent_branch_path)
474
if parent_branch.base == local_branch.base:
475
parent_branch = local_branch
477
local_extra, remote_extra = find_unmerged(local_branch,parent_branch)
479
if local_extra or remote_extra:
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(">"," ")
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")
500
## max_revisions -= 1
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")
510
## max_revisions -= 1
512
info_dialog(_i18n('There are missing revisions'),
515
info_dialog(_i18n('Local branch up to date'),
516
_i18n('There are no missing revisions.'))
519
def on_menuitem_branch_pull_activate(self, widget):
520
""" Branch/Pull menu handler. """
521
branch_to = self.wt.branch
523
location = branch_to.get_parent()
525
error_dialog(_i18n('Parent location is unknown'),
526
_i18n('Pulling is not possible until there is a parent location.'))
529
branch_from = Branch.open(location)
531
if branch_to.get_parent() is None:
532
branch_to.set_parent(branch_from.base)
534
ret = branch_to.pull(branch_from)
536
info_dialog(_i18n('Pull successful'), _i18n('%d revision(s) pulled.') % ret)
539
def on_menuitem_branch_update_activate(self, widget):
540
""" Brranch/checkout update menu handler. """
542
ret = self.wt.update()
543
conflicts = self.wt.conflicts()
545
info_dialog(_i18n('Update successful but conflicts generated'), _i18n('Number of conflicts generated: %d.') % (len(conflicts),) )
547
info_dialog(_i18n('Update successful'), _i18n('No conflicts generated.') )
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:
557
def on_menuitem_branch_revert_activate(self, widget):
558
""" Branch/Revert all changes menu handler. """
559
ret = self.wt.revert(None)
561
warning_dialog(_i18n('Conflicts detected'),
562
_i18n('Please have a look at the working tree before continuing.'))
564
info_dialog(_i18n('Revert successful'),
565
_i18n('All files reverted to last revision.'))
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:
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:
583
if response == gtk.RESPONSE_OK:
588
def on_menuitem_branch_tags_activate(self, widget):
589
""" Branch/Tags... menu handler. """
590
from bzrlib.plugins.gtk.tags import TagsWindow
592
window = TagsWindow(self.wt.branch, self.window)
594
window = TagsWindow(self.remote_branch, self.window)
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.'))
604
branch = self.wt.branch
605
file_id = self.wt.path2id(self.wt.relpath(os.path.join(self.path, self.get_selected_right())))
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)
613
window.annotate(self.wt, branch, file_id)
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.'))
624
warning_dialog(_i18n('Location already bookmarked'),
625
_i18n('The current directory is already bookmarked.\nSee the left panel for reference.'))
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()
635
if response == gtk.RESPONSE_OK:
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()
644
if response == gtk.RESPONSE_OK:
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()
653
if response == gtk.RESPONSE_OK:
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(),
662
response = remove.run()
664
if response != gtk.RESPONSE_NONE:
667
if response == gtk.RESPONSE_OK:
668
self.set_path(self.path)
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)
680
def on_menuitem_stats_infos_activate(self, widget):
681
""" Statistics/Informations... menu handler. """
682
from bzrlib.plugins.gtk.olive.info import InfoDialog
684
info = InfoDialog(self.remote_branch)
686
info = InfoDialog(self.wt.branch)
689
def on_menuitem_stats_log_activate(self, widget):
690
""" Statistics/Log... menu handler. """
693
branch = self.wt.branch
695
branch = self.remote_branch
697
window = branchwin.BranchWindow(branch, [branch.last_revision()], None,
701
def on_menuitem_view_refresh_activate(self, widget):
702
""" View/Refresh menu handler. """
703
# Refresh the left pane
705
# Refresh the right pane
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:
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:
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:
728
from menu import OliveMenu
729
menu = OliveMenu(path=self.get_path(),
730
selected=self.get_selected_left(),
733
menu.left_context_menu().popup(None, None, None, 0,
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:
743
newdir = self.get_selected_left()
747
if self.set_path(newdir):
750
def on_treeview_left_row_activated(self, treeview, path, view_column):
751
""" Occurs when somebody double-clicks or enters an item in the
754
newdir = self.get_selected_left()
758
if self.set_path(newdir):
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:
765
from menu import OliveMenu
766
menu = OliveMenu(path=self.get_path(),
767
selected=self.get_selected_right(),
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
780
from bzrlib.branch import Branch
781
Branch.open_containing(self.get_path())
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)
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)
811
menu.right_context_menu().popup(None, None, None, 0,
814
menu.remote_context_menu().popup(None, None, None, 0,
817
def on_treeview_right_row_activated(self, treeview, path, view_column):
818
""" Occurs when somebody double-clicks or enters an item in the
820
from launch import launch
822
newdir = self.get_selected_right()
827
self.set_path(os.path.split(self.get_path())[0])
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)
837
if self._is_remote_dir(self.get_path() + newdir):
838
self.set_path(self.get_path() + newdir)
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())
854
self.window.destroy()
856
def _load_left(self):
857
""" Load data into the left panel. (Bookmarks) """
859
treestore = gtk.TreeStore(str, str)
862
bookmarks = self.pref.get_bookmarks()
864
# Add them to the TreeStore
865
titer = treestore.append(None, [_i18n('Bookmarks'), None])
867
# Get titles and sort by title
868
bookmarks = [[self.pref.get_bookmark_title(item), item] for item in bookmarks]
870
for title_item in bookmarks:
871
treestore.append(titer, title_item)
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)
879
cell = gtk.CellRendererText()
880
tvcolumn_bookmark.pack_start(cell, True)
881
tvcolumn_bookmark.add_attribute(cell, 'text', 0)
884
self.treeview_left.expand_all()
886
def _load_right(self):
887
""" Load data into the right panel. (Filelist) """
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,
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] == '.':
909
if os.path.isdir(self.path + os.sep + item):
914
if not self.notbranch:
915
branch = self.wt.branch
916
tree2 = self.wt.branch.repository.revision_tree(branch.last_revision())
918
delta = self.wt.changes_from(tree2, want_unchanged=True)
920
# Add'em to the ListStore
923
statinfo = os.stat(self.path + os.sep + item)
925
if e.errno in self.acceptable_errors:
929
liststore.append([ gtk.STOCK_DIRECTORY,
937
self._format_date(statinfo.st_mtime),
942
if not self.notbranch:
943
filename = self.wt.relpath(self.path + os.sep + item)
948
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
949
if rpathnew == filename:
952
for rpath, id, kind in delta.added:
953
if rpath == filename:
956
for rpath, id, kind in delta.removed:
957
if rpath == filename:
960
for rpath, id, kind, text_modified, meta_modified in delta.modified:
961
if rpath == filename:
964
for rpath, id, kind in delta.unchanged:
965
if rpath == filename:
968
for rpath, file_class, kind, id, entry in self.wt.list_files():
969
if rpath == filename and file_class == 'I':
974
if status == 'renamed':
975
st = _i18n('renamed')
976
elif status == 'removed':
977
st = _i18n('removed')
978
elif status == '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')
987
st = _i18n('unknown')
990
statinfo = os.stat(self.path + os.sep + item)
992
if e.errno in self.acceptable_errors:
996
liststore.append([gtk.STOCK_FILE,
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),
1004
self._format_date(statinfo.st_mtime),
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)
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)
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)
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)
1049
self.set_sensitivity()
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()
1059
return model.get_value(iter, 9)
1061
def get_selected_right(self):
1062
""" Get the selected filename. """
1063
treeselection = self.treeview_right.get_selection()
1064
(model, iter) = treeselection.get_selected()
1069
return model.get_value(iter, 2)
1071
def get_selected_left(self):
1072
""" Get the selected bookmark. """
1073
treeselection = self.treeview_left.get_selection()
1074
(model, iter) = treeselection.get_selected()
1079
return model.get_value(iter, 1)
1081
def set_statusbar(self, message):
1082
""" Set the statusbar message. """
1083
self.window.statusbar.push(self.context_id, message)
1085
def clear_statusbar(self):
1086
""" Clean the last message from the statusbar. """
1087
self.window.statusbar.pop(self.context_id)
1089
def set_sensitivity(self):
1090
""" Set menu and toolbar sensitivity. """
1092
self.window.set_view_to_localbranch(self.notbranch)
1094
self.window.set_view_to_remotebranch()
1096
def refresh_left(self):
1097
""" Refresh the bookmark list. """
1099
# Get TreeStore and clear it
1100
treestore = self.treeview_left.get_model()
1103
# Re-read preferences
1107
bookmarks = self.pref.get_bookmarks()
1109
# Add them to the TreeStore
1110
titer = treestore.append(None, [_i18n('Bookmarks'), None])
1112
# Get titles and sort by title
1113
bookmarks = [[self.pref.get_bookmark_title(item), item] for item in bookmarks]
1115
for title_item in bookmarks:
1116
treestore.append(titer, title_item)
1118
# Add the TreeStore to the TreeView
1119
self.treeview_left.set_model(treestore)
1122
self.treeview_left.expand_all()
1124
def refresh_right(self, path=None):
1125
""" Refresh the file list. """
1128
from bzrlib.workingtree import WorkingTree
1131
path = self.get_path()
1133
# A workaround for double-clicking Bookmarks
1134
if not os.path.exists(path):
1137
# Get ListStore and clear it
1138
liststore = self.treeview_right.get_model()
1141
# Show Status column
1142
self._tvcolumn_status.set_visible(True)
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')
1151
for item in os.listdir(path):
1152
if not dotted_files and item[0] == '.':
1154
if os.path.isdir(path + os.sep + item):
1159
# Try to open the working tree
1162
tree1 = WorkingTree.open_containing(path)[0]
1163
except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
1167
branch = tree1.branch
1168
tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
1170
delta = tree1.changes_from(tree2, want_unchanged=True)
1172
# Add'em to the ListStore
1175
statinfo = os.stat(self.path + os.sep + item)
1177
if e.errno in self.acceptable_errors:
1181
liststore.append([gtk.STOCK_DIRECTORY,
1189
self._format_date(statinfo.st_mtime),
1195
filename = tree1.relpath(path + os.sep + item)
1200
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
1201
if rpathnew == filename:
1204
for rpath, id, kind in delta.added:
1205
if rpath == filename:
1208
for rpath, id, kind in delta.removed:
1209
if rpath == filename:
1212
for rpath, id, kind, text_modified, meta_modified in delta.modified:
1213
if rpath == filename:
1216
for rpath, id, kind in delta.unchanged:
1217
if rpath == filename:
1218
status = 'unchanged'
1220
for rpath, file_class, kind, id, entry in self.wt.list_files():
1221
if rpath == filename and file_class == 'I':
1226
if status == 'renamed':
1227
st = _i18n('renamed')
1228
elif status == 'removed':
1229
st = _i18n('removed')
1230
elif status == '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:
1241
st = _i18n('unknown')
1244
statinfo = os.stat(self.path + os.sep + item)
1246
if e.errno in self.acceptable_errors:
1250
liststore.append([gtk.STOCK_FILE,
1255
str(statinfo.st_size),
1256
self._format_size(statinfo.st_size),
1258
self._format_date(statinfo.st_mtime),
1263
# Get ListStore and clear it
1264
liststore = self.treeview_right.get_model()
1267
# Hide Status column
1268
self._tvcolumn_status.set_visible(False)
1273
self._show_stock_image(gtk.STOCK_REFRESH)
1275
for (name, type) in self.remote_entries:
1276
if type.kind == 'directory':
1278
elif type.kind == 'file':
1282
""" Cache based on revision history. """
1283
def __init__(self, history):
1284
self._history = history
1286
def _lookup_revision(self, revid):
1287
for r in self._history:
1288
if r.revision_id == revid:
1290
rev = repo.get_revision(revid)
1291
self._history.append(rev)
1294
repo = self.remote_branch.repository
1296
revhistory = self.remote_branch.revision_history()
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([])
1305
if item.parent_id == self.remote_parent:
1306
rev = cache._lookup_revision(item.revision)
1307
liststore.append([ gtk.STOCK_DIRECTORY,
1315
self._format_date(rev.timestamp),
1318
while gtk.events_pending():
1319
gtk.main_iteration()
1322
if item.parent_id == self.remote_parent:
1323
rev = cache._lookup_revision(item.revision)
1324
liststore.append([ gtk.STOCK_FILE,
1329
str(item.text_size),
1330
self._format_size(item.text_size),
1332
self._format_date(rev.timestamp),
1335
while gtk.events_pending():
1336
gtk.main_iteration()
1338
self.image_location_error.destroy()
1340
# Columns should auto-size
1341
self.treeview_right.columns_autosize()
1344
self.set_sensitivity()
1346
def _harddisks(self):
1347
""" Returns hard drive letters under Win32. """
1352
if sys.platform == 'win32':
1353
print "pyWin32 modules needed to run Olive on Win32."
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+':')
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]))
1370
def _refresh_drives(self, combobox):
1371
if self._just_started:
1373
model = combobox.get_model()
1374
active = combobox.get_active()
1376
drive = model[active][0]
1377
self.set_path(drive + '\\')
1378
self.refresh_right(drive + '\\')
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)
1387
if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
1392
def _sort_filelist_callback(self, model, iter1, iter2, data):
1393
""" The sort callback for the file list, return values:
1398
name1 = model.get_value(iter1, 2)
1399
name2 = model.get_value(iter2, 2)
1401
if model.get_value(iter1, 1):
1402
# item1 is a directory
1403
if not model.get_value(iter2, 1):
1407
# both of them are directories, we compare their names
1410
elif name1 == name2:
1415
# item1 is not a directory
1416
if model.get_value(iter2, 1):
1420
# both of them are files, compare them
1423
elif name1 == name2:
1428
def _format_size(self, size):
1429
""" Format size to a human readable format. """
1431
return "%d[B]" % (size,)
1432
size = size / 1000.0
1434
for metric in ["kB","MB","GB","TB"]:
1437
size = size / 1000.0
1438
return "%.1f[%s]" % (size,metric)
1440
def _format_date(self, timestamp):
1441
""" Format the time (given in secs) to a human readable format. """
1442
return time.ctime(timestamp)
1444
def _is_remote_dir(self, location):
1445
""" Determine whether the given location is a directory or not. """
1447
# We're in local mode
1450
branch, path = Branch.open_containing(location)
1451
for (name, type) in self.remote_entries:
1452
if name == path and type.kind == 'directory':
1455
# Either it's not a directory or not in the inventory
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)
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()
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,
1485
'paned_position': 200 }
1487
# Create a config parser object
1488
self.config = ConfigParser.RawConfigParser()
1492
if sys.platform == 'win32':
1493
# Windows - no dotted files
1494
self._filename = os.path.expanduser('~/olive.conf')
1496
self._filename = os.path.expanduser('~/.olive.conf')
1498
self._filename = path
1500
# Load the configuration
1503
def _get_default(self, option):
1504
""" Get the default option for a preference. """
1506
ret = self.defaults[option]
1513
""" Refresh the configuration. """
1514
# First write out the changes
1516
# Then load the configuration again
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])
1526
""" Write the configuration to the appropriate files. """
1527
fp = open(self._filename, 'w')
1528
self.config.write(fp)
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')
1538
def add_bookmark(self, path):
1539
""" Add bookmark. """
1541
self.config.add_section(path)
1542
except ConfigParser.DuplicateSectionError:
1547
def get_bookmark_title(self, path):
1548
""" Get bookmark title. """
1550
ret = self.config.get(path, 'title')
1551
except ConfigParser.NoOptionError:
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)
1562
def remove_bookmark(self, path):
1563
""" Remove bookmark. """
1564
return self.config.remove_section(path)
1566
def set_preference(self, option, value):
1567
""" Set the value of the given option. """
1570
elif value is False:
1573
if self.config.has_section('preferences'):
1574
self.config.set('preferences', option, value)
1576
self.config.add_section('preferences')
1577
self.config.set('preferences', option, value)
1579
def get_preference(self, option, kind='str'):
1580
""" Get the value of the given option.
1582
:param kind: str/bool/int/float. default: str
1584
if self.config.has_option('preferences', option):
1586
return self.config.getboolean('preferences', option)
1588
return self.config.getint('preferences', option)
1589
elif kind == 'float':
1590
return self.config.getfloat('preferences', option)
1592
return self.config.get('preferences', option)
1595
return self._get_default(option)