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
""" The main Olive GTK frontend class. This is called when launching the
63
self.toplevel = gtk.glade.XML(GLADEFILENAME, 'window_main', 'olive-gtk')
64
self.window = self.toplevel.get_widget('window_main')
65
self.pref = Preferences()
68
# Initialize the statusbar
69
self.statusbar = self.toplevel.get_widget('statusbar')
70
self.context_id = self.statusbar.get_context_id('olive')
73
self.window_main = self.toplevel.get_widget('window_main')
75
self.hpaned_main = self.toplevel.get_widget('hpaned_main')
77
self.treeview_left = self.toplevel.get_widget('treeview_left')
78
self.treeview_right = self.toplevel.get_widget('treeview_right')
79
# Get some important menu items
80
self.menuitem_add_files = self.toplevel.get_widget('menuitem_add_files')
81
self.menuitem_remove_files = self.toplevel.get_widget('menuitem_remove_file')
82
self.menuitem_file_make_directory = self.toplevel.get_widget('menuitem_file_make_directory')
83
self.menuitem_file_rename = self.toplevel.get_widget('menuitem_file_rename')
84
self.menuitem_file_move = self.toplevel.get_widget('menuitem_file_move')
85
self.menuitem_file_annotate = self.toplevel.get_widget('menuitem_file_annotate')
86
self.menuitem_view_show_hidden_files = self.toplevel.get_widget('menuitem_view_show_hidden_files')
87
self.menuitem_branch = self.toplevel.get_widget('menuitem_branch')
88
self.menuitem_branch_init = self.toplevel.get_widget('menuitem_branch_initialize')
89
self.menuitem_branch_get = self.toplevel.get_widget('menuitem_branch_get')
90
self.menuitem_branch_checkout = self.toplevel.get_widget('menuitem_branch_checkout')
91
self.menuitem_branch_pull = self.toplevel.get_widget('menuitem_branch_pull')
92
self.menuitem_branch_push = self.toplevel.get_widget('menuitem_branch_push')
93
self.menuitem_branch_revert = self.toplevel.get_widget('menuitem_branch_revert')
94
self.menuitem_branch_merge = self.toplevel.get_widget('menuitem_branch_merge')
95
self.menuitem_branch_commit = self.toplevel.get_widget('menuitem_branch_commit')
96
self.menuitem_branch_tags = self.toplevel.get_widget('menuitem_branch_tags')
97
self.menuitem_branch_status = self.toplevel.get_widget('menuitem_branch_status')
98
self.menuitem_branch_missing = self.toplevel.get_widget('menuitem_branch_missing_revisions')
99
self.menuitem_branch_conflicts = self.toplevel.get_widget('menuitem_branch_conflicts')
100
self.menuitem_stats = self.toplevel.get_widget('menuitem_stats')
101
self.menuitem_stats_diff = self.toplevel.get_widget('menuitem_stats_diff')
102
self.menuitem_stats_log = self.toplevel.get_widget('menuitem_stats_log')
103
# Get some toolbuttons
104
#self.menutoolbutton_diff = self.toplevel.get_widget('menutoolbutton_diff')
105
self.toolbutton_diff = self.toplevel.get_widget('toolbutton_diff')
106
self.toolbutton_log = self.toplevel.get_widget('toolbutton_log')
107
self.toolbutton_commit = self.toplevel.get_widget('toolbutton_commit')
108
self.toolbutton_pull = self.toplevel.get_widget('toolbutton_pull')
109
self.toolbutton_push = self.toplevel.get_widget('toolbutton_push')
110
# Get the drive selector
111
self.combobox_drive = gtk.combo_box_new_text()
112
self.combobox_drive.connect("changed", self._refresh_drives)
114
# Get the navigation widgets
115
self.hbox_location = self.toplevel.get_widget('hbox_location')
116
self.button_location_up = self.toplevel.get_widget('button_location_up')
117
self.button_location_jump = self.toplevel.get_widget('button_location_jump')
118
self.entry_location = self.toplevel.get_widget('entry_location')
119
self.image_location_error = self.toplevel.get_widget('image_location_error')
121
# Get the History widgets
122
self.check_history = self.toplevel.get_widget('checkbutton_history')
123
self.entry_history = self.toplevel.get_widget('entry_history_revno')
124
self.button_history = self.toplevel.get_widget('button_history_browse')
126
self.vbox_main_right = self.toplevel.get_widget('vbox_main_right')
128
# Dictionary for signal_autoconnect
129
dic = { "on_window_main_destroy": gtk.main_quit,
130
"on_window_main_delete_event": self.on_window_main_delete_event,
131
"on_quit_activate": self.on_window_main_delete_event,
132
"on_about_activate": self.on_about_activate,
133
"on_menuitem_add_files_activate": self.on_menuitem_add_files_activate,
134
"on_menuitem_remove_file_activate": self.on_menuitem_remove_file_activate,
135
"on_menuitem_file_make_directory_activate": self.on_menuitem_file_make_directory_activate,
136
"on_menuitem_file_move_activate": self.on_menuitem_file_move_activate,
137
"on_menuitem_file_rename_activate": self.on_menuitem_file_rename_activate,
138
"on_menuitem_file_annotate_activate": self.on_menuitem_file_annotate_activate,
139
"on_menuitem_view_show_hidden_files_activate": self.on_menuitem_view_show_hidden_files_activate,
140
"on_menuitem_view_refresh_activate": self.on_menuitem_view_refresh_activate,
141
"on_menuitem_branch_initialize_activate": self.on_menuitem_branch_initialize_activate,
142
"on_menuitem_branch_get_activate": self.on_menuitem_branch_get_activate,
143
"on_menuitem_branch_checkout_activate": self.on_menuitem_branch_checkout_activate,
144
"on_menuitem_branch_revert_activate": self.on_menuitem_branch_revert_activate,
145
"on_menuitem_branch_merge_activate": self.on_menuitem_branch_merge_activate,
146
"on_menuitem_branch_commit_activate": self.on_menuitem_branch_commit_activate,
147
"on_menuitem_branch_push_activate": self.on_menuitem_branch_push_activate,
148
"on_menuitem_branch_pull_activate": self.on_menuitem_branch_pull_activate,
149
"on_menuitem_branch_tags_activate": self.on_menuitem_branch_tags_activate,
150
"on_menuitem_branch_status_activate": self.on_menuitem_branch_status_activate,
151
"on_menuitem_branch_missing_revisions_activate": self.on_menuitem_branch_missing_revisions_activate,
152
"on_menuitem_branch_conflicts_activate": self.on_menuitem_branch_conflicts_activate,
153
"on_menuitem_stats_diff_activate": self.on_menuitem_stats_diff_activate,
154
"on_menuitem_stats_log_activate": self.on_menuitem_stats_log_activate,
155
"on_menuitem_stats_infos_activate": self.on_menuitem_stats_infos_activate,
156
"on_toolbutton_refresh_clicked": self.on_menuitem_view_refresh_activate,
157
"on_toolbutton_log_clicked": self.on_menuitem_stats_log_activate,
158
#"on_menutoolbutton_diff_clicked": self.on_menuitem_stats_diff_activate,
159
"on_toolbutton_diff_clicked": self.on_menuitem_stats_diff_activate,
160
"on_toolbutton_commit_clicked": self.on_menuitem_branch_commit_activate,
161
"on_toolbutton_pull_clicked": self.on_menuitem_branch_pull_activate,
162
"on_toolbutton_push_clicked": self.on_menuitem_branch_push_activate,
163
"on_treeview_right_button_press_event": self.on_treeview_right_button_press_event,
164
"on_treeview_right_row_activated": self.on_treeview_right_row_activated,
165
"on_treeview_left_button_press_event": self.on_treeview_left_button_press_event,
166
"on_treeview_left_row_activated": self.on_treeview_left_row_activated,
167
"on_button_location_up_clicked": self.on_button_location_up_clicked,
168
"on_button_location_jump_clicked": self.on_button_location_jump_clicked,
169
"on_entry_location_key_press_event": self.on_entry_location_key_press_event,
170
"on_checkbutton_history_toggled": self.on_checkbutton_history_toggled,
171
"on_entry_history_revno_key_press_event": self.on_entry_history_revno_key_press_event,
172
"on_button_history_browse_clicked": self.on_button_history_browse_clicked
175
# Connect the signals to the handlers
176
self.toplevel.signal_autoconnect(dic)
178
self._just_started = True
180
# Apply window size and position
181
width = self.pref.get_preference('window_width', 'int')
182
height = self.pref.get_preference('window_height', 'int')
183
self.window.resize(width, height)
184
x = self.pref.get_preference('window_x', 'int')
185
y = self.pref.get_preference('window_y', 'int')
186
self.window.move(x, y)
187
# Apply paned position
188
pos = self.pref.get_preference('paned_position', 'int')
189
self.hpaned_main.set_position(pos)
191
# Apply menu to the toolbutton
192
#menubutton = self.toplevel.get_widget('menutoolbutton_diff')
193
#menubutton.set_menu(handler.menu.toolbar_diff)
195
# Now we can show the window
198
# Show drive selector if under Win32
199
if sys.platform == 'win32':
200
self.hbox_location.pack_start(self.combobox_drive, False, False, 0)
201
self.hbox_location.reorder_child(self.combobox_drive, 1)
202
self.combobox_drive.show()
203
self.gen_hard_selector()
208
self.menuitem_view_show_hidden_files.set_active(self.pref.get_preference('dotted_files', 'bool'))
210
# We're starting local
212
self.remote_branch = None
213
self.remote_path = None
214
self.remote_revision = None
216
self.set_path(os.getcwd())
219
self._just_started = False
221
def set_path(self, path, force_remote=False):
222
self.notbranch = False
225
# Forcing remote mode (reading data from inventory)
226
self._show_stock_image(gtk.STOCK_DISCONNECT)
228
br = Branch.open_containing(path)[0]
229
except bzrerrors.NotBranchError:
230
self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
231
self.check_history.set_active(False)
232
self.check_history.set_sensitive(False)
234
except bzrerrors.UnsupportedProtocol:
235
self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
236
self.check_history.set_active(False)
237
self.check_history.set_sensitive(False)
240
self._show_stock_image(gtk.STOCK_CONNECT)
245
self.remote_branch, self.remote_path = Branch.open_containing(path)
247
if self.remote_revision is None:
248
self.remote_revision = self.remote_branch.last_revision()
250
self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
252
if len(self.remote_path) == 0:
253
self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
255
for (name, type) in self.remote_entries:
256
if name == self.remote_path:
257
self.remote_parent = type.file_id
260
if not path.endswith('/'):
263
if self.remote_branch.base == path:
264
self.button_location_up.set_sensitive(False)
266
self.button_location_up.set_sensitive(True)
268
if os.path.isdir(path):
269
self.image_location_error.destroy()
274
self.wt, self.wtpath = WorkingTree.open_containing(path)
275
except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
276
self.notbranch = True
278
# If we're in the root, we cannot go up anymore
279
if sys.platform == 'win32':
280
drive, tail = os.path.splitdrive(path)
281
if tail in ('', '/', '\\'):
282
self.button_location_up.set_sensitive(False)
284
self.button_location_up.set_sensitive(True)
287
self.button_location_up.set_sensitive(False)
289
self.button_location_up.set_sensitive(True)
290
elif not os.path.isfile(path):
291
# Doesn't seem to be a file nor a directory, trying to open a
293
self._show_stock_image(gtk.STOCK_DISCONNECT)
295
br = Branch.open_containing(path)[0]
296
except bzrerrors.NotBranchError:
297
self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
298
self.check_history.set_active(False)
299
self.check_history.set_sensitive(False)
301
except bzrerrors.UnsupportedProtocol:
302
self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
303
self.check_history.set_active(False)
304
self.check_history.set_sensitive(False)
307
self._show_stock_image(gtk.STOCK_CONNECT)
312
self.remote_branch, self.remote_path = Branch.open_containing(path)
314
if self.remote_revision is None:
315
self.remote_revision = self.remote_branch.last_revision()
317
self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
319
if len(self.remote_path) == 0:
320
self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
322
for (name, type) in self.remote_entries:
323
if name == self.remote_path:
324
self.remote_parent = type.file_id
327
if not path.endswith('/'):
330
if self.remote_branch.base == path:
331
self.button_location_up.set_sensitive(False)
333
self.button_location_up.set_sensitive(True)
336
self.check_history.set_active(False)
337
self.check_history.set_sensitive(False)
339
self.check_history.set_sensitive(True)
341
self.statusbar.push(self.context_id, path)
342
self.entry_location.set_text(path)
351
if len(self.remote_path) > 0:
352
return self.remote_branch.base + self.remote_path + '/'
354
return self.remote_branch.base
356
def on_about_activate(self, widget):
357
from bzrlib.plugins.gtk.dialog import about
361
def on_button_history_browse_clicked(self, widget):
362
""" Browse for revision button handler. """
364
br = self.remote_branch
368
revb = RevisionBrowser(br, self.window)
369
response = revb.run()
370
if response != gtk.RESPONSE_NONE:
373
if response == gtk.RESPONSE_OK:
374
if revb.selected_revno is not None:
375
self.entry_history.set_text(revb.selected_revno)
379
def on_button_location_jump_clicked(self, widget):
380
""" Location Jump button handler. """
381
location = self.entry_location.get_text()
383
if self.set_path(location):
386
def on_button_location_up_clicked(self, widget):
387
""" Location Up button handler. """
390
self.set_path(os.path.split(self.get_path())[0])
394
newpath = delim.join(self.get_path().split(delim)[:-2])
396
self.set_path(newpath)
400
def on_checkbutton_history_toggled(self, widget):
401
""" History Mode toggle handler. """
402
if self.check_history.get_active():
403
# History Mode activated
404
self.entry_history.set_sensitive(True)
405
self.button_history.set_sensitive(True)
407
# History Mode deactivated
408
self.entry_history.set_sensitive(False)
409
self.button_history.set_sensitive(False)
412
def on_entry_history_revno_key_press_event(self, widget, event):
413
""" Key pressed handler for the history entry. """
414
if event.keyval == 65293:
415
# Return was hit, so we have to load that specific revision
416
# Emulate being remote, so inventory should be used
417
path = self.get_path()
420
self.remote_branch = self.wt.branch
422
revno = int(self.entry_history.get_text())
423
self.remote_revision = self.remote_branch.get_rev_id(revno)
424
if self.set_path(path, True):
427
def on_entry_location_key_press_event(self, widget, event):
428
""" Key pressed handler for the location entry. """
429
if event.keyval == 65293:
430
# Return was hit, so we have to jump
431
self.on_button_location_jump_clicked(widget)
433
def on_menuitem_add_files_activate(self, widget):
434
""" Add file(s)... menu handler. """
435
from add import OliveAdd
436
add = OliveAdd(self.wt, self.wtpath, self.get_selected_right())
439
def on_menuitem_branch_get_activate(self, widget):
440
""" Branch/Get... menu handler. """
441
from bzrlib.plugins.gtk.branch import BranchDialog
444
branch = BranchDialog(os.getcwd(), self.window, self.remote_branch.base)
446
branch = BranchDialog(self.get_path(), self.window)
447
response = branch.run()
448
if response != gtk.RESPONSE_NONE:
451
if response == gtk.RESPONSE_OK:
456
def on_menuitem_branch_checkout_activate(self, widget):
457
""" Branch/Checkout... menu handler. """
458
from bzrlib.plugins.gtk.checkout import CheckoutDialog
461
checkout = CheckoutDialog(os.getcwd(), self.window, self.remote_branch.base)
463
checkout = CheckoutDialog(self.get_path(), self.window)
464
response = checkout.run()
465
if response != gtk.RESPONSE_NONE:
468
if response == gtk.RESPONSE_OK:
474
def on_menuitem_branch_commit_activate(self, widget):
475
""" Branch/Commit... menu handler. """
476
commit = CommitDialog(self.wt, self.wtpath, self.notbranch, self.get_selected_right(), self.window)
477
response = commit.run()
478
if response != gtk.RESPONSE_NONE:
481
if response == gtk.RESPONSE_OK:
486
def on_menuitem_branch_conflicts_activate(self, widget):
487
""" Branch/Conflicts... menu handler. """
488
conflicts = ConflictsDialog(self.wt, self.window)
489
response = conflicts.run()
490
if response != gtk.RESPONSE_NONE:
493
def on_menuitem_branch_merge_activate(self, widget):
494
""" Branch/Merge... menu handler. """
495
from bzrlib.plugins.gtk.merge import MergeDialog
497
if self.check_for_changes():
498
error_dialog(_('There are local changes in the branch'),
499
_('Please commit or revert the changes before merging.'))
501
merge = MergeDialog(self.wt, self.wtpath)
505
def on_menuitem_branch_missing_revisions_activate(self, widget):
506
""" Branch/Missing revisions menu handler. """
507
local_branch = self.wt.branch
509
other_branch = local_branch.get_parent()
510
if other_branch is None:
511
error_dialog(_('Parent location is unknown'),
512
_('Cannot determine missing revisions if no parent location is known.'))
515
remote_branch = Branch.open(other_branch)
517
if remote_branch.base == local_branch.base:
518
remote_branch = local_branch
520
ret = len(local_branch.missing_revisions(remote_branch))
523
info_dialog(_('There are missing revisions'),
524
_('%d revision(s) missing.') % ret)
526
info_dialog(_('Local branch up to date'),
527
_('There are no missing revisions.'))
530
def on_menuitem_branch_pull_activate(self, widget):
531
""" Branch/Pull menu handler. """
532
branch_to = self.wt.branch
534
location = branch_to.get_parent()
536
error_dialog(_('Parent location is unknown'),
537
_('Pulling is not possible until there is a parent location.'))
540
branch_from = Branch.open(location)
542
if branch_to.get_parent() is None:
543
branch_to.set_parent(branch_from.base)
545
ret = branch_to.pull(branch_from)
547
info_dialog(_('Pull successful'), _('%d revision(s) pulled.') % ret)
549
def on_menuitem_branch_push_activate(self, widget):
550
""" Branch/Push... menu handler. """
551
push = PushDialog(self.wt.branch, self.window)
552
response = push.run()
553
if response != gtk.RESPONSE_NONE:
557
def on_menuitem_branch_revert_activate(self, widget):
558
""" Branch/Revert all changes menu handler. """
559
ret = self.wt.revert([])
561
warning_dialog(_('Conflicts detected'),
562
_('Please have a look at the working tree before continuing.'))
564
info_dialog(_('Revert successful'),
565
_('All files reverted to last revision.'))
568
def on_menuitem_branch_status_activate(self, widget):
569
""" Branch/Status... menu handler. """
570
from bzrlib.plugins.gtk.status import StatusDialog
571
status = StatusDialog(self.wt, self.wtpath)
572
response = status.run()
573
if response != gtk.RESPONSE_NONE:
576
def on_menuitem_branch_initialize_activate(self, widget):
577
""" Initialize current directory. """
578
init = InitDialog(self.path, self.window)
579
response = init.run()
580
if response != gtk.RESPONSE_NONE:
583
if response == gtk.RESPONSE_OK:
588
def on_menuitem_branch_tags_activate(self, widget):
589
""" Branch/Tags... menu handler. """
590
from bzrlib.plugins.gtk.tags import TagsWindow
592
window = TagsWindow(self.wt.branch, self.window)
594
window = TagsWindow(self.remote_branch, self.window)
597
def on_menuitem_file_annotate_activate(self, widget):
598
""" File/Annotate... menu handler. """
599
if self.get_selected_right() is None:
600
error_dialog(_('No file was selected'),
601
_('Please select a file from the list.'))
604
branch = self.wt.branch
605
file_id = self.wt.path2id(self.wt.relpath(os.path.join(self.path, self.get_selected_right())))
607
window = GAnnotateWindow(all=False, plain=False)
608
window.set_title(os.path.join(self.path, self.get_selected_right()) + " - Annotate")
609
config = GAnnotateConfig(window)
613
window.annotate(self.wt, branch, file_id)
617
def on_menuitem_file_make_directory_activate(self, widget):
618
""" File/Make directory... menu handler. """
619
from mkdir import OliveMkdir
620
mkdir = OliveMkdir(self.wt, self.wtpath)
623
def on_menuitem_file_move_activate(self, widget):
624
""" File/Move... menu handler. """
625
from move import OliveMove
626
move = OliveMove(self.wt, self.wtpath, self.get_selected_right())
629
def on_menuitem_file_rename_activate(self, widget):
630
""" File/Rename... menu handler. """
631
from rename import OliveRename
632
rename = OliveRename(self.wt, self.wtpath, self.get_selected_right())
635
def on_menuitem_remove_file_activate(self, widget):
636
""" Remove (unversion) selected file. """
637
from remove import OliveRemoveDialog
638
remove = OliveRemoveDialog(self.wt, self.wtpath,
639
selected=self.get_selected_right(),
641
response = remove.run()
643
if response != gtk.RESPONSE_NONE:
646
if response == gtk.RESPONSE_OK:
647
self.set_path(self.path)
652
def on_menuitem_stats_diff_activate(self, widget):
653
""" Statistics/Differences... menu handler. """
654
window = DiffWindow()
655
parent_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
656
window.set_diff(self.wt.branch.nick, self.wt, parent_tree)
659
def on_menuitem_stats_infos_activate(self, widget):
660
""" Statistics/Informations... menu handler. """
661
from info import OliveInfo
663
info = OliveInfo(self.remote_branch)
665
info = OliveInfo(self.wt.branch)
668
def on_menuitem_stats_log_activate(self, widget):
669
""" Statistics/Log... menu handler. """
670
window = branchwin.BranchWindow()
672
window.set_branch(self.wt.branch, self.wt.branch.last_revision(), None)
674
window.set_branch(self.remote_branch, self.remote_branch.last_revision(), None)
677
def on_menuitem_view_refresh_activate(self, widget):
678
""" View/Refresh menu handler. """
679
# Refresh the left pane
681
# Refresh the right pane
684
def on_menuitem_view_show_hidden_files_activate(self, widget):
685
""" View/Show hidden files menu handler. """
686
self.pref.set_preference('dotted_files', widget.get_active())
687
if self.path is not None:
690
def on_treeview_left_button_press_event(self, widget, event):
691
""" Occurs when somebody right-clicks in the bookmark list. """
692
if event.button == 3:
693
# Don't show context with nothing selected
694
if self.get_selected_left() == None:
698
from menu import OliveMenu
699
menu = OliveMenu(path=self.get_path(),
700
selected=self.get_selected_left(),
703
menu.left_context_menu().popup(None, None, None, 0,
706
def on_treeview_left_row_activated(self, treeview, path, view_column):
707
""" Occurs when somebody double-clicks or enters an item in the
710
newdir = self.get_selected_left()
714
if self.set_path(newdir):
717
def on_treeview_right_button_press_event(self, widget, event):
718
""" Occurs when somebody right-clicks in the file list. """
719
if event.button == 3:
721
from menu import OliveMenu
722
menu = OliveMenu(path=self.get_path(),
723
selected=self.get_selected_right(),
726
m_open = menu.ui.get_widget('/context_right/open')
727
m_add = menu.ui.get_widget('/context_right/add')
728
m_remove = menu.ui.get_widget('/context_right/remove')
729
m_rename = menu.ui.get_widget('/context_right/rename')
730
m_revert = menu.ui.get_widget('/context_right/revert')
731
m_commit = menu.ui.get_widget('/context_right/commit')
732
m_annotate = menu.ui.get_widget('/context_right/annotate')
733
m_diff = menu.ui.get_widget('/context_right/diff')
734
# check if we're in a branch
736
from bzrlib.branch import Branch
737
Branch.open_containing(self.get_path())
739
m_open.set_sensitive(False)
740
m_add.set_sensitive(False)
741
m_remove.set_sensitive(False)
742
m_rename.set_sensitive(False)
743
m_revert.set_sensitive(False)
744
m_commit.set_sensitive(False)
745
m_annotate.set_sensitive(False)
746
m_diff.set_sensitive(False)
748
m_open.set_sensitive(True)
749
m_add.set_sensitive(True)
750
m_remove.set_sensitive(True)
751
m_rename.set_sensitive(True)
752
m_revert.set_sensitive(True)
753
m_commit.set_sensitive(True)
754
m_annotate.set_sensitive(True)
755
m_diff.set_sensitive(True)
756
except bzrerrors.NotBranchError:
757
m_open.set_sensitive(True)
758
m_add.set_sensitive(False)
759
m_remove.set_sensitive(False)
760
m_rename.set_sensitive(False)
761
m_revert.set_sensitive(False)
762
m_commit.set_sensitive(False)
763
m_annotate.set_sensitive(False)
764
m_diff.set_sensitive(False)
767
menu.right_context_menu().popup(None, None, None, 0,
770
menu.remote_context_menu().popup(None, None, None, 0,
773
def on_treeview_right_row_activated(self, treeview, path, view_column):
774
""" Occurs when somebody double-clicks or enters an item in the
776
from launch import launch
778
newdir = self.get_selected_right()
783
self.set_path(os.path.split(self.get_path())[0])
785
fullpath = os.path.join(self.get_path(), newdir)
786
if os.path.isdir(fullpath):
787
# selected item is an existant directory
788
self.set_path(fullpath)
793
if self._is_remote_dir(self.get_path() + newdir):
794
self.set_path(self.get_path() + newdir)
798
def on_window_main_delete_event(self, widget, event=None):
799
""" Do some stuff before exiting. """
800
width, height = self.window_main.get_size()
801
self.pref.set_preference('window_width', width)
802
self.pref.set_preference('window_height', height)
803
x, y = self.window_main.get_position()
804
self.pref.set_preference('window_x', x)
805
self.pref.set_preference('window_y', y)
806
self.pref.set_preference('paned_position',
807
self.hpaned_main.get_position())
810
self.window_main.destroy()
812
def _load_left(self):
813
""" Load data into the left panel. (Bookmarks) """
815
treestore = gtk.TreeStore(str, str)
818
bookmarks = self.pref.get_bookmarks()
820
# Add them to the TreeStore
821
titer = treestore.append(None, [_('Bookmarks'), None])
822
for item in bookmarks:
823
title = self.pref.get_bookmark_title(item)
824
treestore.append(titer, [title, item])
826
# Create the column and add it to the TreeView
827
self.treeview_left.set_model(treestore)
828
tvcolumn_bookmark = gtk.TreeViewColumn(_('Bookmark'))
829
self.treeview_left.append_column(tvcolumn_bookmark)
832
cell = gtk.CellRendererText()
833
tvcolumn_bookmark.pack_start(cell, True)
834
tvcolumn_bookmark.add_attribute(cell, 'text', 0)
837
self.treeview_left.expand_all()
839
def _load_right(self):
840
""" Load data into the right panel. (Filelist) """
842
# Model: [ icon, dir, name, status text, status, size (int), size (human), mtime (int), mtime (local), fileid]
843
liststore = gtk.ListStore(gobject.TYPE_STRING,
844
gobject.TYPE_BOOLEAN,
857
# Fill the appropriate lists
858
dotted_files = self.pref.get_preference('dotted_files', 'bool')
859
for item in os.listdir(self.path):
860
if not dotted_files and item[0] == '.':
862
if os.path.isdir(self.path + os.sep + item):
867
if not self.notbranch:
868
branch = self.wt.branch
869
tree2 = self.wt.branch.repository.revision_tree(branch.last_revision())
871
delta = self.wt.changes_from(tree2, want_unchanged=True)
873
# Add'em to the ListStore
875
statinfo = os.stat(self.path + os.sep + item)
876
liststore.append([ gtk.STOCK_DIRECTORY,
882
self._format_size(statinfo.st_size),
884
self._format_date(statinfo.st_mtime),
889
if not self.notbranch:
890
filename = self.wt.relpath(self.path + os.sep + item)
895
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
896
if rpathnew == filename:
899
for rpath, id, kind in delta.added:
900
if rpath == filename:
903
for rpath, id, kind in delta.removed:
904
if rpath == filename:
907
for rpath, id, kind, text_modified, meta_modified in delta.modified:
908
if rpath == filename:
911
for rpath, id, kind in delta.unchanged:
912
if rpath == filename:
915
for rpath, file_class, kind, id, entry in self.wt.list_files():
916
if rpath == filename and file_class == 'I':
921
if status == 'renamed':
923
elif status == 'removed':
925
elif status == 'added':
927
elif status == 'modified':
929
elif status == 'unchanged':
931
elif status == 'ignored':
936
statinfo = os.stat(self.path + os.sep + item)
937
liststore.append([gtk.STOCK_FILE,
943
self._format_size(statinfo.st_size),
945
self._format_date(statinfo.st_mtime),
948
# Create the columns and add them to the TreeView
949
self.treeview_right.set_model(liststore)
950
self._tvcolumn_filename = gtk.TreeViewColumn(_('Filename'))
951
self._tvcolumn_status = gtk.TreeViewColumn(_('Status'))
952
self._tvcolumn_size = gtk.TreeViewColumn(_('Size'))
953
self._tvcolumn_mtime = gtk.TreeViewColumn(_('Last modified'))
954
self.treeview_right.append_column(self._tvcolumn_filename)
955
self.treeview_right.append_column(self._tvcolumn_status)
956
self.treeview_right.append_column(self._tvcolumn_size)
957
self.treeview_right.append_column(self._tvcolumn_mtime)
960
cellpb = gtk.CellRendererPixbuf()
961
cell = gtk.CellRendererText()
962
self._tvcolumn_filename.pack_start(cellpb, False)
963
self._tvcolumn_filename.pack_start(cell, True)
964
self._tvcolumn_filename.set_attributes(cellpb, stock_id=0)
965
self._tvcolumn_filename.add_attribute(cell, 'text', 2)
966
self._tvcolumn_status.pack_start(cell, True)
967
self._tvcolumn_status.add_attribute(cell, 'text', 3)
968
self._tvcolumn_size.pack_start(cell, True)
969
self._tvcolumn_size.add_attribute(cell, 'text', 6)
970
self._tvcolumn_mtime.pack_start(cell, True)
971
self._tvcolumn_mtime.add_attribute(cell, 'text', 8)
973
# Set up the properties of the TreeView
974
self.treeview_right.set_headers_visible(True)
975
self.treeview_right.set_headers_clickable(True)
976
self.treeview_right.set_search_column(1)
977
self._tvcolumn_filename.set_resizable(True)
978
self._tvcolumn_status.set_resizable(True)
979
self._tvcolumn_size.set_resizable(True)
980
self._tvcolumn_mtime.set_resizable(True)
982
liststore.set_sort_func(13, self._sort_filelist_callback, None)
983
liststore.set_sort_column_id(13, gtk.SORT_ASCENDING)
984
self._tvcolumn_filename.set_sort_column_id(13)
985
self._tvcolumn_status.set_sort_column_id(3)
986
self._tvcolumn_size.set_sort_column_id(5)
987
self._tvcolumn_mtime.set_sort_column_id(7)
990
self.set_sensitivity()
992
def get_selected_fileid(self):
993
""" Get the file_id of the selected file. """
994
treeselection = self.treeview_right.get_selection()
995
(model, iter) = treeselection.get_selected()
1000
return model.get_value(iter, 9)
1002
def get_selected_right(self):
1003
""" Get the selected filename. """
1004
treeselection = self.treeview_right.get_selection()
1005
(model, iter) = treeselection.get_selected()
1010
return model.get_value(iter, 2)
1012
def get_selected_left(self):
1013
""" Get the selected bookmark. """
1014
treeselection = self.treeview_left.get_selection()
1015
(model, iter) = treeselection.get_selected()
1020
return model.get_value(iter, 1)
1022
def set_statusbar(self, message):
1023
""" Set the statusbar message. """
1024
self.statusbar.push(self.context_id, message)
1026
def clear_statusbar(self):
1027
""" Clean the last message from the statusbar. """
1028
self.statusbar.pop(self.context_id)
1030
def set_sensitivity(self):
1031
""" Set menu and toolbar sensitivity. """
1034
self.menuitem_branch_init.set_sensitive(self.notbranch)
1035
self.menuitem_branch_get.set_sensitive(self.notbranch)
1036
self.menuitem_branch_checkout.set_sensitive(self.notbranch)
1037
self.menuitem_branch_pull.set_sensitive(not self.notbranch)
1038
self.menuitem_branch_push.set_sensitive(not self.notbranch)
1039
self.menuitem_branch_revert.set_sensitive(not self.notbranch)
1040
self.menuitem_branch_merge.set_sensitive(not self.notbranch)
1041
self.menuitem_branch_commit.set_sensitive(not self.notbranch)
1042
self.menuitem_branch_tags.set_sensitive(not self.notbranch)
1043
self.menuitem_branch_status.set_sensitive(not self.notbranch)
1044
self.menuitem_branch_missing.set_sensitive(not self.notbranch)
1045
self.menuitem_branch_conflicts.set_sensitive(not self.notbranch)
1046
self.menuitem_stats.set_sensitive(not self.notbranch)
1047
self.menuitem_stats_diff.set_sensitive(not self.notbranch)
1048
self.menuitem_add_files.set_sensitive(not self.notbranch)
1049
self.menuitem_remove_files.set_sensitive(not self.notbranch)
1050
self.menuitem_file_make_directory.set_sensitive(not self.notbranch)
1051
self.menuitem_file_rename.set_sensitive(not self.notbranch)
1052
self.menuitem_file_move.set_sensitive(not self.notbranch)
1053
self.menuitem_file_annotate.set_sensitive(not self.notbranch)
1054
#self.menutoolbutton_diff.set_sensitive(True)
1055
self.toolbutton_diff.set_sensitive(not self.notbranch)
1056
self.toolbutton_log.set_sensitive(not self.notbranch)
1057
self.toolbutton_commit.set_sensitive(not self.notbranch)
1058
self.toolbutton_pull.set_sensitive(not self.notbranch)
1059
self.toolbutton_push.set_sensitive(not self.notbranch)
1062
self.menuitem_branch_init.set_sensitive(False)
1063
self.menuitem_branch_get.set_sensitive(True)
1064
self.menuitem_branch_checkout.set_sensitive(True)
1065
self.menuitem_branch_pull.set_sensitive(False)
1066
self.menuitem_branch_push.set_sensitive(False)
1067
self.menuitem_branch_revert.set_sensitive(False)
1068
self.menuitem_branch_merge.set_sensitive(False)
1069
self.menuitem_branch_commit.set_sensitive(False)
1070
self.menuitem_branch_tags.set_sensitive(True)
1071
self.menuitem_branch_status.set_sensitive(False)
1072
self.menuitem_branch_missing.set_sensitive(False)
1073
self.menuitem_branch_conflicts.set_sensitive(False)
1074
self.menuitem_stats.set_sensitive(True)
1075
self.menuitem_stats_diff.set_sensitive(False)
1076
self.menuitem_add_files.set_sensitive(False)
1077
self.menuitem_remove_files.set_sensitive(False)
1078
self.menuitem_file_make_directory.set_sensitive(False)
1079
self.menuitem_file_rename.set_sensitive(False)
1080
self.menuitem_file_move.set_sensitive(False)
1081
self.menuitem_file_annotate.set_sensitive(False)
1082
#self.menutoolbutton_diff.set_sensitive(True)
1083
self.toolbutton_diff.set_sensitive(False)
1084
self.toolbutton_log.set_sensitive(True)
1085
self.toolbutton_commit.set_sensitive(False)
1086
self.toolbutton_pull.set_sensitive(False)
1087
self.toolbutton_push.set_sensitive(False)
1089
def refresh_left(self):
1090
""" Refresh the bookmark list. """
1092
# Get TreeStore and clear it
1093
treestore = self.treeview_left.get_model()
1096
# Re-read preferences
1100
bookmarks = self.pref.get_bookmarks()
1102
# Add them to the TreeStore
1103
titer = treestore.append(None, [_('Bookmarks'), None])
1104
for item in bookmarks:
1105
title = self.pref.get_bookmark_title(item)
1106
treestore.append(titer, [title, item])
1108
# Add the TreeStore to the TreeView
1109
self.treeview_left.set_model(treestore)
1112
self.treeview_left.expand_all()
1114
def refresh_right(self, path=None):
1115
""" Refresh the file list. """
1118
from bzrlib.workingtree import WorkingTree
1121
path = self.get_path()
1123
# A workaround for double-clicking Bookmarks
1124
if not os.path.exists(path):
1127
# Get ListStore and clear it
1128
liststore = self.treeview_right.get_model()
1131
# Show Status column
1132
self._tvcolumn_status.set_visible(True)
1137
# Fill the appropriate lists
1138
dotted_files = self.pref.get_preference('dotted_files', 'bool')
1139
for item in os.listdir(path):
1140
if not dotted_files and item[0] == '.':
1142
if os.path.isdir(path + os.sep + item):
1147
# Try to open the working tree
1150
tree1 = WorkingTree.open_containing(path)[0]
1151
except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
1155
branch = tree1.branch
1156
tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
1158
delta = tree1.changes_from(tree2, want_unchanged=True)
1160
# Add'em to the ListStore
1162
statinfo = os.stat(self.path + os.sep + item)
1163
liststore.append([gtk.STOCK_DIRECTORY,
1169
self._format_size(statinfo.st_size),
1171
self._format_date(statinfo.st_mtime),
1177
filename = tree1.relpath(path + os.sep + item)
1182
for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
1183
if rpathnew == filename:
1186
for rpath, id, kind in delta.added:
1187
if rpath == filename:
1190
for rpath, id, kind in delta.removed:
1191
if rpath == filename:
1194
for rpath, id, kind, text_modified, meta_modified in delta.modified:
1195
if rpath == filename:
1198
for rpath, id, kind in delta.unchanged:
1199
if rpath == filename:
1200
status = 'unchanged'
1202
for rpath, file_class, kind, id, entry in self.wt.list_files():
1203
if rpath == filename and file_class == 'I':
1208
if status == 'renamed':
1210
elif status == 'removed':
1212
elif status == 'added':
1214
elif status == 'modified':
1216
elif status == 'unchanged':
1218
elif status == 'ignored':
1223
statinfo = os.stat(self.path + os.sep + item)
1224
liststore.append([gtk.STOCK_FILE,
1230
self._format_size(statinfo.st_size),
1232
self._format_date(statinfo.st_mtime),
1237
# Get ListStore and clear it
1238
liststore = self.treeview_right.get_model()
1241
# Hide Status column
1242
self._tvcolumn_status.set_visible(False)
1247
self._show_stock_image(gtk.STOCK_REFRESH)
1249
for (name, type) in self.remote_entries:
1250
if type.kind == 'directory':
1252
elif type.kind == 'file':
1256
""" Cache based on revision history. """
1257
def __init__(self, history):
1258
self._history = history
1260
def _lookup_revision(self, revid):
1261
for r in self._history:
1262
if r.revision_id == revid:
1264
rev = repo.get_revision(revid)
1265
self._history.append(rev)
1268
repo = self.remote_branch.repository
1270
revhistory = self.remote_branch.revision_history()
1272
revs = repo.get_revisions(revhistory)
1273
cache = HistoryCache(revs)
1274
except bzrerrors.InvalidHttpResponse:
1275
# Fallback to dummy algorithm, because of LP: #115209
1276
cache = HistoryCache([])
1279
if item.parent_id == self.remote_parent:
1280
rev = cache._lookup_revision(item.revision)
1281
liststore.append([ gtk.STOCK_DIRECTORY,
1287
self._format_size(0),
1289
self._format_date(rev.timestamp),
1292
while gtk.events_pending():
1293
gtk.main_iteration()
1296
if item.parent_id == self.remote_parent:
1297
rev = cache._lookup_revision(item.revision)
1298
liststore.append([ gtk.STOCK_FILE,
1304
self._format_size(item.text_size),
1306
self._format_date(rev.timestamp),
1309
while gtk.events_pending():
1310
gtk.main_iteration()
1312
self.image_location_error.destroy()
1314
# Columns should auto-size
1315
self.treeview_right.columns_autosize()
1318
self.set_sensitivity()
1320
def _harddisks(self):
1321
""" Returns hard drive letters under Win32. """
1326
if sys.platform == 'win32':
1327
print "pyWin32 modules needed to run Olive on Win32."
1333
for drive in string.ascii_uppercase:
1334
if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED:
1335
driveletters.append(drive+':')
1338
def gen_hard_selector(self):
1339
""" Generate the hard drive selector under Win32. """
1340
drives = self._harddisks()
1341
for drive in drives:
1342
self.combobox_drive.append_text(drive)
1343
self.combobox_drive.set_active(drives.index(os.getcwd()[0:2]))
1345
def _refresh_drives(self, combobox):
1346
if self._just_started:
1348
model = combobox.get_model()
1349
active = combobox.get_active()
1351
drive = model[active][0]
1352
self.set_path(drive + '\\')
1353
self.refresh_right(drive + '\\')
1355
def check_for_changes(self):
1356
""" Check whether there were changes in the current working tree. """
1357
old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
1358
delta = self.wt.changes_from(old_tree)
1362
if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
1367
def _sort_filelist_callback(self, model, iter1, iter2, data):
1368
""" The sort callback for the file list, return values:
1373
name1 = model.get_value(iter1, 2)
1374
name2 = model.get_value(iter2, 2)
1376
if model.get_value(iter1, 1):
1377
# item1 is a directory
1378
if not model.get_value(iter2, 1):
1382
# both of them are directories, we compare their names
1385
elif name1 == name2:
1390
# item1 is not a directory
1391
if model.get_value(iter2, 1):
1395
# both of them are files, compare them
1398
elif name1 == name2:
1403
def _format_size(self, size):
1404
""" Format size to a human readable format. """
1407
def _format_date(self, timestamp):
1408
""" Format the time (given in secs) to a human readable format. """
1409
return time.ctime(timestamp)
1411
def _is_remote_dir(self, location):
1412
""" Determine whether the given location is a directory or not. """
1414
# We're in local mode
1417
branch, path = Branch.open_containing(location)
1418
for (name, type) in self.remote_entries:
1419
if name == path and type.kind == 'directory':
1422
# Either it's not a directory or not in the inventory
1425
def _show_stock_image(self, stock_id):
1426
""" Show a stock image next to the location entry. """
1427
self.image_location_error.destroy()
1428
self.image_location_error = gtk.image_new_from_stock(stock_id, gtk.ICON_SIZE_BUTTON)
1429
self.hbox_location.pack_start(self.image_location_error, False, False, 0)
1430
if sys.platform == 'win32':
1431
self.hbox_location.reorder_child(self.image_location_error, 2)
1433
self.hbox_location.reorder_child(self.image_location_error, 1)
1434
self.image_location_error.show()
1435
while gtk.events_pending():
1436
gtk.main_iteration()
1441
""" A class which handles Olive's preferences. """
1442
def __init__(self, path=None):
1443
""" Initialize the Preferences class. """
1444
# Some default options
1445
self.defaults = { 'strict_commit' : False,
1446
'dotted_files' : False,
1447
'window_width' : 700,
1448
'window_height' : 400,
1451
'paned_position': 200 }
1453
# Create a config parser object
1454
self.config = ConfigParser.RawConfigParser()
1458
if sys.platform == 'win32':
1459
# Windows - no dotted files
1460
self._filename = os.path.expanduser('~/olive.conf')
1462
self._filename = os.path.expanduser('~/.olive.conf')
1464
self._filename = path
1466
# Load the configuration
1469
def _get_default(self, option):
1470
""" Get the default option for a preference. """
1472
ret = self.defaults[option]
1479
""" Refresh the configuration. """
1480
# First write out the changes
1482
# Then load the configuration again
1486
""" Just read the configuration. """
1487
# Re-initialize the config parser object to avoid some bugs
1488
self.config = ConfigParser.RawConfigParser()
1489
self.config.read([self._filename])
1492
""" Write the configuration to the appropriate files. """
1493
fp = open(self._filename, 'w')
1494
self.config.write(fp)
1497
def get_bookmarks(self):
1498
""" Return the list of bookmarks. """
1499
bookmarks = self.config.sections()
1500
if self.config.has_section('preferences'):
1501
bookmarks.remove('preferences')
1504
def add_bookmark(self, path):
1505
""" Add bookmark. """
1507
self.config.add_section(path)
1508
except ConfigParser.DuplicateSectionError:
1513
def get_bookmark_title(self, path):
1514
""" Get bookmark title. """
1516
ret = self.config.get(path, 'title')
1517
except ConfigParser.NoOptionError:
1522
def set_bookmark_title(self, path, title):
1523
""" Set bookmark title. """
1524
# FIXME: What if path isn't listed yet?
1525
# FIXME: Canonicalize paths first?
1526
self.config.set(path, 'title', title)
1528
def remove_bookmark(self, path):
1529
""" Remove bookmark. """
1530
return self.config.remove_section(path)
1532
def set_preference(self, option, value):
1533
""" Set the value of the given option. """
1536
elif value is False:
1539
if self.config.has_section('preferences'):
1540
self.config.set('preferences', option, value)
1542
self.config.add_section('preferences')
1543
self.config.set('preferences', option, value)
1545
def get_preference(self, option, kind='str'):
1546
""" Get the value of the given option.
1548
:param kind: str/bool/int/float. default: str
1550
if self.config.has_option('preferences', option):
1552
return self.config.getboolean('preferences', option)
1554
return self.config.getint('preferences', option)
1555
elif kind == 'float':
1556
return self.config.getfloat('preferences', option)
1558
return self.config.get('preferences', option)
1561
return self._get_default(option)