Attached a quick version of a plugin to automate archiving pages.

What it does:
1/ Adds a menu item "File --> Archive"
    - this menu item moves current page to the archive and adds date stamp
2/ Adds a menu item "View --> Archived versions"
    - shows a dialog with all archived versions of the current page (if any)

Still to do:
1/ Change search dialog to hide archived pages but add a button "show X
results from archived pages"
2/ Change auto-completion in "jump to" dialog to put archive lower in the

Please let me know what you think


# -*- coding: utf-8 -*-

# Copyright 2015 Jaap Karssenberg <>

# TODO - hook into search dialog - hide archive + show button with "show X archived pages"
# TODO - hook into auto-completion - put archive lower in the list

from __future__ import with_statement

import gobject
import gtk

import re
import datetime

import logging

from zim.plugins import PluginClass, extends, WindowExtension
from zim.actions import action
from zim.notebook import Path

from zim.gui.widgets import ui_environment, Dialog, Button, \
	BrowserTreeView, ScrolledWindow

logger = logging.getLogger('zim.plugins.archive')

class ArchivePlugin(PluginClass):

	plugin_info = {
		'name': _('Archive'), # T: plugin name
		'description': _('''\
This plugin uses a section of the notebook as an archive.
This archive section is used to get pages out of the way from the
active notebook structere.
		# T: plugin description
		'author': 'Jaap Karssenberg',
		'help': 'Plugins:Archive',
	plugin_preferences = (
		# key, type, label, default
		('namespace', 'namespace', _('Section'), Path(':Archive')), # T: input label
		#~ ('embedded', 'bool', _('Show calendar in sidepane instead of as dialog'), False), # T: preferences option
		#~ ('pane', 'choice', _('Position in the window'), (LEFT_PANE, TOP), WIDGET_POSITIONS), # T: preferences option
	# TODO disable pane setting if not embedded

	def __init__(self, config=None):
		PluginClass.__init__(self, config)

		#~ self.preferences.connect('changed', self.on_preferences_changed)
		#~ self.on_preferences_changed(self.preferences)

	#~ def on_preferences_changed(self, preferences):
		#~ if preferences['embedded']:
			#~ self.set_extension_class('MainWindow', MainWindowExtensionEmbedded)
		#~ else:
			#~ self.set_extension_class('MainWindow', MainWindowExtensionDialog)

	def archive_page(self, notebook, page):
		postfix ="_%Y%m%d")
		section = self.preferences['namespace']
		newpage = notebook.get_page(section + ( + postfix))
		if newpage.exists():
			i = 1
			postfix = postfix + '_(%i)' % i
			newpage = notebook.get_page(section + ( + postfix))
			while new_page.exists():
				i += 1
				postfix = postfix + '_(%i)' % i
				newpage = notebook.get_page(Path( + postfix))

		notebook.move_page(page, newpage)

	def list_archived_versions(self, notebook, page):
		# Find all pages in archive that look like page + postfix
		# Find all pages in archive that look like page where parent
		# has a postfix
		# Yield pages and archive dates

		def _versions(trunk, remainder):
			basename = remainder.pop(0)
			pattern = re.compile(re.escape(basename) + r'[ _](\d{8})([ _]\(\d+\))?$')
			for path in notebook.index.list_pages(trunk):
				match =
				if match: # branch with archive date
					date =
					if remainder:
						child = path + remainder
						if notebook.get_page(child).exists():
							yield child, date
						yield path, date
				elif path.basename == basename:
					if remainder: # follow trunk
						for r in _versions(path, remainder):
							yield r
						pass # empty parent of archived pages

		section = self.preferences['namespace']
		for path, date in _versions(section,
			yield path, date

class MainWindowExtensionDialog(WindowExtension):
	'''Extension used to add calendar dialog to mainwindow'''

	uimanager_xml = '''
	<menubar name='menubar'>
		<menu action='file_menu'>
			<placeholder name='page_modification_actions'>
				<menuitem action='archive_page'/>
		<menu action='view_menu'>
			<placeholder name='plugin_items'>
				<menuitem action='show_archived_versions'/>

	@action(_('_Archive Page')) # T: menu item
	def archive_page(self):
		self.window.ui.save_page() # XXX
		notebook = self.window.ui.notebook # XXX
		page = self.window.pageview.get_page()
		self.plugin.archive_page(notebook, page)

	@action(_('_Archived Versions...'), tooltip=_('Show Archived Versions...')) # T: menu item
	def show_archived_versions(self):
		notebook = self.window.ui.notebook # XXX
		page = self.window.pageview.get_page()
		opener = self.window.get_resource_opener()
		dialog = ArchivedVersionsDialog(self.window, self.plugin, notebook, page, opener)

#~ @extends('SearchDialog')
#~ class SearchDialogExtension(WindowExtension):

	#~ def __init__(self, plugin, window):
		#~ WindowExtension.__init__(self, plugin, window)

		# Install filter + button

	#~ def teardown(self):
		# REmove filter + button

class ArchivedVersionsDialog(Dialog):

	# TODO "open" button that opens current selected version

	def __init__(self, parent, plugin, notebook, page, opener):
		Dialog.__init__(self, parent, _('Archived Pages'), # T: dialog title
			defaultwindowsize=(400, 300),

		label = gtk.Label(_('Archived versions of: <b>%s</b>') % # T: label, "%s" is the full page name
		self.vbox.pack_start(label, False)

		model = gtk.ListStore(str, str) # date, page
		for path, date in plugin.list_archived_versions(notebook, page):
			# TODO - get ctime and mtime for page
			# TODO - get more user readable rendering of date - same as in "recent pages" dialog
			date = "%s-%s-%s" % (date[0:4], date[4:6] ,date[6:8]) # year, month, day

		model.set_sort_column_id(0, gtk.SORT_DESCENDING)

		treeview = BrowserTreeView()

		cell_renderer = gtk.CellRendererText()
		for name, i in (
			(_('Date'), 0), # T: Column header archive dialog
			(_('Page'), 1), # T: Column header archive dialog
			column = gtk.TreeViewColumn(name, cell_renderer, text=i)


		def on_open_page(view, path, col):
			page = Path( view.get_model()[path][1].decode('utf-8') )

		treeview.connect('row-activated', on_open_page)

Mailing list:
Post to     :
Unsubscribe :
More help   :

Reply via email to