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
dialog = gtk.AboutDialog()
64
dialog.set_name("Olive")
65
dialog.set_version(__version__)
66
dialog.set_copyright("Copyright (C) 2006-2008 Szilveszter Farkas (Phanatic)")
67
dialog.set_website("https://launchpad.net/bzr-gtk")
68
dialog.set_website_label("https://launchpad.net/bzr-gtk")
69
dialog.set_icon_from_file(icon_path("oliveicon2.png"))
70
dialog.set_logo(gtk.gdk.pixbuf_new_from_file(icon_path("oliveicon2.png")))
71
dialog.set_authors([ _i18n("Lead Developer:"),
72
"Szilveszter Farkas <szilveszter.farkas@gmail.com>",
73
_i18n("Contributors:"),
74
"Jelmer Vernooij <jelmer@samba.org>",
75
"Mateusz Korniak <mateusz.korniak@ant.gliwice.pl>",
76
"Gary van der Merwe <garyvdm@gmail.com>" ])
77
dialog.set_artists([ "Simon Pascal Klein <klepas@klepas.org>",
78
"Jakub Steiner <jimmac@novell.com>" ])
86
""" The main Olive GTK frontend class. This is called when launching the
90
self.window = OliveGui(calling_app = self)
92
self.pref = Preferences()
95
# Initialize the statusbar
96
self.context_id = self.window.statusbar.get_context_id('olive')
98
# Get the drive selector
99
self.combobox_drive = gtk.combo_box_new_text()
100
self.combobox_drive.connect("changed", self._refresh_drives)
102
# Get the navigation widgets
103
self.hbox_location = self.window.locationbar
104
self.button_location_up = self.window.button_location_up
105
self.button_location_jump = self.window.button_location_jump
106
self.entry_location = self.window.entry_location
108
# Get the History widgets
109
self.check_history = self.window.checkbutton_history
110
self.entry_history = self.window.entry_history_revno
111
self.button_history = self.window.button_history_browse
113
self._just_started = True
115
# Apply window size and position
116
width = self.pref.get_preference('window_width', 'int')
117
height = self.pref.get_preference('window_height', 'int')
118
self.window.resize(width, height)
119
x = self.pref.get_preference('window_x', 'int')
120
y = self.pref.get_preference('window_y', 'int')
121
self.window.move(x, y)
122
# Apply paned position
123
pos = self.pref.get_preference('paned_position', 'int')
124
self.window.hpaned_main.set_position(pos)
126
# Now we can show the window
129
# Show drive selector if under Win32
130
if sys.platform == 'win32':
131
self.hbox_location.pack_start(self.combobox_drive, False, False, 0)
132
self.hbox_location.reorder_child(self.combobox_drive, 1)
133
self.combobox_drive.show()
134
self.gen_hard_selector()
136
# Acceptable errors when loading files/folders in the treeviews
137
self.acceptable_errors = (errno.ENOENT, errno.ELOOP)
142
self.window.mb_view_showhidden.set_active(self.pref.get_preference('dotted_files', 'bool'))
143
self.window.mb_view_showignored.set_active(self.pref.get_preference('ignored_files', 'bool'))
145
# We're starting local
147
self.remote_branch = None
148
self.remote_path = None
149
self.remote_revision = None
151
self.set_path(os.getcwd())
154
self._just_started = False
156
def set_path(self, path, force_remote=False):
157
self.window.location_status.destroy()
158
self.notbranch = False
161
# Forcing remote mode (reading data from inventory)
162
self.window.set_location_status(gtk.STOCK_DISCONNECT)
164
br = Branch.open_containing(path)[0]
165
except bzrerrors.NotBranchError:
166
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
167
self.check_history.set_active(False)
168
self.check_history.set_sensitive(False)
170
except bzrerrors.UnsupportedProtocol:
171
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
172
self.check_history.set_active(False)
173
self.check_history.set_sensitive(False)
176
self.window.set_location_status(gtk.STOCK_CONNECT)
181
self.remote_branch, self.remote_path = Branch.open_containing(path)
183
if self.remote_revision is None:
184
self.remote_revision = self.remote_branch.last_revision()
186
self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
188
if len(self.remote_path) == 0:
189
self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
191
for (name, type) in self.remote_entries:
192
if name == self.remote_path:
193
self.remote_parent = type.file_id
196
if not path.endswith('/'):
199
if self.remote_branch.base == path:
200
self.button_location_up.set_sensitive(False)
202
self.button_location_up.set_sensitive(True)
204
if os.path.isdir(path):
209
self.wt, self.wtpath = WorkingTree.open_containing(os.path.realpath(path))
210
except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
211
self.notbranch = True
212
except bzrerrors.PermissionDenied:
213
self.window.set_location_status(gtk.STOCK_DIALOG_WARNING, allowPopup=True)
214
self.window.location_status.connect_object('clicked', warning_dialog,
215
*(_i18n('Branch information unreadable'),
216
_i18n('The current folder is a branch but the .bzr folder is not readable')))
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.window.set_location_status(gtk.STOCK_DISCONNECT)
236
br = Branch.open_containing(path)[0]
237
except bzrerrors.NotBranchError:
238
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
239
self.check_history.set_active(False)
240
self.check_history.set_sensitive(False)
242
except bzrerrors.UnsupportedProtocol:
243
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
244
self.check_history.set_active(False)
245
self.check_history.set_sensitive(False)
248
self.window.set_location_status(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
""" Branch/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.') )
544
def on_menuitem_branch_push_activate(self, widget):
545
""" Branch/Push... menu handler. """
546
push = PushDialog(repository=None,revid=None,branch=self.wt.branch, parent=self.window)
547
response = push.run()
548
if response != gtk.RESPONSE_NONE:
552
def on_menuitem_branch_revert_activate(self, widget):
553
""" Branch/Revert all changes menu handler. """
554
ret = self.wt.revert(None)
556
warning_dialog(_i18n('Conflicts detected'),
557
_i18n('Please have a look at the working tree before continuing.'))
559
info_dialog(_i18n('Revert successful'),
560
_i18n('All files reverted to last revision.'))
563
def on_menuitem_branch_status_activate(self, widget):
564
""" Branch/Status... menu handler. """
565
from bzrlib.plugins.gtk.status import StatusDialog
566
status = StatusDialog(self.wt, self.wtpath)
567
response = status.run()
568
if response != gtk.RESPONSE_NONE:
571
def on_menuitem_branch_initialize_activate(self, widget):
572
""" Initialize current directory. """
573
init = InitDialog(self.path, self.window)
574
response = init.run()
575
if response != gtk.RESPONSE_NONE:
578
if response == gtk.RESPONSE_OK:
583
def on_menuitem_branch_tags_activate(self, widget):
584
""" Branch/Tags... menu handler. """
585
from bzrlib.plugins.gtk.tags import TagsWindow
587
window = TagsWindow(self.wt.branch, self.window)
589
window = TagsWindow(self.remote_branch, self.window)
592
def on_menuitem_file_annotate_activate(self, widget):
593
""" File/Annotate... menu handler. """
594
if self.get_selected_right() is None:
595
error_dialog(_i18n('No file was selected'),
596
_i18n('Please select a file from the list.'))
599
branch = self.wt.branch
600
file_id = self.wt.path2id(self.wt.relpath(os.path.join(self.path, self.get_selected_right())))
602
window = GAnnotateWindow(all=False, plain=False, parent=self.window)
603
window.set_title(os.path.join(self.path, self.get_selected_right()) + " - Annotate")
604
config = GAnnotateConfig(window)
608
window.annotate(self.wt, branch, file_id)
612
def on_menuitem_file_bookmark_activate(self, widget):
613
""" File/Bookmark current directory menu handler. """
614
if self.pref.add_bookmark(self.path):
615
info_dialog(_i18n('Bookmark successfully added'),
616
_i18n('The current directory was bookmarked. You can reach\nit by selecting it from the left panel.'))
619
warning_dialog(_i18n('Location already bookmarked'),
620
_i18n('The current directory is already bookmarked.\nSee the left panel for reference.'))
624
def on_menuitem_file_make_directory_activate(self, widget):
625
""" File/Make directory... menu handler. """
626
from bzrlib.plugins.gtk.olive.mkdir import MkdirDialog
627
mkdir = MkdirDialog(self.wt, self.wtpath, self.window)
628
response = mkdir.run()
630
if response == gtk.RESPONSE_OK:
633
def on_menuitem_file_move_activate(self, widget):
634
""" File/Move... menu handler. """
635
from bzrlib.plugins.gtk.olive.move import MoveDialog
636
move = MoveDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
637
response = move.run()
639
if response == gtk.RESPONSE_OK:
642
def on_menuitem_file_rename_activate(self, widget):
643
""" File/Rename... menu handler. """
644
from bzrlib.plugins.gtk.olive.rename import RenameDialog
645
rename = RenameDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
646
response = rename.run()
648
if response == gtk.RESPONSE_OK:
651
def on_menuitem_remove_file_activate(self, widget):
652
""" Remove (unversion) selected file. """
653
from bzrlib.plugins.gtk.olive.remove import RemoveDialog
654
remove = RemoveDialog(self.wt, self.wtpath,
655
selected=self.get_selected_right(),
657
response = remove.run()
659
if response != gtk.RESPONSE_NONE:
662
if response == gtk.RESPONSE_OK:
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())
706
if self.path is not None:
709
def on_menuitem_view_show_ignored_files_activate(self, widget):
710
""" Hide/Show ignored files menu handler. """
711
self.pref.set_preference('ignored_files', widget.get_active())
713
if self.path is not None:
716
def on_treeview_left_button_press_event(self, widget, event):
717
""" Occurs when somebody clicks in the bookmark list. """
718
treepathpos = widget.get_path_at_pos(int(event.x), int(event.y))
719
treeselection = widget.get_selection()
720
if treepathpos is not None:
721
treeselection.select_path(treepathpos[0])
722
if event.button == 1:
723
newdir = self.get_selected_left()
727
if self.set_path(newdir):
729
elif event.button == 3:
730
# Don't show context with nothing selected
731
if self.get_selected_left() == None:
735
from menu import OliveMenu
736
menu = OliveMenu(path=self.get_path(),
737
selected=self.get_selected_left(),
740
menu.left_context_menu().popup(None, None, None, 0,
743
if treeselection is not None:
744
treeselection.unselect_all()
746
def on_treeview_left_row_activated(self, treeview, path, view_column):
747
""" Occurs when somebody double-clicks or enters an item in the
750
newdir = self.get_selected_left()
754
if self.set_path(newdir):
757
def on_treeview_right_button_press_event(self, widget, event):
758
""" Occurs when somebody clicks in the file list. """
759
treepathpos = widget.get_path_at_pos(int(event.x), int(event.y))
760
if event.button == 1:
761
if treepathpos is None and widget.get_selection is not None:
762
treeselection = widget.get_selection()
763
treeselection.unselect_all()
764
elif event.button == 3:
765
treeselection = widget.get_selection()
766
if treepathpos is not None:
767
treeselection.select_path(treepathpos[0])
769
if treeselection is not None:
770
treeselection.unselect_all()
772
from menu import OliveMenu
773
menu = OliveMenu(path=self.get_path(),
774
selected=self.get_selected_right(),
777
m_open = menu.ui.get_widget('/context_right/open')
778
m_add = menu.ui.get_widget('/context_right/add')
779
m_remove = menu.ui.get_widget('/context_right/remove')
780
m_remove_and_delete = menu.ui.get_widget('/context_right/remove_and_delete')
781
m_rename = menu.ui.get_widget('/context_right/rename')
782
m_revert = menu.ui.get_widget('/context_right/revert')
783
m_commit = menu.ui.get_widget('/context_right/commit')
784
m_annotate = menu.ui.get_widget('/context_right/annotate')
785
m_diff = menu.ui.get_widget('/context_right/diff')
786
# check if we're in a branch
788
from bzrlib.branch import Branch
789
Branch.open_containing(self.get_path())
791
m_open.set_sensitive(False)
792
m_add.set_sensitive(False)
793
m_remove.set_sensitive(False)
794
m_remove_and_delete.set_sensitive(False)
795
m_rename.set_sensitive(False)
796
m_revert.set_sensitive(False)
797
m_commit.set_sensitive(False)
798
m_annotate.set_sensitive(False)
799
m_diff.set_sensitive(False)
801
if treepathpos is None:
802
m_open.set_sensitive(False)
803
m_add.set_sensitive(False)
804
m_remove.set_sensitive(False)
805
m_remove_and_delete.set_sensitive(False)
806
m_rename.set_sensitive(False)
807
m_annotate.set_sensitive(False)
808
m_diff.set_sensitive(False)
809
m_revert.set_sensitive(False)
811
m_open.set_sensitive(True)
812
m_add.set_sensitive(True)
813
m_remove.set_sensitive(True)
814
m_remove_and_delete.set_sensitive(True)
815
m_rename.set_sensitive(True)
816
m_annotate.set_sensitive(True)
817
m_diff.set_sensitive(True)
818
m_revert.set_sensitive(True)
819
m_commit.set_sensitive(True)
820
except bzrerrors.NotBranchError:
821
if treepathpos is None:
822
m_open.set_sensitive(False)
824
m_open.set_sensitive(True)
825
m_add.set_sensitive(False)
826
m_remove.set_sensitive(False)
827
m_remove_and_delete.set_sensitive(False)
828
m_rename.set_sensitive(False)
829
m_revert.set_sensitive(False)
830
m_commit.set_sensitive(False)
831
m_annotate.set_sensitive(False)
832
m_diff.set_sensitive(False)
835
menu.right_context_menu().popup(None, None, None, 0,
838
menu.remote_context_menu().popup(None, None, None, 0,
841
def on_treeview_right_row_activated(self, treeview, path, view_column):
842
""" Occurs when somebody double-clicks or enters an item in the
844
from launch import launch
846
newdir = self.get_selected_right()
851
self.set_path(os.path.split(self.get_path())[0])
853
fullpath = os.path.join(self.get_path(), newdir)
854
if os.path.isdir(fullpath):
855
# selected item is an existant directory
856
self.set_path(fullpath)
861
if self._is_remote_dir(self.get_path() + newdir):
862
self.set_path(self.get_path() + newdir)
866
def on_window_main_delete_event(self, widget, event=None):
867
""" Do some stuff before exiting. """
868
width, height = self.window.get_size()
869
self.pref.set_preference('window_width', width)
870
self.pref.set_preference('window_height', height)
871
x, y = self.window.get_position()
872
self.pref.set_preference('window_x', x)
873
self.pref.set_preference('window_y', y)
874
self.pref.set_preference('paned_position',
875
self.window.hpaned_main.get_position())
878
self.window.destroy()
880
def get_selected_fileid(self):
881
""" Get the file_id of the selected file. """
882
treeselection = self.window.treeview_right.get_selection()
883
(model, iter) = treeselection.get_selected()
888
return model.get_value(iter, 9)
890
def get_selected_right(self):
891
""" Get the selected filename. """
892
treeselection = self.window.treeview_right.get_selection()
893
(model, iter) = treeselection.get_selected()
898
return model.get_value(iter, 2)
900
def get_selected_left(self):
901
""" Get the selected bookmark. """
902
treeselection = self.window.treeview_left.get_selection()
903
(model, iter) = treeselection.get_selected()
908
return model.get_value(iter, 1)
910
def set_statusbar(self, message):
911
""" Set the statusbar message. """
912
self.window.statusbar.push(self.context_id, message)
914
def clear_statusbar(self):
915
""" Clean the last message from the statusbar. """
916
self.window.statusbar.pop(self.context_id)
918
def set_sensitivity(self):
919
""" Set menu and toolbar sensitivity. """
921
self.window.set_view_to_localbranch(self.notbranch)
923
self.window.set_view_to_remotebranch()
925
def refresh_left(self):
926
""" Refresh the bookmark list. """
928
# Get ListStore and clear it
929
liststore = self.window.bookmarklist
932
# Re-read preferences
936
bookmarks = self.pref.get_bookmarks()
938
# Get titles and sort by title
939
bookmarks = [[self.pref.get_bookmark_title(item), item, gtk.STOCK_DIRECTORY] for item in bookmarks]
941
for title_item in bookmarks:
942
liststore.append(title_item)
944
# Add the ListStore to the TreeView and refresh column width
945
self.window.treeview_left.set_model(liststore)
946
self.window.treeview_left.columns_autosize()
948
def refresh_right(self):
949
""" Refresh the file list. """
952
from bzrlib.workingtree import WorkingTree
954
path = self.get_path()
956
# Get ListStore and clear it
957
liststore = self.window.filelist
963
# Fill the appropriate lists
964
dotted_files = self.pref.get_preference('dotted_files', 'bool')
965
ignored_files = self.pref.get_preference('ignored_files', 'bool')
967
for item in os.listdir(path):
968
if not dotted_files and item[0] == '.':
970
if os.path.isdir(path + os.sep + item):
975
self.window.col_status.set_visible(False)
976
if not self.notbranch:
978
tree1 = WorkingTree.open_containing(os.path.realpath(path))[0]
979
branch = tree1.branch
980
tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
982
delta = tree1.changes_from(tree2, want_unchanged=True)
985
self.window.col_status.set_visible(True)
986
except bzrerrors.LockContention:
987
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR, allowPopup=True)
988
self.window.location_status.connect_object('clicked', error_dialog,
989
*(_i18n('Branch is locked'),
990
_i18n('The branch in the current folder is locked by another Bazaar program')))
991
self.notbranch = True
992
self.window.set_view_to_localbranch(False)
994
# Add'em to the ListStore
999
if not self.notbranch:
1000
filename = tree1.relpath(os.path.join(os.path.realpath(path), item))
1002
st, status = self.statusmapper(filename, delta)
1003
if not ignored_files and status == 'ignored':
1007
statinfo = os.stat(self.path + os.sep + item)
1009
if e.errno in self.acceptable_errors:
1013
liststore.append([ gtk.STOCK_DIRECTORY,
1021
self._format_date(statinfo.st_mtime),
1027
if not self.notbranch:
1028
filename = tree1.relpath(os.path.join(os.path.realpath(path), item))
1030
st, status = self.statusmapper(filename, delta)
1031
if not ignored_files and status == 'ignored':
1035
statinfo = os.stat(self.path + os.sep + item)
1037
if e.errno in self.acceptable_errors:
1041
liststore.append([gtk.STOCK_FILE,
1046
str(statinfo.st_size),
1047
self._format_size(statinfo.st_size),
1049
self._format_date(statinfo.st_mtime),
1054
# Get ListStore and clear it
1055
liststore = self.window.filelist
1058
# Hide Status column
1059
self.window.col_status.set_visible(False)
1064
self.window.set_location_status(gtk.STOCK_REFRESH)
1066
for (name, type) in self.remote_entries:
1067
if type.kind == 'directory':
1069
elif type.kind == 'file':
1073
""" Cache based on revision history. """
1074
def __init__(self, history):
1075
self._history = history
1077
def _lookup_revision(self, revid):
1078
for r in self._history:
1079
if r.revision_id == revid:
1081
rev = repo.get_revision(revid)
1082
self._history.append(rev)
1085
repo = self.remote_branch.repository
1087
revhistory = self.remote_branch.revision_history()
1089
revs = repo.get_revisions(revhistory)
1090
cache = HistoryCache(revs)
1091
except bzrerrors.InvalidHttpResponse:
1092
# Fallback to dummy algorithm, because of LP: #115209
1093
cache = HistoryCache([])
1096
if item.parent_id == self.remote_parent:
1097
rev = cache._lookup_revision(item.revision)
1098
liststore.append([ gtk.STOCK_DIRECTORY,
1106
self._format_date(rev.timestamp),
1109
while gtk.events_pending():
1110
gtk.main_iteration()
1113
if item.parent_id == self.remote_parent:
1114
rev = cache._lookup_revision(item.revision)
1115
liststore.append([ gtk.STOCK_FILE,
1120
str(item.text_size),
1121
self._format_size(item.text_size),
1123
self._format_date(rev.timestamp),
1126
while gtk.events_pending():
1127
gtk.main_iteration()
1129
self.window.location_status.destroy()
1131
# Columns should auto-size
1132
self.window.treeview_right.columns_autosize()
1135
self.set_sensitivity()
1137
def statusmapper(self, filename, delta):
1142
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
1143
if rpathnew == filename:
1146
for rpath, id, kind in delta.added:
1147
if rpath == filename:
1150
for rpath, id, kind in delta.removed:
1151
if rpath == filename:
1154
for rpath, id, kind, text_modified, meta_modified in delta.modified:
1155
if rpath == filename:
1158
for rpath, id, kind in delta.unchanged:
1159
if rpath == filename:
1160
status = 'unchanged'
1162
for rpath, file_class, kind, id, entry in self.wt.list_files():
1163
if rpath == filename and file_class == 'I':
1168
if status == 'renamed':
1169
st = _i18n('renamed')
1170
elif status == 'removed':
1171
st = _i18n('removed')
1172
elif status == 'added':
1174
elif status == 'modified':
1175
st = _i18n('modified')
1176
elif status == 'unchanged':
1177
st = _i18n('unchanged')
1178
elif status == 'ignored':
1179
st = _i18n('ignored')
1181
st = _i18n('unknown')
1184
def _harddisks(self):
1185
""" Returns hard drive letters under Win32. """
1190
if sys.platform == 'win32':
1191
print "pyWin32 modules needed to run Olive on Win32."
1195
for drive in string.ascii_uppercase:
1196
if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED or\
1197
win32file.GetDriveType(drive+':') == win32file.DRIVE_REMOTE or\
1198
win32file.GetDriveType(drive+':') == win32file.DRIVE_REMOVABLE:
1199
driveletters.append(drive+':')
1202
def gen_hard_selector(self):
1203
""" Generate the hard drive selector under Win32. """
1204
drives = self._harddisks()
1205
for drive in drives:
1206
self.combobox_drive.append_text(drive)
1207
self.combobox_drive.set_active(drives.index(os.getcwd()[0:2]))
1209
def _refresh_drives(self, combobox):
1210
if self._just_started:
1212
model = combobox.get_model()
1213
active = combobox.get_active()
1215
drive = model[active][0]
1216
self.set_path(drive + '\\')
1217
self.refresh_right()
1219
def check_for_changes(self):
1220
""" Check whether there were changes in the current working tree. """
1221
old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
1222
delta = self.wt.changes_from(old_tree)
1226
if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
1231
def _sort_filelist_callback(self, model, iter1, iter2, data):
1232
""" The sort callback for the file list, return values:
1237
name1 = model.get_value(iter1, 2)
1238
name2 = model.get_value(iter2, 2)
1240
if model.get_value(iter1, 1):
1241
# item1 is a directory
1242
if not model.get_value(iter2, 1):
1246
# both of them are directories, we compare their names
1249
elif name1 == name2:
1254
# item1 is not a directory
1255
if model.get_value(iter2, 1):
1259
# both of them are files, compare them
1262
elif name1 == name2:
1267
def _format_size(self, size):
1268
""" Format size to a human readable format. """
1270
return "%d[B]" % (size,)
1271
size = size / 1000.0
1273
for metric in ["kB","MB","GB","TB"]:
1276
size = size / 1000.0
1277
return "%.1f[%s]" % (size,metric)
1279
def _format_date(self, timestamp):
1280
""" Format the time (given in secs) to a human readable format. """
1281
return time.ctime(timestamp)
1283
def _is_remote_dir(self, location):
1284
""" Determine whether the given location is a directory or not. """
1286
# We're in local mode
1289
branch, path = Branch.open_containing(location)
1290
for (name, type) in self.remote_entries:
1291
if name == path and type.kind == 'directory':
1294
# Either it's not a directory or not in the inventory
1300
""" A class which handles Olive's preferences. """
1301
def __init__(self, path=None):
1302
""" Initialize the Preferences class. """
1303
# Some default options
1304
self.defaults = { 'strict_commit' : False,
1305
'dotted_files' : False,
1306
'ignored_files' : True,
1307
'window_width' : 700,
1308
'window_height' : 400,
1311
'paned_position': 200 }
1313
# Create a config parser object
1314
self.config = ConfigParser.RawConfigParser()
1318
if sys.platform == 'win32':
1319
# Windows - no dotted files
1320
self._filename = os.path.expanduser('~/olive.conf')
1322
self._filename = os.path.expanduser('~/.olive.conf')
1324
self._filename = path
1326
# Load the configuration
1329
def _get_default(self, option):
1330
""" Get the default option for a preference. """
1332
ret = self.defaults[option]
1339
""" Refresh the configuration. """
1340
# First write out the changes
1342
# Then load the configuration again
1346
""" Just read the configuration. """
1347
# Re-initialize the config parser object to avoid some bugs
1348
self.config = ConfigParser.RawConfigParser()
1349
self.config.read([self._filename])
1352
""" Write the configuration to the appropriate files. """
1353
fp = open(self._filename, 'w')
1354
self.config.write(fp)
1357
def get_bookmarks(self):
1358
""" Return the list of bookmarks. """
1359
bookmarks = self.config.sections()
1360
if self.config.has_section('preferences'):
1361
bookmarks.remove('preferences')
1364
def add_bookmark(self, path):
1365
""" Add bookmark. """
1367
self.config.add_section(path)
1368
except ConfigParser.DuplicateSectionError:
1373
def get_bookmark_title(self, path):
1374
""" Get bookmark title. """
1376
ret = self.config.get(path, 'title')
1377
except ConfigParser.NoOptionError:
1382
def set_bookmark_title(self, path, title):
1383
""" Set bookmark title. """
1384
# FIXME: What if path isn't listed yet?
1385
# FIXME: Canonicalize paths first?
1386
self.config.set(path, 'title', title)
1388
def remove_bookmark(self, path):
1389
""" Remove bookmark. """
1390
return self.config.remove_section(path)
1392
def set_preference(self, option, value):
1393
""" Set the value of the given option. """
1396
elif value is False:
1399
if self.config.has_section('preferences'):
1400
self.config.set('preferences', option, value)
1402
self.config.add_section('preferences')
1403
self.config.set('preferences', option, value)
1405
def get_preference(self, option, kind='str'):
1406
""" Get the value of the given option.
1408
:param kind: str/bool/int/float. default: str
1410
if self.config.has_option('preferences', option):
1412
return self.config.getboolean('preferences', option)
1414
return self.config.getint('preferences', option)
1415
elif kind == 'float':
1416
return self.config.getfloat('preferences', option)
1418
return self.config.get('preferences', option)
1421
return self._get_default(option)