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)
315
self.on_entry_history_revno_activate()
319
def on_button_location_jump_clicked(self, widget):
320
""" Location Jump button handler. """
321
location = self.entry_location.get_text()
323
if self.set_path(location):
326
def on_button_location_up_clicked(self, widget):
327
""" Location Up button handler. """
330
self.set_path(os.path.split(self.get_path())[0])
334
newpath = delim.join(self.get_path().split(delim)[:-2])
336
self.set_path(newpath)
340
def on_checkbutton_history_toggled(self, widget):
341
""" History Mode toggle handler. """
342
if self.check_history.get_active():
343
# History Mode activated
344
self.entry_history.set_sensitive(True)
345
self.button_history.set_sensitive(True)
346
if self.entry_history.get_text() != "":
347
self.on_entry_history_revno_activate()
349
# History Mode deactivated
350
self.entry_history.set_sensitive(False)
351
self.button_history.set_sensitive(False)
353
# Return right window to normal view by acting like we jump to it
354
self.on_button_location_jump_clicked(widget)
357
def on_entry_history_revno_activate(self, widget=None):
358
""" Key pressed handler for the history entry. """
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_menuitem_add_files_activate(self, widget):
370
""" Add file(s)... menu handler. """
371
from bzrlib.plugins.gtk.olive.add import AddDialog
372
add = AddDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
375
if response == gtk.RESPONSE_OK:
378
def on_menuitem_branch_get_activate(self, widget):
379
""" Branch/Get... menu handler. """
380
from bzrlib.plugins.gtk.branch import BranchDialog
383
branch = BranchDialog(os.getcwd(), self.window, self.remote_branch.base)
385
branch = BranchDialog(self.get_path(), self.window)
386
response = branch.run()
387
if response != gtk.RESPONSE_NONE:
390
if response == gtk.RESPONSE_OK:
395
def on_menuitem_branch_checkout_activate(self, widget):
396
""" Branch/Checkout... menu handler. """
397
from bzrlib.plugins.gtk.checkout import CheckoutDialog
400
checkout = CheckoutDialog(os.getcwd(), self.window, self.remote_branch.base)
402
checkout = CheckoutDialog(self.get_path(), self.window)
403
response = checkout.run()
404
if response != gtk.RESPONSE_NONE:
407
if response == gtk.RESPONSE_OK:
413
def on_menuitem_branch_commit_activate(self, widget):
414
""" Branch/Commit... menu handler. """
415
selected = self.get_selected_right()
417
selected = os.path.join(self.wtpath, selected)
418
commit = CommitDialog(wt=self.wt,
422
response = commit.run()
423
if response != gtk.RESPONSE_NONE:
426
if response == gtk.RESPONSE_OK:
431
def on_menuitem_branch_conflicts_activate(self, widget):
432
""" Branch/Conflicts... menu handler. """
433
conflicts = ConflictsDialog(self.wt, self.window)
434
response = conflicts.run()
435
if response != gtk.RESPONSE_NONE:
438
def on_menuitem_branch_merge_activate(self, widget):
439
""" Branch/Merge... menu handler. """
440
from bzrlib.plugins.gtk.merge import MergeDialog
442
if self.check_for_changes():
443
error_dialog(_i18n('There are local changes in the branch'),
444
_i18n('Please commit or revert the changes before merging.'))
446
parent_branch_path = self.wt.branch.get_parent()
447
merge = MergeDialog(self.wt, self.wtpath, parent_branch_path, self.window)
448
response = merge.run()
450
if response == gtk.RESPONSE_OK:
454
def on_menuitem_branch_missing_revisions_activate(self, widget):
455
""" Branch/Missing revisions menu handler. """
457
from bzrlib.missing import find_unmerged, iter_log_revisions
459
local_branch = self.wt.branch
460
parent_branch_path = local_branch.get_parent()
461
if parent_branch_path is None:
462
error_dialog(_i18n('Parent location is unknown'),
463
_i18n('Cannot determine missing revisions if no parent location is known.'))
466
parent_branch = Branch.open(parent_branch_path)
468
if parent_branch.base == local_branch.base:
469
parent_branch = local_branch
471
local_extra, remote_extra = find_unmerged(local_branch,parent_branch)
473
if local_extra or remote_extra:
475
## def log_revision_one_line_text(log_revision):
476
## """ Generates one line description of log_revison ended with end of line."""
477
## revision = log_revision.rev
478
## txt = "- %s (%s)\n" % (revision.get_summary(), revision.committer, )
479
## txt = txt.replace("<"," ") # Seems < > chars are expected to be xml tags ...
480
## txt = txt.replace(">"," ")
485
dlg_txt += _i18n('%d local extra revision(s). \n') % (len(local_extra),)
486
## NOTE: We do not want such ugly info about missing revisions
487
## Revision Browser should be used there
488
## max_revisions = 10
489
## for log_revision in iter_log_revisions(local_extra, local_branch.repository, verbose=1):
490
## dlg_txt += log_revision_one_line_text(log_revision)
491
## if max_revisions <= 0:
492
## dlg_txt += _i18n("more ... \n")
494
## max_revisions -= 1
497
dlg_txt += _i18n('%d local missing revision(s).\n') % (len(remote_extra),)
498
## max_revisions = 10
499
## for log_revision in iter_log_revisions(remote_extra, parent_branch.repository, verbose=1):
500
## dlg_txt += log_revision_one_line_text(log_revision)
501
## if max_revisions <= 0:
502
## dlg_txt += _i18n("more ... \n")
504
## max_revisions -= 1
506
info_dialog(_i18n('There are missing revisions'),
509
info_dialog(_i18n('Local branch up to date'),
510
_i18n('There are no missing revisions.'))
513
def on_menuitem_branch_pull_activate(self, widget):
514
""" Branch/Pull menu handler. """
515
branch_to = self.wt.branch
517
location = branch_to.get_parent()
519
error_dialog(_i18n('Parent location is unknown'),
520
_i18n('Pulling is not possible until there is a parent location.'))
523
branch_from = Branch.open(location)
525
if branch_to.get_parent() is None:
526
branch_to.set_parent(branch_from.base)
528
ret = branch_to.pull(branch_from)
530
info_dialog(_i18n('Pull successful'), _i18n('%d revision(s) pulled.') % ret)
533
def on_menuitem_branch_update_activate(self, widget):
534
""" Brranch/checkout update menu handler. """
536
ret = self.wt.update()
537
conflicts = self.wt.conflicts()
539
info_dialog(_i18n('Update successful but conflicts generated'), _i18n('Number of conflicts generated: %d.') % (len(conflicts),) )
541
info_dialog(_i18n('Update successful'), _i18n('No conflicts generated.') )
543
def on_menuitem_branch_push_activate(self, widget):
544
""" Branch/Push... menu handler. """
545
push = PushDialog(repository=None,revid=None,branch=self.wt.branch, parent=self.window)
546
response = push.run()
547
if response != gtk.RESPONSE_NONE:
551
def on_menuitem_branch_revert_activate(self, widget):
552
""" Branch/Revert all changes menu handler. """
553
ret = self.wt.revert(None)
555
warning_dialog(_i18n('Conflicts detected'),
556
_i18n('Please have a look at the working tree before continuing.'))
558
info_dialog(_i18n('Revert successful'),
559
_i18n('All files reverted to last revision.'))
562
def on_menuitem_branch_status_activate(self, widget):
563
""" Branch/Status... menu handler. """
564
from bzrlib.plugins.gtk.status import StatusDialog
565
status = StatusDialog(self.wt, self.wtpath)
566
response = status.run()
567
if response != gtk.RESPONSE_NONE:
570
def on_menuitem_branch_initialize_activate(self, widget):
571
""" Initialize current directory. """
572
init = InitDialog(self.path, self.window)
573
response = init.run()
574
if response != gtk.RESPONSE_NONE:
577
if response == gtk.RESPONSE_OK:
582
def on_menuitem_branch_tags_activate(self, widget):
583
""" Branch/Tags... menu handler. """
584
from bzrlib.plugins.gtk.tags import TagsWindow
586
window = TagsWindow(self.wt.branch, self.window)
588
window = TagsWindow(self.remote_branch, self.window)
591
def on_menuitem_file_annotate_activate(self, widget):
592
""" File/Annotate... menu handler. """
593
if self.get_selected_right() is None:
594
error_dialog(_i18n('No file was selected'),
595
_i18n('Please select a file from the list.'))
598
branch = self.wt.branch
599
file_id = self.wt.path2id(self.wt.relpath(os.path.join(self.path, self.get_selected_right())))
601
window = GAnnotateWindow(all=False, plain=False, parent=self.window)
602
window.set_title(os.path.join(self.path, self.get_selected_right()) + " - Annotate")
603
config = GAnnotateConfig(window)
607
window.annotate(self.wt, branch, file_id)
611
def on_menuitem_file_bookmark_activate(self, widget):
612
""" File/Bookmark current directory menu handler. """
613
if self.pref.add_bookmark(self.path):
614
info_dialog(_i18n('Bookmark successfully added'),
615
_i18n('The current directory was bookmarked. You can reach\nit by selecting it from the left panel.'))
618
warning_dialog(_i18n('Location already bookmarked'),
619
_i18n('The current directory is already bookmarked.\nSee the left panel for reference.'))
623
def on_menuitem_file_make_directory_activate(self, widget):
624
""" File/Make directory... menu handler. """
625
from bzrlib.plugins.gtk.olive.mkdir import MkdirDialog
626
mkdir = MkdirDialog(self.wt, self.wtpath, self.window)
627
response = mkdir.run()
629
if response == gtk.RESPONSE_OK:
632
def on_menuitem_file_move_activate(self, widget):
633
""" File/Move... menu handler. """
634
from bzrlib.plugins.gtk.olive.move import MoveDialog
635
move = MoveDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
636
response = move.run()
638
if response == gtk.RESPONSE_OK:
641
def on_menuitem_file_rename_activate(self, widget):
642
""" File/Rename... menu handler. """
643
from bzrlib.plugins.gtk.olive.rename import RenameDialog
644
rename = RenameDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
645
response = rename.run()
647
if response == gtk.RESPONSE_OK:
650
def on_menuitem_remove_file_activate(self, widget):
651
""" Remove (unversion) selected file. """
652
from bzrlib.plugins.gtk.olive.remove import RemoveDialog
653
remove = RemoveDialog(self.wt, self.wtpath,
654
selected=self.get_selected_right(),
656
response = remove.run()
658
if response != gtk.RESPONSE_NONE:
661
if response == gtk.RESPONSE_OK:
662
self.set_path(self.path)
667
def on_menuitem_stats_diff_activate(self, widget):
668
""" Statistics/Differences... menu handler. """
669
window = DiffWindow(parent=self.window)
670
parent_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
671
window.set_diff(self.wt.branch.nick, self.wt, parent_tree)
674
def on_menuitem_stats_infos_activate(self, widget):
675
""" Statistics/Informations... menu handler. """
676
from bzrlib.plugins.gtk.olive.info import InfoDialog
678
info = InfoDialog(self.remote_branch)
680
info = InfoDialog(self.wt.branch)
683
def on_menuitem_stats_log_activate(self, widget):
684
""" Statistics/Log... menu handler. """
687
branch = self.wt.branch
689
branch = self.remote_branch
691
window = branchwin.BranchWindow(branch, [branch.last_revision()], None,
695
def on_menuitem_view_refresh_activate(self, widget):
696
""" View/Refresh menu handler. """
697
# Refresh the left pane
699
# Refresh the right pane
702
def on_menuitem_view_show_hidden_files_activate(self, widget):
703
""" View/Show hidden files menu handler. """
704
self.pref.set_preference('dotted_files', widget.get_active())
705
if self.path is not None:
708
def on_menuitem_view_show_ignored_files_activate(self, widget):
709
""" Hide/Show ignored files menu handler. """
710
self.pref.set_preference('ignored_files', widget.get_active())
711
if self.path is not None:
714
def on_treeview_left_button_press_event(self, widget, event):
715
""" Occurs when somebody clicks in the bookmark list. """
716
treepathpos = widget.get_path_at_pos(int(event.x), int(event.y))
717
treeselection = widget.get_selection()
718
if treepathpos is not None:
719
treeselection.select_path(treepathpos[0])
720
if event.button == 1:
721
newdir = self.get_selected_left()
725
if self.set_path(newdir):
727
elif event.button == 3:
728
# Don't show context with nothing selected
729
if self.get_selected_left() == None:
733
from menu import OliveMenu
734
menu = OliveMenu(path=self.get_path(),
735
selected=self.get_selected_left(),
738
menu.left_context_menu().popup(None, None, None, 0,
741
if treeselection is not None:
742
treeselection.unselect_all()
744
def on_treeview_left_row_activated(self, treeview, path, view_column):
745
""" Occurs when somebody double-clicks or enters an item in the
748
newdir = self.get_selected_left()
752
if self.set_path(newdir):
755
def on_treeview_right_button_press_event(self, widget, event):
756
""" Occurs when somebody clicks in the file list. """
757
treepathpos = widget.get_path_at_pos(int(event.x), int(event.y))
758
if event.button == 1:
759
if treepathpos is None and widget.get_selection is not None:
760
treeselection = widget.get_selection()
761
treeselection.unselect_all()
762
elif event.button == 3:
763
treeselection = widget.get_selection()
764
if treepathpos is not None:
765
treeselection.select_path(treepathpos[0])
767
if treeselection is not None:
768
treeselection.unselect_all()
770
from menu import OliveMenu
771
menu = OliveMenu(path=self.get_path(),
772
selected=self.get_selected_right(),
775
m_open = menu.ui.get_widget('/context_right/open')
776
m_add = menu.ui.get_widget('/context_right/add')
777
m_remove = menu.ui.get_widget('/context_right/remove')
778
m_remove_and_delete = menu.ui.get_widget('/context_right/remove_and_delete')
779
m_rename = menu.ui.get_widget('/context_right/rename')
780
m_revert = menu.ui.get_widget('/context_right/revert')
781
m_commit = menu.ui.get_widget('/context_right/commit')
782
m_annotate = menu.ui.get_widget('/context_right/annotate')
783
m_diff = menu.ui.get_widget('/context_right/diff')
784
# check if we're in a branch
786
from bzrlib.branch import Branch
787
Branch.open_containing(self.get_path())
789
m_open.set_sensitive(False)
790
m_add.set_sensitive(False)
791
m_remove.set_sensitive(False)
792
m_remove_and_delete.set_sensitive(False)
793
m_rename.set_sensitive(False)
794
m_revert.set_sensitive(False)
795
m_commit.set_sensitive(False)
796
m_annotate.set_sensitive(False)
797
m_diff.set_sensitive(False)
799
if treepathpos is None:
800
m_open.set_sensitive(False)
801
m_add.set_sensitive(False)
802
m_remove.set_sensitive(False)
803
m_remove_and_delete.set_sensitive(False)
804
m_rename.set_sensitive(False)
805
m_annotate.set_sensitive(False)
806
m_diff.set_sensitive(False)
807
m_revert.set_sensitive(False)
809
m_open.set_sensitive(True)
810
m_add.set_sensitive(True)
811
m_remove.set_sensitive(True)
812
m_remove_and_delete.set_sensitive(True)
813
m_rename.set_sensitive(True)
814
m_annotate.set_sensitive(True)
815
m_diff.set_sensitive(True)
816
m_revert.set_sensitive(True)
817
m_commit.set_sensitive(True)
818
except bzrerrors.NotBranchError:
819
if treepathpos is None:
820
m_open.set_sensitive(False)
822
m_open.set_sensitive(True)
823
m_add.set_sensitive(False)
824
m_remove.set_sensitive(False)
825
m_remove_and_delete.set_sensitive(False)
826
m_rename.set_sensitive(False)
827
m_revert.set_sensitive(False)
828
m_commit.set_sensitive(False)
829
m_annotate.set_sensitive(False)
830
m_diff.set_sensitive(False)
833
menu.right_context_menu().popup(None, None, None, 0,
836
menu.remote_context_menu().popup(None, None, None, 0,
839
def on_treeview_right_row_activated(self, treeview, path, view_column):
840
""" Occurs when somebody double-clicks or enters an item in the
842
from launch import launch
844
newdir = self.get_selected_right()
849
self.set_path(os.path.split(self.get_path())[0])
851
fullpath = os.path.join(self.get_path(), newdir)
852
if os.path.isdir(fullpath):
853
# selected item is an existant directory
854
self.set_path(fullpath)
859
if self._is_remote_dir(self.get_path() + newdir):
860
self.set_path(self.get_path() + newdir)
864
def on_window_main_delete_event(self, widget, event=None):
865
""" Do some stuff before exiting. """
866
width, height = self.window.get_size()
867
self.pref.set_preference('window_width', width)
868
self.pref.set_preference('window_height', height)
869
x, y = self.window.get_position()
870
self.pref.set_preference('window_x', x)
871
self.pref.set_preference('window_y', y)
872
self.pref.set_preference('paned_position',
873
self.window.hpaned_main.get_position())
876
self.window.destroy()
878
def _load_left(self):
879
""" Load data into the left panel. (Bookmarks) """
881
treestore = gtk.TreeStore(str, str)
884
bookmarks = self.pref.get_bookmarks()
886
# Add them to the TreeStore
887
titer = treestore.append(None, [_i18n('Bookmarks'), None])
889
# Get titles and sort by title
890
bookmarks = [[self.pref.get_bookmark_title(item), item] for item in bookmarks]
892
for title_item in bookmarks:
893
treestore.append(titer, title_item)
895
# Create the column and add it to the TreeView
896
self.treeview_left.set_model(treestore)
897
tvcolumn_bookmark = gtk.TreeViewColumn(_i18n('Bookmark'))
898
self.treeview_left.append_column(tvcolumn_bookmark)
901
cell = gtk.CellRendererText()
902
tvcolumn_bookmark.pack_start(cell, True)
903
tvcolumn_bookmark.add_attribute(cell, 'text', 0)
906
self.treeview_left.expand_all()
908
def _load_right(self):
909
""" Load data into the right panel. (Filelist) """
911
# Model: [ icon, dir, name, status text, status, size (int), size (human), mtime (int), mtime (local), fileid ]
912
liststore = gtk.ListStore(gobject.TYPE_STRING,
913
gobject.TYPE_BOOLEAN,
926
# Fill the appropriate lists
927
dotted_files = self.pref.get_preference('dotted_files', 'bool')
928
for item in os.listdir(self.path):
929
if not dotted_files and item[0] == '.':
931
if os.path.isdir(self.path + os.sep + item):
936
if not self.notbranch:
937
branch = self.wt.branch
938
tree2 = self.wt.branch.repository.revision_tree(branch.last_revision())
940
delta = self.wt.changes_from(tree2, want_unchanged=True)
942
# Add'em to the ListStore
945
statinfo = os.stat(self.path + os.sep + item)
947
if e.errno in self.acceptable_errors:
951
liststore.append([ gtk.STOCK_DIRECTORY,
959
self._format_date(statinfo.st_mtime),
964
if not self.notbranch:
965
filename = self.wt.relpath(self.path + os.sep + item)
970
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
971
if rpathnew == filename:
974
for rpath, id, kind in delta.added:
975
if rpath == filename:
978
for rpath, id, kind in delta.removed:
979
if rpath == filename:
982
for rpath, id, kind, text_modified, meta_modified in delta.modified:
983
if rpath == filename:
986
for rpath, id, kind in delta.unchanged:
987
if rpath == filename:
990
for rpath, file_class, kind, id, entry in self.wt.list_files():
991
if rpath == filename and file_class == 'I':
996
if status == 'renamed':
997
st = _i18n('renamed')
998
elif status == 'removed':
999
st = _i18n('removed')
1000
elif status == 'added':
1002
elif status == 'modified':
1003
st = _i18n('modified')
1004
elif status == 'unchanged':
1005
st = _i18n('unchanged')
1006
elif status == 'ignored':
1007
st = _i18n('ignored')
1009
st = _i18n('unknown')
1012
statinfo = os.stat(self.path + os.sep + item)
1014
if e.errno in self.acceptable_errors:
1018
liststore.append([gtk.STOCK_FILE,
1023
str(statinfo.st_size), # NOTE: if int used there it will fail for large files (size expressed as long int)
1024
self._format_size(statinfo.st_size),
1026
self._format_date(statinfo.st_mtime),
1029
# Create the columns and add them to the TreeView
1030
self.treeview_right.set_model(liststore)
1031
self._tvcolumn_filename = gtk.TreeViewColumn(_i18n('Filename'))
1032
self._tvcolumn_status = gtk.TreeViewColumn(_i18n('Status'))
1033
self._tvcolumn_size = gtk.TreeViewColumn(_i18n('Size'))
1034
self._tvcolumn_mtime = gtk.TreeViewColumn(_i18n('Last modified'))
1035
self.treeview_right.append_column(self._tvcolumn_filename)
1036
self.treeview_right.append_column(self._tvcolumn_status)
1037
self.treeview_right.append_column(self._tvcolumn_size)
1038
self.treeview_right.append_column(self._tvcolumn_mtime)
1041
cellpb = gtk.CellRendererPixbuf()
1042
cell = gtk.CellRendererText()
1043
self._tvcolumn_filename.pack_start(cellpb, False)
1044
self._tvcolumn_filename.pack_start(cell, True)
1045
self._tvcolumn_filename.set_attributes(cellpb, stock_id=0)
1046
self._tvcolumn_filename.add_attribute(cell, 'text', 2)
1047
self._tvcolumn_status.pack_start(cell, True)
1048
self._tvcolumn_status.add_attribute(cell, 'text', 3)
1049
self._tvcolumn_size.pack_start(cell, True)
1050
self._tvcolumn_size.add_attribute(cell, 'text', 6)
1051
self._tvcolumn_mtime.pack_start(cell, True)
1052
self._tvcolumn_mtime.add_attribute(cell, 'text', 8)
1054
# Set up the properties of the TreeView
1055
self.treeview_right.set_headers_visible(True)
1056
self.treeview_right.set_headers_clickable(True)
1057
self.treeview_right.set_search_column(1)
1058
self._tvcolumn_filename.set_resizable(True)
1059
self._tvcolumn_status.set_resizable(True)
1060
self._tvcolumn_size.set_resizable(True)
1061
self._tvcolumn_mtime.set_resizable(True)
1063
liststore.set_sort_func(13, self._sort_filelist_callback, None)
1064
liststore.set_sort_column_id(13, gtk.SORT_ASCENDING)
1065
self._tvcolumn_filename.set_sort_column_id(13)
1066
self._tvcolumn_status.set_sort_column_id(3)
1067
self._tvcolumn_size.set_sort_column_id(5)
1068
self._tvcolumn_mtime.set_sort_column_id(7)
1071
self.set_sensitivity()
1073
def get_selected_fileid(self):
1074
""" Get the file_id of the selected file. """
1075
treeselection = self.treeview_right.get_selection()
1076
(model, iter) = treeselection.get_selected()
1081
return model.get_value(iter, 9)
1083
def get_selected_right(self):
1084
""" Get the selected filename. """
1085
treeselection = self.treeview_right.get_selection()
1086
(model, iter) = treeselection.get_selected()
1091
return model.get_value(iter, 2)
1093
def get_selected_left(self):
1094
""" Get the selected bookmark. """
1095
treeselection = self.treeview_left.get_selection()
1096
(model, iter) = treeselection.get_selected()
1101
return model.get_value(iter, 1)
1103
def set_statusbar(self, message):
1104
""" Set the statusbar message. """
1105
self.window.statusbar.push(self.context_id, message)
1107
def clear_statusbar(self):
1108
""" Clean the last message from the statusbar. """
1109
self.window.statusbar.pop(self.context_id)
1111
def set_sensitivity(self):
1112
""" Set menu and toolbar sensitivity. """
1114
self.window.set_view_to_localbranch(self.notbranch)
1116
self.window.set_view_to_remotebranch()
1118
def refresh_left(self):
1119
""" Refresh the bookmark list. """
1121
# Get TreeStore and clear it
1122
treestore = self.treeview_left.get_model()
1125
# Re-read preferences
1129
bookmarks = self.pref.get_bookmarks()
1131
# Add them to the TreeStore
1132
titer = treestore.append(None, [_i18n('Bookmarks'), None])
1134
# Get titles and sort by title
1135
bookmarks = [[self.pref.get_bookmark_title(item), item] for item in bookmarks]
1137
for title_item in bookmarks:
1138
treestore.append(titer, title_item)
1140
# Add the TreeStore to the TreeView
1141
self.treeview_left.set_model(treestore)
1144
self.treeview_left.expand_all()
1146
def refresh_right(self, path=None):
1147
""" Refresh the file list. """
1150
from bzrlib.workingtree import WorkingTree
1153
path = self.get_path()
1155
# A workaround for double-clicking Bookmarks
1156
if not os.path.exists(path):
1159
# Get ListStore and clear it
1160
liststore = self.treeview_right.get_model()
1163
# Show Status column
1164
self._tvcolumn_status.set_visible(True)
1169
# Fill the appropriate lists
1170
dotted_files = self.pref.get_preference('dotted_files', 'bool')
1171
ignored_files = self.pref.get_preference('ignored_files', 'bool')
1173
for item in os.listdir(path):
1174
if not dotted_files and item[0] == '.':
1176
if os.path.isdir(path + os.sep + item):
1181
# Try to open the working tree
1184
tree1 = WorkingTree.open_containing(path)[0]
1185
except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
1189
branch = tree1.branch
1190
tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
1192
delta = tree1.changes_from(tree2, want_unchanged=True)
1194
# Add'em to the ListStore
1197
statinfo = os.stat(self.path + os.sep + item)
1199
if e.errno in self.acceptable_errors:
1203
liststore.append([gtk.STOCK_DIRECTORY,
1211
self._format_date(statinfo.st_mtime),
1217
filename = tree1.relpath(path + os.sep + item)
1222
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
1223
if rpathnew == filename:
1226
for rpath, id, kind in delta.added:
1227
if rpath == filename:
1230
for rpath, id, kind in delta.removed:
1231
if rpath == filename:
1234
for rpath, id, kind, text_modified, meta_modified in delta.modified:
1235
if rpath == filename:
1238
for rpath, id, kind in delta.unchanged:
1239
if rpath == filename:
1240
status = 'unchanged'
1242
for rpath, file_class, kind, id, entry in self.wt.list_files():
1243
if rpath == filename and file_class == 'I':
1248
if status == 'renamed':
1249
st = _i18n('renamed')
1250
elif status == 'removed':
1251
st = _i18n('removed')
1252
elif status == 'added':
1254
elif status == 'modified':
1255
st = _i18n('modified')
1256
elif status == 'unchanged':
1257
st = _i18n('unchanged')
1258
elif status == 'ignored':
1259
st = _i18n('ignored')
1260
if not ignored_files:
1263
st = _i18n('unknown')
1266
statinfo = os.stat(self.path + os.sep + item)
1268
if e.errno in self.acceptable_errors:
1272
liststore.append([gtk.STOCK_FILE,
1277
str(statinfo.st_size),
1278
self._format_size(statinfo.st_size),
1280
self._format_date(statinfo.st_mtime),
1285
# Get ListStore and clear it
1286
liststore = self.treeview_right.get_model()
1289
# Hide Status column
1290
self._tvcolumn_status.set_visible(False)
1295
self._show_stock_image(gtk.STOCK_REFRESH)
1297
for (name, type) in self.remote_entries:
1298
if type.kind == 'directory':
1300
elif type.kind == 'file':
1304
""" Cache based on revision history. """
1305
def __init__(self, history):
1306
self._history = history
1308
def _lookup_revision(self, revid):
1309
for r in self._history:
1310
if r.revision_id == revid:
1312
rev = repo.get_revision(revid)
1313
self._history.append(rev)
1316
repo = self.remote_branch.repository
1318
revhistory = self.remote_branch.revision_history()
1320
revs = repo.get_revisions(revhistory)
1321
cache = HistoryCache(revs)
1322
except bzrerrors.InvalidHttpResponse:
1323
# Fallback to dummy algorithm, because of LP: #115209
1324
cache = HistoryCache([])
1327
if item.parent_id == self.remote_parent:
1328
rev = cache._lookup_revision(item.revision)
1329
liststore.append([ gtk.STOCK_DIRECTORY,
1337
self._format_date(rev.timestamp),
1340
while gtk.events_pending():
1341
gtk.main_iteration()
1344
if item.parent_id == self.remote_parent:
1345
rev = cache._lookup_revision(item.revision)
1346
liststore.append([ gtk.STOCK_FILE,
1351
str(item.text_size),
1352
self._format_size(item.text_size),
1354
self._format_date(rev.timestamp),
1357
while gtk.events_pending():
1358
gtk.main_iteration()
1360
self.image_location_error.destroy()
1362
# Columns should auto-size
1363
self.treeview_right.columns_autosize()
1366
self.set_sensitivity()
1368
def _harddisks(self):
1369
""" Returns hard drive letters under Win32. """
1374
if sys.platform == 'win32':
1375
print "pyWin32 modules needed to run Olive on Win32."
1379
for drive in string.ascii_uppercase:
1380
if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED or\
1381
win32file.GetDriveType(drive+':') == win32file.DRIVE_REMOTE:
1382
driveletters.append(drive+':')
1385
def gen_hard_selector(self):
1386
""" Generate the hard drive selector under Win32. """
1387
drives = self._harddisks()
1388
for drive in drives:
1389
self.combobox_drive.append_text(drive)
1390
self.combobox_drive.set_active(drives.index(os.getcwd()[0:2]))
1392
def _refresh_drives(self, combobox):
1393
if self._just_started:
1395
model = combobox.get_model()
1396
active = combobox.get_active()
1398
drive = model[active][0]
1399
self.set_path(drive + '\\')
1400
self.refresh_right(drive + '\\')
1402
def check_for_changes(self):
1403
""" Check whether there were changes in the current working tree. """
1404
old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
1405
delta = self.wt.changes_from(old_tree)
1409
if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
1414
def _sort_filelist_callback(self, model, iter1, iter2, data):
1415
""" The sort callback for the file list, return values:
1420
name1 = model.get_value(iter1, 2)
1421
name2 = model.get_value(iter2, 2)
1423
if model.get_value(iter1, 1):
1424
# item1 is a directory
1425
if not model.get_value(iter2, 1):
1429
# both of them are directories, we compare their names
1432
elif name1 == name2:
1437
# item1 is not a directory
1438
if model.get_value(iter2, 1):
1442
# both of them are files, compare them
1445
elif name1 == name2:
1450
def _format_size(self, size):
1451
""" Format size to a human readable format. """
1453
return "%d[B]" % (size,)
1454
size = size / 1000.0
1456
for metric in ["kB","MB","GB","TB"]:
1459
size = size / 1000.0
1460
return "%.1f[%s]" % (size,metric)
1462
def _format_date(self, timestamp):
1463
""" Format the time (given in secs) to a human readable format. """
1464
return time.ctime(timestamp)
1466
def _is_remote_dir(self, location):
1467
""" Determine whether the given location is a directory or not. """
1469
# We're in local mode
1472
branch, path = Branch.open_containing(location)
1473
for (name, type) in self.remote_entries:
1474
if name == path and type.kind == 'directory':
1477
# Either it's not a directory or not in the inventory
1480
def _show_stock_image(self, stock_id):
1481
""" Show a stock image next to the location entry. """
1482
self.image_location_error.destroy()
1483
self.image_location_error = gtk.image_new_from_stock(stock_id, gtk.ICON_SIZE_BUTTON)
1484
self.hbox_location.pack_start(self.image_location_error, False, False, 0)
1485
if sys.platform == 'win32':
1486
self.hbox_location.reorder_child(self.image_location_error, 2)
1488
self.hbox_location.reorder_child(self.image_location_error, 1)
1489
self.image_location_error.show()
1490
while gtk.events_pending():
1491
gtk.main_iteration()
1496
""" A class which handles Olive's preferences. """
1497
def __init__(self, path=None):
1498
""" Initialize the Preferences class. """
1499
# Some default options
1500
self.defaults = { 'strict_commit' : False,
1501
'dotted_files' : False,
1502
'ignored_files' : True,
1503
'window_width' : 700,
1504
'window_height' : 400,
1507
'paned_position': 200 }
1509
# Create a config parser object
1510
self.config = ConfigParser.RawConfigParser()
1514
if sys.platform == 'win32':
1515
# Windows - no dotted files
1516
self._filename = os.path.expanduser('~/olive.conf')
1518
self._filename = os.path.expanduser('~/.olive.conf')
1520
self._filename = path
1522
# Load the configuration
1525
def _get_default(self, option):
1526
""" Get the default option for a preference. """
1528
ret = self.defaults[option]
1535
""" Refresh the configuration. """
1536
# First write out the changes
1538
# Then load the configuration again
1542
""" Just read the configuration. """
1543
# Re-initialize the config parser object to avoid some bugs
1544
self.config = ConfigParser.RawConfigParser()
1545
self.config.read([self._filename])
1548
""" Write the configuration to the appropriate files. """
1549
fp = open(self._filename, 'w')
1550
self.config.write(fp)
1553
def get_bookmarks(self):
1554
""" Return the list of bookmarks. """
1555
bookmarks = self.config.sections()
1556
if self.config.has_section('preferences'):
1557
bookmarks.remove('preferences')
1560
def add_bookmark(self, path):
1561
""" Add bookmark. """
1563
self.config.add_section(path)
1564
except ConfigParser.DuplicateSectionError:
1569
def get_bookmark_title(self, path):
1570
""" Get bookmark title. """
1572
ret = self.config.get(path, 'title')
1573
except ConfigParser.NoOptionError:
1578
def set_bookmark_title(self, path, title):
1579
""" Set bookmark title. """
1580
# FIXME: What if path isn't listed yet?
1581
# FIXME: Canonicalize paths first?
1582
self.config.set(path, 'title', title)
1584
def remove_bookmark(self, path):
1585
""" Remove bookmark. """
1586
return self.config.remove_section(path)
1588
def set_preference(self, option, value):
1589
""" Set the value of the given option. """
1592
elif value is False:
1595
if self.config.has_section('preferences'):
1596
self.config.set('preferences', option, value)
1598
self.config.add_section('preferences')
1599
self.config.set('preferences', option, value)
1601
def get_preference(self, option, kind='str'):
1602
""" Get the value of the given option.
1604
:param kind: str/bool/int/float. default: str
1606
if self.config.has_option('preferences', option):
1608
return self.config.getboolean('preferences', option)
1610
return self.config.getint('preferences', option)
1611
elif kind == 'float':
1612
return self.config.getfloat('preferences', option)
1614
return self.config.get('preferences', option)
1617
return self._get_default(option)