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.nick, self.wt, parent_tree)
671
def on_menuitem_stats_infos_activate(self, widget):
672
""" Statistics/Informations... menu handler. """
673
from bzrlib.plugins.gtk.olive.info import InfoDialog
675
info = InfoDialog(self.remote_branch)
677
info = InfoDialog(self.wt.branch)
680
def on_menuitem_stats_log_activate(self, widget):
681
""" Statistics/Log... menu handler. """
684
branch = self.wt.branch
686
branch = self.remote_branch
688
window = branchwin.BranchWindow(branch, [branch.last_revision()], None,
692
def on_menuitem_view_refresh_activate(self, widget):
693
""" View/Refresh menu handler. """
694
# Refresh the left pane
696
# Refresh the right pane
699
def on_menuitem_view_show_hidden_files_activate(self, widget):
700
""" View/Show hidden files menu handler. """
701
self.pref.set_preference('dotted_files', widget.get_active())
703
if self.path is not None:
706
def on_menuitem_view_show_ignored_files_activate(self, widget):
707
""" Hide/Show ignored files menu handler. """
708
self.pref.set_preference('ignored_files', widget.get_active())
710
if self.path is not None:
713
def on_treeview_left_button_press_event(self, widget, event):
714
""" Occurs when somebody clicks in the bookmark list. """
715
treepathpos = widget.get_path_at_pos(int(event.x), int(event.y))
716
treeselection = widget.get_selection()
717
if treepathpos is not None:
718
treeselection.select_path(treepathpos[0])
719
if event.button == 1:
720
newdir = self.get_selected_left()
724
if self.set_path(newdir):
726
elif event.button == 3:
727
# Don't show context with nothing selected
728
if self.get_selected_left() == None:
732
from menu import OliveMenu
733
menu = OliveMenu(path=self.get_path(),
734
selected=self.get_selected_left(),
737
menu.left_context_menu().popup(None, None, None, 0,
740
if treeselection is not None:
741
treeselection.unselect_all()
743
def on_treeview_left_row_activated(self, treeview, path, view_column):
744
""" Occurs when somebody double-clicks or enters an item in the
747
newdir = self.get_selected_left()
751
if self.set_path(newdir):
754
def on_treeview_right_button_press_event(self, widget, event):
755
""" Occurs when somebody clicks in the file list. """
756
treepathpos = widget.get_path_at_pos(int(event.x), int(event.y))
757
if event.button == 1:
758
if treepathpos is None and widget.get_selection is not None:
759
treeselection = widget.get_selection()
760
treeselection.unselect_all()
761
elif event.button == 3:
762
treeselection = widget.get_selection()
763
if treepathpos is not None:
764
treeselection.select_path(treepathpos[0])
766
if treeselection is not None:
767
treeselection.unselect_all()
769
from menu import OliveMenu
770
menu = OliveMenu(path=self.get_path(),
771
selected=self.get_selected_right(),
774
m_open = menu.ui.get_widget('/context_right/open')
775
m_add = menu.ui.get_widget('/context_right/add')
776
m_remove = menu.ui.get_widget('/context_right/remove')
777
m_remove_and_delete = menu.ui.get_widget('/context_right/remove_and_delete')
778
m_rename = menu.ui.get_widget('/context_right/rename')
779
m_revert = menu.ui.get_widget('/context_right/revert')
780
m_commit = menu.ui.get_widget('/context_right/commit')
781
m_annotate = menu.ui.get_widget('/context_right/annotate')
782
m_diff = menu.ui.get_widget('/context_right/diff')
783
# check if we're in a branch
785
from bzrlib.branch import Branch
786
Branch.open_containing(self.get_path())
788
m_open.set_sensitive(False)
789
m_add.set_sensitive(False)
790
m_remove.set_sensitive(False)
791
m_remove_and_delete.set_sensitive(False)
792
m_rename.set_sensitive(False)
793
m_revert.set_sensitive(False)
794
m_commit.set_sensitive(False)
795
m_annotate.set_sensitive(False)
796
m_diff.set_sensitive(False)
798
if treepathpos is None:
799
m_open.set_sensitive(False)
800
m_add.set_sensitive(False)
801
m_remove.set_sensitive(False)
802
m_remove_and_delete.set_sensitive(False)
803
m_rename.set_sensitive(False)
804
m_annotate.set_sensitive(False)
805
m_diff.set_sensitive(False)
806
m_revert.set_sensitive(False)
808
m_open.set_sensitive(True)
809
m_add.set_sensitive(True)
810
m_remove.set_sensitive(True)
811
m_remove_and_delete.set_sensitive(True)
812
m_rename.set_sensitive(True)
813
m_annotate.set_sensitive(True)
814
m_diff.set_sensitive(True)
815
m_revert.set_sensitive(True)
816
m_commit.set_sensitive(True)
817
except bzrerrors.NotBranchError:
818
if treepathpos is None:
819
m_open.set_sensitive(False)
821
m_open.set_sensitive(True)
822
m_add.set_sensitive(False)
823
m_remove.set_sensitive(False)
824
m_remove_and_delete.set_sensitive(False)
825
m_rename.set_sensitive(False)
826
m_revert.set_sensitive(False)
827
m_commit.set_sensitive(False)
828
m_annotate.set_sensitive(False)
829
m_diff.set_sensitive(False)
832
menu.right_context_menu().popup(None, None, None, 0,
835
menu.remote_context_menu().popup(None, None, None, 0,
838
def on_treeview_right_row_activated(self, treeview, path, view_column):
839
""" Occurs when somebody double-clicks or enters an item in the
841
from launch import launch
843
newdir = self.get_selected_right()
848
self.set_path(os.path.split(self.get_path())[0])
850
fullpath = os.path.join(self.get_path(), newdir)
851
if os.path.isdir(fullpath):
852
# selected item is an existant directory
853
self.set_path(fullpath)
858
if self._is_remote_dir(self.get_path() + newdir):
859
self.set_path(self.get_path() + newdir)
863
def on_window_main_delete_event(self, widget, event=None):
864
""" Do some stuff before exiting. """
865
width, height = self.window.get_size()
866
self.pref.set_preference('window_width', width)
867
self.pref.set_preference('window_height', height)
868
x, y = self.window.get_position()
869
self.pref.set_preference('window_x', x)
870
self.pref.set_preference('window_y', y)
871
self.pref.set_preference('paned_position',
872
self.window.hpaned_main.get_position())
875
self.window.destroy()
877
def get_selected_fileid(self):
878
""" Get the file_id of the selected file. """
879
treeselection = self.window.treeview_right.get_selection()
880
(model, iter) = treeselection.get_selected()
885
return model.get_value(iter, 9)
887
def get_selected_right(self):
888
""" Get the selected filename. """
889
treeselection = self.window.treeview_right.get_selection()
890
(model, iter) = treeselection.get_selected()
895
return model.get_value(iter, 2)
897
def get_selected_left(self):
898
""" Get the selected bookmark. """
899
treeselection = self.window.treeview_left.get_selection()
900
(model, iter) = treeselection.get_selected()
905
return model.get_value(iter, 1)
907
def set_statusbar(self, message):
908
""" Set the statusbar message. """
909
self.window.statusbar.push(self.context_id, message)
911
def clear_statusbar(self):
912
""" Clean the last message from the statusbar. """
913
self.window.statusbar.pop(self.context_id)
915
def set_sensitivity(self):
916
""" Set menu and toolbar sensitivity. """
918
self.window.set_view_to_localbranch(self.notbranch)
920
self.window.set_view_to_remotebranch()
922
def refresh_left(self):
923
""" Refresh the bookmark list. """
925
# Get ListStore and clear it
926
liststore = self.window.bookmarklist
929
# Re-read preferences
933
bookmarks = self.pref.get_bookmarks()
935
# Get titles and sort by title
936
bookmarks = [[self.pref.get_bookmark_title(item), item, gtk.STOCK_DIRECTORY] for item in bookmarks]
938
for title_item in bookmarks:
939
liststore.append(title_item)
941
# Add the ListStore to the TreeView and refresh column width
942
self.window.treeview_left.set_model(liststore)
943
self.window.treeview_left.columns_autosize()
945
def refresh_right(self):
946
""" Refresh the file list. """
949
from bzrlib.workingtree import WorkingTree
951
path = self.get_path()
953
# Get ListStore and clear it
954
liststore = self.window.filelist
960
# Fill the appropriate lists
961
dotted_files = self.pref.get_preference('dotted_files', 'bool')
962
ignored_files = self.pref.get_preference('ignored_files', 'bool')
964
for item in os.listdir(path):
965
if not dotted_files and item[0] == '.':
967
if os.path.isdir(os.path.join(path, item)):
972
self.window.col_status.set_visible(False)
973
if not self.notbranch:
975
tree1 = WorkingTree.open_containing(os.path.realpath(path))[0]
976
branch = tree1.branch
977
tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
979
delta = tree1.changes_from(tree2, want_unchanged=True)
982
self.window.col_status.set_visible(True)
983
except bzrerrors.LockContention:
984
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR, allowPopup=True)
985
self.window.location_status.connect_object('clicked', error_dialog,
986
*(_i18n('Branch is locked'),
987
_i18n('The branch in the current folder is locked by another Bazaar program')))
988
self.notbranch = True
989
self.window.set_view_to_localbranch(False)
991
# Add'em to the ListStore
996
if not self.notbranch:
997
filename = tree1.relpath(os.path.join(os.path.realpath(path), item))
999
st, status = self.statusmapper(filename, delta)
1000
if not ignored_files and status == 'ignored':
1003
statinfo = os.lstat(os.path.join(self.path, item))
1004
liststore.append([ gtk.STOCK_DIRECTORY,
1012
self._format_date(statinfo.st_mtime),
1018
if not self.notbranch:
1019
filename = tree1.relpath(os.path.join(os.path.realpath(path), item))
1021
st, status = self.statusmapper(filename, delta)
1022
if not ignored_files and status == 'ignored':
1025
statinfo = os.lstat(os.path.join(self.path, item))
1026
liststore.append([gtk.STOCK_FILE,
1031
str(statinfo.st_size),
1032
self._format_size(statinfo.st_size),
1034
self._format_date(statinfo.st_mtime),
1039
# Get ListStore and clear it
1040
liststore = self.window.filelist
1043
# Hide Status column
1044
self.window.col_status.set_visible(False)
1049
self.window.set_location_status(gtk.STOCK_REFRESH)
1051
for (name, type) in self.remote_entries:
1052
if type.kind == 'directory':
1054
elif type.kind == 'file':
1058
""" Cache based on revision history. """
1059
def __init__(self, history):
1060
self._history = history
1062
def _lookup_revision(self, revid):
1063
for r in self._history:
1064
if r.revision_id == revid:
1066
rev = repo.get_revision(revid)
1067
self._history.append(rev)
1070
repo = self.remote_branch.repository
1072
revhistory = self.remote_branch.revision_history()
1074
revs = repo.get_revisions(revhistory)
1075
cache = HistoryCache(revs)
1076
except bzrerrors.InvalidHttpResponse:
1077
# Fallback to dummy algorithm, because of LP: #115209
1078
cache = HistoryCache([])
1081
if item.parent_id == self.remote_parent:
1082
rev = cache._lookup_revision(item.revision)
1083
liststore.append([ gtk.STOCK_DIRECTORY,
1091
self._format_date(rev.timestamp),
1094
while gtk.events_pending():
1095
gtk.main_iteration()
1098
if item.parent_id == self.remote_parent:
1099
rev = cache._lookup_revision(item.revision)
1100
liststore.append([ gtk.STOCK_FILE,
1105
str(item.text_size),
1106
self._format_size(item.text_size),
1108
self._format_date(rev.timestamp),
1111
while gtk.events_pending():
1112
gtk.main_iteration()
1114
self.window.location_status.destroy()
1116
# Columns should auto-size
1117
self.window.treeview_right.columns_autosize()
1120
self.set_sensitivity()
1122
def statusmapper(self, filename, delta):
1127
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
1128
if rpathnew == filename:
1131
for rpath, id, kind in delta.added:
1132
if rpath == filename:
1135
for rpath, id, kind in delta.removed:
1136
if rpath == filename:
1139
for rpath, id, kind, text_modified, meta_modified in delta.modified:
1140
if rpath == filename:
1143
for rpath, id, kind in delta.unchanged:
1144
if rpath == filename:
1145
status = 'unchanged'
1147
for rpath, file_class, kind, id, entry in self.wt.list_files():
1148
if rpath == filename and file_class == 'I':
1153
if status == 'renamed':
1154
st = _i18n('renamed')
1155
elif status == 'removed':
1156
st = _i18n('removed')
1157
elif status == 'added':
1159
elif status == 'modified':
1160
st = _i18n('modified')
1161
elif status == 'unchanged':
1162
st = _i18n('unchanged')
1163
elif status == 'ignored':
1164
st = _i18n('ignored')
1166
st = _i18n('unknown')
1169
def _harddisks(self):
1170
""" Returns hard drive letters under Win32. """
1175
if sys.platform == 'win32':
1176
print "pyWin32 modules needed to run Olive on Win32."
1180
for drive in string.ascii_uppercase:
1181
if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED or\
1182
win32file.GetDriveType(drive+':') == win32file.DRIVE_REMOTE or\
1183
win32file.GetDriveType(drive+':') == win32file.DRIVE_REMOVABLE:
1184
driveletters.append(drive+':')
1187
def gen_hard_selector(self):
1188
""" Generate the hard drive selector under Win32. """
1189
drives = self._harddisks()
1190
for drive in drives:
1191
self.combobox_drive.append_text(drive)
1192
self.combobox_drive.set_active(drives.index(os.getcwd()[0:2]))
1194
def _refresh_drives(self, combobox):
1195
if self._just_started:
1197
model = combobox.get_model()
1198
active = combobox.get_active()
1200
drive = model[active][0]
1201
self.set_path(drive + '\\')
1202
self.refresh_right()
1204
def check_for_changes(self):
1205
""" Check whether there were changes in the current working tree. """
1206
old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
1207
delta = self.wt.changes_from(old_tree)
1211
if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
1216
def _sort_filelist_callback(self, model, iter1, iter2, data):
1217
""" The sort callback for the file list, return values:
1222
name1 = model.get_value(iter1, 2)
1223
name2 = model.get_value(iter2, 2)
1225
if model.get_value(iter1, 1):
1226
# item1 is a directory
1227
if not model.get_value(iter2, 1):
1231
# both of them are directories, we compare their names
1234
elif name1 == name2:
1239
# item1 is not a directory
1240
if model.get_value(iter2, 1):
1244
# both of them are files, compare them
1247
elif name1 == name2:
1252
def _format_size(self, size):
1253
""" Format size to a human readable format. """
1255
return "%d[B]" % (size,)
1256
size = size / 1000.0
1258
for metric in ["kB","MB","GB","TB"]:
1261
size = size / 1000.0
1262
return "%.1f[%s]" % (size,metric)
1264
def _format_date(self, timestamp):
1265
""" Format the time (given in secs) to a human readable format. """
1266
return time.ctime(timestamp)
1268
def _is_remote_dir(self, location):
1269
""" Determine whether the given location is a directory or not. """
1271
# We're in local mode
1274
branch, path = Branch.open_containing(location)
1275
for (name, type) in self.remote_entries:
1276
if name == path and type.kind == 'directory':
1279
# Either it's not a directory or not in the inventory
1285
""" A class which handles Olive's preferences. """
1286
def __init__(self, path=None):
1287
""" Initialize the Preferences class. """
1288
# Some default options
1289
self.defaults = { 'strict_commit' : False,
1290
'dotted_files' : False,
1291
'ignored_files' : True,
1292
'window_width' : 700,
1293
'window_height' : 400,
1296
'paned_position': 200 }
1298
# Create a config parser object
1299
self.config = ConfigParser.RawConfigParser()
1303
if sys.platform == 'win32':
1304
# Windows - no dotted files
1305
self._filename = os.path.expanduser('~/olive.conf')
1307
self._filename = os.path.expanduser('~/.olive.conf')
1309
self._filename = path
1311
# Load the configuration
1314
def _get_default(self, option):
1315
""" Get the default option for a preference. """
1317
ret = self.defaults[option]
1324
""" Refresh the configuration. """
1325
# First write out the changes
1327
# Then load the configuration again
1331
""" Just read the configuration. """
1332
# Re-initialize the config parser object to avoid some bugs
1333
self.config = ConfigParser.RawConfigParser()
1334
self.config.read([self._filename])
1337
""" Write the configuration to the appropriate files. """
1338
fp = open(self._filename, 'w')
1339
self.config.write(fp)
1342
def get_bookmarks(self):
1343
""" Return the list of bookmarks. """
1344
bookmarks = self.config.sections()
1345
if self.config.has_section('preferences'):
1346
bookmarks.remove('preferences')
1349
def add_bookmark(self, path):
1350
""" Add bookmark. """
1352
self.config.add_section(path)
1353
except ConfigParser.DuplicateSectionError:
1358
def get_bookmark_title(self, path):
1359
""" Get bookmark title. """
1361
ret = self.config.get(path, 'title')
1362
except ConfigParser.NoOptionError:
1367
def set_bookmark_title(self, path, title):
1368
""" Set bookmark title. """
1369
# FIXME: What if path isn't listed yet?
1370
# FIXME: Canonicalize paths first?
1371
self.config.set(path, 'title', title)
1373
def remove_bookmark(self, path):
1374
""" Remove bookmark. """
1375
return self.config.remove_section(path)
1377
def set_preference(self, option, value):
1378
""" Set the value of the given option. """
1381
elif value is False:
1384
if self.config.has_section('preferences'):
1385
self.config.set('preferences', option, value)
1387
self.config.add_section('preferences')
1388
self.config.set('preferences', option, value)
1390
def get_preference(self, option, kind='str'):
1391
""" Get the value of the given option.
1393
:param kind: str/bool/int/float. default: str
1395
if self.config.has_option('preferences', option):
1397
return self.config.getboolean('preferences', option)
1399
return self.config.getint('preferences', option)
1400
elif kind == 'float':
1401
return self.config.getfloat('preferences', option)
1403
return self.config.get('preferences', option)
1406
return self._get_default(option)