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
34
from handler import OliveHandler
35
import bzrlib.errors as errors
37
# Olive GTK UI version
38
__version__ = '0.10.0'
41
""" The main Olive GTK frontend class. This is called when launching the
46
if sys.platform == 'win32':
47
self.gladefile = os.path.dirname(sys.executable) + "/share/olive/olive.glade"
49
self.gladefile = "/usr/share/olive/olive.glade"
51
if not os.path.exists(self.gladefile):
52
# Load from current directory if not installed
53
self.gladefile = "olive.glade"
55
if not os.path.exists(self.gladefile):
57
print _('Glade file cannot be found.')
60
self.toplevel = gtk.glade.XML(self.gladefile, 'window_main', 'olive-gtk')
62
self.window = self.toplevel.get_widget('window_main')
64
self.pref = OlivePreferences()
65
self.comm = OliveCommunicator(self.toplevel, self.pref)
66
handler = OliveHandler(self.gladefile, self.comm)
68
# Dictionary for signal_autoconnect
69
dic = { "on_window_main_destroy": gtk.main_quit,
70
"on_window_main_delete_event": handler.on_window_main_delete_event,
71
"on_quit_activate": handler.on_window_main_delete_event,
72
"on_about_activate": handler.on_about_activate,
73
"on_menuitem_add_files_activate": handler.on_menuitem_add_files_activate,
74
"on_menuitem_remove_file_activate": handler.on_menuitem_remove_file_activate,
75
"on_menuitem_file_make_directory_activate": handler.on_menuitem_file_make_directory_activate,
76
"on_menuitem_file_move_activate": handler.on_menuitem_file_move_activate,
77
"on_menuitem_file_rename_activate": handler.on_menuitem_file_rename_activate,
78
"on_menuitem_view_show_hidden_files_activate": handler.on_menuitem_view_show_hidden_files_activate,
79
"on_menuitem_view_refresh_activate": handler.on_menuitem_view_refresh_activate,
80
"on_menuitem_branch_initialize_activate": handler.on_menuitem_branch_initialize_activate,
81
"on_menuitem_branch_get_activate": handler.on_menuitem_branch_get_activate,
82
"on_menuitem_branch_checkout_activate": handler.on_menuitem_branch_checkout_activate,
83
"on_menuitem_branch_commit_activate": handler.on_menuitem_branch_commit_activate,
84
"on_menuitem_branch_push_activate": handler.on_menuitem_branch_push_activate,
85
"on_menuitem_branch_pull_activate": handler.on_menuitem_branch_pull_activate,
86
"on_menuitem_branch_status_activate": handler.on_menuitem_branch_status_activate,
87
"on_menuitem_branch_missing_revisions_activate": handler.on_menuitem_branch_missing_revisions_activate,
88
"on_menuitem_stats_diff_activate": handler.on_menuitem_stats_diff_activate,
89
"on_menuitem_stats_log_activate": handler.on_menuitem_stats_log_activate,
90
"on_menuitem_stats_infos_activate": handler.on_menuitem_stats_infos_activate,
91
"on_toolbutton_refresh_clicked": handler.on_menuitem_view_refresh_activate,
92
"on_toolbutton_log_clicked": handler.on_menuitem_stats_log_activate,
93
#"on_menutoolbutton_diff_clicked": handler.on_menuitem_stats_diff_activate,
94
"on_toolbutton_diff_clicked": handler.on_menuitem_stats_diff_activate,
95
"on_toolbutton_commit_clicked": handler.on_menuitem_branch_commit_activate,
96
"on_toolbutton_pull_clicked": handler.on_menuitem_branch_pull_activate,
97
"on_toolbutton_push_clicked": handler.on_menuitem_branch_push_activate,
98
"on_treeview_right_button_press_event": handler.on_treeview_right_button_press_event,
99
"on_treeview_right_row_activated": handler.on_treeview_right_row_activated,
100
"on_treeview_left_button_press_event": handler.on_treeview_left_button_press_event,
101
"on_treeview_left_row_activated": handler.on_treeview_left_row_activated }
103
# Connect the signals to the handlers
104
self.toplevel.signal_autoconnect(dic)
106
# Apply window size and position
107
width = self.pref.get_preference('window_width', 'int')
108
height = self.pref.get_preference('window_height', 'int')
109
self.window.resize(width, height)
110
x = self.pref.get_preference('window_x', 'int')
111
y = self.pref.get_preference('window_y', 'int')
112
self.window.move(x, y)
113
# Apply paned position
114
pos = self.pref.get_preference('paned_position', 'int')
115
self.comm.hpaned_main.set_position(pos)
117
# Apply menu to the toolbutton
118
#menubutton = self.toplevel.get_widget('menutoolbutton_diff')
119
#menubutton.set_menu(handler.menu.toolbar_diff)
121
# Now we can show the window
122
self.window.show_all()
124
# Load default data into the panels
125
self.treeview_left = self.toplevel.get_widget('treeview_left')
126
self.treeview_right = self.toplevel.get_widget('treeview_right')
131
self.comm.menuitem_view_show_hidden_files.set_active(self.pref.get_preference('dotted_files', 'bool'))
133
def _load_left(self):
134
""" Load data into the left panel. (Bookmarks) """
136
self.comm.set_busy(self.treeview_left)
139
treestore = gtk.TreeStore(str, str)
142
bookmarks = self.comm.pref.get_bookmarks()
144
# Add them to the TreeStore
145
titer = treestore.append(None, [_('Bookmarks'), None])
146
for item in bookmarks:
147
title = self.comm.pref.get_bookmark_title(item)
148
treestore.append(titer, [title, item])
150
# Create the column and add it to the TreeView
151
self.treeview_left.set_model(treestore)
152
tvcolumn_bookmark = gtk.TreeViewColumn(_('Bookmark'))
153
self.treeview_left.append_column(tvcolumn_bookmark)
156
cell = gtk.CellRendererText()
157
tvcolumn_bookmark.pack_start(cell, True)
158
tvcolumn_bookmark.add_attribute(cell, 'text', 0)
161
self.treeview_left.expand_all()
163
self.comm.set_busy(self.treeview_left, False)
165
def _load_right(self):
166
""" Load data into the right panel. (Filelist) """
168
self.comm.set_busy(self.treeview_right)
171
liststore = gtk.ListStore(str, str, str)
176
# Fill the appropriate lists
177
path = self.comm.get_path()
178
dotted_files = self.pref.get_preference('dotted_files', 'bool')
179
for item in os.listdir(path):
180
if not dotted_files and item[0] == '.':
182
if os.path.isdir(path + '/' + item):
191
# Add'em to the ListStore
193
liststore.append([gtk.STOCK_DIRECTORY, item, ''])
196
status = check_status(path + '/' + item)
197
except errors.PermissionDenied:
200
if status == 'renamed':
202
elif status == 'removed':
204
elif status == 'added':
206
elif status == 'modified':
208
elif status == 'unchanged':
212
liststore.append([gtk.STOCK_FILE, item, st])
214
# Create the columns and add them to the TreeView
215
self.treeview_right.set_model(liststore)
216
tvcolumn_filename = gtk.TreeViewColumn(_('Filename'))
217
tvcolumn_status = gtk.TreeViewColumn(_('Status'))
218
self.treeview_right.append_column(tvcolumn_filename)
219
self.treeview_right.append_column(tvcolumn_status)
222
cellpb = gtk.CellRendererPixbuf()
223
cell = gtk.CellRendererText()
224
tvcolumn_filename.pack_start(cellpb, False)
225
tvcolumn_filename.pack_start(cell, True)
226
tvcolumn_filename.set_attributes(cellpb, stock_id=0)
227
tvcolumn_filename.add_attribute(cell, 'text', 1)
228
tvcolumn_status.pack_start(cell, True)
229
tvcolumn_status.add_attribute(cell, 'text', 2)
231
# Check if current directory is versioned
233
from bzrlib.branch import Branch
234
Branch.open_containing(self.comm.get_path())
235
# Activate some items
236
self.comm.menuitem_branch_init.set_sensitive(False)
237
self.comm.menuitem_branch_get.set_sensitive(True)
238
self.comm.menuitem_branch_checkout.set_sensitive(True)
239
self.comm.menuitem_branch_pull.set_sensitive(True)
240
self.comm.menuitem_branch_push.set_sensitive(True)
241
self.comm.menuitem_branch_commit.set_sensitive(True)
242
self.comm.menuitem_branch_status.set_sensitive(True)
243
self.comm.menuitem_branch_missing.set_sensitive(True)
244
self.comm.menuitem_stats.set_sensitive(True)
245
self.comm.menuitem_add_files.set_sensitive(True)
246
self.comm.menuitem_remove_files.set_sensitive(True)
247
self.comm.menuitem_file_make_directory.set_sensitive(True)
248
self.comm.menuitem_file_rename.set_sensitive(True)
249
self.comm.menuitem_file_move.set_sensitive(True)
250
#self.comm.menutoolbutton_diff.set_sensitive(True)
251
self.comm.toolbutton_diff.set_sensitive(True)
252
self.comm.toolbutton_log.set_sensitive(True)
253
self.comm.toolbutton_commit.set_sensitive(True)
254
self.comm.toolbutton_pull.set_sensitive(True)
255
self.comm.toolbutton_push.set_sensitive(True)
256
except errors.NotBranchError:
257
# Deactivate some items
258
self.comm.menuitem_branch_init.set_sensitive(True)
259
self.comm.menuitem_branch_get.set_sensitive(False)
260
self.comm.menuitem_branch_checkout.set_sensitive(False)
261
self.comm.menuitem_branch_pull.set_sensitive(False)
262
self.comm.menuitem_branch_push.set_sensitive(False)
263
self.comm.menuitem_branch_commit.set_sensitive(False)
264
self.comm.menuitem_branch_status.set_sensitive(False)
265
self.comm.menuitem_branch_missing.set_sensitive(False)
266
self.comm.menuitem_stats.set_sensitive(False)
267
self.comm.menuitem_add_files.set_sensitive(False)
268
self.comm.menuitem_remove_files.set_sensitive(False)
269
self.comm.menuitem_file_make_directory.set_sensitive(False)
270
self.comm.menuitem_file_rename.set_sensitive(False)
271
self.comm.menuitem_file_move.set_sensitive(False)
272
#self.comm.menutoolbutton_diff.set_sensitive(False)
273
self.comm.toolbutton_diff.set_sensitive(False)
274
self.comm.toolbutton_log.set_sensitive(False)
275
self.comm.toolbutton_commit.set_sensitive(False)
276
self.comm.toolbutton_pull.set_sensitive(False)
277
self.comm.toolbutton_push.set_sensitive(False)
279
# set cursor to default
280
self.comm.set_busy(self.treeview_right, False)
282
class OliveCommunicator:
283
""" This class is responsible for the communication between the different
285
def __init__(self, toplevel, pref):
286
# Get glade main component
287
self.toplevel = toplevel
291
self._path = os.getcwd()
293
# Initialize the statusbar
294
self.statusbar = self.toplevel.get_widget('statusbar')
295
self.context_id = self.statusbar.get_context_id('olive')
297
# Get the main window
298
self.window_main = self.toplevel.get_widget('window_main')
300
self.hpaned_main = self.toplevel.get_widget('hpaned_main')
302
self.treeview_left = self.toplevel.get_widget('treeview_left')
303
self.treeview_right = self.toplevel.get_widget('treeview_right')
304
# Get some important menu items
305
self.menuitem_add_files = self.toplevel.get_widget('menuitem_add_files')
306
self.menuitem_remove_files = self.toplevel.get_widget('menuitem_remove_file')
307
self.menuitem_file_make_directory = self.toplevel.get_widget('menuitem_file_make_directory')
308
self.menuitem_file_rename = self.toplevel.get_widget('menuitem_file_rename')
309
self.menuitem_file_move = self.toplevel.get_widget('menuitem_file_move')
310
self.menuitem_view_show_hidden_files = self.toplevel.get_widget('menuitem_view_show_hidden_files')
311
self.menuitem_branch = self.toplevel.get_widget('menuitem_branch')
312
self.menuitem_branch_init = self.toplevel.get_widget('menuitem_branch_initialize')
313
self.menuitem_branch_get = self.toplevel.get_widget('menuitem_branch_get')
314
self.menuitem_branch_checkout = self.toplevel.get_widget('menuitem_branch_checkout')
315
self.menuitem_branch_pull = self.toplevel.get_widget('menuitem_branch_pull')
316
self.menuitem_branch_push = self.toplevel.get_widget('menuitem_branch_push')
317
self.menuitem_branch_commit = self.toplevel.get_widget('menuitem_branch_commit')
318
self.menuitem_branch_status = self.toplevel.get_widget('menuitem_branch_status')
319
self.menuitem_branch_missing = self.toplevel.get_widget('menuitem_branch_missing_revisions')
320
self.menuitem_stats = self.toplevel.get_widget('menuitem_stats')
321
self.menuitem_stats_diff = self.toplevel.get_widget('menuitem_stats_diff')
322
self.menuitem_stats_log = self.toplevel.get_widget('menuitem_stats_log')
323
# Get some toolbuttons
324
#self.menutoolbutton_diff = self.toplevel.get_widget('menutoolbutton_diff')
325
self.toolbutton_diff = self.toplevel.get_widget('toolbutton_diff')
326
self.toolbutton_log = self.toplevel.get_widget('toolbutton_log')
327
self.toolbutton_commit = self.toplevel.get_widget('toolbutton_commit')
328
self.toolbutton_pull = self.toplevel.get_widget('toolbutton_pull')
329
self.toolbutton_push = self.toplevel.get_widget('toolbutton_push')
331
def set_path(self, path):
332
""" Set the current path while browsing the directories. """
336
""" Get the current path. """
339
def get_selected_right(self):
340
""" Get the selected filename. """
341
treeselection = self.treeview_right.get_selection()
342
(model, iter) = treeselection.get_selected()
347
return model.get_value(iter, 1)
349
def get_selected_left(self):
350
""" Get the selected bookmark. """
351
treeselection = self.treeview_left.get_selection()
352
(model, iter) = treeselection.get_selected()
357
return model.get_value(iter, 1)
359
def set_statusbar(self, message):
360
""" Set the statusbar message. """
361
self.statusbar.push(self.context_id, message)
363
def clear_statusbar(self):
364
""" Clean the last message from the statusbar. """
365
self.statusbar.pop(self.context_id)
367
def refresh_left(self):
368
""" Refresh the bookmark list. """
370
self.set_busy(self.treeview_left)
372
# Get TreeStore and clear it
373
treestore = self.treeview_left.get_model()
377
bookmarks = self.pref.get_bookmarks()
379
# Add them to the TreeStore
380
titer = treestore.append(None, [_('Bookmarks'), None])
381
for item in bookmarks:
382
title = self.pref.get_bookmark_title(item)
383
treestore.append(titer, [title, item])
385
# Add the TreeStore to the TreeView
386
self.treeview_left.set_model(treestore)
389
self.treeview_left.expand_all()
391
self.set_busy(self.treeview_left, False)
393
def refresh_right(self, path=None):
394
""" Refresh the file list. """
395
self.set_busy(self.treeview_right)
398
path = self.get_path()
400
# A workaround for double-clicking Bookmarks
401
if not os.path.exists(path):
402
self.set_busy(self.treeview_right, False)
405
# Get ListStore and clear it
406
liststore = self.treeview_right.get_model()
412
# Fill the appropriate lists
413
dotted_files = self.pref.get_preference('dotted_files', 'bool')
414
for item in os.listdir(path):
415
if not dotted_files and item[0] == '.':
417
if os.path.isdir(path + '/' + item):
426
# Add'em to the ListStore
428
liststore.append([gtk.STOCK_DIRECTORY, item, ''])
431
status = check_status(path + '/' + item)
432
except errors.PermissionDenied:
435
if status == 'renamed':
437
elif status == 'removed':
439
elif status == 'added':
441
elif status == 'modified':
443
elif status == 'unchanged':
447
liststore.append([gtk.STOCK_FILE, item, st])
449
# Add the ListStore to the TreeView
450
self.treeview_right.set_model(liststore)
452
# Check if current directory is a branch
454
from bzrlib.branch import Branch
455
Branch.open_containing(self.get_path())
456
# Activate some items
457
self.menuitem_branch_init.set_sensitive(False)
458
self.menuitem_branch_get.set_sensitive(True)
459
self.menuitem_branch_checkout.set_sensitive(True)
460
self.menuitem_branch_pull.set_sensitive(True)
461
self.menuitem_branch_push.set_sensitive(True)
462
self.menuitem_branch_commit.set_sensitive(True)
463
self.menuitem_branch_status.set_sensitive(True)
464
self.menuitem_branch_missing.set_sensitive(True)
465
self.menuitem_stats.set_sensitive(True)
466
self.menuitem_add_files.set_sensitive(True)
467
self.menuitem_remove_files.set_sensitive(True)
468
self.menuitem_file_make_directory.set_sensitive(True)
469
self.menuitem_file_rename.set_sensitive(True)
470
self.menuitem_file_move.set_sensitive(True)
471
#self.menutoolbutton_diff.set_sensitive(True)
472
self.toolbutton_diff.set_sensitive(True)
473
self.toolbutton_log.set_sensitive(True)
474
self.toolbutton_commit.set_sensitive(True)
475
self.toolbutton_pull.set_sensitive(True)
476
self.toolbutton_push.set_sensitive(True)
477
except errors.NotBranchError:
478
# Deactivate some items
479
self.menuitem_branch_init.set_sensitive(True)
480
self.menuitem_branch_get.set_sensitive(False)
481
self.menuitem_branch_checkout.set_sensitive(False)
482
self.menuitem_branch_pull.set_sensitive(False)
483
self.menuitem_branch_push.set_sensitive(False)
484
self.menuitem_branch_commit.set_sensitive(False)
485
self.menuitem_branch_status.set_sensitive(False)
486
self.menuitem_branch_missing.set_sensitive(False)
487
self.menuitem_stats.set_sensitive(False)
488
self.menuitem_add_files.set_sensitive(False)
489
self.menuitem_remove_files.set_sensitive(False)
490
self.menuitem_file_make_directory.set_sensitive(False)
491
self.menuitem_file_rename.set_sensitive(False)
492
self.menuitem_file_move.set_sensitive(False)
493
#self.menutoolbutton_diff.set_sensitive(False)
494
self.toolbutton_diff.set_sensitive(False)
495
self.toolbutton_log.set_sensitive(False)
496
self.toolbutton_commit.set_sensitive(False)
497
self.toolbutton_pull.set_sensitive(False)
498
self.toolbutton_push.set_sensitive(False)
500
self.set_busy(self.treeview_right, False)
502
def set_busy(self, widget, busy=True):
504
widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
506
widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
508
gtk.main_iteration(0)
510
class OlivePreferences:
511
""" A class which handles Olive's preferences. """
513
""" Initialize the Preferences class. """
514
# Some default options
515
self.defaults = { 'strict_commit' : False,
516
'dotted_files' : False,
517
'window_width' : 700,
518
'window_height' : 400,
521
'paned_position': 200 }
523
# Create a config parser object
524
self.config = ConfigParser.RawConfigParser()
526
# Load the configuration
527
if sys.platform == 'win32':
528
# Windows - no dotted files
529
self.config.read([os.path.expanduser('~/olive.conf')])
531
self.config.read([os.path.expanduser('~/.olive.conf')])
533
def _get_default(self, option):
534
""" Get the default option for a preference. """
536
ret = self.defaults[option]
543
""" Refresh the configuration. """
544
# First write out the changes
546
# Then load the configuration again
547
if sys.platform == 'win32':
548
# Windows - no dotted files
549
self.config.read([os.path.expanduser('~/olive.conf')])
551
self.config.read([os.path.expanduser('~/.olive.conf')])
554
""" Write the configuration to the appropriate files. """
555
if sys.platform == 'win32':
556
# Windows - no dotted files
557
fp = open(os.path.expanduser('~/olive.conf'), 'w')
558
self.config.write(fp)
561
fp = open(os.path.expanduser('~/.olive.conf'), 'w')
562
self.config.write(fp)
565
def get_preference(self, option, kind='str'):
566
""" Get the value of the given option.
568
:param kind: str/bool/int/float. default: str
570
if self.config.has_option('preferences', option):
572
return self.config.getboolean('preferences', option)
574
return self.config.getint('preferences', option)
575
elif kind == 'float':
576
return self.config.getfloat('preferences', option)
578
return self.config.get('preferences', option)
581
return self._get_default(option)
585
def set_preference(self, option, value):
586
""" Set the value of the given option. """
587
if self.config.has_section('preferences'):
588
self.config.set('preferences', option, value)
590
self.config.add_section('preferences')
591
self.config.set('preferences', option, value)
593
def get_bookmarks(self):
594
""" Return the list of bookmarks. """
595
bookmarks = self.config.sections()
596
if self.config.has_section('preferences'):
597
bookmarks.remove('preferences')
600
def add_bookmark(self, path):
601
""" Add bookmark. """
603
self.config.add_section(path)
604
except ConfigParser.DuplicateSectionError:
609
def get_bookmark_title(self, path):
610
""" Get bookmark title. """
612
ret = self.config.get(path, 'title')
613
except ConfigParser.NoOptionError:
618
def set_bookmark_title(self, path, title):
619
""" Set bookmark title. """
620
self.config.set(path, 'title', title)
622
def remove_bookmark(self, path):
623
""" Remove bookmark. """
624
return self.config.remove_section(path)
626
def check_status(filename):
627
""" Get the status of a file.
629
:param filename: the full path to the file
631
:return: renamed | added | removed | modified | unchanged | unknown
634
from bzrlib.delta import compare_trees
635
from bzrlib.workingtree import WorkingTree
638
tree1 = WorkingTree.open_containing(filename)[0]
639
except errors.NotBranchError:
642
branch = tree1.branch
643
tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
645
# find the relative path to the given file (needed for proper delta)
646
wtpath = tree1.basedir
649
wtsplit = wtpath.split('/')
650
fpsplit = fullpath.split('/')
651
fpcopy = fullpath.split('/')
653
if i is not len(wtsplit):
654
if item == wtsplit[i]:
657
rel = '/'.join(fpcopy)
659
delta = tree1.changes_from(tree2,
661
specific_files=[rel])
663
if len(delta.renamed):
665
elif len(delta.added):
667
elif len(delta.removed):
669
elif len(delta.modified):
671
elif len(delta.unchanged):