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
from olive.backend.info import is_branch
36
import olive.backend.errors as errors
38
# Olive GTK UI version
39
__version__ = '0.10.0'
42
""" The main Olive GTK frontend class. This is called when launching the
47
if sys.platform == 'win32':
48
self.gladefile = os.path.dirname(sys.executable) + "/share/olive/olive.glade"
50
self.gladefile = "/usr/share/olive/olive.glade"
52
if not os.path.exists(self.gladefile):
53
# Load from current directory if not installed
54
self.gladefile = "olive.glade"
56
if not os.path.exists(self.gladefile):
58
print _('Glade file cannot be found.')
61
self.toplevel = gtk.glade.XML(self.gladefile, 'window_main', 'olive-gtk')
63
self.window = self.toplevel.get_widget('window_main')
65
self.pref = OlivePreferences()
66
self.comm = OliveCommunicator(self.toplevel, self.pref)
67
handler = OliveHandler(self.gladefile, self.comm)
69
# Dictionary for signal_autoconnect
70
dic = { "on_window_main_destroy": gtk.main_quit,
71
"on_window_main_delete_event": handler.on_window_main_delete_event,
72
"on_quit_activate": handler.on_window_main_delete_event,
73
"on_about_activate": handler.on_about_activate,
74
"on_menuitem_add_files_activate": handler.on_menuitem_add_files_activate,
75
"on_menuitem_remove_file_activate": handler.on_menuitem_remove_file_activate,
76
"on_menuitem_file_make_directory_activate": handler.on_menuitem_file_make_directory_activate,
77
"on_menuitem_file_move_activate": handler.on_menuitem_file_move_activate,
78
"on_menuitem_file_rename_activate": handler.on_menuitem_file_rename_activate,
79
"on_menuitem_view_show_hidden_files_activate": handler.on_menuitem_view_show_hidden_files_activate,
80
"on_menuitem_view_refresh_activate": handler.on_menuitem_view_refresh_activate,
81
"on_menuitem_branch_initialize_activate": handler.on_menuitem_branch_initialize_activate,
82
"on_menuitem_branch_get_activate": handler.on_menuitem_branch_get_activate,
83
"on_menuitem_branch_checkout_activate": handler.on_menuitem_branch_checkout_activate,
84
"on_menuitem_branch_commit_activate": handler.on_menuitem_branch_commit_activate,
85
"on_menuitem_branch_push_activate": handler.on_menuitem_branch_push_activate,
86
"on_menuitem_branch_pull_activate": handler.on_menuitem_branch_pull_activate,
87
"on_menuitem_branch_status_activate": handler.on_menuitem_branch_status_activate,
88
"on_menuitem_branch_missing_revisions_activate": handler.on_menuitem_branch_missing_revisions_activate,
89
"on_menuitem_stats_diff_activate": handler.on_menuitem_stats_diff_activate,
90
"on_menuitem_stats_log_activate": handler.on_menuitem_stats_log_activate,
91
"on_menuitem_stats_infos_activate": handler.on_menuitem_stats_infos_activate,
92
"on_toolbutton_refresh_clicked": handler.on_menuitem_view_refresh_activate,
93
"on_toolbutton_log_clicked": handler.on_menuitem_stats_log_activate,
94
#"on_menutoolbutton_diff_clicked": handler.on_menuitem_stats_diff_activate,
95
"on_toolbutton_diff_clicked": handler.on_menuitem_stats_diff_activate,
96
"on_toolbutton_commit_clicked": handler.on_menuitem_branch_commit_activate,
97
"on_toolbutton_pull_clicked": handler.on_menuitem_branch_pull_activate,
98
"on_toolbutton_push_clicked": handler.on_menuitem_branch_push_activate,
99
"on_treeview_right_button_press_event": handler.on_treeview_right_button_press_event,
100
"on_treeview_right_row_activated": handler.on_treeview_right_row_activated,
101
"on_treeview_left_button_press_event": handler.on_treeview_left_button_press_event,
102
"on_treeview_left_row_activated": handler.on_treeview_left_row_activated }
104
# Connect the signals to the handlers
105
self.toplevel.signal_autoconnect(dic)
107
# Apply window size and position
108
width = self.pref.get_preference('window_width', 'int')
109
height = self.pref.get_preference('window_height', 'int')
110
self.window.resize(width, height)
111
x = self.pref.get_preference('window_x', 'int')
112
y = self.pref.get_preference('window_y', 'int')
113
self.window.move(x, y)
114
# Apply paned position
115
pos = self.pref.get_preference('paned_position', 'int')
116
self.comm.hpaned_main.set_position(pos)
118
# Apply menu to the toolbutton
119
#menubutton = self.toplevel.get_widget('menutoolbutton_diff')
120
#menubutton.set_menu(handler.menu.toolbar_diff)
122
# Now we can show the window
125
# Show drive selector if under Win32
126
if sys.platform == 'win32':
127
self.comm.vbox_main_right.pack_start(self.comm.combobox_drive, False, True, 0)
128
self.comm.vbox_main_right.reorder_child(self.comm.combobox_drive, 0)
129
self.comm.combobox_drive.show()
130
self.comm.gen_hard_selector()
132
# Load default data into the panels
133
self.treeview_left = self.toplevel.get_widget('treeview_left')
134
self.treeview_right = self.toplevel.get_widget('treeview_right')
139
self.comm.menuitem_view_show_hidden_files.set_active(self.pref.get_preference('dotted_files', 'bool'))
141
def _load_left(self):
142
""" Load data into the left panel. (Bookmarks) """
144
self.comm.set_busy(self.treeview_left)
147
treestore = gtk.TreeStore(str, str)
150
bookmarks = self.comm.pref.get_bookmarks()
152
# Add them to the TreeStore
153
titer = treestore.append(None, [_('Bookmarks'), None])
154
for item in bookmarks:
155
title = self.comm.pref.get_bookmark_title(item)
156
treestore.append(titer, [title, item])
158
# Create the column and add it to the TreeView
159
self.treeview_left.set_model(treestore)
160
tvcolumn_bookmark = gtk.TreeViewColumn(_('Bookmark'))
161
self.treeview_left.append_column(tvcolumn_bookmark)
164
cell = gtk.CellRendererText()
165
tvcolumn_bookmark.pack_start(cell, True)
166
tvcolumn_bookmark.add_attribute(cell, 'text', 0)
169
self.treeview_left.expand_all()
171
self.comm.set_busy(self.treeview_left, False)
173
def _load_right(self):
174
""" Load data into the right panel. (Filelist) """
175
import olive.backend.fileops as fileops
178
self.comm.set_busy(self.treeview_right)
181
liststore = gtk.ListStore(str, str, str)
186
# Fill the appropriate lists
187
path = self.comm.get_path()
188
dotted_files = self.pref.get_preference('dotted_files', 'bool')
189
for item in os.listdir(path):
190
if not dotted_files and item[0] == '.':
192
if os.path.isdir(path + '/' + item):
201
# Add'em to the ListStore
203
liststore.append([gtk.STOCK_DIRECTORY, item, ''])
206
status = fileops.status(path + '/' + item)
207
except errors.PermissionDenied:
210
if status == 'renamed':
212
elif status == 'removed':
214
elif status == 'added':
216
elif status == 'modified':
218
elif status == 'unchanged':
222
liststore.append([gtk.STOCK_FILE, item, st])
224
# Create the columns and add them to the TreeView
225
self.treeview_right.set_model(liststore)
226
tvcolumn_filename = gtk.TreeViewColumn(_('Filename'))
227
tvcolumn_status = gtk.TreeViewColumn(_('Status'))
228
self.treeview_right.append_column(tvcolumn_filename)
229
self.treeview_right.append_column(tvcolumn_status)
232
cellpb = gtk.CellRendererPixbuf()
233
cell = gtk.CellRendererText()
234
tvcolumn_filename.pack_start(cellpb, False)
235
tvcolumn_filename.pack_start(cell, True)
236
tvcolumn_filename.set_attributes(cellpb, stock_id=0)
237
tvcolumn_filename.add_attribute(cell, 'text', 1)
238
tvcolumn_status.pack_start(cell, True)
239
tvcolumn_status.add_attribute(cell, 'text', 2)
241
# Check if current directory is a branch
243
br = is_branch(self.comm.get_path())
244
except errors.PermissionDenied:
248
# Activate some items
249
self.comm.menuitem_branch_init.set_sensitive(False)
250
self.comm.menuitem_branch_get.set_sensitive(True)
251
self.comm.menuitem_branch_checkout.set_sensitive(True)
252
self.comm.menuitem_branch_pull.set_sensitive(True)
253
self.comm.menuitem_branch_push.set_sensitive(True)
254
self.comm.menuitem_branch_commit.set_sensitive(True)
255
self.comm.menuitem_branch_status.set_sensitive(True)
256
self.comm.menuitem_branch_missing.set_sensitive(True)
257
self.comm.menuitem_stats.set_sensitive(True)
258
self.comm.menuitem_add_files.set_sensitive(True)
259
self.comm.menuitem_remove_files.set_sensitive(True)
260
self.comm.menuitem_file_make_directory.set_sensitive(True)
261
self.comm.menuitem_file_rename.set_sensitive(True)
262
self.comm.menuitem_file_move.set_sensitive(True)
263
#self.comm.menutoolbutton_diff.set_sensitive(True)
264
self.comm.toolbutton_diff.set_sensitive(True)
265
self.comm.toolbutton_log.set_sensitive(True)
266
self.comm.toolbutton_commit.set_sensitive(True)
267
self.comm.toolbutton_pull.set_sensitive(True)
268
self.comm.toolbutton_push.set_sensitive(True)
270
# Deactivate some items
271
self.comm.menuitem_branch_init.set_sensitive(True)
272
self.comm.menuitem_branch_get.set_sensitive(False)
273
self.comm.menuitem_branch_checkout.set_sensitive(False)
274
self.comm.menuitem_branch_pull.set_sensitive(False)
275
self.comm.menuitem_branch_push.set_sensitive(False)
276
self.comm.menuitem_branch_commit.set_sensitive(False)
277
self.comm.menuitem_branch_status.set_sensitive(False)
278
self.comm.menuitem_branch_missing.set_sensitive(False)
279
self.comm.menuitem_stats.set_sensitive(False)
280
self.comm.menuitem_add_files.set_sensitive(False)
281
self.comm.menuitem_remove_files.set_sensitive(False)
282
self.comm.menuitem_file_make_directory.set_sensitive(False)
283
self.comm.menuitem_file_rename.set_sensitive(False)
284
self.comm.menuitem_file_move.set_sensitive(False)
285
#self.comm.menutoolbutton_diff.set_sensitive(False)
286
self.comm.toolbutton_diff.set_sensitive(False)
287
self.comm.toolbutton_log.set_sensitive(False)
288
self.comm.toolbutton_commit.set_sensitive(False)
289
self.comm.toolbutton_pull.set_sensitive(False)
290
self.comm.toolbutton_push.set_sensitive(False)
292
# set cursor to default
293
self.comm.set_busy(self.treeview_right, False)
295
class OliveCommunicator:
296
""" This class is responsible for the communication between the different
298
def __init__(self, toplevel, pref):
299
# Get glade main component
300
self.toplevel = toplevel
304
self._path = os.getcwd()
306
# Initialize the statusbar
307
self.statusbar = self.toplevel.get_widget('statusbar')
308
self.context_id = self.statusbar.get_context_id('olive')
310
# Get the main window
311
self.window_main = self.toplevel.get_widget('window_main')
313
self.hpaned_main = self.toplevel.get_widget('hpaned_main')
315
self.treeview_left = self.toplevel.get_widget('treeview_left')
316
self.treeview_right = self.toplevel.get_widget('treeview_right')
317
# Get some important menu items
318
self.menuitem_add_files = self.toplevel.get_widget('menuitem_add_files')
319
self.menuitem_remove_files = self.toplevel.get_widget('menuitem_remove_file')
320
self.menuitem_file_make_directory = self.toplevel.get_widget('menuitem_file_make_directory')
321
self.menuitem_file_rename = self.toplevel.get_widget('menuitem_file_rename')
322
self.menuitem_file_move = self.toplevel.get_widget('menuitem_file_move')
323
self.menuitem_view_show_hidden_files = self.toplevel.get_widget('menuitem_view_show_hidden_files')
324
self.menuitem_branch = self.toplevel.get_widget('menuitem_branch')
325
self.menuitem_branch_init = self.toplevel.get_widget('menuitem_branch_initialize')
326
self.menuitem_branch_get = self.toplevel.get_widget('menuitem_branch_get')
327
self.menuitem_branch_checkout = self.toplevel.get_widget('menuitem_branch_checkout')
328
self.menuitem_branch_pull = self.toplevel.get_widget('menuitem_branch_pull')
329
self.menuitem_branch_push = self.toplevel.get_widget('menuitem_branch_push')
330
self.menuitem_branch_commit = self.toplevel.get_widget('menuitem_branch_commit')
331
self.menuitem_branch_status = self.toplevel.get_widget('menuitem_branch_status')
332
self.menuitem_branch_missing = self.toplevel.get_widget('menuitem_branch_missing_revisions')
333
self.menuitem_stats = self.toplevel.get_widget('menuitem_stats')
334
self.menuitem_stats_diff = self.toplevel.get_widget('menuitem_stats_diff')
335
self.menuitem_stats_log = self.toplevel.get_widget('menuitem_stats_log')
336
# Get some toolbuttons
337
#self.menutoolbutton_diff = self.toplevel.get_widget('menutoolbutton_diff')
338
self.toolbutton_diff = self.toplevel.get_widget('toolbutton_diff')
339
self.toolbutton_log = self.toplevel.get_widget('toolbutton_log')
340
self.toolbutton_commit = self.toplevel.get_widget('toolbutton_commit')
341
self.toolbutton_pull = self.toplevel.get_widget('toolbutton_pull')
342
self.toolbutton_push = self.toplevel.get_widget('toolbutton_push')
343
# Get the drive selector
344
self.combobox_drive = gtk.combo_box_new_text()
345
self.combobox_drive.connect("changed", self._refresh_drives)
347
self.vbox_main_right = self.toplevel.get_widget('vbox_main_right')
349
def set_path(self, path):
350
""" Set the current path while browsing the directories. """
354
""" Get the current path. """
357
def get_selected_right(self):
358
""" Get the selected filename. """
359
treeselection = self.treeview_right.get_selection()
360
(model, iter) = treeselection.get_selected()
365
return model.get_value(iter, 1)
367
def get_selected_left(self):
368
""" Get the selected bookmark. """
369
treeselection = self.treeview_left.get_selection()
370
(model, iter) = treeselection.get_selected()
375
return model.get_value(iter, 1)
377
def set_statusbar(self, message):
378
""" Set the statusbar message. """
379
self.statusbar.push(self.context_id, message)
381
def clear_statusbar(self):
382
""" Clean the last message from the statusbar. """
383
self.statusbar.pop(self.context_id)
385
def refresh_left(self):
386
""" Refresh the bookmark list. """
388
self.set_busy(self.treeview_left)
390
# Get TreeStore and clear it
391
treestore = self.treeview_left.get_model()
395
bookmarks = self.pref.get_bookmarks()
397
# Add them to the TreeStore
398
titer = treestore.append(None, [_('Bookmarks'), None])
399
for item in bookmarks:
400
title = self.pref.get_bookmark_title(item)
401
treestore.append(titer, [title, item])
403
# Add the TreeStore to the TreeView
404
self.treeview_left.set_model(treestore)
407
self.treeview_left.expand_all()
409
self.set_busy(self.treeview_left, False)
411
def refresh_right(self, path=None):
412
""" Refresh the file list. """
413
import olive.backend.fileops as fileops
415
self.set_busy(self.treeview_right)
418
path = self.get_path()
420
# A workaround for double-clicking Bookmarks
421
if not os.path.exists(path):
422
self.set_busy(self.treeview_right, False)
425
# Get ListStore and clear it
426
liststore = self.treeview_right.get_model()
432
# Fill the appropriate lists
433
dotted_files = self.pref.get_preference('dotted_files', 'bool')
434
for item in os.listdir(path):
435
if not dotted_files and item[0] == '.':
437
if os.path.isdir(path + '/' + item):
446
# Add'em to the ListStore
448
liststore.append([gtk.STOCK_DIRECTORY, item, ''])
451
status = fileops.status(path + '/' + item)
452
except errors.PermissionDenied:
455
if status == 'renamed':
457
elif status == 'removed':
459
elif status == 'added':
461
elif status == 'modified':
463
elif status == 'unchanged':
467
liststore.append([gtk.STOCK_FILE, item, st])
469
# Add the ListStore to the TreeView
470
self.treeview_right.set_model(liststore)
472
# Check if current directory is a branch
474
br = is_branch(self.get_path())
475
except errors.PermissionDenied:
479
# Activate some items
480
self.menuitem_branch_init.set_sensitive(False)
481
self.menuitem_branch_get.set_sensitive(True)
482
self.menuitem_branch_checkout.set_sensitive(True)
483
self.menuitem_branch_pull.set_sensitive(True)
484
self.menuitem_branch_push.set_sensitive(True)
485
self.menuitem_branch_commit.set_sensitive(True)
486
self.menuitem_branch_status.set_sensitive(True)
487
self.menuitem_branch_missing.set_sensitive(True)
488
self.menuitem_stats.set_sensitive(True)
489
self.menuitem_add_files.set_sensitive(True)
490
self.menuitem_remove_files.set_sensitive(True)
491
self.menuitem_file_make_directory.set_sensitive(True)
492
self.menuitem_file_rename.set_sensitive(True)
493
self.menuitem_file_move.set_sensitive(True)
494
#self.menutoolbutton_diff.set_sensitive(True)
495
self.toolbutton_diff.set_sensitive(True)
496
self.toolbutton_log.set_sensitive(True)
497
self.toolbutton_commit.set_sensitive(True)
498
self.toolbutton_pull.set_sensitive(True)
499
self.toolbutton_push.set_sensitive(True)
501
# Deactivate some items
502
self.menuitem_branch_init.set_sensitive(True)
503
self.menuitem_branch_get.set_sensitive(False)
504
self.menuitem_branch_checkout.set_sensitive(False)
505
self.menuitem_branch_pull.set_sensitive(False)
506
self.menuitem_branch_push.set_sensitive(False)
507
self.menuitem_branch_commit.set_sensitive(False)
508
self.menuitem_branch_status.set_sensitive(False)
509
self.menuitem_branch_missing.set_sensitive(False)
510
self.menuitem_stats.set_sensitive(False)
511
self.menuitem_add_files.set_sensitive(False)
512
self.menuitem_remove_files.set_sensitive(False)
513
self.menuitem_file_make_directory.set_sensitive(False)
514
self.menuitem_file_rename.set_sensitive(False)
515
self.menuitem_file_move.set_sensitive(False)
516
#self.menutoolbutton_diff.set_sensitive(False)
517
self.toolbutton_diff.set_sensitive(False)
518
self.toolbutton_log.set_sensitive(False)
519
self.toolbutton_commit.set_sensitive(False)
520
self.toolbutton_pull.set_sensitive(False)
521
self.toolbutton_push.set_sensitive(False)
523
self.set_busy(self.treeview_right, False)
525
def set_busy(self, widget, busy=True):
527
widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
529
widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
531
gtk.main_iteration(0)
533
def _harddisks(self):
534
""" Returns hard drive letters under Win32. """
539
if sys.platform == 'win32':
540
print "pyWin32 modules needed to run Olive on Win32."
546
for drive in string.ascii_uppercase:
547
if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED:
548
driveletters.append(drive+':')
551
def gen_hard_selector(self):
552
""" Generate the hard drive selector under Win32. """
553
drives = self._harddisks()
555
self.combobox_drive.append_text(drive)
557
def _refresh_drives(self, combobox):
558
model = combobox.get_model()
559
active = combobox.get_active()
561
drive = model[active][0]
562
self.refresh_right(drive + '\\')
564
class OlivePreferences:
565
""" A class which handles Olive's preferences. """
567
""" Initialize the Preferences class. """
568
# Some default options
569
self.defaults = { 'strict_commit' : False,
570
'dotted_files' : False,
571
'window_width' : 700,
572
'window_height' : 400,
575
'paned_position': 200 }
577
# Create a config parser object
578
self.config = ConfigParser.RawConfigParser()
580
# Load the configuration
581
if sys.platform == 'win32':
582
# Windows - no dotted files
583
self.config.read([os.path.expanduser('~/olive.conf')])
585
self.config.read([os.path.expanduser('~/.olive.conf')])
587
def _get_default(self, option):
588
""" Get the default option for a preference. """
590
ret = self.defaults[option]
597
""" Refresh the configuration. """
598
# First write out the changes
600
# Then load the configuration again
601
if sys.platform == 'win32':
602
# Windows - no dotted files
603
self.config.read([os.path.expanduser('~/olive.conf')])
605
self.config.read([os.path.expanduser('~/.olive.conf')])
608
""" Write the configuration to the appropriate files. """
609
if sys.platform == 'win32':
610
# Windows - no dotted files
611
fp = open(os.path.expanduser('~/olive.conf'), 'w')
612
self.config.write(fp)
615
fp = open(os.path.expanduser('~/.olive.conf'), 'w')
616
self.config.write(fp)
619
def get_preference(self, option, kind='str'):
620
""" Get the value of the given option.
622
:param kind: str/bool/int/float. default: str
624
if self.config.has_option('preferences', option):
626
return self.config.getboolean('preferences', option)
628
return self.config.getint('preferences', option)
629
elif kind == 'float':
630
return self.config.getfloat('preferences', option)
632
return self.config.get('preferences', option)
635
return self._get_default(option)
639
def set_preference(self, option, value):
640
""" Set the value of the given option. """
641
if self.config.has_section('preferences'):
642
self.config.set('preferences', option, value)
644
self.config.add_section('preferences')
645
self.config.set('preferences', option, value)
647
def get_bookmarks(self):
648
""" Return the list of bookmarks. """
649
bookmarks = self.config.sections()
650
if self.config.has_section('preferences'):
651
bookmarks.remove('preferences')
654
def add_bookmark(self, path):
655
""" Add bookmark. """
657
self.config.add_section(path)
658
except ConfigParser.DuplicateSectionError:
663
def get_bookmark_title(self, path):
664
""" Get bookmark title. """
666
ret = self.config.get(path, 'title')
667
except ConfigParser.NoOptionError:
672
def set_bookmark_title(self, path, title):
673
""" Set bookmark title. """
674
self.config.set(path, 'title', title)
676
def remove_bookmark(self, path):
677
""" Remove bookmark. """
678
return self.config.remove_section(path)