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()
139
self.window.mb_view_showhidden.set_active(self.pref.get_preference('dotted_files', 'bool'))
140
self.window.mb_view_showignored.set_active(self.pref.get_preference('ignored_files', 'bool'))
142
# We're starting local
144
self.remote_branch = None
145
self.remote_path = None
146
self.remote_revision = None
148
self.set_path(os.getcwd())
151
self._just_started = False
153
def set_path(self, path, force_remote=False):
154
self.window.location_status.destroy()
155
self.notbranch = False
158
# Forcing remote mode (reading data from inventory)
159
self.window.set_location_status(gtk.STOCK_DISCONNECT)
161
br = Branch.open_containing(path)[0]
162
except bzrerrors.NotBranchError:
163
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
164
self.check_history.set_active(False)
165
self.check_history.set_sensitive(False)
167
except bzrerrors.UnsupportedProtocol:
168
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
169
self.check_history.set_active(False)
170
self.check_history.set_sensitive(False)
173
self.window.set_location_status(gtk.STOCK_CONNECT)
178
self.remote_branch, self.remote_path = Branch.open_containing(path)
180
if self.remote_revision is None:
181
self.remote_revision = self.remote_branch.last_revision()
183
self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
185
if len(self.remote_path) == 0:
186
self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
188
for (name, type) in self.remote_entries:
189
if name == self.remote_path:
190
self.remote_parent = type.file_id
193
if not path.endswith('/'):
196
if self.remote_branch.base == path:
197
self.button_location_up.set_sensitive(False)
199
self.button_location_up.set_sensitive(True)
201
if os.path.isdir(path):
206
self.wt, self.wtpath = WorkingTree.open_containing(os.path.realpath(path))
207
except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
208
self.notbranch = True
209
except bzrerrors.PermissionDenied:
210
self.window.set_location_status(gtk.STOCK_DIALOG_WARNING, allowPopup=True)
211
self.window.location_status.connect_object('clicked', warning_dialog,
212
*(_i18n('Branch information unreadable'),
213
_i18n('The current folder is a branch but the .bzr folder is not readable')))
214
self.notbranch = True
216
# If we're in the root, we cannot go up anymore
217
if sys.platform == 'win32':
218
drive, tail = os.path.splitdrive(path)
219
if tail in ('', '/', '\\'):
220
self.button_location_up.set_sensitive(False)
222
self.button_location_up.set_sensitive(True)
225
self.button_location_up.set_sensitive(False)
227
self.button_location_up.set_sensitive(True)
228
elif not os.path.isfile(path):
229
# Doesn't seem to be a file nor a directory, trying to open a
231
self.window.set_location_status(gtk.STOCK_DISCONNECT)
233
br = Branch.open_containing(path)[0]
234
except bzrerrors.NotBranchError:
235
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
236
self.check_history.set_active(False)
237
self.check_history.set_sensitive(False)
239
except bzrerrors.UnsupportedProtocol:
240
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
241
self.check_history.set_active(False)
242
self.check_history.set_sensitive(False)
245
self.window.set_location_status(gtk.STOCK_CONNECT)
250
self.remote_branch, self.remote_path = Branch.open_containing(path)
252
if self.remote_revision is None:
253
self.remote_revision = self.remote_branch.last_revision()
255
self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
257
if len(self.remote_path) == 0:
258
self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
260
for (name, type) in self.remote_entries:
261
if name == self.remote_path:
262
self.remote_parent = type.file_id
265
if not path.endswith('/'):
268
if self.remote_branch.base == path:
269
self.button_location_up.set_sensitive(False)
271
self.button_location_up.set_sensitive(True)
274
self.check_history.set_active(False)
275
self.check_history.set_sensitive(False)
277
self.check_history.set_sensitive(True)
279
self.window.statusbar.push(self.context_id, path)
280
self.entry_location.set_text(path)
289
if len(self.remote_path) > 0:
290
return self.remote_branch.base + self.remote_path + '/'
292
return self.remote_branch.base
294
def on_about_activate(self, widget):
297
def on_button_history_browse_clicked(self, widget):
298
""" Browse for revision button handler. """
300
br = self.remote_branch
304
revb = RevisionBrowser(br, self.window)
305
response = revb.run()
306
if response != gtk.RESPONSE_NONE:
309
if response == gtk.RESPONSE_OK:
310
if revb.selected_revno is not None:
311
self.entry_history.set_text(revb.selected_revno)
312
self.on_entry_history_revno_activate()
316
def on_button_location_jump_clicked(self, widget):
317
""" Location Jump button handler. """
318
location = self.entry_location.get_text()
320
if self.set_path(location):
323
def on_button_location_up_clicked(self, widget):
324
""" Location Up button handler. """
327
self.set_path(os.path.split(self.get_path())[0])
331
newpath = delim.join(self.get_path().split(delim)[:-2])
333
self.set_path(newpath)
337
def on_checkbutton_history_toggled(self, widget):
338
""" History Mode toggle handler. """
339
if self.check_history.get_active():
340
# History Mode activated
341
self.entry_history.set_sensitive(True)
342
self.button_history.set_sensitive(True)
343
if self.entry_history.get_text() != "":
344
self.on_entry_history_revno_activate()
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_activate(self, widget=None):
355
""" Key pressed handler for the history entry. """
356
path = self.get_path()
359
self.remote_branch = self.wt.branch
361
revno = int(self.entry_history.get_text())
362
self.remote_revision = self.remote_branch.get_rev_id(revno)
363
if self.set_path(path, True):
366
def on_menuitem_add_files_activate(self, widget):
367
""" Add file(s)... menu handler. """
368
from bzrlib.plugins.gtk.olive.add import AddDialog
369
add = AddDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
372
if response == gtk.RESPONSE_OK:
375
def on_menuitem_branch_get_activate(self, widget):
376
""" Branch/Get... menu handler. """
377
from bzrlib.plugins.gtk.branch import BranchDialog
380
branch = BranchDialog(os.getcwd(), self.window, self.remote_branch.base)
382
branch = BranchDialog(self.get_path(), self.window)
383
response = branch.run()
384
if response != gtk.RESPONSE_NONE:
387
if response == gtk.RESPONSE_OK:
392
def on_menuitem_branch_checkout_activate(self, widget):
393
""" Branch/Checkout... menu handler. """
394
from bzrlib.plugins.gtk.checkout import CheckoutDialog
397
checkout = CheckoutDialog(os.getcwd(), self.window, self.remote_branch.base)
399
checkout = CheckoutDialog(self.get_path(), self.window)
400
response = checkout.run()
401
if response != gtk.RESPONSE_NONE:
404
if response == gtk.RESPONSE_OK:
410
def on_menuitem_branch_commit_activate(self, widget):
411
""" Branch/Commit... menu handler. """
412
selected = self.get_selected_right()
414
selected = os.path.join(self.wtpath, selected)
415
commit = CommitDialog(wt=self.wt,
419
response = commit.run()
420
if response != gtk.RESPONSE_NONE:
423
if response == gtk.RESPONSE_OK:
428
def on_menuitem_branch_conflicts_activate(self, widget):
429
""" Branch/Conflicts... menu handler. """
430
conflicts = ConflictsDialog(self.wt, self.window)
431
response = conflicts.run()
432
if response != gtk.RESPONSE_NONE:
435
def on_menuitem_branch_merge_activate(self, widget):
436
""" Branch/Merge... menu handler. """
437
from bzrlib.plugins.gtk.merge import MergeDialog
439
if self.check_for_changes():
440
error_dialog(_i18n('There are local changes in the branch'),
441
_i18n('Please commit or revert the changes before merging.'))
443
parent_branch_path = self.wt.branch.get_parent()
444
merge = MergeDialog(self.wt, self.wtpath, parent_branch_path, self.window)
445
response = merge.run()
447
if response == gtk.RESPONSE_OK:
451
def on_menuitem_branch_missing_revisions_activate(self, widget):
452
""" Branch/Missing revisions menu handler. """
454
from bzrlib.missing import find_unmerged, iter_log_revisions
456
local_branch = self.wt.branch
457
parent_branch_path = local_branch.get_parent()
458
if parent_branch_path is None:
459
error_dialog(_i18n('Parent location is unknown'),
460
_i18n('Cannot determine missing revisions if no parent location is known.'))
463
parent_branch = Branch.open(parent_branch_path)
465
if parent_branch.base == local_branch.base:
466
parent_branch = local_branch
468
local_extra, remote_extra = find_unmerged(local_branch,parent_branch)
470
if local_extra or remote_extra:
472
## def log_revision_one_line_text(log_revision):
473
## """ Generates one line description of log_revison ended with end of line."""
474
## revision = log_revision.rev
475
## txt = "- %s (%s)\n" % (revision.get_summary(), revision.committer, )
476
## txt = txt.replace("<"," ") # Seems < > chars are expected to be xml tags ...
477
## txt = txt.replace(">"," ")
482
dlg_txt += _i18n('%d local extra revision(s). \n') % (len(local_extra),)
483
## NOTE: We do not want such ugly info about missing revisions
484
## Revision Browser should be used there
485
## max_revisions = 10
486
## for log_revision in iter_log_revisions(local_extra, local_branch.repository, verbose=1):
487
## dlg_txt += log_revision_one_line_text(log_revision)
488
## if max_revisions <= 0:
489
## dlg_txt += _i18n("more ... \n")
491
## max_revisions -= 1
494
dlg_txt += _i18n('%d local missing revision(s).\n') % (len(remote_extra),)
495
## max_revisions = 10
496
## for log_revision in iter_log_revisions(remote_extra, parent_branch.repository, verbose=1):
497
## dlg_txt += log_revision_one_line_text(log_revision)
498
## if max_revisions <= 0:
499
## dlg_txt += _i18n("more ... \n")
501
## max_revisions -= 1
503
info_dialog(_i18n('There are missing revisions'),
506
info_dialog(_i18n('Local branch up to date'),
507
_i18n('There are no missing revisions.'))
510
def on_menuitem_branch_pull_activate(self, widget):
511
""" Branch/Pull menu handler. """
512
branch_to = self.wt.branch
514
location = branch_to.get_parent()
516
error_dialog(_i18n('Parent location is unknown'),
517
_i18n('Pulling is not possible until there is a parent location.'))
520
branch_from = Branch.open(location)
522
if branch_to.get_parent() is None:
523
branch_to.set_parent(branch_from.base)
525
ret = branch_to.pull(branch_from)
527
info_dialog(_i18n('Pull successful'), _i18n('%d revision(s) pulled.') % ret)
530
def on_menuitem_branch_update_activate(self, widget):
531
""" Branch/checkout update menu handler. """
533
ret = self.wt.update()
534
conflicts = self.wt.conflicts()
536
info_dialog(_i18n('Update successful but conflicts generated'), _i18n('Number of conflicts generated: %d.') % (len(conflicts),) )
538
info_dialog(_i18n('Update successful'), _i18n('No conflicts generated.') )
541
def on_menuitem_branch_push_activate(self, widget):
542
""" Branch/Push... menu handler. """
543
push = PushDialog(repository=None,revid=None,branch=self.wt.branch, parent=self.window)
544
response = push.run()
545
if response != gtk.RESPONSE_NONE:
549
def on_menuitem_branch_revert_activate(self, widget):
550
""" Branch/Revert all changes menu handler. """
551
ret = self.wt.revert(None)
553
warning_dialog(_i18n('Conflicts detected'),
554
_i18n('Please have a look at the working tree before continuing.'))
556
info_dialog(_i18n('Revert successful'),
557
_i18n('All files reverted to last revision.'))
560
def on_menuitem_branch_status_activate(self, widget):
561
""" Branch/Status... menu handler. """
562
from bzrlib.plugins.gtk.status import StatusDialog
563
status = StatusDialog(self.wt, self.wtpath)
564
response = status.run()
565
if response != gtk.RESPONSE_NONE:
568
def on_menuitem_branch_initialize_activate(self, widget):
569
""" Initialize current directory. """
570
init = InitDialog(self.path, self.window)
571
response = init.run()
572
if response != gtk.RESPONSE_NONE:
575
if response == gtk.RESPONSE_OK:
580
def on_menuitem_branch_tags_activate(self, widget):
581
""" Branch/Tags... menu handler. """
582
from bzrlib.plugins.gtk.tags import TagsWindow
584
window = TagsWindow(self.wt.branch, self.window)
586
window = TagsWindow(self.remote_branch, self.window)
589
def on_menuitem_file_annotate_activate(self, widget):
590
""" File/Annotate... menu handler. """
591
if self.get_selected_right() is None:
592
error_dialog(_i18n('No file was selected'),
593
_i18n('Please select a file from the list.'))
596
branch = self.wt.branch
597
file_id = self.wt.path2id(self.wt.relpath(os.path.join(self.path, self.get_selected_right())))
599
window = GAnnotateWindow(all=False, plain=False, parent=self.window)
600
window.set_title(os.path.join(self.path, self.get_selected_right()) + " - Annotate")
601
config = GAnnotateConfig(window)
605
window.annotate(self.wt, branch, file_id)
609
def on_menuitem_file_bookmark_activate(self, widget):
610
""" File/Bookmark current directory menu handler. """
611
if self.pref.add_bookmark(self.path):
612
info_dialog(_i18n('Bookmark successfully added'),
613
_i18n('The current directory was bookmarked. You can reach\nit by selecting it from the left panel.'))
616
warning_dialog(_i18n('Location already bookmarked'),
617
_i18n('The current directory is already bookmarked.\nSee the left panel for reference.'))
621
def on_menuitem_file_make_directory_activate(self, widget):
622
""" File/Make directory... menu handler. """
623
from bzrlib.plugins.gtk.olive.mkdir import MkdirDialog
624
mkdir = MkdirDialog(self.wt, self.wtpath, self.window)
625
response = mkdir.run()
627
if response == gtk.RESPONSE_OK:
630
def on_menuitem_file_move_activate(self, widget):
631
""" File/Move... menu handler. """
632
from bzrlib.plugins.gtk.olive.move import MoveDialog
633
move = MoveDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
634
response = move.run()
636
if response == gtk.RESPONSE_OK:
639
def on_menuitem_file_rename_activate(self, widget):
640
""" File/Rename... menu handler. """
641
from bzrlib.plugins.gtk.olive.rename import RenameDialog
642
rename = RenameDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
643
response = rename.run()
645
if response == gtk.RESPONSE_OK:
648
def on_menuitem_remove_file_activate(self, widget):
649
""" Remove (unversion) selected file. """
650
from bzrlib.plugins.gtk.olive.remove import RemoveDialog
651
remove = RemoveDialog(self.wt, self.wtpath,
652
selected=self.get_selected_right(),
654
response = remove.run()
656
if response != gtk.RESPONSE_NONE:
659
if response == gtk.RESPONSE_OK:
664
def on_menuitem_stats_diff_activate(self, widget):
665
""" Statistics/Differences... menu handler. """
666
window = DiffWindow(parent=self.window)
667
parent_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
668
window.set_diff(self.wt.branch._get_nick(local=True), self.wt,
672
def on_menuitem_stats_infos_activate(self, widget):
673
""" Statistics/Informations... menu handler. """
674
from bzrlib.plugins.gtk.olive.info import InfoDialog
676
info = InfoDialog(self.remote_branch)
678
info = InfoDialog(self.wt.branch)
681
def on_menuitem_stats_log_activate(self, widget):
682
""" Statistics/Log... menu handler. """
685
branch = self.wt.branch
687
branch = self.remote_branch
689
window = branchwin.BranchWindow(branch, [branch.last_revision()], None,
693
def on_menuitem_view_refresh_activate(self, widget):
694
""" View/Refresh menu handler. """
695
# Refresh the left pane
697
# Refresh the right pane
700
def on_menuitem_view_show_hidden_files_activate(self, widget):
701
""" View/Show hidden files menu handler. """
702
self.pref.set_preference('dotted_files', widget.get_active())
704
if self.path is not None:
707
def on_menuitem_view_show_ignored_files_activate(self, widget):
708
""" Hide/Show ignored files menu handler. """
709
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 get_selected_fileid(self):
879
""" Get the file_id of the selected file. """
880
treeselection = self.window.treeview_right.get_selection()
881
(model, iter) = treeselection.get_selected()
886
return model.get_value(iter, 9)
888
def get_selected_right(self):
889
""" Get the selected filename. """
890
treeselection = self.window.treeview_right.get_selection()
891
(model, iter) = treeselection.get_selected()
896
return model.get_value(iter, 2)
898
def get_selected_left(self):
899
""" Get the selected bookmark. """
900
treeselection = self.window.treeview_left.get_selection()
901
(model, iter) = treeselection.get_selected()
906
return model.get_value(iter, 1)
908
def set_statusbar(self, message):
909
""" Set the statusbar message. """
910
self.window.statusbar.push(self.context_id, message)
912
def clear_statusbar(self):
913
""" Clean the last message from the statusbar. """
914
self.window.statusbar.pop(self.context_id)
916
def set_sensitivity(self):
917
""" Set menu and toolbar sensitivity. """
919
self.window.set_view_to_localbranch(self.notbranch)
921
self.window.set_view_to_remotebranch()
923
def refresh_left(self):
924
""" Refresh the bookmark list. """
926
# Get ListStore and clear it
927
liststore = self.window.bookmarklist
930
# Re-read preferences
934
bookmarks = self.pref.get_bookmarks()
936
# Get titles and sort by title
937
bookmarks = [[self.pref.get_bookmark_title(item), item, gtk.STOCK_DIRECTORY] for item in bookmarks]
939
for title_item in bookmarks:
940
liststore.append(title_item)
942
# Add the ListStore to the TreeView and refresh column width
943
self.window.treeview_left.set_model(liststore)
944
self.window.treeview_left.columns_autosize()
946
def refresh_right(self):
947
""" Refresh the file list. """
950
from bzrlib.workingtree import WorkingTree
952
path = self.get_path()
954
# Get ListStore and clear it
955
liststore = self.window.filelist
961
# Fill the appropriate lists
962
dotted_files = self.pref.get_preference('dotted_files', 'bool')
963
ignored_files = self.pref.get_preference('ignored_files', 'bool')
965
for item in os.listdir(path):
966
if not dotted_files and item[0] == '.':
968
if os.path.isdir(os.path.join(path, item)):
973
self.window.col_status.set_visible(False)
974
if not self.notbranch:
976
tree1 = WorkingTree.open_containing(os.path.realpath(path))[0]
977
branch = tree1.branch
978
tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
980
delta = tree1.changes_from(tree2, want_unchanged=True)
983
self.window.col_status.set_visible(True)
984
except bzrerrors.LockContention:
985
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR, allowPopup=True)
986
self.window.location_status.connect_object('clicked', error_dialog,
987
*(_i18n('Branch is locked'),
988
_i18n('The branch in the current folder is locked by another Bazaar program')))
989
self.notbranch = True
990
self.window.set_view_to_localbranch(False)
992
# Add'em to the ListStore
997
if not self.notbranch:
998
filename = tree1.relpath(os.path.join(os.path.realpath(path), item))
1000
st, status = self.statusmapper(filename, delta)
1001
if not ignored_files and status == 'ignored':
1004
statinfo = os.lstat(os.path.join(self.path, item))
1005
liststore.append([ gtk.STOCK_DIRECTORY,
1013
self._format_date(statinfo.st_mtime),
1019
if not self.notbranch:
1020
filename = tree1.relpath(os.path.join(os.path.realpath(path), item))
1022
st, status = self.statusmapper(filename, delta)
1023
if not ignored_files and status == 'ignored':
1026
statinfo = os.lstat(os.path.join(self.path, item))
1027
liststore.append([gtk.STOCK_FILE,
1032
str(statinfo.st_size),
1033
self._format_size(statinfo.st_size),
1035
self._format_date(statinfo.st_mtime),
1040
# Get ListStore and clear it
1041
liststore = self.window.filelist
1044
# Hide Status column
1045
self.window.col_status.set_visible(False)
1050
self.window.set_location_status(gtk.STOCK_REFRESH)
1052
for (name, type) in self.remote_entries:
1053
if type.kind == 'directory':
1055
elif type.kind == 'file':
1059
""" Cache based on revision history. """
1060
def __init__(self, history):
1061
self._history = history
1063
def _lookup_revision(self, revid):
1064
for r in self._history:
1065
if r.revision_id == revid:
1067
rev = repo.get_revision(revid)
1068
self._history.append(rev)
1071
repo = self.remote_branch.repository
1073
revhistory = self.remote_branch.revision_history()
1075
revs = repo.get_revisions(revhistory)
1076
cache = HistoryCache(revs)
1077
except bzrerrors.InvalidHttpResponse:
1078
# Fallback to dummy algorithm, because of LP: #115209
1079
cache = HistoryCache([])
1082
if item.parent_id == self.remote_parent:
1083
rev = cache._lookup_revision(item.revision)
1084
liststore.append([ gtk.STOCK_DIRECTORY,
1092
self._format_date(rev.timestamp),
1095
while gtk.events_pending():
1096
gtk.main_iteration()
1099
if item.parent_id == self.remote_parent:
1100
rev = cache._lookup_revision(item.revision)
1101
liststore.append([ gtk.STOCK_FILE,
1106
str(item.text_size),
1107
self._format_size(item.text_size),
1109
self._format_date(rev.timestamp),
1112
while gtk.events_pending():
1113
gtk.main_iteration()
1115
self.window.location_status.destroy()
1117
# Columns should auto-size
1118
self.window.treeview_right.columns_autosize()
1121
self.set_sensitivity()
1123
def statusmapper(self, filename, delta):
1128
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
1129
if rpathnew == filename:
1132
for rpath, id, kind in delta.added:
1133
if rpath == filename:
1136
for rpath, id, kind in delta.removed:
1137
if rpath == filename:
1140
for rpath, id, kind, text_modified, meta_modified in delta.modified:
1141
if rpath == filename:
1144
for rpath, id, kind in delta.unchanged:
1145
if rpath == filename:
1146
status = 'unchanged'
1148
for rpath, file_class, kind, id, entry in self.wt.list_files():
1149
if rpath == filename and file_class == 'I':
1154
if status == 'renamed':
1155
st = _i18n('renamed')
1156
elif status == 'removed':
1157
st = _i18n('removed')
1158
elif status == 'added':
1160
elif status == 'modified':
1161
st = _i18n('modified')
1162
elif status == 'unchanged':
1163
st = _i18n('unchanged')
1164
elif status == 'ignored':
1165
st = _i18n('ignored')
1167
st = _i18n('unknown')
1170
def _harddisks(self):
1171
""" Returns hard drive letters under Win32. """
1176
if sys.platform == 'win32':
1177
print "pyWin32 modules needed to run Olive on Win32."
1181
for drive in string.ascii_uppercase:
1182
if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED or\
1183
win32file.GetDriveType(drive+':') == win32file.DRIVE_REMOTE or\
1184
win32file.GetDriveType(drive+':') == win32file.DRIVE_REMOVABLE:
1185
driveletters.append(drive+':')
1188
def gen_hard_selector(self):
1189
""" Generate the hard drive selector under Win32. """
1190
drives = self._harddisks()
1191
for drive in drives:
1192
self.combobox_drive.append_text(drive)
1193
self.combobox_drive.set_active(drives.index(os.getcwd()[0:2]))
1195
def _refresh_drives(self, combobox):
1196
if self._just_started:
1198
model = combobox.get_model()
1199
active = combobox.get_active()
1201
drive = model[active][0]
1202
self.set_path(drive + '\\')
1203
self.refresh_right()
1205
def check_for_changes(self):
1206
""" Check whether there were changes in the current working tree. """
1207
old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
1208
delta = self.wt.changes_from(old_tree)
1212
if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
1217
def _sort_filelist_callback(self, model, iter1, iter2, data):
1218
""" The sort callback for the file list, return values:
1223
name1 = model.get_value(iter1, 2)
1224
name2 = model.get_value(iter2, 2)
1226
if model.get_value(iter1, 1):
1227
# item1 is a directory
1228
if not model.get_value(iter2, 1):
1232
# both of them are directories, we compare their names
1235
elif name1 == name2:
1240
# item1 is not a directory
1241
if model.get_value(iter2, 1):
1245
# both of them are files, compare them
1248
elif name1 == name2:
1253
def _format_size(self, size):
1254
""" Format size to a human readable format. """
1256
return "%d[B]" % (size,)
1257
size = size / 1000.0
1259
for metric in ["kB","MB","GB","TB"]:
1262
size = size / 1000.0
1263
return "%.1f[%s]" % (size,metric)
1265
def _format_date(self, timestamp):
1266
""" Format the time (given in secs) to a human readable format. """
1267
return time.ctime(timestamp)
1269
def _is_remote_dir(self, location):
1270
""" Determine whether the given location is a directory or not. """
1272
# We're in local mode
1275
branch, path = Branch.open_containing(location)
1276
for (name, type) in self.remote_entries:
1277
if name == path and type.kind == 'directory':
1280
# Either it's not a directory or not in the inventory
1286
""" A class which handles Olive's preferences. """
1287
def __init__(self, path=None):
1288
""" Initialize the Preferences class. """
1289
# Some default options
1290
self.defaults = { 'strict_commit' : False,
1291
'dotted_files' : False,
1292
'ignored_files' : True,
1293
'window_width' : 700,
1294
'window_height' : 400,
1297
'paned_position': 200 }
1299
# Create a config parser object
1300
self.config = ConfigParser.RawConfigParser()
1304
if sys.platform == 'win32':
1305
# Windows - no dotted files
1306
self._filename = os.path.expanduser('~/olive.conf')
1308
self._filename = os.path.expanduser('~/.olive.conf')
1310
self._filename = path
1312
# Load the configuration
1315
def _get_default(self, option):
1316
""" Get the default option for a preference. """
1318
ret = self.defaults[option]
1325
""" Refresh the configuration. """
1326
# First write out the changes
1328
# Then load the configuration again
1332
""" Just read the configuration. """
1333
# Re-initialize the config parser object to avoid some bugs
1334
self.config = ConfigParser.RawConfigParser()
1335
self.config.read([self._filename])
1338
""" Write the configuration to the appropriate files. """
1339
fp = open(self._filename, 'w')
1340
self.config.write(fp)
1343
def get_bookmarks(self):
1344
""" Return the list of bookmarks. """
1345
bookmarks = self.config.sections()
1346
if self.config.has_section('preferences'):
1347
bookmarks.remove('preferences')
1350
def add_bookmark(self, path):
1351
""" Add bookmark. """
1353
self.config.add_section(path)
1354
except ConfigParser.DuplicateSectionError:
1359
def get_bookmark_title(self, path):
1360
""" Get bookmark title. """
1362
ret = self.config.get(path, 'title')
1363
except ConfigParser.NoOptionError:
1368
def set_bookmark_title(self, path, title):
1369
""" Set bookmark title. """
1370
# FIXME: What if path isn't listed yet?
1371
# FIXME: Canonicalize paths first?
1372
self.config.set(path, 'title', title)
1374
def remove_bookmark(self, path):
1375
""" Remove bookmark. """
1376
return self.config.remove_section(path)
1378
def set_preference(self, option, value):
1379
""" Set the value of the given option. """
1382
elif value is False:
1385
if self.config.has_section('preferences'):
1386
self.config.set('preferences', option, value)
1388
self.config.add_section('preferences')
1389
self.config.set('preferences', option, value)
1391
def get_preference(self, option, kind='str'):
1392
""" Get the value of the given option.
1394
:param kind: str/bool/int/float. default: str
1396
if self.config.has_option('preferences', option):
1398
return self.config.getboolean('preferences', option)
1400
return self.config.getint('preferences', option)
1401
elif kind == 'float':
1402
return self.config.getfloat('preferences', option)
1404
return self.config.get('preferences', option)
1407
return self._get_default(option)