~jaap.karssenberg/zim/pyzim-gtk3

« back to all changes in this revision

Viewing changes to zim/gui/__init__.py

  • Committer: Jaap Karssenberg
  • Date: 2014-03-08 11:47:43 UTC
  • mfrom: (668.1.49 pyzim-refactor)
  • Revision ID: jaap.karssenberg@gmail.com-20140308114743-fero6uvy9zirbb4o
Merge branch with refactoring

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
import logging
20
20
import gobject
21
21
import gtk
22
 
 
23
 
import zim
24
 
from zim import NotebookInterface, NotebookLookupError, ZimCmd
 
22
import threading
 
23
 
 
24
 
 
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
266
268
        pass
267
269
 
268
270
 
269
 
class RLock(object):
270
 
        '''Re-entrant lock that keeps a stack count
271
 
 
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()}.
278
 
        '''
279
 
 
280
 
        __slots__ = ('count',)
281
 
 
282
 
        def __init__(self):
283
 
                self.count = 0
284
 
 
285
 
        def __nonzero__(self):
286
 
                return self.count > 0
287
 
 
288
 
        def increment(self):
289
 
                '''Increase counter'''
290
 
                self.count += 1
291
 
 
292
 
        def decrement(self):
293
 
                '''Decrease counter'''
294
 
                if self.count == 0:
295
 
                        raise AssertionError, 'BUG: RLock count can not go below zero'
296
 
                self.count -= 1
297
 
 
298
 
 
299
 
class GtkInterface(NotebookInterface):
 
271
class PageHasUnSavedChangesError(Error):
 
272
        '''Exception raised when page could not be saved'''
 
273
 
 
274
        msg = _('Page has un-saved changes')
 
275
                # T: Error description
 
276
 
 
277
 
 
278
class WindowManager(object):
 
279
 
 
280
        def __iter__(self):
 
281
                for window in gtk.window_list_toplevels():
 
282
                        if isinstance(window, Window): # implies a zim object
 
283
                                yield Window
 
284
 
 
285
        def present(self):
 
286
                assert False, 'TODO pick window to present'
 
287
 
 
288
 
 
289
 
 
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.
302
293
 
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.
320
311
 
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
334
325
        @ivar history: the L{History} object
335
326
        @ivar uimanager: the C{gtk.UIManager} (see the methods
336
327
        L{add_actions()} and L{add_ui()} for wrappers)
337
 
        @ivar preferences_register: a L{ListDict} with preferences to show
 
328
        @ivar preferences_register: a L{ConfigDict} with preferences to show
338
329
        in the preferences dialog, see L{register_preferences()} to add
339
330
        to more preferences
340
331
 
348
339
        final page closure before quiting the application. This it is only
349
340
        a hint, so do not destroy any ui components when 'C{final}' is set,
350
341
        but it can be used to decide to do some actions async or not.
351
 
        @signal: C{new-window (C{Window})}: Emitted when a new window is
352
 
        created, can be used as a hook by plugins
353
342
        @signal: C{read-only-changed ()}: Emitted when the ui changed from
354
343
        read-write to read-only or back
355
344
        @signal: C{quit ()}: Emitted when the application is about to quit
363
352
        __gsignals__ = {
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, ()),
371
359
        }
372
360
 
373
 
        ui_type = 'gtk' #: UI type - typically checked by plugins instead of class
374
 
 
375
 
        def __init__(self, notebook=None, page=None,
 
361
        def __init__(self, notebook, page=None, config=None,
376
362
                fullscreen=False, geometry=None):
377
363
                '''Constructor
378
364
 
 
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}"
383
370
                '''
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
387
 
 
388
 
                NotebookInterface.__init__(self)
389
 
                self.preferences_register = ListDict()
 
371
                gobject.GObject.__init__(self)
 
372
 
 
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)
 
378
 
 
379
                logger.debug('Opening notebook: %s', notebook)
 
380
                self.notebook = notebook
 
381
 
 
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'])
 
386
 
 
387
                self.plugins = PluginManager(self.config)
 
388
                self.plugins.extend(notebook.index)
 
389
                self.plugins.extend(notebook)
 
390
 
 
391
                self.preferences_register = ConfigDict()
390
392
                self.page = None
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
396
 
                        # saving.
397
395
                self.readonly = False
398
396
                self.hideonclose = False
399
 
                self.windows = set()
