24
from zim import NotebookInterface, NotebookLookupError, ZimCmd
25
from zim.main import get_zim_application
25
26
from zim.fs import File, Dir, normalize_win32_share
26
27
from zim.errors import Error, TrashNotSupportedError, TrashCancelledError
27
from zim.signals import DelayedCallback
28
from zim.notebook import Path, Page
28
from zim.environ import environ
29
from zim.signals import DelayedCallback, SignalHandler
30
from zim.notebook import Notebook, NotebookInfo, Path, Page, build_notebook
29
31
from zim.stores import encode_filename
30
32
from zim.index import LINK_DIR_BACKWARD
31
from zim.config import data_file, config_file, data_dirs, ListDict, value_is_coord, set_environ
33
from zim.config import data_file, data_dirs, ConfigDict, value_is_coord, ConfigManager
34
from zim.plugins import PluginManager
32
35
from zim.parsing import url_encode, url_decode, URL_ENCODE_DATA, is_win32_share_re, is_url_re, is_uri_re
33
36
from zim.history import History, HistoryPath
34
37
from zim.templates import list_templates, get_template
88
91
('open_document_root', 'gtk-open', _('Open _Document Root'), '', '', True), # T: Menu item
89
92
('open_document_folder', 'gtk-open', _('Open _Document Folder'), '', '', True), # T: Menu item
90
93
('attach_file', 'zim-attachment', _('Attach _File'), '', _('Attach external file'), False), # T: Menu item
91
('show_clean_notebook', None, _('_Cleanup Attachments'), '', '', False), # T: Menu item
92
94
('edit_page_source', 'gtk-edit', _('Edit _Source'), '', '', False), # T: Menu item
93
95
('show_server_gui', None, _('Start _Web Server'), '', '', True), # T: Menu item
94
96
('reload_index', None, _('Update Index'), '', '', False), # T: Menu item
270
'''Re-entrant lock that keeps a stack count
272
The lock will increase a counter each time L{increment()} is called
273
and decrease the counter each time L{decrement()} is called. When
274
the counter is non-zero the object will evaluate C{True}. This is
275
used to e.g. handle recursive calls to the same handler while
276
waiting for user input. Because of the counter the lock only is
277
freed when the outer most wrapper calls L{decrement()}.
280
__slots__ = ('count',)
285
def __nonzero__(self):
286
return self.count > 0
289
'''Increase counter'''
293
'''Decrease counter'''
295
raise AssertionError, 'BUG: RLock count can not go below zero'
299
class GtkInterface(NotebookInterface):
271
class PageHasUnSavedChangesError(Error):
272
'''Exception raised when page could not be saved'''
274
msg = _('Page has un-saved changes')
275
# T: Error description
278
class WindowManager(object):
281
for window in gtk.window_list_toplevels():
282
if isinstance(window, Window): # implies a zim object
286
assert False, 'TODO pick window to present'
290
class GtkInterface(gobject.GObject):
300
291
'''Main class for the zim Gtk interface. This object wraps a single
301
292
notebook and provides actions to manipulate and access this notebook.
318
309
B{NOTE:} the L{plugin<zim.plugins>} base class has it's own wrappers
319
310
for these things. Plugin writers should look there first.
321
@ivar preferences: L{ConfigDict} for global preferences, maps to
312
@ivar preferences: L{ConfigSectionsDict} for global preferences, maps to
322
313
the X{preferences.conf} config file.
323
@ivar uistate: L{ConfigDict} for current state of the user interface,
314
@ivar uistate: L{ConfigSectionsDict} for current state of the user interface,
324
315
maps to the X{state.conf} config file per notebook.
325
316
@ivar notebook: The L{Notebook} object
326
317
@ivar page: The L{Page} object for the current page in the
364
353
'open-page': (gobject.SIGNAL_RUN_LAST, None, (object, object)),
365
354
'close-page': (gobject.SIGNAL_RUN_LAST, None, (object, bool)),
366
'new-window': (gobject.SIGNAL_RUN_LAST, None, (object,)),
367
355
'readonly-changed': (gobject.SIGNAL_RUN_LAST, None, ()),
368
356
'quit': (gobject.SIGNAL_RUN_LAST, None, ()),
369
357
'start-index-update': (gobject.SIGNAL_RUN_LAST, None, ()),
370
358
'end-index-update': (gobject.SIGNAL_RUN_LAST, None, ()),
373
ui_type = 'gtk' #: UI type - typically checked by plugins instead of class
375
def __init__(self, notebook=None, page=None,
361
def __init__(self, notebook, page=None, config=None,
376
362
fullscreen=False, geometry=None):
365
@param config: a C{ConfigManager} object
379
366
@param notebook: a L{Notebook} object
380
367
@param page: a L{Path} object
381
368
@param fullscreen: if C{True} open fullscreen
382
369
@param geometry: window geometry as string in format "C{WxH+X+Y}"
384
assert not (page and notebook is None), 'BUG: can not give page while notebook is None'
385
self._finalize_ui = False
386
# initalize this one early, before any call to load_plugin can happen
388
NotebookInterface.__init__(self)
389
self.preferences_register = ListDict()
371
gobject.GObject.__init__(self)
373
if isinstance(notebook, basestring): # deal with IPC call
374
info = NotebookInfo(notebook)
375
notebook, x = build_notebook(info)
376
elif not isinstance(notebook, Notebook):
377
notebook, x = build_notebook(notebook)
379
logger.debug('Opening notebook: %s', notebook)
380
self.notebook = notebook
382
self.config = config or ConfigManager(profile=notebook.profile)
383
self.preferences = self.config.get_config_dict('<profile>/preferences.conf') ### preferences attrib should just be one section
384
self.preferences['General'].setdefault('plugins',
385
['calendar', 'insertsymbol', 'printtobrowser', 'versioncontrol'])
387
self.plugins = PluginManager(self.config)
388
self.plugins.extend(notebook.index)
389
self.plugins.extend(notebook)
391
self.preferences_register = ConfigDict()
391
393
self._path_context = None
392
394
self.history = None
393
self._autosave_lock = RLock()
394
# used to prevent autosave triggering while we are
395
# doing a (async) save, or when we have an error during
397
395
self.readonly = False
398
396
self.hideonclose = False
400
397
self.url_handlers = {}
399
self._autosave_thread = None
402
401
logger.debug('Gtk version is %s' % str(gtk.gtk_version))
403
402
logger.debug('Pygtk version is %s' % str(gtk.pygtk_version))
455
454
self._custom_tool_iconfactory = None
456
455
self.load_custom_tools()
457
self.preferences.connect('changed', self.do_preferences_changed)
458
458
self.do_preferences_changed()
460
# Deal with commandline arguments for notebook and page
462
self.open_notebook(notebook)
463
# If it fails here an error dialog is shown and main()
464
# will prompt the notebook list
466
if self.notebook and page:
467
if isinstance(page, basestring):
468
page = self.notebook.resolve_path(page)
472
assert isinstance(page, Path)
460
self._init_notebook(self.notebook)
461
if page and isinstance(page, basestring): # IPC call
462
page = self.notebook.resolve_path(page)
464
self._first_page = page # XXX HACK - if we call open_page here, plugins are not yet initialized
466
def _init_notebook(self, notebook):
467
if notebook.cache_dir:
468
# may not exist during tests
469
from zim.config import INIConfigFile
470
self.uistate = INIConfigFile(
471
notebook.cache_dir.file('state.conf') )
475
pass # Will check default in main()
477
def load_plugin(self, name):
478
plugin = NotebookInterface.load_plugin(self, name)
479
if plugin and self._finalize_ui:
480
plugin.finalize_ui(self)
473
from zim.config import SectionedConfigDict
474
self.uistate = SectionedConfigDict()
476
def move_away(o, path):
477
if path == self.page or self.page.ischild(path):
478
self.open_page_back() \
479
or self.open_page_parent \
480
or self.open_page_home
482
def follow(o, path, newpath, update_links):
483
if self.page == path:
484
self.open_page(newpath)
485
elif self.page.ischild(path):
486
newpath = newpath + self.page.relname(path)
487
newpath = Path(newpath.name) # IndexPath -> Path
488
self.open_page(newpath)
490
def save_page(o, p, *a):
491
page = self.mainwindow.pageview.get_page()
492
if p == page and page.modified:
495
self.history = History(notebook, self.uistate)
496
self.on_notebook_properties_changed(notebook)
497
notebook.connect('properties-changed', self.on_notebook_properties_changed)
498
notebook.connect('delete-page', save_page) # before action
499
notebook.connect('deleted-page', move_away) # after action
500
notebook.connect('move-page', save_page) # before action
501
notebook.connect('moved-page', follow) # after action
503
def new_child(index, indexpath):
504
if self.page and indexpath.ischild(self.page):
505
child = self.actiongroup.get_action('open_page_child')
506
child.set_sensitive(True)
508
def child_deleted(index, indexpath):
509
if self.page and indexpath.ischild(self.page):
510
ourpath = index.lookup_path(self.page)
511
child = self.actiongroup.get_action('open_page_child')
512
child.set_sensitive(ourpath.haschildren)
514
notebook.index.connect('page-inserted', new_child)
515
notebook.index.connect('page-deleted', child_deleted)
517
# Start a lightweight background check of the index
518
self.notebook.index.update_async()
520
self.set_readonly(notebook.readonly)
522
def on_notebook_properties_changed(self, notebook):
523
self.config.set_profile(notebook.profile)
525
has_doc_root = not notebook.document_root is None
526
for action in ('open_document_root', 'open_document_folder'):
527
action = self.actiongroup.get_action(action)
528
action.set_sensitive(has_doc_root)
484
531
'''Wrapper for C{gtk.main()}, runs main loop of the application.
486
533
a number of initialization actions, like prompting the
487
534
L{NotebookDialog} if needed and will show the main window.
489
if self.notebook is None:
490
import zim.gui.notebookdialog
491
notebook = zim.gui.notebookdialog.prompt_notebook()
493
self.open_notebook(notebook)
495
# User canceled notebook dialog
536
assert self.notebook is not None
498
538
if self.notebook.dir:
499
539
os.chdir(self.notebook.dir.path)
500
set_environ('PWD', self.notebook.dir.path)
540
environ['PWD'] = self.notebook.dir.path
502
if self.page is None:
503
path = self.history.get_current()
507
self.open_page_home()
542
if self._first_page is None:
543
self._first_page = self.history.get_current()
509
545
# We schedule the autosave on idle to try to make it impact
510
546
# the performance of the application less. Of course using the
511
547
# async interface also helps, but we need to account for cases
512
548
# where asynchronous actions are not supported.
515
page = self.mainwindow.pageview.get_page()
516
if page.modified and not self._autosave_lock:
517
self.save_page_async(page)
519
550
def schedule_autosave():
520
schedule_on_idle(autosave)
551
schedule_on_idle(self.do_autosave)
521
552
return True # keep ticking
523
554
# older gobject version doesn't know about seconds
524
555
self.preferences['GtkInterface'].setdefault('autosave_timeout', 10)
525
556
timeout = self.preferences['GtkInterface']['autosave_timeout'] * 1000 # s -> ms
526
557
self._autosave_timer = gobject.timeout_add(timeout, schedule_autosave)
527
# FIXME make this more intelligent
530
self._finalize_ui = True
531
for plugin in self.plugins:
532
plugin.finalize_ui(self)
533
# Must happens before window.show_all()
534
# so side panes are initialized when we set uistate
536
# Check notebook (after loading plugins)
537
561
self.check_notebook_needs_upgrade()
539
563
# Update menus etc.
597
621
gtk.accel_map_get().connect('changed', on_accel_map_changed)
599
# if prefs are modified during init we should save them
600
if self.preferences.modified:
601
self.save_preferences()
603
623
# And here we go!
604
624
self.mainwindow.show_all()
626
# HACK: Delay opening first page till after show_all() -- else plugins are not initialized
627
# FIXME need to do extension & initialization of uistate earlier
629
self.open_page(self._first_page)
632
self.open_page_home()
605
634
self.mainwindow.pageview.grab_focus()
637
def check_notebook_needs_upgrade(self):
638
'''Check whether the notebook needs to be upgraded and prompt
639
the user to do so if this is the case.
641
Interactive wrapper for
642
L{Notebook.upgrade_notebook()<zim.notebook.Notebook.upgrade_notebook()>}.
644
if not self.notebook.needs_upgrade:
647
ok = QuestionDialog(None, (
648
_('Upgrade Notebook?'), # T: Short question for question prompt
649
_('This notebook was created by an older of version of zim.\n'
650
'Do you want to upgrade it to the latest version now?\n\n'
651
'Upgrading will take some time and may make various changes\n'
652
'to the notebook. In general it is a good idea to make a\n'
653
'backup before doing this.\n\n'
654
'If you choose not to upgrade now, some features\n'
655
'may not work as expected') # T: Explanation for question to upgrade notebook
661
with ProgressBarDialog(self, _('Upgrading notebook')) as dialog: # T: Title of progressbar dialog
662
self.notebook.index.ensure_update(callback=lambda p: dialog.pulse(p.name))
663
dialog.set_total(self.notebook.index.n_list_all_pages())
664
self.notebook.upgrade_notebook(callback=lambda p: dialog.pulse(p.name))
608
666
def present(self, page=None, fullscreen=None, geometry=None):
609
667
'''Present the mainwindow. Typically used to bring back a
610
668
the application after it was hidden. Also used for remote
1041
1100
'''Register a new window for the application.
1042
1101
Called by windows and dialogs to register themselves. Used e.g.
1043
1102
by plugins that want to add some widget to specific windows.
1046
1104
#~ print 'WINDOW:', window
1047
self.emit('new-window', window)
1105
self.plugins.extend(window)
1049
def do_new_window(self, window):
1050
self.windows.add(window)
1051
window.connect('destroy', lambda w: self.windows.discard(w))
1108
if hasattr(window, 'pageview'):
1109
self.plugins.extend(window.pageview)
1053
1111
def register_url_handler(self, scheme, function):
1054
1112
'''Register a handler for a particular URL scheme
1089
1147
the user with the L{NotebookDialog}
1090
1148
@emits: open-notebook
1092
if not self.notebook:
1093
assert not notebook is None, 'BUG: first initialize notebook'
1095
page = NotebookInterface.open_notebook(self, notebook)
1096
except NotebookLookupError, error:
1097
ErrorDialog(self, error).run()
1100
self.open_page(page)
1101
elif notebook is None:
1150
if notebook is None:
1102
1151
# Handle menu item for 'open another notebook'
1103
1152
from zim.gui.notebookdialog import NotebookDialog
1104
1153
NotebookDialog.unique(self, self, callback=self.open_notebook).show() # implicit recurs
1106
# Could be call back from open notebook dialog
1107
# We are already initialized, so let another process handle it
1155
# Could be call back from open notebook dialog - XXX
1109
1157
if zim.ipc.in_child_process():
1110
1158
if isinstance(notebook, basestring) \
1118
1166
notebook = zim.ipc.ServerProxy().get_notebook(notebook)
1119
1167
notebook.present(page=pagename)
1121
ZimCmd(('--gui', notebook)).spawn()
1123
def do_open_notebook(self, notebook):
1125
def move_away(o, path):
1126
if path == self.page or self.page.ischild(path):
1127
self.open_page_back() \
1128
or self.open_page_parent \
1129
or self.open_page_home
1131
def follow(o, path, newpath, update_links):
1132
if self.page == path:
1133
self.open_page(newpath)
1134
elif self.page.ischild(path):
1135
newpath = newpath + self.page.relname(path)
1136
newpath = Path(newpath.name) # IndexPath -> Path
1137
self.open_page(newpath)
1139
def autosave(o, p, *a):
1140
# Here we explicitly do not save async
1141
# and also explicitly no need for _autosave_lock
1142
page = self.mainwindow.pageview.get_page()
1143
if p == page and page.modified:
1144
self.save_page(page)
1146
NotebookInterface.do_open_notebook(self, notebook)
1147
self.history = History(notebook, self.uistate)
1148
self.on_notebook_properties_changed(notebook)
1149
notebook.connect('properties-changed', self.on_notebook_properties_changed)
1150
notebook.connect('delete-page', autosave) # before action
1151
notebook.connect('move-page', autosave) # before action
1152
notebook.connect('deleted-page', move_away)
1153
notebook.connect('moved-page', follow)
1155
# Start a lightweight background check of the index
1156
self.notebook.index.update_async()
1158
self.set_readonly(notebook.readonly)
1160
def check_notebook_needs_upgrade(self):
1161
'''Check whether the notebook needs to be upgraded and prompt
1162
the user to do so if this is the case.
1164
Interactive wrapper for
1165
L{Notebook.upgrade_notebook()<zim.notebook.Notebook.upgrade_notebook()>}.
1167
if not self.notebook.needs_upgrade:
1170
ok = QuestionDialog(None, (
1171
_('Upgrade Notebook?'), # T: Short question for question prompt
1172
_('This notebook was created by an older of version of zim.\n'
1173
'Do you want to upgrade it to the latest version now?\n\n'
1174
'Upgrading will take some time and may make various changes\n'
1175
'to the notebook. In general it is a good idea to make a\n'
1176
'backup before doing this.\n\n'
1177
'If you choose not to upgrade now, some features\n'
1178
'may not work as expected') # T: Explanation for question to upgrade notebook
1184
with ProgressBarDialog(self, _('Upgrading notebook')) as dialog: # T: Title of progressbar dialog
1185
self.notebook.index.ensure_update(callback=lambda p: dialog.pulse(p.name))
1186
dialog.set_total(self.notebook.index.n_list_all_pages())
1187
self.notebook.upgrade_notebook(callback=lambda p: dialog.pulse(p.name))
1189
def on_notebook_properties_changed(self, notebook):
1190
has_doc_root = not notebook.document_root is None
1191
for action in ('open_document_root', 'open_document_folder'):
1192
action = self.actiongroup.get_action(action)
1193
action.set_sensitive(has_doc_root)
1169
if hasattr(notebook, 'uri'):
1170
get_zim_application('--gui', notebook.uri).spawn()
1172
get_zim_application('--gui', notebook).spawn()
1195
1174
def open_page(self, path=None):
1196
1175
'''Method to open a page in the mainwindow, and menu action for
1486
1469
page = self._get_path_context()
1487
1470
PageWindow(self, page).show_all()
1489
def save_page(self, page=None):
1490
'''Menu action to save a page.
1473
def do_autosave(self):
1474
if self._check_autosave_done():
1475
page = self.mainwindow.pageview.get_page()
1477
and self._save_page_check_page(page):
1479
self._autosave_thread = self.notebook.store_page_async(page)
1481
# probably means backend does not support async store
1482
# AND failed storing - re-try immediatly
1483
logger.exception('Error during autosave - re-try')
1486
self._autosave_thread = None
1490
def _check_autosave_done(self):
1491
## Returning True here does not mean previous save was OK, just that it finished!
1492
if not self._autosave_thread:
1494
elif not self._autosave_thread.done:
1496
elif self._autosave_thread.error:
1497
# FIXME - should we force page.modified = True here ?
1498
logger.error('Error during autosave - re-try',
1499
exc_info=self._autosave_thread.exc_info)
1500
self._save_page(self.mainwindow.pageview.get_page()) # force normal save
1503
return True # Done and no error ..
1505
def assert_save_page_if_modified(self):
1506
'''Like C{save_page()} but only saves when needed.
1507
@raises PageHasUnSavedChangesError: when page was not saved
1509
page = self.mainwindow.pageview.get_page()
1513
if self._autosave_thread \
1514
and not self._autosave_thread.done:
1515
self._autosave_thread.join() # wait to finish
1517
self._check_autosave_done() # handle errors if any
1520
return self._save_page(page)
1524
def save_page(self):
1525
'''Menu action to save the current page.
1492
1527
Can result in a L{SavePageErrorDialog} when there is an error
1493
1528
while saving a page.
1495
@param page: a L{Page} object, when C{None} the current page is
1497
1530
@returns: C{True} when successful, C{False} when the page still
1498
1531
has unsaved changes
1500
page = self._save_page_check_page(page)
1533
page = self.mainwindow.pageview.get_page()
1534
assert page is not None
1536
if self._autosave_thread \
1537
and not self._autosave_thread.done:
1538
self._autosave_thread.join() # wait to finish
1540
# No error handling here for autosave, we save anyway
1542
return self._save_page(page)
1544
def _save_page(self, page):
1545
if not self._save_page_check_page(page):
1504
1548
## HACK - otherwise we get a bug when saving a new page immediatly
1514
1558
self.notebook.store_page(page)
1515
1559
except Exception, error:
1516
1560
logger.exception('Failed to save page: %s', page.name)
1517
self._autosave_lock.increment()
1518
# We need this flag to prevent autosave trigger while we
1519
# are showing the SavePageErrorDialog
1520
SavePageErrorDialog(self, error, page).run()
1521
self._autosave_lock.decrement()
1561
with self.do_autosave.blocked():
1562
# Avoid new autosave (on idle) while dialog is seen
1563
SavePageErrorDialog(self, error, page).run()
1523
1565
return not page.modified
1525
def save_page_async(self, page=None):
1526
'''Save a page asynchronously
1528
Like L{save_page()} but asynchronously, used e.g. when auto
1531
@param page: a L{Page} object, when C{None} the current page is
1534
page = self._save_page_check_page(page)
1538
logger.debug('Saving page (async): %s', page)
1540
def callback(ok, error, exc_info, name):
1541
# This callback is called back here in the main thread.
1542
# We fetch the page again just to be sure in case of strange
1543
# edge cases. The SavePageErrorDialog will just take the
1544
# current state of the page, not the state that it had in
1545
# the async thread. This is done on purpose, current state
1546
# is what the user is concerned with anyway.
1547
#~ print '!!', ok, exc_info, name
1549
page = self.notebook.get_page(Path(name))
1550
logger.error('Failed to save page: %s', page.name, exc_info=exc_info)
1551
SavePageErrorDialog(self, error, page).run()
1552
self._autosave_lock.decrement()
1554
self._autosave_lock.increment()
1555
# Prevent any new auto save to be scheduled while we are
1556
# still busy with this call.
1557
self.notebook.store_page_async(page, callback=callback, data=page.name)
1559
1567
def _save_page_check_page(self, page):
1560
# Code shared between save_page() and save_page_async()
1562
page = self.mainwindow.pageview.get_page()
1568
# Ensure that the page can be saved in the first place
1564
1570
if self.readonly:
1565
1571
raise AssertionError, 'BUG: can not save page when read-only'
1567
raise AssertionError, 'BUG: no page loaded'
1568
1572
elif page.readonly:
1569
1573
raise AssertionError, 'BUG: can not save read-only page'
1570
1574
except Exception, error:
1571
SavePageErrorDialog(self, error, page).run()
1575
with self.do_autosave.blocked():
1576
# Avoid new autosave (on idle) while dialog is seen
1577
SavePageErrorDialog(self, error, page).run()
1576
1582
def save_copy(self):
1577
1583
'''Menu action to show a L{SaveCopyDialog}'''
2290
2281
# define signals we want to use - (closure type, return type and arg types)
2291
2282
__gsignals__ = {
2292
2283
'fullscreen-changed': (gobject.SIGNAL_RUN_LAST, None, ()),
2284
'init-uistate': (gobject.SIGNAL_RUN_LAST, None, ()),
2295
def __init__(self, ui, fullscreen=False, geometry=None):
2287
def __init__(self, ui, preferences=None, fullscreen=False, geometry=None):
2297
2289
@param ui: the L{GtkInterFace}
2290
@param preferences: a C{ConfigDict} with preferences
2298
2291
@param fullscreen: if C{True} the window is shown fullscreen,
2299
2292
if C{None} the previous state is restored
2300
2293
@param geometry: the window geometry as string in format
2722
2717
- C{TOOLBAR_ICONS_ONLY}
2723
2718
- C{TOOLBAR_TEXT_ONLY}
2726
# ignore, trust system default
2727
# TODO: is there some way to reset to system default here ?
2730
assert style in ('icons_and_text', 'icons_only', 'text_only'), style
2731
if not self.uistate['toolbar_style'] and style == 'icons_and_text':
2732
# Exception since this is the default action that is active
2733
# when we just follow system default
2734
self.do_set_toolbar_style(style)
2736
self.actiongroup.get_action('set_toolbar_'+style).activate()
2720
assert style in ('icons_and_text', 'icons_only', 'text_only'), style
2721
self.actiongroup.get_action('set_toolbar_'+style).activate()
2722
self.do_set_toolbar_style(style)
2723
# if no configuration set, active may not represent actual case - force activation
2738
2725
def do_set_toolbar_style(self, name):
2739
2726
if name.startswith('set_toolbar_'):
2759
2746
- C{TOOLBAR_ICONS_SMALL}
2760
2747
- C{TOOLBAR_ICONS_TINY}
2763
# ignore, trust system default
2764
# TODO: is there some way to reset to system default here ?
2767
assert size in ('large', 'small', 'tiny'), size
2768
if not self.uistate['toolbar_size'] and size == 'large':
2769
# Exception since this is the default action that is active
2770
# when we just follow system default
2771
self.do_set_toolbar_size(size)
2773
self.actiongroup.get_action('set_toolbar_icons_'+size).activate()
2749
assert size in ('large', 'small', 'tiny'), size
2750
self.actiongroup.get_action('set_toolbar_icons_'+size).activate()
2751
self.do_set_toolbar_size(size)
2752
# if no configuration set, active may not represent actual case - force activation
2775
2754
def do_set_toolbar_size(self, name):
2776
2755
if name.startswith('set_toolbar_icons_'):
2849
2828
self.uistate.setdefault('show_statusbar_fullscreen', False)
2850
2829
self.uistate.setdefault('pathbar_type', PATHBAR_RECENT, PATHBAR_TYPES)
2851
2830
self.uistate.setdefault('pathbar_type_fullscreen', PATHBAR_NONE, PATHBAR_TYPES)
2852
self.uistate.setdefault('toolbar_style', None, check=basestring)
2853
self.uistate.setdefault('toolbar_size', None, check=basestring)
2832
# For these two "None" means system default, but we don't know what that default is :(
2833
self.preferences['GtkInterface'].setdefault('toolbar_style', None,
2834
(TOOLBAR_ICONS_ONLY, TOOLBAR_ICONS_AND_TEXT, TOOLBAR_TEXT_ONLY))
2835
self.preferences['GtkInterface'].setdefault('toolbar_size', None,
2836
(TOOLBAR_ICONS_TINY, TOOLBAR_ICONS_SMALL, TOOLBAR_ICONS_LARGE))
2855
2839
self._set_widgets_visable()
2857
2841
Window.init_uistate(self) # takes care of sidepane positions etc
2859
self.set_toolbar_style(self.uistate['toolbar_style'])
2860
self.set_toolbar_size(self.uistate['toolbar_size'])
2843
if self.preferences['GtkInterface']['toolbar_style'] is not None:
2844
self.set_toolbar_style(self.preferences['GtkInterface']['toolbar_style'])
2846
if self.preferences['GtkInterface']['toolbar_size'] is not None:
2847
self.set_toolbar_size(self.preferences['GtkInterface']['toolbar_size'])
2862
2849
self.toggle_fullscreen(show=self._set_fullscreen)
2916
2906
if path and isinstance(path, HistoryPath) and not path.cursor is None:
2917
2907
cursor = path.cursor
2918
elif self.ui.preferences['GtkInterface']['always_use_last_cursor_pos']:
2908
elif self.preferences['GtkInterface']['always_use_last_cursor_pos']:
2919
2909
cursor, _ = self.ui.history.get_state(page)
2923
2913
self.pageview.set_page(page, cursor)
2925
n = ui.notebook.index.n_list_links(page, zim.index.LINK_DIR_BACKWARD)
2915
n = ui.notebook.index.n_list_links(page, LINK_DIR_BACKWARD)
2926
2916
label = self.statusbar_backlinks_button.label
2927
2917
label.set_text_with_mnemonic(
2928
2918
ngettext('%i _Backlink...', '%i _Backlinks...', n) % n)
3038
3028
def __init__(self, ui, error, page):
3039
title = _('Could not save page: %s') % page.name
3029
msg = _('Could not save page: %s') % page.name
3040
3030
# T: Heading of error dialog
3041
explanation = _('''\
3031
desc = unicode(error).encode('utf-8').strip() \
3042
3034
To continue you can save a copy of this page or discard
3043
3035
any changes. If you save a copy changes will be also
3044
3036
discarded, but you can restore the copy later.''')
3045
3037
# T: text in error dialog when saving page failed
3046
gtk.MessageDialog.__init__(
3047
self, parent=get_window(ui),
3048
type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_NONE,
3049
message_format=title
3051
#~ self.set_default_size(450, -1)
3052
self.format_secondary_text(
3053
unicode(error).encode('utf-8').strip()+'\n\n'+explanation)
3038
ErrorDialog.__init__(self, ui, (msg, desc), buttons=gtk.BUTTONS_NONE)
3055
3040
self.page = page
3056
3041
self.error = error
3165
3150
('page', 'page', _('Page Name'), (path or ui.page)), # T: Input label
3166
3151
('template', 'choice', _('Page Template'), templates) # T: Choice label
3169
3153
self.form['template'] = default
3170
self.form.widgets['template'].set_no_show_all(True) # TEMP: hide feature
3171
self.form.widgets['template'].set_property('visible', False) # TEMP: hide feature
3154
# TODO: reset default when page input changed -
3155
# especially if namespace has other template
3174
3158
self.form.widgets['page'].subpaths_only = True
3176
# TODO: reset default when page input changed
3178
3160
def do_response_ok(self):
3179
3161
path = self.form['page']