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.11.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
124
# Show drive selector if under Win32
125
if sys.platform == 'win32':
126
self.comm.vbox_main_right.pack_start(self.comm.combobox_drive, False, True, 0)
127
self.comm.vbox_main_right.reorder_child(self.comm.combobox_drive, 0)
128
self.comm.combobox_drive.show()
129
self.comm.gen_hard_selector()
131
# Load default data into the panels
132
self.treeview_left = self.toplevel.get_widget('treeview_left')
133
self.treeview_right = self.toplevel.get_widget('treeview_right')
138
self.comm.menuitem_view_show_hidden_files.set_active(self.pref.get_preference('dotted_files', 'bool'))
140
def _load_left(self):
141
""" Load data into the left panel. (Bookmarks) """
143
self.comm.set_busy(self.treeview_left)
146
treestore = gtk.TreeStore(str, str)
149
bookmarks = self.comm.pref.get_bookmarks()
151
# Add them to the TreeStore
152
titer = treestore.append(None, [_('Bookmarks'), None])
153
for item in bookmarks:
154
title = self.comm.pref.get_bookmark_title(item)
155
treestore.append(titer, [title, item])
157
# Create the column and add it to the TreeView
158
self.treeview_left.set_model(treestore)
159
tvcolumn_bookmark = gtk.TreeViewColumn(_('Bookmark'))
160
self.treeview_left.append_column(tvcolumn_bookmark)
163
cell = gtk.CellRendererText()
164
tvcolumn_bookmark.pack_start(cell, True)
165
tvcolumn_bookmark.add_attribute(cell, 'text', 0)
168
self.treeview_left.expand_all()
170
self.comm.set_busy(self.treeview_left, False)
172
def _load_right(self):
173
""" Load data into the right panel. (Filelist) """
174
from bzrlib.workingtree import WorkingTree
177
self.comm.set_busy(self.treeview_right)
180
liststore = gtk.ListStore(str, str, str)
185
# Fill the appropriate lists
186
path = self.comm.get_path()
187
dotted_files = self.pref.get_preference('dotted_files', 'bool')
188
for item in os.listdir(path):
189
if not dotted_files and item[0] == '.':
191
if os.path.isdir(path + os.sep + item):
200
# Try to open the working tree
203
tree1 = WorkingTree.open_containing(path)[0]
204
except errors.NotBranchError:
206
except errors.PermissionDenied:
207
print "DEBUG: permission denied."
210
branch = tree1.branch
211
tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
213
delta = tree1.changes_from(tree2, want_unchanged=True)
215
# Add'em to the ListStore
217
liststore.append([gtk.STOCK_DIRECTORY, item, ''])
221
filename = tree1.relpath(path + os.sep + item)
223
for rpath, id, kind, text_modified, meta_modified in delta.renamed:
224
if rpath == filename:
226
for rpath, id, kind in delta.added:
227
if rpath == filename:
229
for rpath, id, kind, text_modified, meta_modified in delta.removed:
230
if rpath == filename:
232
for rpath, id, kind, text_modified, meta_modified in delta.modified:
233
if rpath == filename:
235
for rpath, id, kind in delta.unchanged:
236
if rpath == filename:
240
# status = fileops.status(path + os.sep + item)
241
#except errors.PermissionDenied:
244
if status == 'renamed':
246
elif status == 'removed':
248
elif status == 'added':
250
elif status == 'modified':
252
elif status == 'unchanged':
256
liststore.append([gtk.STOCK_FILE, item, st])
258
# Create the columns and add them to the TreeView
259
self.treeview_right.set_model(liststore)
260
tvcolumn_filename = gtk.TreeViewColumn(_('Filename'))
261
tvcolumn_status = gtk.TreeViewColumn(_('Status'))
262
self.treeview_right.append_column(tvcolumn_filename)
263
self.treeview_right.append_column(tvcolumn_status)
266
cellpb = gtk.CellRendererPixbuf()
267
cell = gtk.CellRendererText()
268
tvcolumn_filename.pack_start(cellpb, False)
269
tvcolumn_filename.pack_start(cell, True)
270
tvcolumn_filename.set_attributes(cellpb, stock_id=0)
271
tvcolumn_filename.add_attribute(cell, 'text', 1)
272
tvcolumn_status.pack_start(cell, True)
273
tvcolumn_status.add_attribute(cell, 'text', 2)
275
# Check if current directory is a branch
277
# Activate some items
278
self.comm.menuitem_branch_init.set_sensitive(False)
279
self.comm.menuitem_branch_get.set_sensitive(True)
280
self.comm.menuitem_branch_checkout.set_sensitive(True)
281
self.comm.menuitem_branch_pull.set_sensitive(True)
282
self.comm.menuitem_branch_push.set_sensitive(True)
283
self.comm.menuitem_branch_commit.set_sensitive(True)
284
self.comm.menuitem_branch_status.set_sensitive(True)
285
self.comm.menuitem_branch_missing.set_sensitive(True)
286
self.comm.menuitem_stats.set_sensitive(True)
287
self.comm.menuitem_add_files.set_sensitive(True)
288
self.comm.menuitem_remove_files.set_sensitive(True)
289
self.comm.menuitem_file_make_directory.set_sensitive(True)
290
self.comm.menuitem_file_rename.set_sensitive(True)
291
self.comm.menuitem_file_move.set_sensitive(True)
292
#self.comm.menutoolbutton_diff.set_sensitive(True)
293
self.comm.toolbutton_diff.set_sensitive(True)
294
self.comm.toolbutton_log.set_sensitive(True)
295
self.comm.toolbutton_commit.set_sensitive(True)
296
self.comm.toolbutton_pull.set_sensitive(True)
297
self.comm.toolbutton_push.set_sensitive(True)
299
# Deactivate some items
300
self.comm.menuitem_branch_init.set_sensitive(True)
301
self.comm.menuitem_branch_get.set_sensitive(False)
302
self.comm.menuitem_branch_checkout.set_sensitive(False)
303
self.comm.menuitem_branch_pull.set_sensitive(False)
304
self.comm.menuitem_branch_push.set_sensitive(False)
305
self.comm.menuitem_branch_commit.set_sensitive(False)
306
self.comm.menuitem_branch_status.set_sensitive(False)
307
self.comm.menuitem_branch_missing.set_sensitive(False)
308
self.comm.menuitem_stats.set_sensitive(False)
309
self.comm.menuitem_add_files.set_sensitive(False)
310
self.comm.menuitem_remove_files.set_sensitive(False)
311
self.comm.menuitem_file_make_directory.set_sensitive(False)
312
self.comm.menuitem_file_rename.set_sensitive(False)
313
self.comm.menuitem_file_move.set_sensitive(False)
314
#self.comm.menutoolbutton_diff.set_sensitive(False)
315
self.comm.toolbutton_diff.set_sensitive(False)
316
self.comm.toolbutton_log.set_sensitive(False)
317
self.comm.toolbutton_commit.set_sensitive(False)
318
self.comm.toolbutton_pull.set_sensitive(False)
319
self.comm.toolbutton_push.set_sensitive(False)
321
# set cursor to default
322
self.comm.set_busy(self.treeview_right, False)
324
class OliveCommunicator:
325
""" This class is responsible for the communication between the different
327
def __init__(self, toplevel, pref):
328
# Get glade main component
329
self.toplevel = toplevel
333
self._path = os.getcwd()
335
# Initialize the statusbar
336
self.statusbar = self.toplevel.get_widget('statusbar')
337
self.context_id = self.statusbar.get_context_id('olive')
339
# Get the main window
340
self.window_main = self.toplevel.get_widget('window_main')
342
self.hpaned_main = self.toplevel.get_widget('hpaned_main')
344
self.treeview_left = self.toplevel.get_widget('treeview_left')
345
self.treeview_right = self.toplevel.get_widget('treeview_right')
346
# Get some important menu items
347
self.menuitem_add_files = self.toplevel.get_widget('menuitem_add_files')
348
self.menuitem_remove_files = self.toplevel.get_widget('menuitem_remove_file')
349
self.menuitem_file_make_directory = self.toplevel.get_widget('menuitem_file_make_directory')
350
self.menuitem_file_rename = self.toplevel.get_widget('menuitem_file_rename')
351
self.menuitem_file_move = self.toplevel.get_widget('menuitem_file_move')
352
self.menuitem_view_show_hidden_files = self.toplevel.get_widget('menuitem_view_show_hidden_files')
353
self.menuitem_branch = self.toplevel.get_widget('menuitem_branch')
354
self.menuitem_branch_init = self.toplevel.get_widget('menuitem_branch_initialize')
355
self.menuitem_branch_get = self.toplevel.get_widget('menuitem_branch_get')
356
self.menuitem_branch_checkout = self.toplevel.get_widget('menuitem_branch_checkout')
357
self.menuitem_branch_pull = self.toplevel.get_widget('menuitem_branch_pull')
358
self.menuitem_branch_push = self.toplevel.get_widget('menuitem_branch_push')
359
self.menuitem_branch_commit = self.toplevel.get_widget('menuitem_branch_commit')
360
self.menuitem_branch_status = self.toplevel.get_widget('menuitem_branch_status')
361
self.menuitem_branch_missing = self.toplevel.get_widget('menuitem_branch_missing_revisions')
362
self.menuitem_stats = self.toplevel.get_widget('menuitem_stats')
363
self.menuitem_stats_diff = self.toplevel.get_widget('menuitem_stats_diff')
364
self.menuitem_stats_log = self.toplevel.get_widget('menuitem_stats_log')
365
# Get some toolbuttons
366
#self.menutoolbutton_diff = self.toplevel.get_widget('menutoolbutton_diff')
367
self.toolbutton_diff = self.toplevel.get_widget('toolbutton_diff')
368
self.toolbutton_log = self.toplevel.get_widget('toolbutton_log')
369
self.toolbutton_commit = self.toplevel.get_widget('toolbutton_commit')
370
self.toolbutton_pull = self.toplevel.get_widget('toolbutton_pull')
371
self.toolbutton_push = self.toplevel.get_widget('toolbutton_push')
372
# Get the drive selector
373
self.combobox_drive = gtk.combo_box_new_text()
374
self.combobox_drive.connect("changed", self._refresh_drives)
376
self.vbox_main_right = self.toplevel.get_widget('vbox_main_right')
378
def set_path(self, path):
379
""" Set the current path while browsing the directories. """
383
""" Get the current path. """
386
def get_selected_right(self):
387
""" Get the selected filename. """
388
treeselection = self.treeview_right.get_selection()
389
(model, iter) = treeselection.get_selected()
394
return model.get_value(iter, 1)
396
def get_selected_left(self):
397
""" Get the selected bookmark. """
398
treeselection = self.treeview_left.get_selection()
399
(model, iter) = treeselection.get_selected()
404
return model.get_value(iter, 1)
406
def set_statusbar(self, message):
407
""" Set the statusbar message. """
408
self.statusbar.push(self.context_id, message)
410
def clear_statusbar(self):
411
""" Clean the last message from the statusbar. """
412
self.statusbar.pop(self.context_id)
414
def refresh_left(self):
415
""" Refresh the bookmark list. """
417
self.set_busy(self.treeview_left)
419
# Get TreeStore and clear it
420
treestore = self.treeview_left.get_model()
424
bookmarks = self.pref.get_bookmarks()
426
# Add them to the TreeStore
427
titer = treestore.append(None, [_('Bookmarks'), None])
428
for item in bookmarks:
429
title = self.pref.get_bookmark_title(item)
430
treestore.append(titer, [title, item])
432
# Add the TreeStore to the TreeView
433
self.treeview_left.set_model(treestore)
436
self.treeview_left.expand_all()
438
self.set_busy(self.treeview_left, False)
440
def refresh_right(self, path=None):
441
""" Refresh the file list. """
442
from bzrlib.workingtree import WorkingTree
444
self.set_busy(self.treeview_right)
447
path = self.get_path()
449
# A workaround for double-clicking Bookmarks
450
if not os.path.exists(path):
451
self.set_busy(self.treeview_right, False)
454
# Get ListStore and clear it
455
liststore = self.treeview_right.get_model()
461
# Fill the appropriate lists
462
dotted_files = self.pref.get_preference('dotted_files', 'bool')
463
for item in os.listdir(path):
464
if not dotted_files and item[0] == '.':
466
if os.path.isdir(path + os.sep + item):
475
# Try to open the working tree
478
tree1 = WorkingTree.open_containing(path)[0]
479
except errors.NotBranchError:
481
except errors.PermissionDenied:
482
print "DEBUG: permission denied."
485
branch = tree1.branch
486
tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
488
delta = tree1.changes_from(tree2, want_unchanged=True)
490
# Add'em to the ListStore
492
liststore.append([gtk.STOCK_DIRECTORY, item, ''])
496
filename = tree1.relpath(path + os.sep + item)
498
for rpath, id, kind, text_modified, meta_modified in delta.renamed:
499
if rpath == filename:
501
for rpath, id, kind in delta.added:
502
if rpath == filename:
504
for rpath, id, kind, text_modified, meta_modified in delta.removed:
505
if rpath == filename:
507
for rpath, id, kind, text_modified, meta_modified in delta.modified:
508
if rpath == filename:
510
for rpath, id, kind in delta.unchanged:
511
if rpath == filename:
515
# status = fileops.status(path + os.sep + item)
516
#except errors.PermissionDenied:
519
if status == 'renamed':
521
elif status == 'removed':
523
elif status == 'added':
525
elif status == 'modified':
527
elif status == 'unchanged':
531
liststore.append([gtk.STOCK_FILE, item, st])
533
# Add the ListStore to the TreeView
534
self.treeview_right.set_model(liststore)
536
# Check if current directory is a branch
538
# Activate some items
539
self.menuitem_branch_init.set_sensitive(False)
540
self.menuitem_branch_get.set_sensitive(True)
541
self.menuitem_branch_checkout.set_sensitive(True)
542
self.menuitem_branch_pull.set_sensitive(True)
543
self.menuitem_branch_push.set_sensitive(True)
544
self.menuitem_branch_commit.set_sensitive(True)
545
self.menuitem_branch_status.set_sensitive(True)
546
self.menuitem_branch_missing.set_sensitive(True)
547
self.menuitem_stats.set_sensitive(True)
548
self.menuitem_add_files.set_sensitive(True)
549
self.menuitem_remove_files.set_sensitive(True)
550
self.menuitem_file_make_directory.set_sensitive(True)
551
self.menuitem_file_rename.set_sensitive(True)
552
self.menuitem_file_move.set_sensitive(True)
553
#self.menutoolbutton_diff.set_sensitive(True)
554
self.toolbutton_diff.set_sensitive(True)
555
self.toolbutton_log.set_sensitive(True)
556
self.toolbutton_commit.set_sensitive(True)
557
self.toolbutton_pull.set_sensitive(True)
558
self.toolbutton_push.set_sensitive(True)
560
# Deactivate some items
561
self.menuitem_branch_init.set_sensitive(True)
562
self.menuitem_branch_get.set_sensitive(False)
563
self.menuitem_branch_checkout.set_sensitive(False)
564
self.menuitem_branch_pull.set_sensitive(False)
565
self.menuitem_branch_push.set_sensitive(False)
566
self.menuitem_branch_commit.set_sensitive(False)
567
self.menuitem_branch_status.set_sensitive(False)
568
self.menuitem_branch_missing.set_sensitive(False)
569
self.menuitem_stats.set_sensitive(False)
570
self.menuitem_add_files.set_sensitive(False)
571
self.menuitem_remove_files.set_sensitive(False)
572
self.menuitem_file_make_directory.set_sensitive(False)
573
self.menuitem_file_rename.set_sensitive(False)
574
self.menuitem_file_move.set_sensitive(False)
575
#self.menutoolbutton_diff.set_sensitive(False)
576
self.toolbutton_diff.set_sensitive(False)
577
self.toolbutton_log.set_sensitive(False)
578
self.toolbutton_commit.set_sensitive(False)
579
self.toolbutton_pull.set_sensitive(False)
580
self.toolbutton_push.set_sensitive(False)
582
self.set_busy(self.treeview_right, False)
584
def set_busy(self, widget, busy=True):
586
widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
588
widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
590
gtk.main_iteration(0)
592
def _harddisks(self):
593
""" Returns hard drive letters under Win32. """
598
if sys.platform == 'win32':
599
print "pyWin32 modules needed to run Olive on Win32."
605
for drive in string.ascii_uppercase:
606
if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED:
607
driveletters.append(drive+':')
610
def gen_hard_selector(self):
611
""" Generate the hard drive selector under Win32. """
612
drives = self._harddisks()
614
self.combobox_drive.append_text(drive)
616
def _refresh_drives(self, combobox):
617
model = combobox.get_model()
618
active = combobox.get_active()
620
drive = model[active][0]
621
self.refresh_right(drive + '\\')
623
class OlivePreferences:
624
""" A class which handles Olive's preferences. """
626
""" Initialize the Preferences class. """
627
# Some default options
628
self.defaults = { 'strict_commit' : False,
629
'dotted_files' : False,
630
'window_width' : 700,
631
'window_height' : 400,
634
'paned_position': 200 }
636
# Create a config parser object
637
self.config = ConfigParser.RawConfigParser()
639
# Load the configuration
640
if sys.platform == 'win32':
641
# Windows - no dotted files
642
self.config.read([os.path.expanduser('~/olive.conf')])
644
self.config.read([os.path.expanduser('~/.olive.conf')])
646
def _get_default(self, option):
647
""" Get the default option for a preference. """
649
ret = self.defaults[option]
656
""" Refresh the configuration. """
657
# First write out the changes
659
# Then load the configuration again
660
if sys.platform == 'win32':
661
# Windows - no dotted files
662
self.config.read([os.path.expanduser('~/olive.conf')])
664
self.config.read([os.path.expanduser('~/.olive.conf')])
667
""" Write the configuration to the appropriate files. """
668
if sys.platform == 'win32':
669
# Windows - no dotted files
670
fp = open(os.path.expanduser('~/olive.conf'), 'w')
671
self.config.write(fp)
674
fp = open(os.path.expanduser('~/.olive.conf'), 'w')
675
self.config.write(fp)
678
def get_preference(self, option, kind='str'):
679
""" Get the value of the given option.
681
:param kind: str/bool/int/float. default: str
683
if self.config.has_option('preferences', option):
685
return self.config.getboolean('preferences', option)
687
return self.config.getint('preferences', option)
688
elif kind == 'float':
689
return self.config.getfloat('preferences', option)
691
return self.config.get('preferences', option)
694
return self._get_default(option)
698
def set_preference(self, option, value):
699
""" Set the value of the given option. """
700
if self.config.has_section('preferences'):
701
self.config.set('preferences', option, value)
703
self.config.add_section('preferences')
704
self.config.set('preferences', option, value)
706
def get_bookmarks(self):
707
""" Return the list of bookmarks. """
708
bookmarks = self.config.sections()
709
if self.config.has_section('preferences'):
710
bookmarks.remove('preferences')
713
def add_bookmark(self, path):
714
""" Add bookmark. """
716
self.config.add_section(path)
717
except ConfigParser.DuplicateSectionError:
722
def get_bookmark_title(self, path):
723
""" Get bookmark title. """
725
ret = self.config.get(path, 'title')
726
except ConfigParser.NoOptionError:
731
def set_bookmark_title(self, path, title):
732
""" Set bookmark title. """
733
self.config.set(path, 'title', title)
735
def remove_bookmark(self, path):
736
""" Remove bookmark. """
737
return self.config.remove_section(path)