400
397
                self.url_handlers = {}
401
398
 
 
399
                self._autosave_thread = None
 
400
 
402
401
                logger.debug('Gtk version is %s' % str(gtk.gtk_version))
403
402
                logger.debug('Pygtk version is %s' % str(gtk.pygtk_version))
404
403
 
425
424
                        gtk.rc_parse_string('gtk-error-bell = 0')
426
425
 
427
426
                # Init UI
428
 
                self.mainwindow = MainWindow(self, fullscreen, geometry)
 
427
                self.mainwindow = MainWindow(self, self.preferences, fullscreen, geometry)
429
428
 
430
429
                self.add_actions(ui_actions, self)
431
430
                self.add_actions(ui_actions_window, self.mainwindow)
455
454
                self._custom_tool_iconfactory = None
456
455
                self.load_custom_tools()
457
456
 
 
457
                self.preferences.connect('changed', self.do_preferences_changed)
458
458
                self.do_preferences_changed()
459
459
 
460
 
                # Deal with commandline arguments for notebook and page
461
 
                if notebook:
462
 
                        self.open_notebook(notebook)
463
 
                        # If it fails here an error dialog is shown and main()
464
 
                        # will prompt the notebook list
465
 
 
466
 
                        if self.notebook and page:
467
 
                                if isinstance(page, basestring):
468
 
                                        page = self.notebook.resolve_path(page)
469
 
                                        if not page is None:
470
 
                                                self.open_page(page)
471
 
                                else:
472
 
                                        assert isinstance(page, Path)
473
 
                                        self.open_page(page)
 
460
                self._init_notebook(self.notebook)
 
461
                if page and isinstance(page, basestring): # IPC call
 
462
                        page = self.notebook.resolve_path(page)
 
463
 
 
464
                self._first_page = page # XXX HACK - if we call open_page here, plugins are not yet initialized
 
465
 
 
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') )
474
472
                else:
475
 
                        pass # Will check default in main()
476
 
 
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)
481
 
                return plugin
 
473
                        from zim.config import SectionedConfigDict
 
474
                        self.uistate = SectionedConfigDict()
 
475
 
 
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
 
481
 
 
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)
 
489
 
 
490
                def save_page(o, p, *a):
 
491
                        page = self.mainwindow.pageview.get_page()
 
492
                        if p == page and page.modified:
 
493
                                self.save_page(page)
 
494
 
 
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
 
502
 
 
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)
 
507
 
 
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)
 
513
 
 
514
                notebook.index.connect('page-inserted', new_child)
 
515
                notebook.index.connect('page-deleted', child_deleted)
 
516
 
 
517
                # Start a lightweight background check of the index
 
518
                self.notebook.index.update_async()
 
519
 
 
520
                self.set_readonly(notebook.readonly)
 
521
 
 
522
        def on_notebook_properties_changed(self, notebook):
 
523
                self.config.set_profile(notebook.profile)
 
524
 
 
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)
482
529
 
483
530
        def main(self):
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.
488
535
                '''
489
 
                if self.notebook is None:
490
 
                        import zim.gui.notebookdialog
491
 
                        notebook = zim.gui.notebookdialog.prompt_notebook()
492
 
                        if notebook:
493
 
                                self.open_notebook(notebook)
494
 
                        else:
495
 
                                # User canceled notebook dialog
496
 
                                return
 
536
                assert self.notebook is not None
497
537
 
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
501
541
 
502
 
                if self.page is None:
503
 
                        path = self.history.get_current()
504
 
                        if path:
505
 
                                self.open_page(path)
506
 
                        else:
507
 
                                self.open_page_home()
 
542
                if self._first_page is None:
 
543
                        self._first_page = self.history.get_current()
508
544
 
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.
513
549
 
514
 
                def autosave():
515
 
                        page = self.mainwindow.pageview.get_page()
516
 
                        if page.modified and not self._autosave_lock:
517
 
                                        self.save_page_async(page)
518
 
 
519
550
                def schedule_autosave():
520
 
                        schedule_on_idle(autosave)
 
551
                        schedule_on_idle(self.do_autosave)
521
552
                        return True # keep ticking
522
553
 
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
528
 
 
529
 
                # Finalize plugin ui
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
535
 
 
536
 
                # Check notebook (after loading plugins)
 
558
 
 
559
 
 
560
                # Check notebook
537
561
                self.check_notebook_needs_upgrade()
538
562
 
539
563
                # Update menus etc.
585
609
                                                self.fsbutton.tap_and_hold_setup(menu) # attach app menu to fullscreen button for N900
586
610
                                                break
587
611
 
588
 
                accelmap = config_file('accelmap').file
 
612
                accelmap = self.config.get_config_file('accelmap').file
589
613
                logger.debug('Accelmap: %s', accelmap.path)
590
614
                if accelmap.exists():
591
615
                        gtk.accel_map_load(accelmap.path)
596
620
 
597
621
                gtk.accel_map_get().connect('changed', on_accel_map_changed)
598
622
 
599
 
                # if prefs are modified during init we should save them
600
 
                if self.preferences.modified:
601
 
                        self.save_preferences()
602
 
 
603
623
                # And here we go!
604
624
                self.mainwindow.show_all()
 
625
 
 
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
 
628
                if self._first_page:
 
629
                        self.open_page(self._first_page)
 
630
                        del self._first_page
 
631
                else:
 
632
                        self.open_page_home()
 
633
 
605
634
                self.mainwindow.pageview.grab_focus()
606
635
                gtk.main()
607
636
 
 
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.
 
640
 
 
641
                Interactive wrapper for
 
642
                L{Notebook.upgrade_notebook()<zim.notebook.Notebook.upgrade_notebook()>}.
 
643
                '''
 
