1
# Copyright (C) 2006 by Szilveszter Farkas (Phanatic) <szilveszter.farkas@gmail.com>
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
gettext.install('olive-gtk')
36
from bzrlib.branch import Branch
37
import bzrlib.errors as bzrerrors
38
from bzrlib.lazy_import import lazy_import
39
from bzrlib.ui import ui_factory
40
from bzrlib.workingtree import WorkingTree
42
from bzrlib.plugins.gtk import _i18n
43
from bzrlib.plugins.gtk.dialog import error_dialog, info_dialog, warning_dialog
44
from bzrlib.plugins.gtk.errors import show_bzr_error
45
from bzrlib.plugins.gtk.olive.window import OliveGui
47
from bzrlib.plugins.gtk.diff import DiffWindow
48
lazy_import(globals(), """
49
from bzrlib.plugins.gtk.viz import branchwin
51
from bzrlib.plugins.gtk.annotate.gannotate import GAnnotateWindow
52
from bzrlib.plugins.gtk.annotate.config import GAnnotateConfig
53
from bzrlib.plugins.gtk.commit import CommitDialog
54
from bzrlib.plugins.gtk.conflicts import ConflictsDialog
55
from bzrlib.plugins.gtk.initialize import InitDialog
56
from bzrlib.plugins.gtk.push import PushDialog
57
from bzrlib.plugins.gtk.revbrowser import RevisionBrowser
60
""" Display the AboutDialog. """
61
from bzrlib.plugins.gtk import __version__, icon_path
63
iconpath = icon_path() + os.sep
65
dialog = gtk.AboutDialog()
66
dialog.set_name("Olive")
67
dialog.set_version(__version__)
68
dialog.set_copyright("Copyright (C) 2006 Szilveszter Farkas (Phanatic)")
69
dialog.set_website("https://launchpad.net/products/olive")
70
dialog.set_website_label("https://launchpad.net/products/olive")
71
dialog.set_icon_from_file(iconpath+"oliveicon2.png")
72
dialog.set_logo(gtk.gdk.pixbuf_new_from_file(iconpath+"oliveicon2.png"))
73
dialog.set_authors([ _i18n("Lead Developer:"),
74
"Szilveszter Farkas <szilveszter.farkas@gmail.com>",
75
_i18n("Contributors:"),
76
"Jelmer Vernooij <jelmer@samba.org>",
77
"Mateusz Korniak <mateusz.korniak@ant.gliwice.pl>",
78
"Gary van der Merwe <garyvdm@gmail.com>" ])
79
dialog.set_artists([ "Simon Pascal Klein <klepas@klepas.org>",
80
"Jakub Steiner <jimmac@novell.com>" ])
87
""" The main Olive GTK frontend class. This is called when launching the
91
self.window = OliveGui(calling_app = self)
93
self.pref = Preferences()
96
# Initialize the statusbar
97
self.context_id = self.window.statusbar.get_context_id('olive')
99
# Get the drive selector
100
self.combobox_drive = gtk.combo_box_new_text()
101
self.combobox_drive.connect("changed", self._refresh_drives)
103
# Get the navigation widgets
104
self.hbox_location = self.window.locationbar
105
self.button_location_up = self.window.button_location_up
106
self.button_location_jump = self.window.button_location_jump
107
self.entry_location = self.window.entry_location
109
# Get the History widgets
110
self.check_history = self.window.checkbutton_history
111
self.entry_history = self.window.entry_history_revno
112
self.button_history = self.window.button_history_browse
114
self._just_started = True
116
# Apply window size and position
117
width = self.pref.get_preference('window_width', 'int')
118
height = self.pref.get_preference('window_height', 'int')
119
self.window.resize(width, height)
120
x = self.pref.get_preference('window_x', 'int')
121
y = self.pref.get_preference('window_y', 'int')
122
self.window.move(x, y)
123
# Apply paned position
124
pos = self.pref.get_preference('paned_position', 'int')
125
self.window.hpaned_main.set_position(pos)
127
# Now we can show the window
130
# Show drive selector if under Win32
131
if sys.platform == 'win32':
132
self.hbox_location.pack_start(self.combobox_drive, False, False, 0)
133
self.hbox_location.reorder_child(self.combobox_drive, 1)
134
self.combobox_drive.show()
135
self.gen_hard_selector()
137
# Acceptable errors when loading files/folders in the treeviews
138
self.acceptable_errors = (errno.ENOENT, errno.ELOOP)
143
self.window.mb_view_showhidden.set_active(self.pref.get_preference('dotted_files', 'bool'))
144
self.window.mb_view_showignored.set_active(self.pref.get_preference('ignored_files', 'bool'))
146
# We're starting local
148
self.remote_branch = None
149
self.remote_path = None
150
self.remote_revision = None
152
self.set_path(os.getcwd())
155
self._just_started = False
157
def set_path(self, path, force_remote=False):
158
self.window.location_status.destroy()
159
self.notbranch = False
162
# Forcing remote mode (reading data from inventory)
163
self.window.set_location_status(gtk.STOCK_DISCONNECT)
165
br = Branch.open_containing(path)[0]
166
except bzrerrors.NotBranchError:
167
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
168
self.check_history.set_active(False)
169
self.check_history.set_sensitive(False)
171
except bzrerrors.UnsupportedProtocol:
172
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
173
self.check_history.set_active(False)
174
self.check_history.set_sensitive(False)
177
self.window.set_location_status(gtk.STOCK_CONNECT)
182
self.remote_branch, self.remote_path = Branch.open_containing(path)
184
if self.remote_revision is None:
185
self.remote_revision = self.remote_branch.last_revision()
187
self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
189
if len(self.remote_path) == 0:
190
self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
192
for (name, type) in self.remote_entries:
193
if name == self.remote_path:
194
self.remote_parent = type.file_id
197
if not path.endswith('/'):
200
if self.remote_branch.base == path:
201
self.button_location_up.set_sensitive(False)
203
self.button_location_up.set_sensitive(True)
205
if os.path.isdir(path):
210
self.wt, self.wtpath = WorkingTree.open_containing(os.path.realpath(path))
211
except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
212
self.notbranch = True
213
except bzrerrors.PermissionDenied:
214
self.window.set_location_status(gtk.STOCK_DIALOG_WARNING, allowPopup=True)
215
self.window.location_status.connect_object('clicked', warning_dialog,
216
*(_i18n('Branch information unreadable'),
217
_i18n('The current folder is a branch but the .bzr folder is not readable')))
218
self.notbranch = True
220
# If we're in the root, we cannot go up anymore
221
if sys.platform == 'win32':
222
drive, tail = os.path.splitdrive(path)
223
if tail in ('', '/', '\\'):
224
self.button_location_up.set_sensitive(False)
226
self.button_location_up.set_sensitive(True)
229
self.button_location_up.set_sensitive(False)
231
self.button_location_up.set_sensitive(True)
232
elif not os.path.isfile(path):
233
# Doesn't seem to be a file nor a directory, trying to open a
235
self.window.set_location_status(gtk.STOCK_DISCONNECT)
237
br = Branch.open_containing(path)[0]
238
except bzrerrors.NotBranchError:
239
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
240
self.check_history.set_active(False)
241
self.check_history.set_sensitive(False)
243
except bzrerrors.UnsupportedProtocol:
244
self.window.set_location_status(gtk.STOCK_DIALOG_ERROR)
245
self.check_history.set_active(False)
246
self.check_history.set_sensitive(False)
249
self.window.set_location_status(gtk.STOCK_CONNECT)
254
self.remote_branch, self.remote_path = Branch.open_containing(path)
256
if self.remote_revision is None:
257
self.remote_revision = self.remote_branch.last_revision()
259
self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
261
if len(self.remote_path) == 0:
262
self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
264
for (name, type) in self.remote_entries:
265
if name == self.remote_path:
266
self.remote_parent = type.file_id
269
if not path.endswith('/'):
272
if self.remote_branch.base == path:
273
self.button_location_up.set_sensitive(False)
275
self.button_location_up.set_sensitive(True)
278
self.check_history.set_active(False)
279
self.check_history.set_sensitive(False)
281
self.check_history.set_sensitive(True)
283
self.window.statusbar.push(self.context_id, path)
284
self.entry_location.set_text(path)
293
if len(self.remote_path) > 0:
294
return self.remote_branch.base + self.remote_path + '/'
296
return self.remote_branch.base
298
def on_about_activate(self, widget):
301
def on_button_history_browse_clicked(self, widget):
302
""" Browse for revision button handler. """
304
br = self.remote_branch
308
revb = RevisionBrowser(br, self.window)
309
response = revb.run()
310
if response != gtk.RESPONSE_NONE:
313
if response == gtk.RESPONSE_OK:
314
if revb.selected_revno is not None:
315
self.entry_history.set_text(revb.selected_revno)
316
self.on_entry_history_revno_activate()
320
def on_button_location_jump_clicked(self, widget):
321
""" Location Jump button handler. """
322
location = self.entry_location.get_text()
324
if self.set_path(location):
327
def on_button_location_up_clicked(self, widget):
328
""" Location Up button handler. """
331
self.set_path(os.path.split(self.get_path())[0])
335
newpath = delim.join(self.get_path().split(delim)[:-2])
337
self.set_path(newpath)
341
def on_checkbutton_history_toggled(self, widget):
342
""" History Mode toggle handler. """
343
if self.check_history.get_active():
344
# History Mode activated
345
self.entry_history.set_sensitive(True)
346
self.button_history.set_sensitive(True)
347
if self.entry_history.get_text() != "":
348
self.on_entry_history_revno_activate()
350
# History Mode deactivated
351
self.entry_history.set_sensitive(False)
352
self.button_history.set_sensitive(False)
354
# Return right window to normal view by acting like we jump to it
355
self.on_button_location_jump_clicked(widget)
358
def on_entry_history_revno_activate(self, widget=None):
359
""" Key pressed handler for the history entry. """
360
path = self.get_path()
363
self.remote_branch = self.wt.branch
365
revno = int(self.entry_history.get_text())
366
self.remote_revision = self.remote_branch.get_rev_id(revno)
367
if self.set_path(path, True):
370
def on_menuitem_add_files_activate(self, widget):
371
""" Add file(s)... menu handler. """
372
from bzrlib.plugins.gtk.olive.add import AddDialog
373
add = AddDialog(self.wt, self.wtpath, self.get_selected_right(), self.window)
376
if response == gtk.RESPONSE_OK:
379
def on_menuitem_branch_get_activate(self, widget):
380
""" Branch/Get... menu handler. """
381
from bzrlib.plugins.gtk.branch import BranchDialog
384
branch = BranchDialog(os.getcwd(), self.window, self.remote_branch.base)
386
branch = BranchDialog(self.get_path(), self.window)
387
response = branch.run()
388
if response != gtk.RESPONSE_NONE:
391
if response == gtk.RESPONSE_OK:
396
def on_menuitem_branch_checkout_activate(self, widget):
397
""" Branch/Checkout... menu handler. """
398
from bzrlib.plugins.gtk.checkout import CheckoutDialog
401
checkout = CheckoutDialog(os.getcwd(), self.window, self.remote_branch.base)
403
checkout = CheckoutDialog(self.get_path(), self.window)
404
response = checkout.run()
405
if response != gtk.RESPONSE_NONE:
408
if response == gtk.RESPONSE_OK:
414
def on_menuitem_branch_commit_activate(self, widget):
415
""" Branch/Commit... menu handler. """
416
selected = self.get_selected_right()
418
selected = os.path.join(self.wtpath, selected)
419
commit = CommitDialog(wt=self.wt,
423
response = commit.run()
424
if response != gtk.RESPONSE_NONE:
427
if response == gtk.RESPONSE_OK:
432
def on_menuitem_branch_conflicts_activate(self, widget):
433
""" Branch/Conflicts... menu handler. """
434
conflicts = ConflictsDialog(self.wt, self.window)
435
response = conflicts.run()
436
if response != gtk.RESPONSE_NONE:
439
def on_menuitem_branch_merge_activate(self, widget):
440
""" Branch/Merge... menu handler. """
441
from bzrlib.plugins.gtk.merge import MergeDialog
443
if self.check_for_changes():
444
error_dialog(_i18n('There are local changes in the branch'),
445
_i18n('Please commit or revert the changes before merging.'))
447
parent_branch_path = self.wt.branch.get_parent()
448
merge = MergeDialog(self.wt, self.wtpath, parent_branch_path, self.window)
449
response = merge.run()
451
if response == gtk.RESPONSE_OK:
455
def on_menuitem_branch_missing_revisions_activate(self, widget):
456
""" Branch/Missing revisions menu handler. """
458
from bzrlib.missing import find_unmerged, iter_log_revisions
460
local_branch = self.wt.branch
461
parent_branch_path = local_branch.get_parent()
462
if parent_branch_path is None:
463
error_dialog(_i18n('Parent location is unknown'),
464
_i18n('Cannot determine missing revisions if no parent location is known.'))
467
parent_branch = Branch.open(parent_branch_path)
469
if parent_branch.base == local_branch.base:
470
parent_branch = local_branch
472
local_extra, remote_extra = find_unmerged(local_branch,parent_branch)
474
if local_extra or remote_extra:
476
## def log_revision_one_line_text(log_revision):
477
## """ Generates one line description of log_revison ended with end of line."""
478
## revision = log_revision.rev
479
## txt = "- %s (%s)\n" % (revision.get_summary(), revision.committer, )
480
## txt = txt.replace("<"," ") # Seems < > chars are expected to be xml tags ...
481
## txt = txt.replace(">"," ")
486
dlg_txt += _i18n('%d local extra revision(s). \n') % (len(local_extra),)
487
## NOTE: We do not want such ugly info about missing revisions
488
## Revision Browser should be used there
489
## max_revisions = 10
490
## for log_revision in iter_log_revisions(local_extra, local_branch.repository, verbose=1):
491
## dlg_txt += log_revision_one_line_text(log_revision)
492
## if max_revisions <= 0:
493
## dlg_txt += _i18n("more ... \n")
495
## max_revisions -= 1
498
dlg_txt += _i18n('%d local missing revision(s).\n') % (len(remote_extra),)
499
## max_revisions = 10
500
## for log_revision in iter_log_revisions(remote_extra, parent_branch.repository, verbose=1):
501
## dlg_txt += log_revision_one_line_text(log_revision)
502
## if max_revisions <= 0:
503
## dlg_txt += _i18n("more ... \n")
505
## max_revisions -= 1
507
info_dialog(_i18n('There are missing revisions'),
510
info_dialog(_i18n('Local branch up to date'),
511
_i18n('There are no missing revisions.'))
514
def on_menuitem_branch_pull_activate(self, widget):
515
""" Branch/Pull menu handler. """
516
branch_to = self.wt.branch
518
location = branch_to.get_parent()
520
error_dialog(_i18n('Parent location is unknown'),
521
_i18n('Pulling is not possible until there is a parent location.'))
524
branch_from = Branch.open(location)
526
if branch_to.get_parent() is None:
527
branch_to.set_parent(branch_from.base)
529
ret = branch_to.pull(branch_from)
531
info_dialog(_i18n('Pull successful'), _i18n('%d revision(s) pulled.') % ret)
534
def on_menuitem_branch_update_activate(self, widget):
535
""" Brranch/checkout update menu handler. """
537
ret = self.wt.update()
538
conflicts = self.wt.conflicts()
540
info_dialog(_i18n('Update successful but conflicts generated'), _i18n('Number of conflicts generated: %d.') % (len(conflicts),) )
542
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:
663
self.set_path(self.path)
668
def on_menuitem_stats_diff_activate(self, widget):
669
""" Statistics/Differences... menu handler. """
670
window = DiffWindow(parent=self.window)
671
parent_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
672
window.set_diff(self.wt.branch.nick, self.wt, parent_tree)
675
def on_menuitem_stats_infos_activate(self, widget):
676
""" Statistics/Informations... menu handler. """
677
from bzrlib.plugins.gtk.olive.info import InfoDialog
679
info = InfoDialog(self.remote_branch)
681
info = InfoDialog(self.wt.branch)
684
def on_menuitem_stats_log_activate(self, widget):
685
""" Statistics/Log... menu handler. """
688
branch = self.wt.branch
690
branch = self.remote_branch
692
window = branchwin.BranchWindow(branch, [branch.last_revision()], None,
696
def on_menuitem_view_refresh_activate(self, widget):
697
""" View/Refresh menu handler. """
698
# Refresh the left pane
700
# Refresh the right pane
703
def on_menuitem_view_show_hidden_files_activate(self, widget):
704
""" View/Show hidden files menu handler. """
705
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())
712
if self.path is not None:
715
def on_treeview_left_button_press_event(self, widget, event):
716
""" Occurs when somebody clicks in the bookmark list. """
717
treepathpos = widget.get_path_at_pos(int(event.x), int(event.y))
718
treeselection = widget.get_selection()
719
if treepathpos is not None:
720
treeselection.select_path(treepathpos[0])
721
if event.button == 1:
722
newdir = self.get_selected_left()
726
if self.set_path(newdir):
728
elif event.button == 3:
729
# Don't show context with nothing selected
730
if self.get_selected_left() == None:
734
from menu import OliveMenu
735
menu = OliveMenu(path=self.get_path(),
736
selected=self.get_selected_left(),
739
menu.left_context_menu().popup(None, None, None, 0,
742
if treeselection is not None:
743
treeselection.unselect_all()
745
def on_treeview_left_row_activated(self, treeview, path, view_column):
746
""" Occurs when somebody double-clicks or enters an item in the
749
newdir = self.get_selected_left()
753
if self.set_path(newdir):
756
def on_treeview_right_button_press_event(self, widget, event):
757
""" Occurs when somebody clicks in the file list. """
758
treepathpos = widget.get_path_at_pos(int(event.x), int(event.y))
759
if event.button == 1:
760
if treepathpos is None and widget.get_selection is not None:
761
treeselection = widget.get_selection()
762
treeselection.unselect_all()
763
elif event.button == 3:
764
treeselection = widget.get_selection()
765
if treepathpos is not None:
766
treeselection.select_path(treepathpos[0])
768
if treeselection is not None:
769
treeselection.unselect_all()
771
from menu import OliveMenu
772
menu = OliveMenu(path=self.get_path(),
773
selected=self.get_selected_right(),
776
m_open = menu.ui.get_widget('/context_right/open')
777
m_add = menu.ui.get_widget('/context_right/add')
778
m_remove = menu.ui.get_widget('/context_right/remove')
779
m_remove_and_delete = menu.ui.get_widget('/context_right/remove_and_delete')
780
m_rename = menu.ui.get_widget('/context_right/rename')
781
m_revert = menu.ui.get_widget('/context_right/revert')
782
m_commit = menu.ui.get_widget('/context_right/commit')
783
m_annotate = menu.ui.get_widget('/context_right/annotate')
784
m_diff = menu.ui.get_widget('/context_right/diff')
785
# check if we're in a branch
787
from bzrlib.branch import Branch
788
Branch.open_containing(self.get_path())
790
m_open.set_sensitive(False)
791
m_add.set_sensitive(False)
792
m_remove.set_sensitive(False)
793
m_remove_and_delete.set_sensitive(False)
794
m_rename.set_sensitive(False)
795
m_revert.set_sensitive(False)
796
m_commit.set_sensitive(False)
797
m_annotate.set_sensitive(False)
798
m_diff.set_sensitive(False)
800
if treepathpos is None:
801
m_open.set_sensitive(False)
802
m_add.set_sensitive(False)
803
m_remove.set_sensitive(False)
804
m_remove_and_delete.set_sensitive(False)
805
m_rename.set_sensitive(False)
806
m_annotate.set_sensitive(False)
807
m_diff.set_sensitive(False)
808
m_revert.set_sensitive(False)
810
m_open.set_sensitive(True)
811
m_add.set_sensitive(True)
812
m_remove.set_sensitive(True)
813
m_remove_and_delete.set_sensitive(True)
814
m_rename.set_sensitive(True)
815
m_annotate.set_sensitive(True)
816
m_diff.set_sensitive(True)
817
m_revert.set_sensitive(True)
818
m_commit.set_sensitive(True)
819
except bzrerrors.NotBranchError:
820
if treepathpos is None:
821
m_open.set_sensitive(False)
823
m_open.set_sensitive(True)
824
m_add.set_sensitive(False)
825
m_remove.set_sensitive(False)
826
m_remove_and_delete.set_sensitive(False)
827
m_rename.set_sensitive(False)
828
m_revert.set_sensitive(False)
829
m_commit.set_sensitive(False)
830
m_annotate.set_sensitive(False)
831
m_diff.set_sensitive(False)
834
menu.right_context_menu().popup(None, None, None, 0,
837
menu.remote_context_menu().popup(None, None, None, 0,
840
def on_treeview_right_row_activated(self, treeview, path, view_column):
841
""" Occurs when somebody double-clicks or enters an item in the
843
from launch import launch
845
newdir = self.get_selected_right()
850
self.set_path(os.path.split(self.get_path())[0])
852
fullpath = os.path.join(self.get_path(), newdir)
853
if os.path.isdir(fullpath):
854
# selected item is an existant directory
855
self.set_path(fullpath)
860
if self._is_remote_dir(self.get_path() + newdir):
861
self.set_path(self.get_path() + newdir)
865
def on_window_main_delete_event(self, widget, event=None):
866
""" Do some stuff before exiting. """
867
width, height = self.window.get_size()
868
self.pref.set_preference('window_width', width)
869
self.pref.set_preference('window_height', height)
870
x, y = self.window.get_position()
871
self.pref.set_preference('window_x', x)
872
self.pref.set_preference('window_y', y)
873
self.pref.set_preference('paned_position',
874
self.window.hpaned_main.get_position())
877
self.window.destroy()
879
def get_selected_fileid(self):
880
""" Get the file_id of the selected file. """
881
treeselection = self.window.treeview_right.get_selection()
882
(model, iter) = treeselection.get_selected()
887
return model.get_value(iter, 9)
889
def get_selected_right(self):
890
""" Get the selected filename. """
891
treeselection = self.window.treeview_right.get_selection()
892
(model, iter) = treeselection.get_selected()
897
return model.get_value(iter, 2)
899
def get_selected_left(self):
900
""" Get the selected bookmark. """
901
treeselection = self.window.treeview_left.get_selection()
902
(model, iter) = treeselection.get_selected()
907
return model.get_value(iter, 1)
909
def set_statusbar(self, message):
910
""" Set the statusbar message. """
911
self.window.statusbar.push(self.context_id, message)
913
def clear_statusbar(self):
914
""" Clean the last message from the statusbar. """
915
self.window.statusbar.pop(self.context_id)
917
def set_sensitivity(self):
918
""" Set menu and toolbar sensitivity. """
920
self.window.set_view_to_localbranch(self.notbranch)
922
self.window.set_view_to_remotebranch()
924
def refresh_left(self):
925
""" Refresh the bookmark list. """
927
# Get ListStore and clear it
928
liststore = self.window.bookmarklist
931
# Re-read preferences
935
bookmarks = self.pref.get_bookmarks()
937
# Get titles and sort by title
938
bookmarks = [[self.pref.get_bookmark_title(item), item, gtk.STOCK_DIRECTORY] for item in bookmarks]
940
for title_item in bookmarks:
941
liststore.append(title_item)
943
# Add the ListStore to the TreeView
944
self.window.treeview_left.set_model(liststore)
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(path + os.sep + 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':
1005
statinfo = os.stat(self.path + os.sep + item)
1007
if e.errno in self.acceptable_errors:
1011
liststore.append([ gtk.STOCK_DIRECTORY,
1019
self._format_date(statinfo.st_mtime),
1025
if not self.notbranch:
1026
filename = tree1.relpath(os.path.join(os.path.realpath(path), item))
1028
st, status = self.statusmapper(filename, delta)
1029
if not ignored_files and status == 'ignored':
1033
statinfo = os.stat(self.path + os.sep + item)
1035
if e.errno in self.acceptable_errors:
1039
liststore.append([gtk.STOCK_FILE,
1044
str(statinfo.st_size),
1045
self._format_size(statinfo.st_size),
1047
self._format_date(statinfo.st_mtime),
1052
# Get ListStore and clear it
1053
liststore = self.window.filelist
1056
# Hide Status column
1057
self.window.col_status.set_visible(False)
1062
self.window.set_location_status(gtk.STOCK_REFRESH)
1064
for (name, type) in self.remote_entries:
1065
if type.kind == 'directory':
1067
elif type.kind == 'file':
1071
""" Cache based on revision history. """
1072
def __init__(self, history):
1073
self._history = history
1075
def _lookup_revision(self, revid):
1076
for r in self._history:
1077
if r.revision_id == revid:
1079
rev = repo.get_revision(revid)
1080
self._history.append(rev)
1083
repo = self.remote_branch.repository
1085
revhistory = self.remote_branch.revision_history()
1087
revs = repo.get_revisions(revhistory)
1088
cache = HistoryCache(revs)
1089
except bzrerrors.InvalidHttpResponse:
1090
# Fallback to dummy algorithm, because of LP: #115209
1091
cache = HistoryCache([])
1094
if item.parent_id == self.remote_parent:
1095
rev = cache._lookup_revision(item.revision)
1096
liststore.append([ gtk.STOCK_DIRECTORY,
1104
self._format_date(rev.timestamp),
1107
while gtk.events_pending():
1108
gtk.main_iteration()
1111
if item.parent_id == self.remote_parent:
1112
rev = cache._lookup_revision(item.revision)
1113
liststore.append([ gtk.STOCK_FILE,
1118
str(item.text_size),
1119
self._format_size(item.text_size),
1121
self._format_date(rev.timestamp),
1124
while gtk.events_pending():
1125
gtk.main_iteration()
1127
self.window.location_status.destroy()
1129
# Columns should auto-size
1130
self.window.treeview_right.columns_autosize()
1133
self.set_sensitivity()
1135
def statusmapper(self, filename, delta):
1140
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
1141
if rpathnew == filename:
1144
for rpath, id, kind in delta.added:
1145
if rpath == filename:
1148
for rpath, id, kind in delta.removed:
1149
if rpath == filename:
1152
for rpath, id, kind, text_modified, meta_modified in delta.modified:
1153
if rpath == filename:
1156
for rpath, id, kind in delta.unchanged:
1157
if rpath == filename:
1158
status = 'unchanged'
1160
for rpath, file_class, kind, id, entry in self.wt.list_files():
1161
if rpath == filename and file_class == 'I':
1166
if status == 'renamed':
1167
st = _i18n('renamed')
1168
elif status == 'removed':
1169
st = _i18n('removed')
1170
elif status == 'added':
1172
elif status == 'modified':
1173
st = _i18n('modified')
1174
elif status == 'unchanged':
1175
st = _i18n('unchanged')
1176
elif status == 'ignored':
1177
st = _i18n('ignored')
1179
st = _i18n('unknown')
1182
def _harddisks(self):
1183
""" Returns hard drive letters under Win32. """
1188
if sys.platform == 'win32':
1189
print "pyWin32 modules needed to run Olive on Win32."
1193
for drive in string.ascii_uppercase:
1194
if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED or\
1195
win32file.GetDriveType(drive+':') == win32file.DRIVE_REMOTE or\
1196
win32file.GetDriveType(drive+':') == win32file.DRIVE_REMOVABLE:
1197
driveletters.append(drive+':')
1200
def gen_hard_selector(self):
1201
""" Generate the hard drive selector under Win32. """
1202
drives = self._harddisks()
1203
for drive in drives:
1204
self.combobox_drive.append_text(drive)
1205
self.combobox_drive.set_active(drives.index(os.getcwd()[0:2]))
1207
def _refresh_drives(self, combobox):
1208
if self._just_started:
1210
model = combobox.get_model()
1211
active = combobox.get_active()
1213
drive = model[active][0]
1214
self.set_path(drive + '\\')
1215
self.refresh_right()
1217
def check_for_changes(self):
1218
""" Check whether there were changes in the current working tree. """
1219
old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
1220
delta = self.wt.changes_from(old_tree)
1224
if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
1229
def _sort_filelist_callback(self, model, iter1, iter2, data):
1230
""" The sort callback for the file list, return values:
1235
name1 = model.get_value(iter1, 2)
1236
name2 = model.get_value(iter2, 2)
1238
if model.get_value(iter1, 1):
1239
# item1 is a directory
1240
if not model.get_value(iter2, 1):
1244
# both of them are directories, we compare their names
1247
elif name1 == name2:
1252
# item1 is not a directory
1253
if model.get_value(iter2, 1):
1257
# both of them are files, compare them
1260
elif name1 == name2:
1265
def _format_size(self, size):
1266
""" Format size to a human readable format. """
1268
return "%d[B]" % (size,)
1269
size = size / 1000.0
1271
for metric in ["kB","MB","GB","TB"]:
1274
size = size / 1000.0
1275
return "%.1f[%s]" % (size,metric)
1277
def _format_date(self, timestamp):
1278
""" Format the time (given in secs) to a human readable format. """
1279
return time.ctime(timestamp)
1281
def _is_remote_dir(self, location):
1282
""" Determine whether the given location is a directory or not. """
1284
# We're in local mode
1287
branch, path = Branch.open_containing(location)
1288
for (name, type) in self.remote_entries:
1289
if name == path and type.kind == 'directory':
1292
# Either it's not a directory or not in the inventory
1298
""" A class which handles Olive's preferences. """
1299
def __init__(self, path=None):
1300
""" Initialize the Preferences class. """
1301
# Some default options
1302
self.defaults = { 'strict_commit' : False,
1303
'dotted_files' : False,
1304
'ignored_files' : True,
1305
'window_width' : 700,
1306
'window_height' : 400,
1309
'paned_position': 200 }
1311
# Create a config parser object
1312
self.config = ConfigParser.RawConfigParser()
1316
if sys.platform == 'win32':
1317
# Windows - no dotted files
1318
self._filename = os.path.expanduser('~/olive.conf')
1320
self._filename = os.path.expanduser('~/.olive.conf')
1322
self._filename = path
1324
# Load the configuration
1327
def _get_default(self, option):
1328
""" Get the default option for a preference. """
1330
ret = self.defaults[option]
1337
""" Refresh the configuration. """
1338
# First write out the changes
1340
# Then load the configuration again
1344
""" Just read the configuration. """
1345
# Re-initialize the config parser object to avoid some bugs
1346
self.config = ConfigParser.RawConfigParser()
1347
self.config.read([self._filename])
1350
""" Write the configuration to the appropriate files. """
1351
fp = open(self._filename, 'w')
1352
self.config.write(fp)
1355
def get_bookmarks(self):
1356
""" Return the list of bookmarks. """
1357
bookmarks = self.config.sections()
1358
if self.config.has_section('preferences'):
1359
bookmarks.remove('preferences')
1362
def add_bookmark(self, path):
1363
""" Add bookmark. """
1365
self.config.add_section(path)
1366
except ConfigParser.DuplicateSectionError:
1371
def get_bookmark_title(self, path):
1372
""" Get bookmark title. """
1374
ret = self.config.get(path, 'title')
1375
except ConfigParser.NoOptionError:
1380
def set_bookmark_title(self, path, title):
1381
""" Set bookmark title. """
1382
# FIXME: What if path isn't listed yet?
1383
# FIXME: Canonicalize paths first?
1384
self.config.set(path, 'title', title)
1386
def remove_bookmark(self, path):
1387
""" Remove bookmark. """
1388
return self.config.remove_section(path)
1390
def set_preference(self, option, value):
1391
""" Set the value of the given option. """
1394
elif value is False:
1397
if self.config.has_section('preferences'):
1398
self.config.set('preferences', option, value)
1400
self.config.add_section('preferences')
1401
self.config.set('preferences', option, value)
1403
def get_preference(self, option, kind='str'):
1404
""" Get the value of the given option.
1406
:param kind: str/bool/int/float. default: str
1408
if self.config.has_option('preferences', option):
1410
return self.config.getboolean('preferences', option)
1412
return self.config.getint('preferences', option)
1413
elif kind == 'float':
1414
return self.config.getfloat('preferences', option)
1416
return self.config.get('preferences', option)
1419
return self._get_default(option)