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
23
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.dialog import error_dialog, info_dialog, warning_dialog
43
from bzrlib.plugins.gtk.errors import show_bzr_error
44
from guifiles import GLADEFILENAME
46
from bzrlib.plugins.gtk.diff import DiffWindow
47
lazy_import(globals(), """
48
from bzrlib.plugins.gtk.viz import branchwin
50
from bzrlib.plugins.gtk.annotate.gannotate import GAnnotateWindow
51
from bzrlib.plugins.gtk.annotate.config import GAnnotateConfig
52
from bzrlib.plugins.gtk.commit import CommitDialog
53
from bzrlib.plugins.gtk.conflicts import ConflictsDialog
54
from bzrlib.plugins.gtk.initialize import InitDialog
55
from bzrlib.plugins.gtk.push import PushDialog
56
from bzrlib.plugins.gtk.revbrowser import RevisionBrowser
59
""" Display the AboutDialog. """
60
from bzrlib.plugins.gtk import __version__
61
from bzrlib.plugins.gtk.olive.guifiles import GLADEFILENAME
63
# Load AboutDialog description
64
dglade = gtk.glade.XML(GLADEFILENAME, 'aboutdialog')
65
dialog = dglade.get_widget('aboutdialog')
68
dialog.set_version(__version__)
75
""" The main Olive GTK frontend class. This is called when launching the
79
self.toplevel = gtk.glade.XML(GLADEFILENAME, 'window_main', 'olive-gtk')
80
self.window = self.toplevel.get_widget('window_main')
81
self.pref = Preferences()
84
# Initialize the statusbar
85
self.statusbar = self.toplevel.get_widget('statusbar')
86
self.context_id = self.statusbar.get_context_id('olive')
89
self.window_main = self.toplevel.get_widget('window_main')
91
self.hpaned_main = self.toplevel.get_widget('hpaned_main')
93
self.treeview_left = self.toplevel.get_widget('treeview_left')
94
self.treeview_right = self.toplevel.get_widget('treeview_right')
95
# Get some important menu items
96
self.menuitem_add_files = self.toplevel.get_widget('menuitem_add_files')
97
self.menuitem_remove_files = self.toplevel.get_widget('menuitem_remove_file')
98
self.menuitem_file_make_directory = self.toplevel.get_widget('menuitem_file_make_directory')
99
self.menuitem_file_rename = self.toplevel.get_widget('menuitem_file_rename')
100
self.menuitem_file_move = self.toplevel.get_widget('menuitem_file_move')
101
self.menuitem_file_annotate = self.toplevel.get_widget('menuitem_file_annotate')
102
self.menuitem_view_show_hidden_files = self.toplevel.get_widget('menuitem_view_show_hidden_files')
103
self.menuitem_branch = self.toplevel.get_widget('menuitem_branch')
104
self.menuitem_branch_init = self.toplevel.get_widget('menuitem_branch_initialize')
105
self.menuitem_branch_get = self.toplevel.get_widget('menuitem_branch_get')
106
self.menuitem_branch_checkout = self.toplevel.get_widget('menuitem_branch_checkout')
107
self.menuitem_branch_pull = self.toplevel.get_widget('menuitem_branch_pull')
108
self.menuitem_branch_push = self.toplevel.get_widget('menuitem_branch_push')
109
self.menuitem_branch_revert = self.toplevel.get_widget('menuitem_branch_revert')
110
self.menuitem_branch_merge = self.toplevel.get_widget('menuitem_branch_merge')
111
self.menuitem_branch_commit = self.toplevel.get_widget('menuitem_branch_commit')
112
self.menuitem_branch_tags = self.toplevel.get_widget('menuitem_branch_tags')
113
self.menuitem_branch_status = self.toplevel.get_widget('menuitem_branch_status')
114
self.menuitem_branch_missing = self.toplevel.get_widget('menuitem_branch_missing_revisions')
115
self.menuitem_branch_conflicts = self.toplevel.get_widget('menuitem_branch_conflicts')
116
self.menuitem_stats = self.toplevel.get_widget('menuitem_stats')
117
self.menuitem_stats_diff = self.toplevel.get_widget('menuitem_stats_diff')
118
self.menuitem_stats_log = self.toplevel.get_widget('menuitem_stats_log')
119
# Get some toolbuttons
120
#self.menutoolbutton_diff = self.toplevel.get_widget('menutoolbutton_diff')
121
self.toolbutton_diff = self.toplevel.get_widget('toolbutton_diff')
122
self.toolbutton_log = self.toplevel.get_widget('toolbutton_log')
123
self.toolbutton_commit = self.toplevel.get_widget('toolbutton_commit')
124
self.toolbutton_pull = self.toplevel.get_widget('toolbutton_pull')
125
self.toolbutton_push = self.toplevel.get_widget('toolbutton_push')
126
# Get the drive selector
127
self.combobox_drive = gtk.combo_box_new_text()
128
self.combobox_drive.connect("changed", self._refresh_drives)
130
# Get the navigation widgets
131
self.hbox_location = self.toplevel.get_widget('hbox_location')
132
self.button_location_up = self.toplevel.get_widget('button_location_up')
133
self.button_location_jump = self.toplevel.get_widget('button_location_jump')
134
self.entry_location = self.toplevel.get_widget('entry_location')
135
self.image_location_error = self.toplevel.get_widget('image_location_error')
137
# Get the History widgets
138
self.check_history = self.toplevel.get_widget('checkbutton_history')
139
self.entry_history = self.toplevel.get_widget('entry_history_revno')
140
self.button_history = self.toplevel.get_widget('button_history_browse')
142
self.vbox_main_right = self.toplevel.get_widget('vbox_main_right')
144
# Dictionary for signal_autoconnect
145
dic = { "on_window_main_destroy": gtk.main_quit,
146
"on_window_main_delete_event": self.on_window_main_delete_event,
147
"on_quit_activate": self.on_window_main_delete_event,
148
"on_about_activate": self.on_about_activate,
149
"on_menuitem_add_files_activate": self.on_menuitem_add_files_activate,
150
"on_menuitem_remove_file_activate": self.on_menuitem_remove_file_activate,
151
"on_menuitem_file_make_directory_activate": self.on_menuitem_file_make_directory_activate,
152
"on_menuitem_file_move_activate": self.on_menuitem_file_move_activate,
153
"on_menuitem_file_rename_activate": self.on_menuitem_file_rename_activate,
154
"on_menuitem_file_annotate_activate": self.on_menuitem_file_annotate_activate,
155
"on_menuitem_view_show_hidden_files_activate": self.on_menuitem_view_show_hidden_files_activate,
156
"on_menuitem_view_refresh_activate": self.on_menuitem_view_refresh_activate,
157
"on_menuitem_branch_initialize_activate": self.on_menuitem_branch_initialize_activate,
158
"on_menuitem_branch_get_activate": self.on_menuitem_branch_get_activate,
159
"on_menuitem_branch_checkout_activate": self.on_menuitem_branch_checkout_activate,
160
"on_menuitem_branch_revert_activate": self.on_menuitem_branch_revert_activate,
161
"on_menuitem_branch_merge_activate": self.on_menuitem_branch_merge_activate,
162
"on_menuitem_branch_commit_activate": self.on_menuitem_branch_commit_activate,
163
"on_menuitem_branch_push_activate": self.on_menuitem_branch_push_activate,
164
"on_menuitem_branch_pull_activate": self.on_menuitem_branch_pull_activate,
165
"on_menuitem_branch_tags_activate": self.on_menuitem_branch_tags_activate,
166
"on_menuitem_branch_status_activate": self.on_menuitem_branch_status_activate,
167
"on_menuitem_branch_missing_revisions_activate": self.on_menuitem_branch_missing_revisions_activate,
168
"on_menuitem_branch_conflicts_activate": self.on_menuitem_branch_conflicts_activate,
169
"on_menuitem_stats_diff_activate": self.on_menuitem_stats_diff_activate,
170
"on_menuitem_stats_log_activate": self.on_menuitem_stats_log_activate,
171
"on_menuitem_stats_infos_activate": self.on_menuitem_stats_infos_activate,
172
"on_toolbutton_refresh_clicked": self.on_menuitem_view_refresh_activate,
173
"on_toolbutton_log_clicked": self.on_menuitem_stats_log_activate,
174
#"on_menutoolbutton_diff_clicked": self.on_menuitem_stats_diff_activate,
175
"on_toolbutton_diff_clicked": self.on_menuitem_stats_diff_activate,
176
"on_toolbutton_commit_clicked": self.on_menuitem_branch_commit_activate,
177
"on_toolbutton_pull_clicked": self.on_menuitem_branch_pull_activate,
178
"on_toolbutton_push_clicked": self.on_menuitem_branch_push_activate,
179
"on_treeview_right_button_press_event": self.on_treeview_right_button_press_event,
180
"on_treeview_right_row_activated": self.on_treeview_right_row_activated,
181
"on_treeview_left_button_press_event": self.on_treeview_left_button_press_event,
182
"on_treeview_left_row_activated": self.on_treeview_left_row_activated,
183
"on_button_location_up_clicked": self.on_button_location_up_clicked,
184
"on_button_location_jump_clicked": self.on_button_location_jump_clicked,
185
"on_entry_location_key_press_event": self.on_entry_location_key_press_event,
186
"on_checkbutton_history_toggled": self.on_checkbutton_history_toggled,
187
"on_entry_history_revno_key_press_event": self.on_entry_history_revno_key_press_event,
188
"on_button_history_browse_clicked": self.on_button_history_browse_clicked
191
# Connect the signals to the handlers
192
self.toplevel.signal_autoconnect(dic)
194
self._just_started = True
196
# Apply window size and position
197
width = self.pref.get_preference('window_width', 'int')
198
height = self.pref.get_preference('window_height', 'int')
199
self.window.resize(width, height)
200
x = self.pref.get_preference('window_x', 'int')
201
y = self.pref.get_preference('window_y', 'int')
202
self.window.move(x, y)
203
# Apply paned position
204
pos = self.pref.get_preference('paned_position', 'int')
205
self.hpaned_main.set_position(pos)
207
# Apply menu to the toolbutton
208
#menubutton = self.toplevel.get_widget('menutoolbutton_diff')
209
#menubutton.set_menu(handler.menu.toolbar_diff)
211
# Now we can show the window
214
# Show drive selector if under Win32
215
if sys.platform == 'win32':
216
self.hbox_location.pack_start(self.combobox_drive, False, False, 0)
217
self.hbox_location.reorder_child(self.combobox_drive, 1)
218
self.combobox_drive.show()
219
self.gen_hard_selector()
224
self.menuitem_view_show_hidden_files.set_active(self.pref.get_preference('dotted_files', 'bool'))
226
# We're starting local
228
self.remote_branch = None
229
self.remote_path = None
230
self.remote_revision = None
232
self.set_path(os.getcwd())
235
self._just_started = False
237
def set_path(self, path, force_remote=False):
238
self.notbranch = False
241
# Forcing remote mode (reading data from inventory)
242
self._show_stock_image(gtk.STOCK_DISCONNECT)
244
br = Branch.open_containing(path)[0]
245
except bzrerrors.NotBranchError:
246
self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
247
self.check_history.set_active(False)
248
self.check_history.set_sensitive(False)
250
except bzrerrors.UnsupportedProtocol:
251
self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
252
self.check_history.set_active(False)
253
self.check_history.set_sensitive(False)
256
self._show_stock_image(gtk.STOCK_CONNECT)
261
self.remote_branch, self.remote_path = Branch.open_containing(path)
263
if self.remote_revision is None:
264
self.remote_revision = self.remote_branch.last_revision()
266
self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
268
if len(self.remote_path) == 0:
269
self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
271
for (name, type) in self.remote_entries:
272
if name == self.remote_path:
273
self.remote_parent = type.file_id
276
if not path.endswith('/'):
279
if self.remote_branch.base == path:
280
self.button_location_up.set_sensitive(False)
282
self.button_location_up.set_sensitive(True)
284
if os.path.isdir(path):
285
self.image_location_error.destroy()
290
self.wt, self.wtpath = WorkingTree.open_containing(path)
291
except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
292
self.notbranch = True
294
# If we're in the root, we cannot go up anymore
295
if sys.platform == 'win32':
296
drive, tail = os.path.splitdrive(path)
297
if tail in ('', '/', '\\'):
298
self.button_location_up.set_sensitive(False)
300
self.button_location_up.set_sensitive(True)
303
self.button_location_up.set_sensitive(False)
305
self.button_location_up.set_sensitive(True)
306
elif not os.path.isfile(path):
307
# Doesn't seem to be a file nor a directory, trying to open a
309
self._show_stock_image(gtk.STOCK_DISCONNECT)
311
br = Branch.open_containing(path)[0]
312
except bzrerrors.NotBranchError:
313
self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
314
self.check_history.set_active(False)
315
self.check_history.set_sensitive(False)
317
except bzrerrors.UnsupportedProtocol:
318
self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
319
self.check_history.set_active(False)
320
self.check_history.set_sensitive(False)
323
self._show_stock_image(gtk.STOCK_CONNECT)
328
self.remote_branch, self.remote_path = Branch.open_containing(path)
330
if self.remote_revision is None:
331
self.remote_revision = self.remote_branch.last_revision()
333
self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
335
if len(self.remote_path) == 0:
336
self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
338
for (name, type) in self.remote_entries:
339
if name == self.remote_path:
340
self.remote_parent = type.file_id
343
if not path.endswith('/'):
346
if self.remote_branch.base == path:
347
self.button_location_up.set_sensitive(False)
349
self.button_location_up.set_sensitive(True)
352
self.check_history.set_active(False)
353
self.check_history.set_sensitive(False)
355
self.check_history.set_sensitive(True)
357
self.statusbar.push(self.context_id, path)
358
self.entry_location.set_text(path)
367
if len(self.remote_path) > 0:
368
return self.remote_branch.base + self.remote_path + '/'
370
return self.remote_branch.base
372
def on_about_activate(self, widget):
375
def on_button_history_browse_clicked(self, widget):
376
""" Browse for revision button handler. """
378
br = self.remote_branch
382
revb = RevisionBrowser(br, self.window)
383
response = revb.run()
384
if response != gtk.RESPONSE_NONE:
387
if response == gtk.RESPONSE_OK:
388
if revb.selected_revno is not None:
389
self.entry_history.set_text(revb.selected_revno)
393
def on_button_location_jump_clicked(self, widget):
394
""" Location Jump button handler. """
395
location = self.entry_location.get_text()
397
if self.set_path(location):
400
def on_button_location_up_clicked(self, widget):
401
""" Location Up button handler. """
404
self.set_path(os.path.split(self.get_path())[0])
408
newpath = delim.join(self.get_path().split(delim)[:-2])
410
self.set_path(newpath)
414
def on_checkbutton_history_toggled(self, widget):
415
""" History Mode toggle handler. """
416
if self.check_history.get_active():
417
# History Mode activated
418
self.entry_history.set_sensitive(True)
419
self.button_history.set_sensitive(True)
421
# History Mode deactivated
422
self.entry_history.set_sensitive(False)
423
self.button_history.set_sensitive(False)
426
def on_entry_history_revno_key_press_event(self, widget, event):
427
""" Key pressed handler for the history entry. """
428
if event.keyval == 65293:
429
# Return was hit, so we have to load that specific revision
430
# Emulate being remote, so inventory should be used
431
path = self.get_path()
434
self.remote_branch = self.wt.branch
436
revno = int(self.entry_history.get_text())
437
self.remote_revision = self.remote_branch.get_rev_id(revno)
438
if self.set_path(path, True):
441
def on_entry_location_key_press_event(self, widget, event):
442
""" Key pressed handler for the location entry. """
443
if event.keyval == 65293:
444
# Return was hit, so we have to jump
445
self.on_button_location_jump_clicked(widget)
447
def on_menuitem_add_files_activate(self, widget):
448
""" Add file(s)... menu handler. """
449
from add import OliveAdd
450
add = OliveAdd(self.wt, self.wtpath, self.get_selected_right())
453
def on_menuitem_branch_get_activate(self, widget):
454
""" Branch/Get... menu handler. """
455
from bzrlib.plugins.gtk.branch import BranchDialog
458
branch = BranchDialog(os.getcwd(), self.window, self.remote_branch.base)
460
branch = BranchDialog(self.get_path(), self.window)
461
response = branch.run()
462
if response != gtk.RESPONSE_NONE:
465
if response == gtk.RESPONSE_OK:
470
def on_menuitem_branch_checkout_activate(self, widget):
471
""" Branch/Checkout... menu handler. """
472
from bzrlib.plugins.gtk.checkout import CheckoutDialog
475
checkout = CheckoutDialog(os.getcwd(), self.window, self.remote_branch.base)
477
checkout = CheckoutDialog(self.get_path(), self.window)
478
response = checkout.run()
479
if response != gtk.RESPONSE_NONE:
482
if response == gtk.RESPONSE_OK:
488
def on_menuitem_branch_commit_activate(self, widget):
489
""" Branch/Commit... menu handler. """
490
commit = CommitDialog(self.wt, self.wtpath, self.notbranch, self.get_selected_right(), self.window)
491
response = commit.run()
492
if response != gtk.RESPONSE_NONE:
495
if response == gtk.RESPONSE_OK:
500
def on_menuitem_branch_conflicts_activate(self, widget):
501
""" Branch/Conflicts... menu handler. """
502
conflicts = ConflictsDialog(self.wt, self.window)
503
response = conflicts.run()
504
if response != gtk.RESPONSE_NONE:
507
def on_menuitem_branch_merge_activate(self, widget):
508
""" Branch/Merge... menu handler. """
509
from bzrlib.plugins.gtk.merge import MergeDialog
511
if self.check_for_changes():
512
error_dialog(_('There are local changes in the branch'),
513
_('Please commit or revert the changes before merging.'))
515
merge = MergeDialog(self.wt, self.wtpath)
519
def on_menuitem_branch_missing_revisions_activate(self, widget):
520
""" Branch/Missing revisions menu handler. """
521
local_branch = self.wt.branch
523
other_branch = local_branch.get_parent()
524
if other_branch is None:
525
error_dialog(_('Parent location is unknown'),
526
_('Cannot determine missing revisions if no parent location is known.'))
529
remote_branch = Branch.open(other_branch)
531
if remote_branch.base == local_branch.base:
532
remote_branch = local_branch
534
ret = len(local_branch.missing_revisions(remote_branch))
537
info_dialog(_('There are missing revisions'),
538
_('%d revision(s) missing.') % ret)
540
info_dialog(_('Local branch up to date'),
541
_('There are no missing revisions.'))
544
def on_menuitem_branch_pull_activate(self, widget):
545
""" Branch/Pull menu handler. """
546
branch_to = self.wt.branch
548
location = branch_to.get_parent()
550
error_dialog(_('Parent location is unknown'),
551
_('Pulling is not possible until there is a parent location.'))
554
branch_from = Branch.open(location)
556
if branch_to.get_parent() is None:
557
branch_to.set_parent(branch_from.base)
559
ret = branch_to.pull(branch_from)
561
info_dialog(_('Pull successful'), _('%d revision(s) pulled.') % ret)
563
def on_menuitem_branch_push_activate(self, widget):
564
""" Branch/Push... menu handler. """
565
push = PushDialog(self.wt.branch, self.window)
566
response = push.run()
567
if response != gtk.RESPONSE_NONE:
571
def on_menuitem_branch_revert_activate(self, widget):
572
""" Branch/Revert all changes menu handler. """
573
ret = self.wt.revert([])
575
warning_dialog(_('Conflicts detected'),
576
_('Please have a look at the working tree before continuing.'))
578
info_dialog(_('Revert successful'),
579
_('All files reverted to last revision.'))
582
def on_menuitem_branch_status_activate(self, widget):
583
""" Branch/Status... menu handler. """
584
from bzrlib.plugins.gtk.status import StatusDialog
585
status = StatusDialog(self.wt, self.wtpath)
586
response = status.run()
587
if response != gtk.RESPONSE_NONE:
590
def on_menuitem_branch_initialize_activate(self, widget):
591
""" Initialize current directory. """
592
init = InitDialog(self.path, self.window)
593
response = init.run()
594
if response != gtk.RESPONSE_NONE:
597
if response == gtk.RESPONSE_OK:
602
def on_menuitem_branch_tags_activate(self, widget):
603
""" Branch/Tags... menu handler. """
604
from bzrlib.plugins.gtk.tags import TagsWindow
606
window = TagsWindow(self.wt.branch, self.window)
608
window = TagsWindow(self.remote_branch, self.window)
611
def on_menuitem_file_annotate_activate(self, widget):
612
""" File/Annotate... menu handler. """
613
if self.get_selected_right() is None:
614
error_dialog(_('No file was selected'),
615
_('Please select a file from the list.'))
618
branch = self.wt.branch
619
file_id = self.wt.path2id(self.wt.relpath(os.path.join(self.path, self.get_selected_right())))
621
window = GAnnotateWindow(all=False, plain=False)
622
window.set_title(os.path.join(self.path, self.get_selected_right()) + " - Annotate")
623
config = GAnnotateConfig(window)
627
window.annotate(self.wt, branch, file_id)
631
def on_menuitem_file_make_directory_activate(self, widget):
632
""" File/Make directory... menu handler. """
633
from mkdir import OliveMkdir
634
mkdir = OliveMkdir(self.wt, self.wtpath)
637
def on_menuitem_file_move_activate(self, widget):
638
""" File/Move... menu handler. """
639
from move import OliveMove
640
move = OliveMove(self.wt, self.wtpath, self.get_selected_right())
643
def on_menuitem_file_rename_activate(self, widget):
644
""" File/Rename... menu handler. """
645
from rename import OliveRename
646
rename = OliveRename(self.wt, self.wtpath, self.get_selected_right())
649
def on_menuitem_remove_file_activate(self, widget):
650
""" Remove (unversion) selected file. """
651
from remove import OliveRemoveDialog
652
remove = OliveRemoveDialog(self.wt, self.wtpath,
653
selected=self.get_selected_right(),
655
response = remove.run()
657
if response != gtk.RESPONSE_NONE:
660
if response == gtk.RESPONSE_OK:
661
self.set_path(self.path)
666
def on_menuitem_stats_diff_activate(self, widget):
667
""" Statistics/Differences... menu handler. """
668
window = DiffWindow()
669
parent_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
670
window.set_diff(self.wt.branch.nick, self.wt, parent_tree)
673
def on_menuitem_stats_infos_activate(self, widget):
674
""" Statistics/Informations... menu handler. """
675
from info import OliveInfo
677
info = OliveInfo(self.remote_branch)
679
info = OliveInfo(self.wt.branch)
682
def on_menuitem_stats_log_activate(self, widget):
683
""" Statistics/Log... menu handler. """
684
window = branchwin.BranchWindow()
686
window.set_branch(self.wt.branch, self.wt.branch.last_revision(), None)
688
window.set_branch(self.remote_branch, self.remote_branch.last_revision(), None)
691
def on_menuitem_view_refresh_activate(self, widget):
692
""" View/Refresh menu handler. """
693
# Refresh the left pane
695
# Refresh the right pane
698
def on_menuitem_view_show_hidden_files_activate(self, widget):
699
""" View/Show hidden files menu handler. """
700
self.pref.set_preference('dotted_files', widget.get_active())
701
if self.path is not None:
704
def on_treeview_left_button_press_event(self, widget, event):
705
""" Occurs when somebody right-clicks in the bookmark list. """
706
if event.button == 3:
707
# Don't show context with nothing selected
708
if self.get_selected_left() == None:
712
from menu import OliveMenu
713
menu = OliveMenu(path=self.get_path(),
714
selected=self.get_selected_left(),
717
menu.left_context_menu().popup(None, None, None, 0,
720
def on_treeview_left_row_activated(self, treeview, path, view_column):
721
""" Occurs when somebody double-clicks or enters an item in the
724
newdir = self.get_selected_left()
728
if self.set_path(newdir):
731
def on_treeview_right_button_press_event(self, widget, event):
732
""" Occurs when somebody right-clicks in the file list. """
733
if event.button == 3:
735
from menu import OliveMenu
736
menu = OliveMenu(path=self.get_path(),
737
selected=self.get_selected_right(),
740
m_open = menu.ui.get_widget('/context_right/open')
741
m_add = menu.ui.get_widget('/context_right/add')
742
m_remove = menu.ui.get_widget('/context_right/remove')
743
m_rename = menu.ui.get_widget('/context_right/rename')
744
m_revert = menu.ui.get_widget('/context_right/revert')
745
m_commit = menu.ui.get_widget('/context_right/commit')
746
m_annotate = menu.ui.get_widget('/context_right/annotate')
747
m_diff = menu.ui.get_widget('/context_right/diff')
748
# check if we're in a branch
750
from bzrlib.branch import Branch
751
Branch.open_containing(self.get_path())
753
m_open.set_sensitive(False)
754
m_add.set_sensitive(False)
755
m_remove.set_sensitive(False)
756
m_rename.set_sensitive(False)
757
m_revert.set_sensitive(False)
758
m_commit.set_sensitive(False)
759
m_annotate.set_sensitive(False)
760
m_diff.set_sensitive(False)
762
m_open.set_sensitive(True)
763
m_add.set_sensitive(True)
764
m_remove.set_sensitive(True)
765
m_rename.set_sensitive(True)
766
m_revert.set_sensitive(True)
767
m_commit.set_sensitive(True)
768
m_annotate.set_sensitive(True)
769
m_diff.set_sensitive(True)
770
except bzrerrors.NotBranchError:
771
m_open.set_sensitive(True)
772
m_add.set_sensitive(False)
773
m_remove.set_sensitive(False)
774
m_rename.set_sensitive(False)
775
m_revert.set_sensitive(False)
776
m_commit.set_sensitive(False)
777
m_annotate.set_sensitive(False)
778
m_diff.set_sensitive(False)
781
menu.right_context_menu().popup(None, None, None, 0,
784
menu.remote_context_menu().popup(None, None, None, 0,
787
def on_treeview_right_row_activated(self, treeview, path, view_column):
788
""" Occurs when somebody double-clicks or enters an item in the
790
from launch import launch
792
newdir = self.get_selected_right()
797
self.set_path(os.path.split(self.get_path())[0])
799
fullpath = os.path.join(self.get_path(), newdir)
800
if os.path.isdir(fullpath):
801
# selected item is an existant directory
802
self.set_path(fullpath)
807
if self._is_remote_dir(self.get_path() + newdir):
808
self.set_path(self.get_path() + newdir)
812
def on_window_main_delete_event(self, widget, event=None):
813
""" Do some stuff before exiting. """
814
width, height = self.window_main.get_size()
815
self.pref.set_preference('window_width', width)
816
self.pref.set_preference('window_height', height)
817
x, y = self.window_main.get_position()
818
self.pref.set_preference('window_x', x)
819
self.pref.set_preference('window_y', y)
820
self.pref.set_preference('paned_position',
821
self.hpaned_main.get_position())
824
self.window_main.destroy()
826
def _load_left(self):
827
""" Load data into the left panel. (Bookmarks) """
829
treestore = gtk.TreeStore(str, str)
832
bookmarks = self.pref.get_bookmarks()
834
# Add them to the TreeStore
835
titer = treestore.append(None, [_('Bookmarks'), None])
836
for item in bookmarks:
837
title = self.pref.get_bookmark_title(item)
838
treestore.append(titer, [title, item])
840
# Create the column and add it to the TreeView
841
self.treeview_left.set_model(treestore)
842
tvcolumn_bookmark = gtk.TreeViewColumn(_('Bookmark'))
843
self.treeview_left.append_column(tvcolumn_bookmark)
846
cell = gtk.CellRendererText()
847
tvcolumn_bookmark.pack_start(cell, True)
848
tvcolumn_bookmark.add_attribute(cell, 'text', 0)
851
self.treeview_left.expand_all()
853
def _load_right(self):
854
""" Load data into the right panel. (Filelist) """
856
# Model: [ icon, dir, name, status text, status, size (int), size (human), mtime (int), mtime (local), fileid]
857
liststore = gtk.ListStore(gobject.TYPE_STRING,
858
gobject.TYPE_BOOLEAN,
871
# Fill the appropriate lists
872
dotted_files = self.pref.get_preference('dotted_files', 'bool')
873
for item in os.listdir(self.path):
874
if not dotted_files and item[0] == '.':
876
if os.path.isdir(self.path + os.sep + item):
881
if not self.notbranch:
882
branch = self.wt.branch
883
tree2 = self.wt.branch.repository.revision_tree(branch.last_revision())
885
delta = self.wt.changes_from(tree2, want_unchanged=True)
887
# Add'em to the ListStore
889
statinfo = os.stat(self.path + os.sep + item)
890
liststore.append([ gtk.STOCK_DIRECTORY,
896
self._format_size(statinfo.st_size),
898
self._format_date(statinfo.st_mtime),
903
if not self.notbranch:
904
filename = self.wt.relpath(self.path + os.sep + item)
909
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
910
if rpathnew == filename:
913
for rpath, id, kind in delta.added:
914
if rpath == filename:
917
for rpath, id, kind in delta.removed:
918
if rpath == filename:
921
for rpath, id, kind, text_modified, meta_modified in delta.modified:
922
if rpath == filename:
925
for rpath, id, kind in delta.unchanged:
926
if rpath == filename:
929
for rpath, file_class, kind, id, entry in self.wt.list_files():
930
if rpath == filename and file_class == 'I':
935
if status == 'renamed':
937
elif status == 'removed':
939
elif status == 'added':
941
elif status == 'modified':
943
elif status == 'unchanged':
945
elif status == 'ignored':
950
statinfo = os.stat(self.path + os.sep + item)
951
liststore.append([gtk.STOCK_FILE,
957
self._format_size(statinfo.st_size),
959
self._format_date(statinfo.st_mtime),
962
# Create the columns and add them to the TreeView
963
self.treeview_right.set_model(liststore)
964
self._tvcolumn_filename = gtk.TreeViewColumn(_('Filename'))
965
self._tvcolumn_status = gtk.TreeViewColumn(_('Status'))
966
self._tvcolumn_size = gtk.TreeViewColumn(_('Size'))
967
self._tvcolumn_mtime = gtk.TreeViewColumn(_('Last modified'))
968
self.treeview_right.append_column(self._tvcolumn_filename)
969
self.treeview_right.append_column(self._tvcolumn_status)
970
self.treeview_right.append_column(self._tvcolumn_size)
971
self.treeview_right.append_column(self._tvcolumn_mtime)
974
cellpb = gtk.CellRendererPixbuf()
975
cell = gtk.CellRendererText()
976
self._tvcolumn_filename.pack_start(cellpb, False)
977
self._tvcolumn_filename.pack_start(cell, True)
978
self._tvcolumn_filename.set_attributes(cellpb, stock_id=0)
979
self._tvcolumn_filename.add_attribute(cell, 'text', 2)
980
self._tvcolumn_status.pack_start(cell, True)
981
self._tvcolumn_status.add_attribute(cell, 'text', 3)
982
self._tvcolumn_size.pack_start(cell, True)
983
self._tvcolumn_size.add_attribute(cell, 'text', 6)
984
self._tvcolumn_mtime.pack_start(cell, True)
985
self._tvcolumn_mtime.add_attribute(cell, 'text', 8)
987
# Set up the properties of the TreeView
988
self.treeview_right.set_headers_visible(True)
989
self.treeview_right.set_headers_clickable(True)
990
self.treeview_right.set_search_column(1)
991
self._tvcolumn_filename.set_resizable(True)
992
self._tvcolumn_status.set_resizable(True)
993
self._tvcolumn_size.set_resizable(True)
994
self._tvcolumn_mtime.set_resizable(True)
996
liststore.set_sort_func(13, self._sort_filelist_callback, None)
997
liststore.set_sort_column_id(13, gtk.SORT_ASCENDING)
998
self._tvcolumn_filename.set_sort_column_id(13)
999
self._tvcolumn_status.set_sort_column_id(3)
1000
self._tvcolumn_size.set_sort_column_id(5)
1001
self._tvcolumn_mtime.set_sort_column_id(7)
1004
self.set_sensitivity()
1006
def get_selected_fileid(self):
1007
""" Get the file_id of the selected file. """
1008
treeselection = self.treeview_right.get_selection()
1009
(model, iter) = treeselection.get_selected()
1014
return model.get_value(iter, 9)
1016
def get_selected_right(self):
1017
""" Get the selected filename. """
1018
treeselection = self.treeview_right.get_selection()
1019
(model, iter) = treeselection.get_selected()
1024
return model.get_value(iter, 2)
1026
def get_selected_left(self):
1027
""" Get the selected bookmark. """
1028
treeselection = self.treeview_left.get_selection()
1029
(model, iter) = treeselection.get_selected()
1034
return model.get_value(iter, 1)
1036
def set_statusbar(self, message):
1037
""" Set the statusbar message. """
1038
self.statusbar.push(self.context_id, message)
1040
def clear_statusbar(self):
1041
""" Clean the last message from the statusbar. """
1042
self.statusbar.pop(self.context_id)
1044
def set_sensitivity(self):
1045
""" Set menu and toolbar sensitivity. """
1048
self.menuitem_branch_init.set_sensitive(self.notbranch)
1049
self.menuitem_branch_get.set_sensitive(self.notbranch)
1050
self.menuitem_branch_checkout.set_sensitive(self.notbranch)
1051
self.menuitem_branch_pull.set_sensitive(not self.notbranch)
1052
self.menuitem_branch_push.set_sensitive(not self.notbranch)
1053
self.menuitem_branch_revert.set_sensitive(not self.notbranch)
1054
self.menuitem_branch_merge.set_sensitive(not self.notbranch)
1055
self.menuitem_branch_commit.set_sensitive(not self.notbranch)
1056
self.menuitem_branch_tags.set_sensitive(not self.notbranch)
1057
self.menuitem_branch_status.set_sensitive(not self.notbranch)
1058
self.menuitem_branch_missing.set_sensitive(not self.notbranch)
1059
self.menuitem_branch_conflicts.set_sensitive(not self.notbranch)
1060
self.menuitem_stats.set_sensitive(not self.notbranch)
1061
self.menuitem_stats_diff.set_sensitive(not self.notbranch)
1062
self.menuitem_add_files.set_sensitive(not self.notbranch)
1063
self.menuitem_remove_files.set_sensitive(not self.notbranch)
1064
self.menuitem_file_make_directory.set_sensitive(not self.notbranch)
1065
self.menuitem_file_rename.set_sensitive(not self.notbranch)
1066
self.menuitem_file_move.set_sensitive(not self.notbranch)
1067
self.menuitem_file_annotate.set_sensitive(not self.notbranch)
1068
#self.menutoolbutton_diff.set_sensitive(True)
1069
self.toolbutton_diff.set_sensitive(not self.notbranch)
1070
self.toolbutton_log.set_sensitive(not self.notbranch)
1071
self.toolbutton_commit.set_sensitive(not self.notbranch)
1072
self.toolbutton_pull.set_sensitive(not self.notbranch)
1073
self.toolbutton_push.set_sensitive(not self.notbranch)
1076
self.menuitem_branch_init.set_sensitive(False)
1077
self.menuitem_branch_get.set_sensitive(True)
1078
self.menuitem_branch_checkout.set_sensitive(True)
1079
self.menuitem_branch_pull.set_sensitive(False)
1080
self.menuitem_branch_push.set_sensitive(False)
1081
self.menuitem_branch_revert.set_sensitive(False)
1082
self.menuitem_branch_merge.set_sensitive(False)
1083
self.menuitem_branch_commit.set_sensitive(False)
1084
self.menuitem_branch_tags.set_sensitive(True)
1085
self.menuitem_branch_status.set_sensitive(False)
1086
self.menuitem_branch_missing.set_sensitive(False)
1087
self.menuitem_branch_conflicts.set_sensitive(False)
1088
self.menuitem_stats.set_sensitive(True)
1089
self.menuitem_stats_diff.set_sensitive(False)
1090
self.menuitem_add_files.set_sensitive(False)
1091
self.menuitem_remove_files.set_sensitive(False)
1092
self.menuitem_file_make_directory.set_sensitive(False)
1093
self.menuitem_file_rename.set_sensitive(False)
1094
self.menuitem_file_move.set_sensitive(False)
1095
self.menuitem_file_annotate.set_sensitive(False)
1096
#self.menutoolbutton_diff.set_sensitive(True)
1097
self.toolbutton_diff.set_sensitive(False)
1098
self.toolbutton_log.set_sensitive(True)
1099
self.toolbutton_commit.set_sensitive(False)
1100
self.toolbutton_pull.set_sensitive(False)
1101
self.toolbutton_push.set_sensitive(False)
1103
def refresh_left(self):
1104
""" Refresh the bookmark list. """
1106
# Get TreeStore and clear it
1107
treestore = self.treeview_left.get_model()
1110
# Re-read preferences
1114
bookmarks = self.pref.get_bookmarks()
1116
# Add them to the TreeStore
1117
titer = treestore.append(None, [_('Bookmarks'), None])
1118
for item in bookmarks:
1119
title = self.pref.get_bookmark_title(item)
1120
treestore.append(titer, [title, item])
1122
# Add the TreeStore to the TreeView
1123
self.treeview_left.set_model(treestore)
1126
self.treeview_left.expand_all()
1128
def refresh_right(self, path=None):
1129
""" Refresh the file list. """
1132
from bzrlib.workingtree import WorkingTree
1135
path = self.get_path()
1137
# A workaround for double-clicking Bookmarks
1138
if not os.path.exists(path):
1141
# Get ListStore and clear it
1142
liststore = self.treeview_right.get_model()
1145
# Show Status column
1146
self._tvcolumn_status.set_visible(True)
1151
# Fill the appropriate lists
1152
dotted_files = self.pref.get_preference('dotted_files', 'bool')
1153
for item in os.listdir(path):
1154
if not dotted_files and item[0] == '.':
1156
if os.path.isdir(path + os.sep + item):
1161
# Try to open the working tree
1164
tree1 = WorkingTree.open_containing(path)[0]
1165
except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
1169
branch = tree1.branch
1170
tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
1172
delta = tree1.changes_from(tree2, want_unchanged=True)
1174
# Add'em to the ListStore
1176
statinfo = os.stat(self.path + os.sep + item)
1177
liststore.append([gtk.STOCK_DIRECTORY,
1183
self._format_size(statinfo.st_size),
1185
self._format_date(statinfo.st_mtime),
1191
filename = tree1.relpath(path + os.sep + item)
1196
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
1197
if rpathnew == filename:
1200
for rpath, id, kind in delta.added:
1201
if rpath == filename:
1204
for rpath, id, kind in delta.removed:
1205
if rpath == filename:
1208
for rpath, id, kind, text_modified, meta_modified in delta.modified:
1209
if rpath == filename:
1212
for rpath, id, kind in delta.unchanged:
1213
if rpath == filename:
1214
status = 'unchanged'
1216
for rpath, file_class, kind, id, entry in self.wt.list_files():
1217
if rpath == filename and file_class == 'I':
1222
if status == 'renamed':
1224
elif status == 'removed':
1226
elif status == 'added':
1228
elif status == 'modified':
1230
elif status == 'unchanged':
1232
elif status == 'ignored':
1237
statinfo = os.stat(self.path + os.sep + item)
1238
liststore.append([gtk.STOCK_FILE,
1244
self._format_size(statinfo.st_size),
1246
self._format_date(statinfo.st_mtime),
1251
# Get ListStore and clear it
1252
liststore = self.treeview_right.get_model()
1255
# Hide Status column
1256
self._tvcolumn_status.set_visible(False)
1261
self._show_stock_image(gtk.STOCK_REFRESH)
1263
for (name, type) in self.remote_entries:
1264
if type.kind == 'directory':
1266
elif type.kind == 'file':
1270
""" Cache based on revision history. """
1271
def __init__(self, history):
1272
self._history = history
1274
def _lookup_revision(self, revid):
1275
for r in self._history:
1276
if r.revision_id == revid:
1278
rev = repo.get_revision(revid)
1279
self._history.append(rev)
1282
repo = self.remote_branch.repository
1284
revhistory = self.remote_branch.revision_history()
1286
revs = repo.get_revisions(revhistory)
1287
cache = HistoryCache(revs)
1288
except bzrerrors.InvalidHttpResponse:
1289
# Fallback to dummy algorithm, because of LP: #115209
1290
cache = HistoryCache([])
1293
if item.parent_id == self.remote_parent:
1294
rev = cache._lookup_revision(item.revision)
1295
liststore.append([ gtk.STOCK_DIRECTORY,
1301
self._format_size(0),
1303
self._format_date(rev.timestamp),
1306
while gtk.events_pending():
1307
gtk.main_iteration()
1310
if item.parent_id == self.remote_parent:
1311
rev = cache._lookup_revision(item.revision)
1312
liststore.append([ gtk.STOCK_FILE,
1318
self._format_size(item.text_size),
1320
self._format_date(rev.timestamp),
1323
while gtk.events_pending():
1324
gtk.main_iteration()
1326
self.image_location_error.destroy()
1328
# Columns should auto-size
1329
self.treeview_right.columns_autosize()
1332
self.set_sensitivity()
1334
def _harddisks(self):
1335
""" Returns hard drive letters under Win32. """
1340
if sys.platform == 'win32':
1341
print "pyWin32 modules needed to run Olive on Win32."
1347
for drive in string.ascii_uppercase:
1348
if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED:
1349
driveletters.append(drive+':')
1352
def gen_hard_selector(self):
1353
""" Generate the hard drive selector under Win32. """
1354
drives = self._harddisks()
1355
for drive in drives:
1356
self.combobox_drive.append_text(drive)
1357
self.combobox_drive.set_active(drives.index(os.getcwd()[0:2]))
1359
def _refresh_drives(self, combobox):
1360
if self._just_started:
1362
model = combobox.get_model()
1363
active = combobox.get_active()
1365
drive = model[active][0]
1366
self.set_path(drive + '\\')
1367
self.refresh_right(drive + '\\')
1369
def check_for_changes(self):
1370
""" Check whether there were changes in the current working tree. """
1371
old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
1372
delta = self.wt.changes_from(old_tree)
1376
if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
1381
def _sort_filelist_callback(self, model, iter1, iter2, data):
1382
""" The sort callback for the file list, return values:
1387
name1 = model.get_value(iter1, 2)
1388
name2 = model.get_value(iter2, 2)
1390
if model.get_value(iter1, 1):
1391
# item1 is a directory
1392
if not model.get_value(iter2, 1):
1396
# both of them are directories, we compare their names
1399
elif name1 == name2:
1404
# item1 is not a directory
1405
if model.get_value(iter2, 1):
1409
# both of them are files, compare them
1412
elif name1 == name2:
1417
def _format_size(self, size):
1418
""" Format size to a human readable format. """
1421
def _format_date(self, timestamp):
1422
""" Format the time (given in secs) to a human readable format. """
1423
return time.ctime(timestamp)
1425
def _is_remote_dir(self, location):
1426
""" Determine whether the given location is a directory or not. """
1428
# We're in local mode
1431
branch, path = Branch.open_containing(location)
1432
for (name, type) in self.remote_entries:
1433
if name == path and type.kind == 'directory':
1436
# Either it's not a directory or not in the inventory
1439
def _show_stock_image(self, stock_id):
1440
""" Show a stock image next to the location entry. """
1441
self.image_location_error.destroy()
1442
self.image_location_error = gtk.image_new_from_stock(stock_id, gtk.ICON_SIZE_BUTTON)
1443
self.hbox_location.pack_start(self.image_location_error, False, False, 0)
1444
if sys.platform == 'win32':
1445
self.hbox_location.reorder_child(self.image_location_error, 2)
1447
self.hbox_location.reorder_child(self.image_location_error, 1)
1448
self.image_location_error.show()
1449
while gtk.events_pending():
1450
gtk.main_iteration()
1455
""" A class which handles Olive's preferences. """
1456
def __init__(self, path=None):
1457
""" Initialize the Preferences class. """
1458
# Some default options
1459
self.defaults = { 'strict_commit' : False,
1460
'dotted_files' : False,
1461
'window_width' : 700,
1462
'window_height' : 400,
1465
'paned_position': 200 }
1467
# Create a config parser object
1468
self.config = ConfigParser.RawConfigParser()
1472
if sys.platform == 'win32':
1473
# Windows - no dotted files
1474
self._filename = os.path.expanduser('~/olive.conf')
1476
self._filename = os.path.expanduser('~/.olive.conf')
1478
self._filename = path
1480
# Load the configuration
1483
def _get_default(self, option):
1484
""" Get the default option for a preference. """
1486
ret = self.defaults[option]
1493
""" Refresh the configuration. """
1494
# First write out the changes
1496
# Then load the configuration again
1500
""" Just read the configuration. """
1501
# Re-initialize the config parser object to avoid some bugs
1502
self.config = ConfigParser.RawConfigParser()
1503
self.config.read([self._filename])
1506
""" Write the configuration to the appropriate files. """
1507
fp = open(self._filename, 'w')
1508
self.config.write(fp)
1511
def get_bookmarks(self):
1512
""" Return the list of bookmarks. """
1513
bookmarks = self.config.sections()
1514
if self.config.has_section('preferences'):
1515
bookmarks.remove('preferences')
1518
def add_bookmark(self, path):
1519
""" Add bookmark. """
1521
self.config.add_section(path)
1522
except ConfigParser.DuplicateSectionError:
1527
def get_bookmark_title(self, path):
1528
""" Get bookmark title. """
1530
ret = self.config.get(path, 'title')
1531
except ConfigParser.NoOptionError:
1536
def set_bookmark_title(self, path, title):
1537
""" Set bookmark title. """
1538
# FIXME: What if path isn't listed yet?
1539
# FIXME: Canonicalize paths first?
1540
self.config.set(path, 'title', title)
1542
def remove_bookmark(self, path):
1543
""" Remove bookmark. """
1544
return self.config.remove_section(path)
1546
def set_preference(self, option, value):
1547
""" Set the value of the given option. """
1550
elif value is False:
1553
if self.config.has_section('preferences'):
1554
self.config.set('preferences', option, value)
1556
self.config.add_section('preferences')
1557
self.config.set('preferences', option, value)
1559
def get_preference(self, option, kind='str'):
1560
""" Get the value of the given option.
1562
:param kind: str/bool/int/float. default: str
1564
if self.config.has_option('preferences', option):
1566
return self.config.getboolean('preferences', option)
1568
return self.config.getint('preferences', option)
1569
elif kind == 'float':
1570
return self.config.getfloat('preferences', option)
1572
return self.config.get('preferences', option)
1575
return self._get_default(option)