644
                if not self.notebook.needs_upgrade:
 
645
                        return
 
646
 
 
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
 
656
                ) ).run()
 
657
 
 
658
                if not ok:
 
659
                        return
 
660
 
 
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))
 
665
 
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
663
721
                        # Do not quit if page not saved
664
722
                        return False
665
723
 
 
724
                self.notebook.index.stop_updating() # XXX - avoid long wait
 
725
                self.mainwindow.hide() # look more responsive
 
726
                while gtk.events_pending():
 
727
                        gtk.main_iteration(block=False)
 
728
 
666
729
                self.emit('quit')
667
 
                if self.mainwindow.get_property('visible'):
668
 
                        self.mainwindow.destroy()
669
730
 
670
731
                if gtk.main_level() > 0:
671
732
                        gtk.main_quit()
820
881
                try:
821
882
                        method(*arg)
822
883
                except Exception, error:
823
 
                        ErrorDialog(None, error).run()
 
884
                        ErrorDialog(self.mainwindow, error).run()
824
885
                        # error dialog also does logging automatically
825
886
 
826
887
        def _radio_action_handler(self, object, action, method):
972
1033
 
973
1034
                @emits: readonly-changed
974
1035
                '''
975
 
                if not self.readonly:
 
1036
                if not self.readonly and self.page:
976
1037
                        # Save any modification now - will not be allowed after switch
977
 
                        page = self.mainwindow.pageview.get_page()
978
 
                        if page and page.modified:
979
 
                                self.save_page(page)
 
1038
                        self.assert_save_page_if_modified()
980
1039
 
981
1040
                for group in self.uimanager.get_action_groups():
982
1041
                        for action in group.list_actions():
1014
1073
                See L{zim.gui.widgets.InputForm.add_inputs()} for valid values of
1015
1074
                the option type.
1016
1075
 
1017
 
                See L{zim.config.ListDict.setdefault())} for usage of the
 
1076
                See L{zim.config.ConfigDict.setdefault())} for usage of the
1018
1077
                optional check value.
1019
1078
 
1020
1079
                @todo: unify the check for setdefault() and the option type to
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.
1044
 
                @emits: new-window
1045
1103
                '''
1046
1104
                #~ print 'WINDOW:', window
1047
 
                self.emit('new-window', window)
 
1105
                self.plugins.extend(window)
1048
1106
 
1049
 
        def do_new_window(self, window):
1050
 
                self.windows.add(window)
1051
 
                window.connect('destroy', lambda w: self.windows.discard(w))
 
1107
                # HACK
 
1108
                if hasattr(window, 'pageview'):
 
1109
                        self.plugins.extend(window.pageview)
