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__)
69
dialog.set_artists(["Simon Pascal Klein"])
76
""" The main Olive GTK frontend class. This is called when launching the
80
self.toplevel = gtk.glade.XML(GLADEFILENAME, 'window_main', 'olive-gtk')
81
self.window = self.toplevel.get_widget('window_main')
82
self.pref = Preferences()
85
# Initialize the statusbar
86
self.statusbar = self.toplevel.get_widget('statusbar')
87
self.context_id = self.statusbar.get_context_id('olive')
90
self.window_main = self.toplevel.get_widget('window_main')
92
self.hpaned_main = self.toplevel.get_widget('hpaned_main')
94
self.treeview_left = self.toplevel.get_widget('treeview_left')
95
self.treeview_right = self.toplevel.get_widget('treeview_right')
96
# Get some important menu items
97
self.menuitem_add_files = self.toplevel.get_widget('menuitem_add_files')
98
self.menuitem_remove_files = self.toplevel.get_widget('menuitem_remove_file')
99
self.menuitem_file_make_directory = self.toplevel.get_widget('menuitem_file_make_directory')
100
self.menuitem_file_rename = self.toplevel.get_widget('menuitem_file_rename')
101
self.menuitem_file_move = self.toplevel.get_widget('menuitem_file_move')
102
self.menuitem_file_annotate = self.toplevel.get_widget('menuitem_file_annotate')
103
self.menuitem_view_show_hidden_files = self.toplevel.get_widget('menuitem_view_show_hidden_files')
104
self.menuitem_branch = self.toplevel.get_widget('menuitem_branch')
105
self.menuitem_branch_init = self.toplevel.get_widget('menuitem_branch_initialize')
106
self.menuitem_branch_get = self.toplevel.get_widget('menuitem_branch_get')
107
self.menuitem_branch_checkout = self.toplevel.get_widget('menuitem_branch_checkout')
108
self.menuitem_branch_pull = self.toplevel.get_widget('menuitem_branch_pull')
109
self.menuitem_branch_push = self.toplevel.get_widget('menuitem_branch_push')
110
self.menuitem_branch_revert = self.toplevel.get_widget('menuitem_branch_revert')
111
self.menuitem_branch_merge = self.toplevel.get_widget('menuitem_branch_merge')
112
self.menuitem_branch_commit = self.toplevel.get_widget('menuitem_branch_commit')
113
self.menuitem_branch_tags = self.toplevel.get_widget('menuitem_branch_tags')
114
self.menuitem_branch_status = self.toplevel.get_widget('menuitem_branch_status')
115
self.menuitem_branch_missing = self.toplevel.get_widget('menuitem_branch_missing_revisions')
116
self.menuitem_branch_conflicts = self.toplevel.get_widget('menuitem_branch_conflicts')
117
self.menuitem_stats = self.toplevel.get_widget('menuitem_stats')
118
self.menuitem_stats_diff = self.toplevel.get_widget('menuitem_stats_diff')
119
self.menuitem_stats_log = self.toplevel.get_widget('menuitem_stats_log')
120
# Get some toolbuttons
121
#self.menutoolbutton_diff = self.toplevel.get_widget('menutoolbutton_diff')
122
self.toolbutton_diff = self.toplevel.get_widget('toolbutton_diff')
123
self.toolbutton_log = self.toplevel.get_widget('toolbutton_log')
124
self.toolbutton_commit = self.toplevel.get_widget('toolbutton_commit')
125
self.toolbutton_pull = self.toplevel.get_widget('toolbutton_pull')
126
self.toolbutton_push = self.toplevel.get_widget('toolbutton_push')
127
# Get the drive selector
128
self.combobox_drive = gtk.combo_box_new_text()
129
self.combobox_drive.connect("changed", self._refresh_drives)
131
# Get the navigation widgets
132
self.hbox_location = self.toplevel.get_widget('hbox_location')
133
self.button_location_up = self.toplevel.get_widget('button_location_up')
134
self.button_location_jump = self.toplevel.get_widget('button_location_jump')
135
self.entry_location = self.toplevel.get_widget('entry_location')
136
self.image_location_error = self.toplevel.get_widget('image_location_error')
138
# Get the History widgets
139
self.check_history = self.toplevel.get_widget('checkbutton_history')
140
self.entry_history = self.toplevel.get_widget('entry_history_revno')
141
self.button_history = self.toplevel.get_widget('button_history_browse')
143
self.vbox_main_right = self.toplevel.get_widget('vbox_main_right')
145
# Dictionary for signal_autoconnect
146
dic = { "on_window_main_destroy": gtk.main_quit,
147
"on_window_main_delete_event": self.on_window_main_delete_event,
148
"on_quit_activate": self.on_window_main_delete_event,
149
"on_about_activate": self.on_about_activate,
150
"on_menuitem_add_files_activate": self.on_menuitem_add_files_activate,
151
"on_menuitem_remove_file_activate": self.on_menuitem_remove_file_activate,
152
"on_menuitem_file_make_directory_activate": self.on_menuitem_file_make_directory_activate,
153
"on_menuitem_file_move_activate": self.on_menuitem_file_move_activate,
154
"on_menuitem_file_rename_activate": self.on_menuitem_file_rename_activate,
155
"on_menuitem_file_annotate_activate": self.on_menuitem_file_annotate_activate,
156
"on_menuitem_view_show_hidden_files_activate": self.on_menuitem_view_show_hidden_files_activate,
157
"on_menuitem_view_refresh_activate": self.on_menuitem_view_refresh_activate,
158
"on_menuitem_branch_initialize_activate": self.on_menuitem_branch_initialize_activate,
159
"on_menuitem_branch_get_activate": self.on_menuitem_branch_get_activate,
160
"on_menuitem_branch_checkout_activate": self.on_menuitem_branch_checkout_activate,
161
"on_menuitem_branch_revert_activate": self.on_menuitem_branch_revert_activate,
162
"on_menuitem_branch_merge_activate": self.on_menuitem_branch_merge_activate,
163
"on_menuitem_branch_commit_activate": self.on_menuitem_branch_commit_activate,
164
"on_menuitem_branch_push_activate": self.on_menuitem_branch_push_activate,
165
"on_menuitem_branch_pull_activate": self.on_menuitem_branch_pull_activate,
166
"on_menuitem_branch_tags_activate": self.on_menuitem_branch_tags_activate,
167
"on_menuitem_branch_status_activate": self.on_menuitem_branch_status_activate,
168
"on_menuitem_branch_missing_revisions_activate": self.on_menuitem_branch_missing_revisions_activate,
169
"on_menuitem_branch_conflicts_activate": self.on_menuitem_branch_conflicts_activate,
170
"on_menuitem_stats_diff_activate": self.on_menuitem_stats_diff_activate,
171
"on_menuitem_stats_log_activate": self.on_menuitem_stats_log_activate,
172
"on_menuitem_stats_infos_activate": self.on_menuitem_stats_infos_activate,
173
"on_toolbutton_refresh_clicked": self.on_menuitem_view_refresh_activate,
174
"on_toolbutton_log_clicked": self.on_menuitem_stats_log_activate,
175
#"on_menutoolbutton_diff_clicked": self.on_menuitem_stats_diff_activate,
176
"on_toolbutton_diff_clicked": self.on_menuitem_stats_diff_activate,
177
"on_toolbutton_commit_clicked": self.on_menuitem_branch_commit_activate,
178
"on_toolbutton_pull_clicked": self.on_menuitem_branch_pull_activate,
179
"on_toolbutton_push_clicked": self.on_menuitem_branch_push_activate,
180
"on_treeview_right_button_press_event": self.on_treeview_right_button_press_event,
181
"on_treeview_right_row_activated": self.on_treeview_right_row_activated,
182
"on_treeview_left_button_press_event": self.on_treeview_left_button_press_event,
183
"on_treeview_left_row_activated": self.on_treeview_left_row_activated,
184
"on_button_location_up_clicked": self.on_button_location_up_clicked,
185
"on_button_location_jump_clicked": self.on_button_location_jump_clicked,
186
"on_entry_location_key_press_event": self.on_entry_location_key_press_event,
187
"on_checkbutton_history_toggled": self.on_checkbutton_history_toggled,
188
"on_entry_history_revno_key_press_event": self.on_entry_history_revno_key_press_event,
189
"on_button_history_browse_clicked": self.on_button_history_browse_clicked
192
# Connect the signals to the handlers
193
self.toplevel.signal_autoconnect(dic)
195
self._just_started = True
197
# Apply window size and position
198
width = self.pref.get_preference('window_width', 'int')
199
height = self.pref.get_preference('window_height', 'int')
200
self.window.resize(width, height)
201
x = self.pref.get_preference('window_x', 'int')
202
y = self.pref.get_preference('window_y', 'int')
203
self.window.move(x, y)
204
# Apply paned position
205
pos = self.pref.get_preference('paned_position', 'int')
206
self.hpaned_main.set_position(pos)
208
# Apply menu to the toolbutton
209
#menubutton = self.toplevel.get_widget('menutoolbutton_diff')
210
#menubutton.set_menu(handler.menu.toolbar_diff)
212
# Now we can show the window
215
# Show drive selector if under Win32
216
if sys.platform == 'win32':
217
self.hbox_location.pack_start(self.combobox_drive, False, False, 0)
218
self.hbox_location.reorder_child(self.combobox_drive, 1)
219
self.combobox_drive.show()
220
self.gen_hard_selector()
225
self.menuitem_view_show_hidden_files.set_active(self.pref.get_preference('dotted_files', 'bool'))
227
# We're starting local
229
self.remote_branch = None
230
self.remote_path = None
231
self.remote_revision = None
233
self.set_path(os.getcwd())
236
self._just_started = False
238
def set_path(self, path, force_remote=False):
239
self.notbranch = False
242
# Forcing remote mode (reading data from inventory)
243
self._show_stock_image(gtk.STOCK_DISCONNECT)
245
br = Branch.open_containing(path)[0]
246
except bzrerrors.NotBranchError:
247
self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
248
self.check_history.set_active(False)
249
self.check_history.set_sensitive(False)
251
except bzrerrors.UnsupportedProtocol:
252
self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
253
self.check_history.set_active(False)
254
self.check_history.set_sensitive(False)
257
self._show_stock_image(gtk.STOCK_CONNECT)
262
self.remote_branch, self.remote_path = Branch.open_containing(path)
264
if self.remote_revision is None:
265
self.remote_revision = self.remote_branch.last_revision()
267
self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
269
if len(self.remote_path) == 0:
270
self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
272
for (name, type) in self.remote_entries:
273
if name == self.remote_path:
274
self.remote_parent = type.file_id
277
if not path.endswith('/'):
280
if self.remote_branch.base == path:
281
self.button_location_up.set_sensitive(False)
283
self.button_location_up.set_sensitive(True)
285
if os.path.isdir(path):
286
self.image_location_error.destroy()
291
self.wt, self.wtpath = WorkingTree.open_containing(path)
292
except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
293
self.notbranch = True
295
# If we're in the root, we cannot go up anymore
296
if sys.platform == 'win32':
297
drive, tail = os.path.splitdrive(path)
298
if tail in ('', '/', '\\'):
299
self.button_location_up.set_sensitive(False)
301
self.button_location_up.set_sensitive(True)
304
self.button_location_up.set_sensitive(False)
306
self.button_location_up.set_sensitive(True)
307
elif not os.path.isfile(path):
308
# Doesn't seem to be a file nor a directory, trying to open a
310
self._show_stock_image(gtk.STOCK_DISCONNECT)
312
br = Branch.open_containing(path)[0]
313
except bzrerrors.NotBranchError:
314
self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
315
self.check_history.set_active(False)
316
self.check_history.set_sensitive(False)
318
except bzrerrors.UnsupportedProtocol:
319
self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
320
self.check_history.set_active(False)
321
self.check_history.set_sensitive(False)
324
self._show_stock_image(gtk.STOCK_CONNECT)
329
self.remote_branch, self.remote_path = Branch.open_containing(path)
331
if self.remote_revision is None:
332
self.remote_revision = self.remote_branch.last_revision()
334
self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
336
if len(self.remote_path) == 0:
337
self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
339
for (name, type) in self.remote_entries:
340
if name == self.remote_path:
341
self.remote_parent = type.file_id
344
if not path.endswith('/'):
347
if self.remote_branch.base == path:
348
self.button_location_up.set_sensitive(False)
350
self.button_location_up.set_sensitive(True)
353
self.check_history.set_active(False)
354
self.check_history.set_sensitive(False)
356
self.check_history.set_sensitive(True)
358
self.statusbar.push(self.context_id, path)
359
self.entry_location.set_text(path)
368
if len(self.remote_path) > 0:
369
return self.remote_branch.base + self.remote_path + '/'
371
return self.remote_branch.base
373
def on_about_activate(self, widget):
376
def on_button_history_browse_clicked(self, widget):
377
""" Browse for revision button handler. """
379
br = self.remote_branch
383
revb = RevisionBrowser(br, self.window)
384
response = revb.run()
385
if response != gtk.RESPONSE_NONE:
388
if response == gtk.RESPONSE_OK:
389
if revb.selected_revno is not None:
390
self.entry_history.set_text(revb.selected_revno)
394
def on_button_location_jump_clicked(self, widget):
395
""" Location Jump button handler. """
396
location = self.entry_location.get_text()
398
if self.set_path(location):
401
def on_button_location_up_clicked(self, widget):
402
""" Location Up button handler. """
405
self.set_path(os.path.split(self.get_path())[0])
409
newpath = delim.join(self.get_path().split(delim)[:-2])
411
self.set_path(newpath)
415
def on_checkbutton_history_toggled(self, widget):
416
""" History Mode toggle handler. """
417
if self.check_history.get_active():
418
# History Mode activated
419
self.entry_history.set_sensitive(True)
420
self.button_history.set_sensitive(True)
422
# History Mode deactivated
423
self.entry_history.set_sensitive(False)
424
self.button_history.set_sensitive(False)
427
def on_entry_history_revno_key_press_event(self, widget, event):
428
""" Key pressed handler for the history entry. """
429
if event.keyval == 65293:
430
# Return was hit, so we have to load that specific revision
431
# Emulate being remote, so inventory should be used
432
path = self.get_path()
435
self.remote_branch = self.wt.branch
437
revno = int(self.entry_history.get_text())
438
self.remote_revision = self.remote_branch.get_rev_id(revno)
439
if self.set_path(path, True):
442
def on_entry_location_key_press_event(self, widget, event):
443
""" Key pressed handler for the location entry. """
444
if event.keyval == 65293:
445
# Return was hit, so we have to jump
446
self.on_button_location_jump_clicked(widget)
448
def on_menuitem_add_files_activate(self, widget):
449
""" Add file(s)... menu handler. """
450
from add import OliveAdd
451
add = OliveAdd(self.wt, self.wtpath, self.get_selected_right())
454
def on_menuitem_branch_get_activate(self, widget):
455
""" Branch/Get... menu handler. """
456
from bzrlib.plugins.gtk.branch import BranchDialog
459
branch = BranchDialog(os.getcwd(), self.window, self.remote_branch.base)
461
branch = BranchDialog(self.get_path(), self.window)
462
response = branch.run()
463
if response != gtk.RESPONSE_NONE:
466
if response == gtk.RESPONSE_OK:
471
def on_menuitem_branch_checkout_activate(self, widget):
472
""" Branch/Checkout... menu handler. """
473
from bzrlib.plugins.gtk.checkout import CheckoutDialog
476
checkout = CheckoutDialog(os.getcwd(), self.window, self.remote_branch.base)
478
checkout = CheckoutDialog(self.get_path(), self.window)
479
response = checkout.run()
480
if response != gtk.RESPONSE_NONE:
483
if response == gtk.RESPONSE_OK:
489
def on_menuitem_branch_commit_activate(self, widget):
490
""" Branch/Commit... menu handler. """
491
commit = CommitDialog(self.wt, self.wtpath, self.notbranch, self.get_selected_right(), self.window)
492
response = commit.run()
493
if response != gtk.RESPONSE_NONE:
496
if response == gtk.RESPONSE_OK:
501
def on_menuitem_branch_conflicts_activate(self, widget):
502
""" Branch/Conflicts... menu handler. """
503
conflicts = ConflictsDialog(self.wt, self.window)
504
response = conflicts.run()
505
if response != gtk.RESPONSE_NONE:
508
def on_menuitem_branch_merge_activate(self, widget):
509
""" Branch/Merge... menu handler. """
510
from bzrlib.plugins.gtk.merge import MergeDialog
512
if self.check_for_changes():
513
error_dialog(_('There are local changes in the branch'),
514
_('Please commit or revert the changes before merging.'))
516
merge = MergeDialog(self.wt, self.wtpath)
520
def on_menuitem_branch_missing_revisions_activate(self, widget):
521
""" Branch/Missing revisions menu handler. """
522
local_branch = self.wt.branch
524
other_branch = local_branch.get_parent()
525
if other_branch is None:
526
error_dialog(_('Parent location is unknown'),
527
_('Cannot determine missing revisions if no parent location is known.'))
530
remote_branch = Branch.open(other_branch)
532
if remote_branch.base == local_branch.base:
533
remote_branch = local_branch
535
ret = len(local_branch.missing_revisions(remote_branch))
538
info_dialog(_('There are missing revisions'),
539
_('%d revision(s) missing.') % ret)
541
info_dialog(_('Local branch up to date'),
542
_('There are no missing revisions.'))
545
def on_menuitem_branch_pull_activate(self, widget):
546
""" Branch/Pull menu handler. """
547
branch_to = self.wt.branch
549
location = branch_to.get_parent()
551
error_dialog(_('Parent location is unknown'),
552
_('Pulling is not possible until there is a parent location.'))
555
branch_from = Branch.open(location)
557
if branch_to.get_parent() is None:
558
branch_to.set_parent(branch_from.base)
560
ret = branch_to.pull(branch_from)
562
info_dialog(_('Pull successful'), _('%d revision(s) pulled.') % ret)
564
def on_menuitem_branch_push_activate(self, widget):
565
""" Branch/Push... menu handler. """
566
push = PushDialog(self.wt.branch, self.window)
567
response = push.run()
568
if response != gtk.RESPONSE_NONE:
572
def on_menuitem_branch_revert_activate(self, widget):
573
""" Branch/Revert all changes menu handler. """
574
ret = self.wt.revert([])
576
warning_dialog(_('Conflicts detected'),
577
_('Please have a look at the working tree before continuing.'))
579
info_dialog(_('Revert successful'),
580
_('All files reverted to last revision.'))
583
def on_menuitem_branch_status_activate(self, widget):
584
""" Branch/Status... menu handler. """
585
from bzrlib.plugins.gtk.status import StatusDialog
586
status = StatusDialog(self.wt, self.wtpath)
587
response = status.run()
588
if response != gtk.RESPONSE_NONE:
591
def on_menuitem_branch_initialize_activate(self, widget):
592
""" Initialize current directory. """
593
init = InitDialog(self.path, self.window)
594
response = init.run()
595
if response != gtk.RESPONSE_NONE:
598
if response == gtk.RESPONSE_OK:
603
def on_menuitem_branch_tags_activate(self, widget):
604
""" Branch/Tags... menu handler. """
605
from bzrlib.plugins.gtk.tags import TagsWindow
607
window = TagsWindow(self.wt.branch, self.window)
609
window = TagsWindow(self.remote_branch, self.window)
612
def on_menuitem_file_annotate_activate(self, widget):
613
""" File/Annotate... menu handler. """
614
if self.get_selected_right() is None:
615
error_dialog(_('No file was selected'),
616
_('Please select a file from the list.'))
619
branch = self.wt.branch
620
file_id = self.wt.path2id(self.wt.relpath(os.path.join(self.path, self.get_selected_right())))
622
window = GAnnotateWindow(all=False, plain=False)
623
window.set_title(os.path.join(self.path, self.get_selected_right()) + " - Annotate")
624
config = GAnnotateConfig(window)
628
window.annotate(self.wt, branch, file_id)
632
def on_menuitem_file_make_directory_activate(self, widget):
633
""" File/Make directory... menu handler. """
634
from mkdir import OliveMkdir
635
mkdir = OliveMkdir(self.wt, self.wtpath)
638
def on_menuitem_file_move_activate(self, widget):
639
""" File/Move... menu handler. """
640
from move import OliveMove
641
move = OliveMove(self.wt, self.wtpath, self.get_selected_right())
644
def on_menuitem_file_rename_activate(self, widget):
645
""" File/Rename... menu handler. """
646
from rename import OliveRename
647
rename = OliveRename(self.wt, self.wtpath, self.get_selected_right())
650
def on_menuitem_remove_file_activate(self, widget):
651
""" Remove (unversion) selected file. """
652
from remove import OliveRemoveDialog
653
remove = OliveRemoveDialog(self.wt, self.wtpath,
654
selected=self.get_selected_right(),
656
response = remove.run()
658
if response != gtk.RESPONSE_NONE:
661
if response == gtk.RESPONSE_OK:
662
self.set_path(self.path)
667
def on_menuitem_stats_diff_activate(self, widget):
668
""" Statistics/Differences... menu handler. """
669
window = DiffWindow()
670
parent_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
671
window.set_diff(self.wt.branch.nick, self.wt, parent_tree)
674
def on_menuitem_stats_infos_activate(self, widget):
675
""" Statistics/Informations... menu handler. """
676
from info import OliveInfo
678
info = OliveInfo(self.remote_branch)
680
info = OliveInfo(self.wt.branch)
683
def on_menuitem_stats_log_activate(self, widget):
684
""" Statistics/Log... menu handler. """
685
window = branchwin.BranchWindow()
687
window.set_branch(self.wt.branch, self.wt.branch.last_revision(), None)
689
window.set_branch(self.remote_branch, self.remote_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())
702
if self.path is not None:
705
def on_treeview_left_button_press_event(self, widget, event):
706
""" Occurs when somebody right-clicks in the bookmark list. """
707
if event.button == 3:
708
# Don't show context with nothing selected
709
if self.get_selected_left() == None:
713
from menu import OliveMenu
714
menu = OliveMenu(path=self.get_path(),
715
selected=self.get_selected_left(),
718
menu.left_context_menu().popup(None, None, None, 0,
721
def on_treeview_left_row_activated(self, treeview, path, view_column):
722
""" Occurs when somebody double-clicks or enters an item in the
725
newdir = self.get_selected_left()
729
if self.set_path(newdir):
732
def on_treeview_right_button_press_event(self, widget, event):
733
""" Occurs when somebody right-clicks in the file list. """
734
if event.button == 3:
736
from menu import OliveMenu
737
menu = OliveMenu(path=self.get_path(),
738
selected=self.get_selected_right(),
741
m_open = menu.ui.get_widget('/context_right/open')
742
m_add = menu.ui.get_widget('/context_right/add')
743
m_remove = menu.ui.get_widget('/context_right/remove')
744
m_rename = menu.ui.get_widget('/context_right/rename')
745
m_revert = menu.ui.get_widget('/context_right/revert')
746
m_commit = menu.ui.get_widget('/context_right/commit')
747
m_annotate = menu.ui.get_widget('/context_right/annotate')
748
m_diff = menu.ui.get_widget('/context_right/diff')
749
# check if we're in a branch
751
from bzrlib.branch import Branch
752
Branch.open_containing(self.get_path())
754
m_open.set_sensitive(False)
755
m_add.set_sensitive(False)
756
m_remove.set_sensitive(False)
757
m_rename.set_sensitive(False)
758
m_revert.set_sensitive(False)
759
m_commit.set_sensitive(False)
760
m_annotate.set_sensitive(False)
761
m_diff.set_sensitive(False)
763
m_open.set_sensitive(True)
764
m_add.set_sensitive(True)
765
m_remove.set_sensitive(True)
766
m_rename.set_sensitive(True)
767
m_revert.set_sensitive(True)
768
m_commit.set_sensitive(True)
769
m_annotate.set_sensitive(True)
770
m_diff.set_sensitive(True)
771
except bzrerrors.NotBranchError:
772
m_open.set_sensitive(True)
773
m_add.set_sensitive(False)
774
m_remove.set_sensitive(False)
775
m_rename.set_sensitive(False)
776
m_revert.set_sensitive(False)
777
m_commit.set_sensitive(False)
778
m_annotate.set_sensitive(False)
779
m_diff.set_sensitive(False)
782
menu.right_context_menu().popup(None, None, None, 0,
785
menu.remote_context_menu().popup(None, None, None, 0,
788
def on_treeview_right_row_activated(self, treeview, path, view_column):
789
""" Occurs when somebody double-clicks or enters an item in the
791
from launch import launch
793
newdir = self.get_selected_right()
798
self.set_path(os.path.split(self.get_path())[0])
800
fullpath = os.path.join(self.get_path(), newdir)
801
if os.path.isdir(fullpath):
802
# selected item is an existant directory
803
self.set_path(fullpath)
808
if self._is_remote_dir(self.get_path() + newdir):
809
self.set_path(self.get_path() + newdir)
813
def on_window_main_delete_event(self, widget, event=None):
814
""" Do some stuff before exiting. """
815
width, height = self.window_main.get_size()
816
self.pref.set_preference('window_width', width)
817
self.pref.set_preference('window_height', height)
818
x, y = self.window_main.get_position()
819
self.pref.set_preference('window_x', x)
820
self.pref.set_preference('window_y', y)
821
self.pref.set_preference('paned_position',
822
self.hpaned_main.get_position())
825
self.window_main.destroy()
827
def _load_left(self):
828
""" Load data into the left panel. (Bookmarks) """
830
treestore = gtk.TreeStore(str, str)
833
bookmarks = self.pref.get_bookmarks()
835
# Add them to the TreeStore
836
titer = treestore.append(None, [_('Bookmarks'), None])
837
for item in bookmarks:
838
title = self.pref.get_bookmark_title(item)
839
treestore.append(titer, [title, item])
841
# Create the column and add it to the TreeView
842
self.treeview_left.set_model(treestore)
843
tvcolumn_bookmark = gtk.TreeViewColumn(_('Bookmark'))
844
self.treeview_left.append_column(tvcolumn_bookmark)
847
cell = gtk.CellRendererText()
848
tvcolumn_bookmark.pack_start(cell, True)
849
tvcolumn_bookmark.add_attribute(cell, 'text', 0)
852
self.treeview_left.expand_all()
854
def _load_right(self):
855
""" Load data into the right panel. (Filelist) """
857
# Model: [ icon, dir, name, status text, status, size (int), size (human), mtime (int), mtime (local), fileid ]
858
liststore = gtk.ListStore(gobject.TYPE_STRING,
859
gobject.TYPE_BOOLEAN,
872
# Fill the appropriate lists
873
dotted_files = self.pref.get_preference('dotted_files', 'bool')
874
for item in os.listdir(self.path):
875
if not dotted_files and item[0] == '.':
877
if os.path.isdir(self.path + os.sep + item):
882
if not self.notbranch:
883
branch = self.wt.branch
884
tree2 = self.wt.branch.repository.revision_tree(branch.last_revision())
886
delta = self.wt.changes_from(tree2, want_unchanged=True)
888
# Add'em to the ListStore
890
statinfo = os.stat(self.path + os.sep + item)
891
liststore.append([ gtk.STOCK_DIRECTORY,
899
self._format_date(statinfo.st_mtime),
904
if not self.notbranch:
905
filename = self.wt.relpath(self.path + os.sep + item)
910
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
911
if rpathnew == filename:
914
for rpath, id, kind in delta.added:
915
if rpath == filename:
918
for rpath, id, kind in delta.removed:
919
if rpath == filename:
922
for rpath, id, kind, text_modified, meta_modified in delta.modified:
923
if rpath == filename:
926
for rpath, id, kind in delta.unchanged:
927
if rpath == filename:
930
for rpath, file_class, kind, id, entry in self.wt.list_files():
931
if rpath == filename and file_class == 'I':
936
if status == 'renamed':
938
elif status == 'removed':
940
elif status == 'added':
942
elif status == 'modified':
944
elif status == 'unchanged':
946
elif status == 'ignored':
951
statinfo = os.stat(self.path + os.sep + item)
952
liststore.append([gtk.STOCK_FILE,
957
str(statinfo.st_size), # NOTE: if int used there it will fail for large files (size expressed as long int)
958
self._format_size(statinfo.st_size),
960
self._format_date(statinfo.st_mtime),
963
# Create the columns and add them to the TreeView
964
self.treeview_right.set_model(liststore)
965
self._tvcolumn_filename = gtk.TreeViewColumn(_('Filename'))
966
self._tvcolumn_status = gtk.TreeViewColumn(_('Status'))
967
self._tvcolumn_size = gtk.TreeViewColumn(_('Size'))
968
self._tvcolumn_mtime = gtk.TreeViewColumn(_('Last modified'))
969
self.treeview_right.append_column(self._tvcolumn_filename)
970
self.treeview_right.append_column(self._tvcolumn_status)
971
self.treeview_right.append_column(self._tvcolumn_size)
972
self.treeview_right.append_column(self._tvcolumn_mtime)
975
cellpb = gtk.CellRendererPixbuf()
976
cell = gtk.CellRendererText()
977
self._tvcolumn_filename.pack_start(cellpb, False)
978
self._tvcolumn_filename.pack_start(cell, True)
979
self._tvcolumn_filename.set_attributes(cellpb, stock_id=0)
980
self._tvcolumn_filename.add_attribute(cell, 'text', 2)
981
self._tvcolumn_status.pack_start(cell, True)
982
self._tvcolumn_status.add_attribute(cell, 'text', 3)
983
self._tvcolumn_size.pack_start(cell, True)
984
self._tvcolumn_size.add_attribute(cell, 'text', 6)
985
self._tvcolumn_mtime.pack_start(cell, True)
986
self._tvcolumn_mtime.add_attribute(cell, 'text', 8)
988
# Set up the properties of the TreeView
989
self.treeview_right.set_headers_visible(True)
990
self.treeview_right.set_headers_clickable(True)
991
self.treeview_right.set_search_column(1)
992
self._tvcolumn_filename.set_resizable(True)
993
self._tvcolumn_status.set_resizable(True)
994
self._tvcolumn_size.set_resizable(True)
995
self._tvcolumn_mtime.set_resizable(True)
997
liststore.set_sort_func(13, self._sort_filelist_callback, None)
998
liststore.set_sort_column_id(13, gtk.SORT_ASCENDING)
999
self._tvcolumn_filename.set_sort_column_id(13)
1000
self._tvcolumn_status.set_sort_column_id(3)
1001
self._tvcolumn_size.set_sort_column_id(5)
1002
self._tvcolumn_mtime.set_sort_column_id(7)
1005
self.set_sensitivity()
1007
def get_selected_fileid(self):
1008
""" Get the file_id of the selected file. """
1009
treeselection = self.treeview_right.get_selection()
1010
(model, iter) = treeselection.get_selected()
1015
return model.get_value(iter, 9)
1017
def get_selected_right(self):
1018
""" Get the selected filename. """
1019
treeselection = self.treeview_right.get_selection()
1020
(model, iter) = treeselection.get_selected()
1025
return model.get_value(iter, 2)
1027
def get_selected_left(self):
1028
""" Get the selected bookmark. """
1029
treeselection = self.treeview_left.get_selection()
1030
(model, iter) = treeselection.get_selected()
1035
return model.get_value(iter, 1)
1037
def set_statusbar(self, message):
1038
""" Set the statusbar message. """
1039
self.statusbar.push(self.context_id, message)
1041
def clear_statusbar(self):
1042
""" Clean the last message from the statusbar. """
1043
self.statusbar.pop(self.context_id)
1045
def set_sensitivity(self):
1046
""" Set menu and toolbar sensitivity. """
1049
self.menuitem_branch_init.set_sensitive(self.notbranch)
1050
self.menuitem_branch_get.set_sensitive(self.notbranch)
1051
self.menuitem_branch_checkout.set_sensitive(self.notbranch)
1052
self.menuitem_branch_pull.set_sensitive(not self.notbranch)
1053
self.menuitem_branch_push.set_sensitive(not self.notbranch)
1054
self.menuitem_branch_revert.set_sensitive(not self.notbranch)
1055
self.menuitem_branch_merge.set_sensitive(not self.notbranch)
1056
self.menuitem_branch_commit.set_sensitive(not self.notbranch)
1057
self.menuitem_branch_tags.set_sensitive(not self.notbranch)
1058
self.menuitem_branch_status.set_sensitive(not self.notbranch)
1059
self.menuitem_branch_missing.set_sensitive(not self.notbranch)
1060
self.menuitem_branch_conflicts.set_sensitive(not self.notbranch)
1061
self.menuitem_stats.set_sensitive(not self.notbranch)
1062
self.menuitem_stats_diff.set_sensitive(not self.notbranch)
1063
self.menuitem_add_files.set_sensitive(not self.notbranch)
1064
self.menuitem_remove_files.set_sensitive(not self.notbranch)
1065
self.menuitem_file_make_directory.set_sensitive(not self.notbranch)
1066
self.menuitem_file_rename.set_sensitive(not self.notbranch)
1067
self.menuitem_file_move.set_sensitive(not self.notbranch)
1068
self.menuitem_file_annotate.set_sensitive(not self.notbranch)
1069
#self.menutoolbutton_diff.set_sensitive(True)
1070
self.toolbutton_diff.set_sensitive(not self.notbranch)
1071
self.toolbutton_log.set_sensitive(not self.notbranch)
1072
self.toolbutton_commit.set_sensitive(not self.notbranch)
1073
self.toolbutton_pull.set_sensitive(not self.notbranch)
1074
self.toolbutton_push.set_sensitive(not self.notbranch)
1077
self.menuitem_branch_init.set_sensitive(False)
1078
self.menuitem_branch_get.set_sensitive(True)
1079
self.menuitem_branch_checkout.set_sensitive(True)
1080
self.menuitem_branch_pull.set_sensitive(False)
1081
self.menuitem_branch_push.set_sensitive(False)
1082
self.menuitem_branch_revert.set_sensitive(False)
1083
self.menuitem_branch_merge.set_sensitive(False)
1084
self.menuitem_branch_commit.set_sensitive(False)
1085
self.menuitem_branch_tags.set_sensitive(True)
1086
self.menuitem_branch_status.set_sensitive(False)
1087
self.menuitem_branch_missing.set_sensitive(False)
1088
self.menuitem_branch_conflicts.set_sensitive(False)
1089
self.menuitem_stats.set_sensitive(True)
1090
self.menuitem_stats_diff.set_sensitive(False)
1091
self.menuitem_add_files.set_sensitive(False)
1092
self.menuitem_remove_files.set_sensitive(False)
1093
self.menuitem_file_make_directory.set_sensitive(False)
1094
self.menuitem_file_rename.set_sensitive(False)
1095
self.menuitem_file_move.set_sensitive(False)
1096
self.menuitem_file_annotate.set_sensitive(False)
1097
#self.menutoolbutton_diff.set_sensitive(True)
1098
self.toolbutton_diff.set_sensitive(False)
1099
self.toolbutton_log.set_sensitive(True)
1100
self.toolbutton_commit.set_sensitive(False)
1101
self.toolbutton_pull.set_sensitive(False)
1102
self.toolbutton_push.set_sensitive(False)
1104
def refresh_left(self):
1105
""" Refresh the bookmark list. """
1107
# Get TreeStore and clear it
1108
treestore = self.treeview_left.get_model()
1111
# Re-read preferences
1115
bookmarks = self.pref.get_bookmarks()
1117
# Add them to the TreeStore
1118
titer = treestore.append(None, [_('Bookmarks'), None])
1119
for item in bookmarks:
1120
title = self.pref.get_bookmark_title(item)
1121
treestore.append(titer, [title, item])
1123
# Add the TreeStore to the TreeView
1124
self.treeview_left.set_model(treestore)
1127
self.treeview_left.expand_all()
1129
def refresh_right(self, path=None):
1130
""" Refresh the file list. """
1133
from bzrlib.workingtree import WorkingTree
1136
path = self.get_path()
1138
# A workaround for double-clicking Bookmarks
1139
if not os.path.exists(path):
1142
# Get ListStore and clear it
1143
liststore = self.treeview_right.get_model()
1146
# Show Status column
1147
self._tvcolumn_status.set_visible(True)
1152
# Fill the appropriate lists
1153
dotted_files = self.pref.get_preference('dotted_files', 'bool')
1154
for item in os.listdir(path):
1155
if not dotted_files and item[0] == '.':
1157
if os.path.isdir(path + os.sep + item):
1162
# Try to open the working tree
1165
tree1 = WorkingTree.open_containing(path)[0]
1166
except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
1170
branch = tree1.branch
1171
tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
1173
delta = tree1.changes_from(tree2, want_unchanged=True)
1175
# Add'em to the ListStore
1177
statinfo = os.stat(self.path + os.sep + item)
1178
liststore.append([gtk.STOCK_DIRECTORY,
1186
self._format_date(statinfo.st_mtime),
1192
filename = tree1.relpath(path + os.sep + item)
1197
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
1198
if rpathnew == filename:
1201
for rpath, id, kind in delta.added:
1202
if rpath == filename:
1205
for rpath, id, kind in delta.removed:
1206
if rpath == filename:
1209
for rpath, id, kind, text_modified, meta_modified in delta.modified:
1210
if rpath == filename:
1213
for rpath, id, kind in delta.unchanged:
1214
if rpath == filename:
1215
status = 'unchanged'
1217
for rpath, file_class, kind, id, entry in self.wt.list_files():
1218
if rpath == filename and file_class == 'I':
1223
if status == 'renamed':
1225
elif status == 'removed':
1227
elif status == 'added':
1229
elif status == 'modified':
1231
elif status == 'unchanged':
1233
elif status == 'ignored':
1238
statinfo = os.stat(self.path + os.sep + item)
1239
liststore.append([gtk.STOCK_FILE,
1244
str(statinfo.st_size),
1245
self._format_size(statinfo.st_size),
1247
self._format_date(statinfo.st_mtime),
1252
# Get ListStore and clear it
1253
liststore = self.treeview_right.get_model()
1256
# Hide Status column
1257
self._tvcolumn_status.set_visible(False)
1262
self._show_stock_image(gtk.STOCK_REFRESH)
1264
for (name, type) in self.remote_entries:
1265
if type.kind == 'directory':
1267
elif type.kind == 'file':
1271
""" Cache based on revision history. """
1272
def __init__(self, history):
1273
self._history = history
1275
def _lookup_revision(self, revid):
1276
for r in self._history:
1277
if r.revision_id == revid:
1279
rev = repo.get_revision(revid)
1280
self._history.append(rev)
1283
repo = self.remote_branch.repository
1285
revhistory = self.remote_branch.revision_history()
1287
revs = repo.get_revisions(revhistory)
1288
cache = HistoryCache(revs)
1289
except bzrerrors.InvalidHttpResponse:
1290
# Fallback to dummy algorithm, because of LP: #115209
1291
cache = HistoryCache([])
1294
if item.parent_id == self.remote_parent:
1295
rev = cache._lookup_revision(item.revision)
1296
liststore.append([ gtk.STOCK_DIRECTORY,
1304
self._format_date(rev.timestamp),
1307
while gtk.events_pending():
1308
gtk.main_iteration()
1311
if item.parent_id == self.remote_parent:
1312
rev = cache._lookup_revision(item.revision)
1313
liststore.append([ gtk.STOCK_FILE,
1318
str(item.text_size),
1319
self._format_size(item.text_size),
1321
self._format_date(rev.timestamp),
1324
while gtk.events_pending():
1325
gtk.main_iteration()
1327
self.image_location_error.destroy()
1329
# Columns should auto-size
1330
self.treeview_right.columns_autosize()
1333
self.set_sensitivity()
1335
def _harddisks(self):
1336
""" Returns hard drive letters under Win32. """
1341
if sys.platform == 'win32':
1342
print "pyWin32 modules needed to run Olive on Win32."
1348
for drive in string.ascii_uppercase:
1349
if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED:
1350
driveletters.append(drive+':')
1353
def gen_hard_selector(self):
1354
""" Generate the hard drive selector under Win32. """
1355
drives = self._harddisks()
1356
for drive in drives:
1357
self.combobox_drive.append_text(drive)
1358
self.combobox_drive.set_active(drives.index(os.getcwd()[0:2]))
1360
def _refresh_drives(self, combobox):
1361
if self._just_started:
1363
model = combobox.get_model()
1364
active = combobox.get_active()
1366
drive = model[active][0]
1367
self.set_path(drive + '\\')
1368
self.refresh_right(drive + '\\')
1370
def check_for_changes(self):
1371
""" Check whether there were changes in the current working tree. """
1372
old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
1373
delta = self.wt.changes_from(old_tree)
1377
if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
1382
def _sort_filelist_callback(self, model, iter1, iter2, data):
1383
""" The sort callback for the file list, return values:
1388
name1 = model.get_value(iter1, 2)
1389
name2 = model.get_value(iter2, 2)
1391
if model.get_value(iter1, 1):
1392
# item1 is a directory
1393
if not model.get_value(iter2, 1):
1397
# both of them are directories, we compare their names
1400
elif name1 == name2:
1405
# item1 is not a directory
1406
if model.get_value(iter2, 1):
1410
# both of them are files, compare them
1413
elif name1 == name2:
1418
def _format_size(self, size):
1419
""" Format size to a human readable format. """
1421
return "%d[B]" % (size,)
1422
size = size / 1000.0
1424
for metric in ["kB","MB","GB","TB"]:
1427
size = size / 1000.0
1428
return "%.1f[%s]" % (size,metric)
1430
def _format_date(self, timestamp):
1431
""" Format the time (given in secs) to a human readable format. """
1432
return time.ctime(timestamp)
1434
def _is_remote_dir(self, location):
1435
""" Determine whether the given location is a directory or not. """
1437
# We're in local mode
1440
branch, path = Branch.open_containing(location)
1441
for (name, type) in self.remote_entries:
1442
if name == path and type.kind == 'directory':
1445
# Either it's not a directory or not in the inventory
1448
def _show_stock_image(self, stock_id):
1449
""" Show a stock image next to the location entry. """
1450
self.image_location_error.destroy()
1451
self.image_location_error = gtk.image_new_from_stock(stock_id, gtk.ICON_SIZE_BUTTON)
1452
self.hbox_location.pack_start(self.image_location_error, False, False, 0)
1453
if sys.platform == 'win32':
1454
self.hbox_location.reorder_child(self.image_location_error, 2)
1456
self.hbox_location.reorder_child(self.image_location_error, 1)
1457
self.image_location_error.show()
1458
while gtk.events_pending():
1459
gtk.main_iteration()
1464
""" A class which handles Olive's preferences. """
1465
def __init__(self, path=None):
1466
""" Initialize the Preferences class. """
1467
# Some default options
1468
self.defaults = { 'strict_commit' : False,
1469
'dotted_files' : False,
1470
'window_width' : 700,
1471
'window_height' : 400,
1474
'paned_position': 200 }
1476
# Create a config parser object
1477
self.config = ConfigParser.RawConfigParser()
1481
if sys.platform == 'win32':
1482
# Windows - no dotted files
1483
self._filename = os.path.expanduser('~/olive.conf')
1485
self._filename = os.path.expanduser('~/.olive.conf')
1487
self._filename = path
1489
# Load the configuration
1492
def _get_default(self, option):
1493
""" Get the default option for a preference. """
1495
ret = self.defaults[option]
1502
""" Refresh the configuration. """
1503
# First write out the changes
1505
# Then load the configuration again
1509
""" Just read the configuration. """
1510
# Re-initialize the config parser object to avoid some bugs
1511
self.config = ConfigParser.RawConfigParser()
1512
self.config.read([self._filename])
1515
""" Write the configuration to the appropriate files. """
1516
fp = open(self._filename, 'w')
1517
self.config.write(fp)
1520
def get_bookmarks(self):
1521
""" Return the list of bookmarks. """
1522
bookmarks = self.config.sections()
1523
if self.config.has_section('preferences'):
1524
bookmarks.remove('preferences')
1527
def add_bookmark(self, path):
1528
""" Add bookmark. """
1530
self.config.add_section(path)
1531
except ConfigParser.DuplicateSectionError:
1536
def get_bookmark_title(self, path):
1537
""" Get bookmark title. """
1539
ret = self.config.get(path, 'title')
1540
except ConfigParser.NoOptionError:
1545
def set_bookmark_title(self, path, title):
1546
""" Set bookmark title. """
1547
# FIXME: What if path isn't listed yet?
1548
# FIXME: Canonicalize paths first?
1549
self.config.set(path, 'title', title)
1551
def remove_bookmark(self, path):
1552
""" Remove bookmark. """
1553
return self.config.remove_section(path)
1555
def set_preference(self, option, value):
1556
""" Set the value of the given option. """
1559
elif value is False:
1562
if self.config.has_section('preferences'):
1563
self.config.set('preferences', option, value)
1565
self.config.add_section('preferences')
1566
self.config.set('preferences', option, value)
1568
def get_preference(self, option, kind='str'):
1569
""" Get the value of the given option.
1571
:param kind: str/bool/int/float. default: str
1573
if self.config.has_option('preferences', option):
1575
return self.config.getboolean('preferences', option)
1577
return self.config.getint('preferences', option)
1578
elif kind == 'float':
1579
return self.config.getfloat('preferences', option)
1581
return self.config.get('preferences', option)
1584
return self._get_default(option)