def testSerialize(self): '''Test parsing the history from the state file''' uistate = INIConfigFile(VirtualFile([])) history = History(self.notebook, uistate) for page in self.pages: history.append(page) self.assertHistoryEquals(history, self.pages) self.assertCurrentEquals(history, self.pages[-1]) # rewind 2 for i in range(2): prev = history.get_previous() history.set_current(prev) # check state #~ import pprint #~ pprint.pprint(uistate) self.assertHistoryEquals(history, uistate['History']['list']) self.assertRecentEquals(history, uistate['History']['recent']) self.assertEqual(uistate['History']['current'], len(self.pages) - 3) # clone uistate by text lines = uistate.dump() newuistate = INIConfigFile(VirtualFile(lines)) newuistate['History'].setdefault('list', []) newuistate['History'].setdefault('recent', []) newuistate['History'].setdefault('current', 0) # check new state self.assertHistoryEquals( history, [Path(t[0]) for t in newuistate['History']['list']]) self.assertRecentEquals( history, [Path(t[0]) for t in newuistate['History']['recent']]) self.assertEqual(newuistate['History']['current'], len(self.pages) - 3) # and compare resulting history object newhistory = History(self.notebook, newuistate) self.assertEqual(list(newhistory.get_history()), list(history.get_history())) self.assertEqual(list(newhistory.get_recent()), list(history.get_recent())) self.assertEqual(newhistory.get_current(), history.get_current()) # Check recent is initialized if needed newuistate = INIConfigFile(VirtualFile(lines)) newuistate['History'].setdefault('recent', []) newuistate['History'].pop('recent') newhistory = History(self.notebook, newuistate) self.assertEqual(list(newhistory.get_history()), list(history.get_history())) self.assertEqual(list(newhistory.get_recent()), list(history.get_recent())) self.assertEqual(newhistory.get_current(), history.get_current())
def __init__(self, cache_dir, config, folder, layout, index): '''Constructor @param cache_dir: a L{Folder} object used for caching the notebook state @param config: a L{NotebookConfig} object @param folder: a L{Folder} object for the notebook location @param layout: a L{NotebookLayout} object @param index: an L{Index} object ''' self.folder = folder self.cache_dir = cache_dir self.state = INIConfigFile(cache_dir.file('state.conf')) self.config = config self.properties = config['Notebook'] self.layout = layout self.index = index self._operation_check = NOOP self.readonly = not _iswritable(folder) if self.readonly: logger.info('Notebook read-only: %s', folder.path) self._page_cache = weakref.WeakValueDictionary() self.name = None self.icon = None self.document_root = None self.interwiki = None if folder.watcher is None: from zim.newfs.helpers import FileTreeWatcher folder.watcher = FileTreeWatcher() from .index import PagesView, LinksView, TagsView self.pages = PagesView.new_from_index(self.index) self.links = LinksView.new_from_index(self.index) self.tags = TagsView.new_from_index(self.index) def on_page_row_changed(o, row, oldrow): if row['name'] in self._page_cache: self._page_cache[ row['name']].haschildren = row['n_children'] > 0 self.emit('page-info-changed', self._page_cache[row['name']]) def on_page_row_deleted(o, row): if row['name'] in self._page_cache: self._page_cache[row['name']].haschildren = False self.emit('page-info-changed', self._page_cache[row['name']]) self.index.update_iter.pages.connect('page-row-changed', on_page_row_changed) self.index.update_iter.pages.connect('page-row-deleted', on_page_row_deleted) self.connectto(self.properties, 'changed', self.on_properties_changed) self.on_properties_changed(self.properties)
def testRobustness(self): '''Test history can deal with garbage data''' uistate = INIConfigFile(VirtualFile([])) uistate['History'].input({ 'list': 'FOOOO', 'recent': [["BARRRR", 0]], 'cursor': 'Not an integer', }) with tests.LoggingFilter(logger='zim.config', message='Invalid config'): with tests.LoggingFilter(logger='zim.history', message='Could not parse'): history = History(self.notebook, uistate) self.assertEqual(list(history.get_history()), []) self.assertEqual(list(history.get_recent()), []) self.assertIsNone(history.get_current())
def parse(self, text): '''Parses the config and cache and populates the list Format is:: [NotebookList] Default=uri1 1=uri1 2=uri2 [Notebook 1] name=Foo uri=uri1 Then followed by more "[Notebook]" sections that are cache data @param text: a string or a list of lines ''' # Format <= 0.60 was: # # [NotebookList] # Default=uri1 # uri1 # uri2 # # [Notebook] # name=Foo # uri=uri1 if isinstance(text, str): text = text.splitlines(True) assert text[0].strip() == '[NotebookList]' # Backward compatibility, make valid INI file: # - make redundant [Notebook] sections numbered # - prefix lines without a key with a number n = 0 l = 0 for i, line in enumerate(text): if line.strip() == '[Notebook]': n += 1 text[i] = '[Notebook %i]\n' % n elif line and not line.isspace() \ and not line.lstrip().startswith('[') \ and not line.lstrip().startswith('#') \ and not '=' in line: l += 1 text[i] = ('%i=' % l) + line ### config = INIConfigFile(VirtualFile(text)) mylist = config['NotebookList'] mylist.define(Default=String(None)) mylist.define((k, String(None)) for k in list(mylist._input.keys())) # XXX for key, uri in list(config['NotebookList'].items()): if key == 'Default': continue section = config['Notebook %s' % key] section.define( uri=String(None), name=String(None), icon=String(None), mtime=String(None), interwiki=String(None) ) if section['uri'] == uri: info = NotebookInfo(**section) else: info = NotebookInfo(uri) self.append(info) if 'Default' in config['NotebookList'] \ and config['NotebookList']['Default']: self.set_default(config['NotebookList']['Default'])
def __init__(self, notebook, config, page=None, fullscreen=False, geometry=None): '''Constructor @param notebook: the L{Notebook} to show in this window @param config: a C{ConfigManager} object @param page: a C{Path} object to open @param fullscreen: if C{True} the window is shown fullscreen, if C{None} the previous state is restored @param geometry: the window geometry as string in format "C{WxH+X+Y}", if C{None} the previous state is restored ''' Window.__init__(self) self.notebook = notebook self.page = None # will be set later by open_page self.navigation = NavigationModel(self) self.hideonclose = False self.config = config self.preferences = config.preferences['GtkInterface'] self.preferences.define( toggle_on_ctrlspace=Boolean(False), remove_links_on_delete=Boolean(True), always_use_last_cursor_pos=Boolean(True), ) self.preferences.connect('changed', self.do_preferences_changed) self.maximized = False self.isfullscreen = False self.connect_after('window-state-event', self.__class__.on_window_state_event) # Hidden setting to force the gtk bell off. Otherwise it # can bell every time you reach the begin or end of the text # buffer. Especially specific gtk version on windows. # See bug lp:546920 self.preferences.setdefault('gtk_bell', False) if not self.preferences['gtk_bell']: Gtk.rc_parse_string('gtk-error-bell = 0') self._block_toggle_panes = False self._sidepane_autoclose = False self._switch_focus_accelgroup = None # Catching this signal prevents the window to actually be destroyed # when the user tries to close it. The action for close should either # hide or destroy the window. def do_delete_event(*a): logger.debug('Action: close (delete-event)') self.close() return True # Do not destroy - let close() handle it self.connect('delete-event', do_delete_event) # setup uistate if not hasattr(config, 'uistate'): config.uistate = INIConfigFile( notebook.cache_dir.file('state.conf')) self.uistate = self.config.uistate['MainWindow'] self.history = History(notebook, config.uistate) # init uimanager self.uimanager = Gtk.UIManager() self.uimanager.add_ui_from_string(''' <ui> <menubar name="menubar"> </menubar> <toolbar name="toolbar"> </toolbar> </ui> ''') # setup menubar and toolbar self.add_accel_group(self.uimanager.get_accel_group()) self.menubar = self.uimanager.get_widget('/menubar') self.toolbar = self.uimanager.get_widget('/toolbar') self.toolbar.connect('popup-context-menu', self.do_toolbar_popup) self.add_bar(self.menubar) self.add_bar(self.toolbar) self.pageview = PageView(self.notebook, config, self.navigation) self.connect_object('readonly-changed', PageView.set_readonly, self.pageview) self.pageview.connect_after('textstyle-changed', self.on_textview_textstyle_changed) self.pageview.textview.connect_after('toggle-overwrite', self.on_textview_toggle_overwrite) self.pageview.textview.connect('link-enter', self.on_link_enter) self.pageview.textview.connect('link-leave', self.on_link_leave) self.add(self.pageview) # create statusbar self.statusbar = Gtk.Statusbar() self.statusbar.push(0, '<page>') self.add_bar(self.statusbar, start=False) self.statusbar.set_property('margin', 0) def statusbar_element(string, size): frame = Gtk.Frame() frame.set_shadow_type(Gtk.ShadowType.IN) self.statusbar.pack_end(frame, False, True, 0) label = Gtk.Label(label=string) label.set_size_request(size, 10) label.set_alignment(0.1, 0.5) frame.add(label) return label # specify statusbar elements right-to-left self.statusbar_style_label = statusbar_element('<style>', 100) self.statusbar_insert_label = statusbar_element('INS', 60) # and build the widget for backlinks self.statusbar_backlinks_button = \ BackLinksMenuButton(self.notebook, self.open_page, status_bar_style=True) frame = Gtk.Frame() frame.set_shadow_type(Gtk.ShadowType.IN) self.statusbar.pack_end(frame, False, True, 0) frame.add(self.statusbar_backlinks_button) self.move_bottom_minimized_tabs_to_statusbar(self.statusbar) self.do_preferences_changed() self._geometry_set = False self._set_fullscreen = False if geometry: try: self.parse_geometry(geometry) self._geometry_set = True except: logger.exception('Parsing geometry string failed:') elif fullscreen: self._set_fullscreen = True # Init mouse settings self.preferences.setdefault('mouse_nav_button_back', 8) self.preferences.setdefault('mouse_nav_button_forw', 9) # Finish uimanager self._uiactions = UIActions(self, self.notebook, self.page, self.config, self.navigation) group = get_gtk_actiongroup(self._uiactions) self.uimanager.insert_action_group(group, 0) group = get_gtk_actiongroup(self.pageview) self.uimanager.insert_action_group(group, 0) group = get_gtk_actiongroup(self) group.add_actions(MENU_ACTIONS) self.uimanager.insert_action_group(group, 0) group.get_action('open_page_back').set_sensitive(False) group.get_action('open_page_forward').set_sensitive(False) fname = 'menubar.xml' self.uimanager.add_ui_from_string(data_file(fname).read()) # Do this last, else menu items show up in wrong place self.pageview.notebook = self.notebook # XXX self._customtools = CustomToolManagerUI(self.uimanager, self.config, self.pageview) self._insertedobjects = InsertedObjectUI(self.uimanager, self.pageview) # XXX: would like to do this in PageView itself, but need access to uimanager # Setup notebook signals notebook.connect('page-info-changed', self.do_page_info_changed) def move_away(o, path): # Try several options to get awaay actions = [ self.open_page_back, self.open_page_parent, self.open_page_home ] while (path == self.page or self.page.ischild(path)) and actions: action = actions.pop(0) action() notebook.connect('deleted-page', move_away) # after action def follow(o, path, newpath): if path == self.page: self.open_page(newpath) elif self.page.ischild(path): newpath = newpath + self.page.relname(path) self.open_page(newpath) else: pass notebook.connect('moved-page', follow) # after action # init page page = page or self.history.get_current() if page: page = notebook.get_page(page) self.open_page(page) else: self.open_page_home() self.pageview.grab_focus()