1052
1110
 
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
1091
1149
                '''
1092
 
                if not self.notebook:
1093
 
                        assert not notebook is None, 'BUG: first initialize notebook'
1094
 
                        try:
1095
 
                                page = NotebookInterface.open_notebook(self, notebook)
1096
 
                        except NotebookLookupError, error:
1097
 
                                ErrorDialog(self, error).run()
1098
 
                        else:
1099
 
                                if page:
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
1105
1154
                else:
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
1108
1156
                        import zim.ipc
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)
1120
1168
                        else:
1121
 
                                ZimCmd(('--gui', notebook)).spawn()
1122
 
 
1123
 
        def do_open_notebook(self, notebook):
1124
 
 
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
1130
 
 
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)
1138
 
 
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)
1145
 
 
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)
1154
 
 
1155
 
                # Start a lightweight background check of the index
1156
 
                self.notebook.index.update_async()
1157
 
 
1158
 
                self.set_readonly(notebook.readonly)
1159
 
 
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.
1163
 
 
1164
 
                Interactive wrapper for
1165
 
                L{Notebook.upgrade_notebook()<zim.notebook.Notebook.upgrade_notebook()>}.
1166
 
                '''
1167
 
                if not self.notebook.needs_upgrade:
1168
 
                        return
1169
 
 
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
1179
 
                ) ).run()
1180
 
 
1181
 
                if not ok:
1182
 
                        return
1183
 
 
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))
1188
 
 
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()
 
1171
                                else:
 
1172
                                        get_zim_application('--gui', notebook).spawn()
1194
1173
 
1195
1174
        def open_page(self, path=None):
1196
1175
                '''Method to open a page in the mainwindow, and menu action for
1247
1226
                        forward.set_sensitive(False)
1248
1227
 
1249
1228
                parent.set_sensitive(len(page.namespace) > 0)
1250
 
                child.set_sensitive(page.haschildren)
 
1229
 
 
1230
                indexpath = self.notebook.index.lookup_path(page)
 
1231
                child.set_sensitive(indexpath.haschildren)
 
1232
                        # FIXME: Need index path here, page.haschildren is also True
 
1233
                        #        when the page just has a attachment folder
1251
1234
 
1252
1235
        def close_page(self, page=None, final=False):
1253
1236
                '''Close the page and try to save any changes in the page.
1271
1254
                return not page.modified
1272
1255
 
1273
1256
        def do_close_page(self, page, final):
1274
 
                if page.modified:
1275
 
                        self.save_page(page) # No async here -- for now
 
1257
                self.assert_save_page_if_modified()
1276
1258
 
1277
1259
                current = self.history.get_current()
1278
1260
                if current == page:
1342
1324
                        self.open_page(record)
1343
1325
                else:
1344
1326
                        pages = list(self.notebook.index.list_pages(self.page))
1345
 
                        self.open_page(pages[0])
 
1327
                        if pages:
 
1328
                                self.open_page(pages[0])
1346
1329
                return True
1347
1330
 
1348
1331
        def open_page_previous(self):
1486
1469
                        page = self._get_path_context()
1487
1470
                PageWindow(self, page).show_all()
1488
1471
 
1489
 
        def save_page(self, page=None):
1490
 
                '''Menu action to save a page.
 
1472
        @SignalHandler
 
1473
        def do_autosave(self):
 
1474
                if self._check_autosave_done():
 
1475
                        page = self.mainwindow.pageview.get_page()
 
1476
                        if page.modified \
 
1477
                        and self._save_page_check_page(page):
 
1478
                                try:
 
1479
                                        self._autosave_thread = self.notebook.store_page_async(page)
 
1480
                                except:
 
1481
                                        # probably means backend does not support async store
 
1482
                                        # AND failed storing - re-try immediatly
 
1483
                                        logger.exception('Error during autosave - re-try')
 
1484
                                        self.save_page()
 
1485
                        else:
 
1486
                                self._autosave_thread = None
 
1487
                else:
 
1488
                        pass # still busy
 
1489
 
 
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:
 
1493
                        return True
 
1494
                elif not self._autosave_thread.done:
 
1495
                        return False
 
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
 
1501
                        return True
 
1502
                else:
 
1503
                        return True # Done and no error ..
 
1504
 
 
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
 
1508
                '''
 
1509
                page = self.mainwindow.pageview.get_page()
 
1510
                if page is None:
 
1511
                        return
 
1512
 
 
1513
                if self._autosave_thread \
 
1514
                and not self._autosave_thread.done:
 
1515
                        self._autosave_thread.join() # wait to finish
 
1516
 
 
1517
                self._check_autosave_done() # handle errors if any
 
1518
 
 
1519
                if page.modified:
 
1520
                        return self._save_page(page)
 
1521
                else:
 
1522
                        return True
 
1523
 
 
1524
        def save_page(self):
 
1525
                '''Menu action to save the current page.
1491
1526
 
1492
1527
                Can result in a L{SavePageErrorDialog} when there is an error
1493
1528
                while saving a page.
1494
1529
 
1495
 
                @param page: a L{Page} object, when C{None} the current page is
1496
 
                saved
1497
1530
                @returns: C{True} when successful, C{False} when the page still
1498
1531
                has unsaved changes
1499
1532
                '''
1500
 
                page = self._save_page_check_page(page)
1501
 
                if page is None:
 
1533
                page = self.mainwindow.pageview.get_page()
 
1534
                assert page is not None
 
1535
 
 
1536
                if self._autosave_thread \
 
1537
                and not self._autosave_thread.done:
 
1538
                        self._autosave_thread.join() # wait to finish
 
1539
 
 
1540
                # No error handling here for autosave, we save anyway
 
1541
 
 
1542
                return self._save_page(page)
 
1543
 
 
1544
        def _save_page(self, page):
 
1545
                if not self._save_page_check_page(page):
1502
1546
                        return
1503
1547
 
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()
1522
1564
 
1523
1565
                return not page.modified
1524
1566
 
1525
 
        def save_page_async(self, page=None):
1526
 
                '''Save a page asynchronously
1527
 
 
1528
 
                Like L{save_page()} but asynchronously, used e.g. when auto
1529
 
                saving.
1530
 
 
1531
 
                @param page: a L{Page} object, when C{None} the current page is
1532
 
                saved
1533
 
                '''
1534
 
                page = self._save_page_check_page(page)
1535
 
                if page is None:
1536
 
                        return
1537
 
 
1538
 
                logger.debug('Saving page (async): %s', page)
1539
 
 
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
1548
 
                        if exc_info:
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()
1553
 
 
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)
1558
 
 
1559
1567
        def _save_page_check_page(self, page):
1560
 
                # Code shared between save_page() and save_page_async()
1561
 
                if page is None:
1562
 
                        page = self.mainwindow.pageview.get_page()
 
1568
                # Ensure that the page can be saved in the first place
1563
1569
                try:
1564
1570
                        if self.readonly:
1565
1571
                                raise AssertionError, 'BUG: can not save page when read-only'
1566
 
                        elif not page:
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()
1572
 
                        return None
 
1575
                        with self.do_autosave.blocked():
 
1576
                                # Avoid new autosave (on idle) while dialog is seen
 
1577
                                SavePageErrorDialog(self, error, page).run()
 
1578
                        return False
1573
1579
                else:
1574
 
                        return page
 
1580
                        return True
1575
1581
 
1576
1582
        def save_copy(self):
1577
1583
                '''Menu action to show a L{SaveCopyDialog}'''
1612
1618
                notebook.move_page but wrapping with all the proper exception
1613
1619
                dialogs. Returns boolean for success.
1614
1620
                '''
1615
 
                if path == self.page and self.page.modified \
1616
 
                and not self.save_page(self.page):
1617
 
                        raise AssertionError, 'Could not save page'
1618
 
                        # assert statement could be optimized away
1619
 
                        # FIXME - is this raise needed ?
 
1621
                self.assert_save_page_if_modified()
1620
1622
 
1621
1623
                return self._wrap_move_page(
1622
1624
                        lambda update_links, callback: self.notebook.move_page(
1638
1640
                notebook.rename_page but wrapping with all the proper exception
1639
1641
                dialogs. Returns boolean for success.
1640
1642
                '''
1641
 
                if path == self.page and self.page.modified \
1642
 
                and not self.save_page(self.page):
1643
 
                        raise AssertionError, 'Could not save page'
1644
 
                        # assert statement could be optimized away
1645
 
                        # FIXME - is this raise needed ?
 
1643
                self.assert_save_page_if_modified()
1646
1644
 
1647
1645
                return self._wrap_move_page(
1648
1646
                        lambda update_links, callback: self.notebook.rename_page(
1752
1750
                from zim.gui.preferencesdialog import PreferencesDialog
1753
1751
                PreferencesDialog(self).run()
1754
1752
 
1755
 
        def do_preferences_changed(self):
 
1753
        def do_preferences_changed(self, *a):
1756
1754
                self.uimanager.set_add_tearoffs(
1757
1755
                        self.preferences['GtkInterface']['tearoff_menus'] )
1758
1756
 
1760
1758
                '''Menu action to reload the current page. Will first try
1761
1759
                to save any unsaved changes, then reload the page from disk.
1762
1760
                '''
1763
 
                if self.page.modified \
1764
 
                and not self.save_page(self.page):
1765
 
                        raise AssertionError, 'Could not save page'
1766
 
                        # assert statement could be optimized away
 
1761
                self.assert_save_page_if_modified()
1767
1762
                self.notebook.flush_page_cache(self.page)
1768
1763
                self.open_page(self.notebook.get_page(self.page))
1769
1764
 
1796
1791
                file.copyto(dest)
1797
1792
                return dest
1798
1793
 
1799
 
        def show_clean_notebook(self):
1800
 
                '''Menu action to show the L{CleanNotebookDialog}'''
1801
 
                from zim.gui.cleannotebookdialog import CleanNotebookDialog
1802
 
                CleanNotebookDialog(self).run()
1803
 
 
1804
1794
        def open_dir(self, dir):
1805
1795
                '''Open a L{Dir} object and prompt to create it if it doesn't
1806
1796
                exist yet.
1902
1892
                        # Special case for outlook folder paths on windows
1903
1893
                        os.startfile(url)
1904
1894
                else:
 
1895
                        from zim.gui.applications import get_mimetype
1905
1896
                        manager = ApplicationManager()
1906
 
                        type = zim.gui.applications.get_mimetype(url)
 
1897
                        type = get_mimetype(url)
1907
1898
                        logger.debug('Got type "%s" for "%s"', type, url)
1908
1899
                        entry = manager.get_default_application(type)
1909
1900
                        if entry:
1997
1988
                        ErrorDialog(self, 'This page does not have a source file').run()
1998
1989
                        return
1999
1990
 
2000
 
                if page.modified:
2001
 
                        ok = self.save_page(page)
2002
 
                        if not ok:
2003
 
                                ErrorDialog(self, 'Page has unsaved changes')
2004
 
                                return
 
1991
                self.assert_save_page_if_modified()
2005
1992
 
2006
1993
                self.edit_file(self.page.source, istextfile=True)
2007
1994
                if page == self.page:
2080
2067
                L{zim.gui.server}. Spawns a new zim instance for the server.
2081
2068
                '''
2082
2069
                # TODO instead of spawn, include in this process
2083
 
                ZimCmd(('--server', '--gui', self.notebook.uri)).spawn()
 
2070
                get_zim_application('--server', '--gui', self.notebook.uri).spawn()
2084
2071
 
2085
2072
        def reload_index(self, flush=False):
2086
2073
                '''Check the notebook for changes and update the index.
2210
2197
                @param page: manual page to show (string)
2211
2198
                '''
2212
2199
                if page:
2213
 
                        ZimCmd(('--manual', page)).spawn()
 
2200
                        get_zim_application('--manual', page).spawn()
2214
2201
                else:
2215
 
                        ZimCmd(('--manual',)).spawn()
 
2202
                        get_zim_application('--manual').spawn()
2216
2203
 
2217
2204
        def show_help_faq(self):
2218
2205
                '''Menu action to show the 'FAQ' page in the user manual'''
2235
2222
                        dialog.set_program_name('Zim')
2236
2223
                except AttributeError:
2237
2224
                        pass
 
2225
 
 
2226
                import zim
2238
2227
                dialog.set_version(zim.__version__)
2239
2228
                dialog.set_comments(_('A desktop wiki'))
2240
2229
                        # T: General description of zim itself
2265
2254
                else:
2266
2255
                        self.window.ui.open_page(path) # XXX
2267
2256
 
 
2257
                return self.window.pageview # XXX
 
2258
 
2268
2259
        def open_dir(self, dir):
2269
2260
                self.window.ui.open_dir(dir)
2270
2261
 
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, ()),
2293
2285
        }
2294
2286
 
2295
 
        def __init__(self, ui, fullscreen=False, geometry=None):
 
2287
        def __init__(self, ui, preferences=None, fullscreen=False, geometry=None):
2296
2288
                '''Constructor
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
2304
2297
                self.isfullscreen = False
2305
2298
                self.ui = ui
2306
2299
 
 
2300
                self.preferences = preferences # XXX should be just prefernces dict - use "config" otherwise
 
2301
                self.preferences.connect('changed', self.do_preferences_changed)
 
2302
 
2307
2303
                ui.connect('open-page', self.on_open_page)
2308
2304
                ui.connect('close-page', self.on_close_page)
2309
 
                ui.connect('preferences-changed', self.do_preferences_changed)
2310
2305
 
2311
2306
                self._block_toggle_panes = False
2312
2307
                self._sidepane_autoclose = False
2398
2393
                        self._set_fullscreen = True
2399
2394
 
2400
2395
                # Init mouse settings
2401
 
                self.ui.preferences['GtkInterface'].setdefault('mouse_nav_button_back', 8)
2402
 
                self.ui.preferences['GtkInterface'].setdefault('mouse_nav_button_forw', 9)
 
2396
                self.preferences['GtkInterface'].setdefault('mouse_nav_button_back', 8)
 
2397
                self.preferences['GtkInterface'].setdefault('mouse_nav_button_forw', 9)
2403
2398
 
2404
2399
        def do_update_statusbar(self, *a):
2405
2400
                page = self.pageview.get_page()
2457
2452
                space = gtk.gdk.unicode_to_keyval(ord(' '))
2458
2453
                group = gtk.AccelGroup()
2459
2454
 
2460
 
                self.ui.preferences['GtkInterface'].setdefault('toggle_on_altspace', False)
2461
 
                if self.ui.preferences['GtkInterface']['toggle_on_altspace']:
 
2455
                self.preferences['GtkInterface'].setdefault('toggle_on_altspace', False)
 
2456
                if self.preferences['GtkInterface']['toggle_on_altspace']:
2462
2457
                        # Hidden param, disabled because it causes problems with
2463
2458
                        # several international layouts (space mistaken for alt-space,
2464
2459
                        # see bug lp:620315)
2468
2463
 
2469
2464
                # Toggled by preference menu, also causes issues with international
2470
2465
                # layouts - esp. when switching input method on Ctrl-Space
2471
 
                if self.ui.preferences['GtkInterface']['toggle_on_ctrlspace']:
 
2466
                if self.preferences['GtkInterface']['toggle_on_ctrlspace']:
2472
2467
                        group.connect_group( # <Ctrl><Space>
2473
2468
                                space, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE,
2474
2469
                                self.toggle_sidepane_focus)
2722
2717
                        - C{TOOLBAR_ICONS_ONLY}
2723
2718
                        - C{TOOLBAR_TEXT_ONLY}
2724
2719
                '''
2725
 
                if not style:
2726
 
                        # ignore, trust system default
2727
 
                        # TODO: is there some way to reset to system default here ?
2728
 
                        return
2729
 
                else:
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)
2735
 
                        else:
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
2737
2724
 
2738
2725
        def do_set_toolbar_style(self, name):
2739
2726
                if name.startswith('set_toolbar_'):
2750
2737
                else:
2751
2738
                        assert False, 'BUG: Unkown toolbar style: %s' % style
2752
2739
 
2753
 
                self.uistate['toolbar_style'] = style
 
2740
                self.preferences['GtkInterface']['toolbar_style'] = style
2754
2741
 
2755
2742
        def set_toolbar_size(self, size):
2756
2743
                '''Set the toolbar style
2759
2746
                        - C{TOOLBAR_ICONS_SMALL}
2760
2747
                        - C{TOOLBAR_ICONS_TINY}
2761
2748
                '''
2762
 
                if not size:
2763
 
                        # ignore, trust system default
2764
 
                        # TODO: is there some way to reset to system default here ?
2765
 
                        return
2766
 
                else:
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)
2772
 
                        else:
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
2774
2753
 
2775
2754
        def do_set_toolbar_size(self, name):
2776
2755
                if name.startswith('set_toolbar_icons_'):
2787
2766
                else:
2788
2767
                        assert False, 'BUG: Unkown toolbar size: %s' % size
2789
2768
 
2790
 
                self.uistate['toolbar_size'] = size
 
2769
                self.preferences['GtkInterface']['toolbar_size'] = size
2791
2770
 
2792
2771
        def toggle_readonly(self, readonly=None):
2793
2772
                '''Menu action to toggle the read-only state of the application
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)
 
2831
 
 
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))
 
2837
 
2854
2838
 
2855
2839
                self._set_widgets_visable()
2856
2840
 
2857
2841
                Window.init_uistate(self) # takes care of sidepane positions etc
2858
2842
 
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'])
 
2845
 
 
2846
                if self.preferences['GtkInterface']['toolbar_size'] is not None:
 
2847
                        self.set_toolbar_size(self.preferences['GtkInterface']['toolbar_size'])
2861
2848
 
2862
2849
                self.toggle_fullscreen(show=self._set_fullscreen)
2863
2850
 
2879
2866
                self.pageview.connect('modified-changed', self.do_update_statusbar)
2880
2867
                self.ui.notebook.connect_after('stored-page', self.do_update_statusbar)
2881
2868
 
 
2869
                # Notify plugins
 
2870
                self.emit('init-uistate')
 
2871
 
2882
2872
        def _set_widgets_visable(self):
2883
2873
                # Convenience method to switch visibility of all widgets
2884
2874
                if self.isfullscreen:
2915
2905
 
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)
2920
2910
                else:
2921
2911
                        cursor = None
2922
2912
 
2923
2913
                self.pageview.set_page(page, cursor)
2924
2914
 
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)
2948
2938
        def do_button_press_event(self, event):
2949
2939
                ## Try to capture buttons for navigation
2950
2940
                if event.button > 3:
2951
 
                        if event.button == self.ui.preferences['GtkInterface']['mouse_nav_button_back']:
 
2941
                        if event.button == self.preferences['GtkInterface']['mouse_nav_button_back']:
2952
2942
                                self.ui.open_page_back()
2953
 
                        elif event.button == self.ui.preferences['GtkInterface']['mouse_nav_button_forw']:
 
2943
                        elif event.button == self.preferences['GtkInterface']['mouse_nav_button_forw']:
2954
2944
                                self.ui.open_page_forward()
2955
2945
                        else:
2956
2946
                                logger.debug("Unused mouse button %i", event.button)
3036
3026
        '''
3037
3027
 
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() \
 
3032
                                + '\n\n' \
 
3033
                                + _('''\
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
3050
 
                )
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)
3054
3039
 
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
3167
3152
                ])
3168
 
 
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
3172
3156
 
3173
3157
                if subpage:
3174
3158
                        self.form.widgets['page'].subpaths_only = True
3175
3159
 
3176
 
                # TODO: reset default when page input changed
3177
 
 
3178
3160
        def do_response_ok(self):
3179
3161
                path = self.form['page']
3180
3162
                if not path:
3253
3235
                Dialog.__init__(self, ui, _('Move Page')) # T: Dialog title
3254
3236
                self.path = path
3255
3237
 
3256
 
                if isinstance(self.path, Page) \
3257
 
                and self.path.modified \
3258
 
                and not self.ui.save_page(self.path):
3259
 
                        raise AssertionError, 'Could not save page'
3260
 
                        # assert statement could be optimized away
 
3238
                self.ui.assert_save_page_if_modified()
3261
3239
 
3262
3240
                self.vbox.add(gtk.Label(_('Move page "%s"') % self.path.name))
3263
3241
                        # T: Heading in 'move page' dialog - %s is the page name
3265
3243
                indexpath = self.ui.notebook.index.lookup_path(self.path)
3266
3244
                if indexpath:
3267
3245
                        i = self.ui.notebook.index.n_list_links_to_tree(
3268
 
                                        indexpath, zim.index.LINK_DIR_BACKWARD )
 
3246
                                        indexpath, LINK_DIR_BACKWARD )
3269
3247
                else:
3270
3248
                        i = 0
3271
3249
 
3314
3292
                indexpath = self.ui.notebook.index.lookup_path(self.path)
3315
3293
                if indexpath:
3316
3294
                        i = self.ui.notebook.index.n_list_links_to_tree(
3317
 
                                        indexpath, zim.index.LINK_DIR_BACKWARD )
 
3295
                                        indexpath, LINK_DIR_BACKWARD )
3318
3296
                else:
3319
3297
                        i = 0
3320
3298
 
3385
3363
                indexpath = self.ui.notebook.index.lookup_path(self.path)
3386
3364
                if indexpath:
3387
3365
                        i = self.ui.notebook.index.n_list_links_to_tree(
3388
 
                                        indexpath, zim.index.LINK_DIR_BACKWARD )
 
3366
                                        indexpath, LINK_DIR_BACKWARD )
3389
3367
                else:
3390
3368
                        i = 0
3391
3369