Example #1
0
	def __init__(self, ui):
		gobject.GObject.__init__(self)
		self.ui = ui
		assert 'name' in self.plugin_info, 'Plugins should provide a name in the info dict'
		assert 'description' in self.plugin_info, 'Plugins should provide a description in the info dict'
		assert 'author' in self.plugin_info, 'Plugins should provide a author in the info dict'
		if self.plugin_preferences:
			assert isinstance(self.plugin_preferences[0], tuple), 'BUG: preferences should be defined as tupels'
		section = self.__class__.__name__
		self.preferences = self.ui.preferences[section]
		for key, type, label, default in self.plugin_preferences:
				self.preferences.setdefault(key, default)
		self.uistate = ListDict()
		self._is_image_generator_plugin = False
		self.ui.connect_after('open-notebook', self._merge_uistate)
Example #2
0
	def __init__(self, ui):
		'''Constructor

		@param ui: a L{NotebookInterface} object

		@implementation: sub-classes may override this constructor,
		but it is advised instead to do the work of initializing the
		plugin in the methods L{initialize_ui()}, L{initialize_notebook()},
		L{finalize_ui()} and L{finalize_notebook()} where apropriate.
		'''
		# NOTE: this method is decorated by the meta class
		gobject.GObject.__init__(self)
		self.ui = ui
		assert 'name' in self.plugin_info, 'Plugins should provide a name in the info dict'
		assert 'description' in self.plugin_info, 'Plugins should provide a description in the info dict'
		assert 'author' in self.plugin_info, 'Plugins should provide a author in the info dict'
		if self.plugin_preferences:
			assert isinstance(self.plugin_preferences[0], tuple), 'BUG: preferences should be defined as tuples'
		section = self.__class__.__name__
		self.preferences = self.ui.preferences[section]
		for pref in self.plugin_preferences:
				if len(pref) == 4:
					key, type, label, default = pref
					self.preferences.setdefault(key, default)
				else:
					key, type, label, default, check = pref
					self.preferences.setdefault(key, default, check=check)

		self._is_image_generator_plugin = False

		if self.ui.notebook:
			section = self.__class__.__name__
			self.uistate = self.ui.uistate[section]
		else:
			self.uistate = ListDict()

		# Find related extension classes in same module
		# any class with the "__extends__" field will be added
		# (Being subclass of Extension is optional)
		self.extension_classes = {}
		self._extensions = []
		module = get_module(self.__class__.__module__)
		for name, klass in inspect.getmembers(module, inspect.isclass):
			if hasattr(klass, '__extends__') and klass.__extends__:
				assert klass.__extends__ not in self.extension_classes, \
					'Extension point %s used multiple times in %s' % (klass.__extends__, module.__name__)
				self.extension_classes[klass.__extends__] = klass
Example #3
0
class PluginClass(ConnectorMixin, gobject.GObject):
	'''Base class for plugins. Every module containing a plugin should
	have exactly one class derived from this base class. That class
	will be initialized when the plugin is loaded.

	Plugin classes should define two class attributes: L{plugin_info} and
	L{plugin_preferences}. Optionally, they can also define the class
	attribute L{is_profile_independent}.

	This class inherits from L{ConnectorMixin} and calls
	L{ConnectorMixin.disconnect_all()} when the plugin is destroyed.
	Therefore it is highly recommended to use the L{ConnectorMixin}
	methods in sub-classes.

	@cvar plugin_info: A dict with basic information about the plugin,
	it should contain at least the following keys:

		- C{name}: short name
		- C{description}: one paragraph description
		- C{author}: name of the author
		- C{help}: page name in the manual (optional)

	This info will be used e.g. in the plugin tab of the preferences
	dialog.

	@cvar plugin_preferences: A tuple or list defining the global
	preferences for this plugin. Each preference is defined by a 4-tuple
	containing the following items:

		1. the key in the config file
		2. an option type (see InputForm.add_inputs for more details)
		3. a label to show in the dialog
		4. a default value

	These preferences will be initialized to their default value if not
	configured by the user and the values can be found in the
	L{preferences} dict. The type and label will be used to render a
	default configure dialog when triggered from the preferences dialog.
	Changes to these preferences will be stored in a config file so
	they are persistent.

	@cvar is_profile_independent: A boolean indicating that the plugin
	configuration is global and not meant to change between notebooks.
	The default value (if undefined) is False. Plugins that set
	L{is_profile_independent} to True will be initialized before
	opening the notebook. All other plugins will only be loaded after
	the notebook is initialized.

	@ivar ui: the main application object, e.g. an instance of
	L{zim.gui.GtkInterface} or L{zim.www.WWWInterface}
	@ivar preferences: a C{ListDict()} with plugin preferences

	Preferences are the global configuration of the plugin, they are
	stored in the X{preferences.conf} config file.

	@ivar uistate: a C{ListDict()} with plugin ui state

	The "uistate" is the per notebook state of the interface, it is
	intended for stuff like the last folder opened by the user or the
	size of a dialog after resizing. It is stored in the X{state.conf}
	file in the notebook cache folder.

	@signal: C{preferences-changed ()}: emitted after the preferences
	were changed, triggers the L{do_preferences_changed} handler
	'''

	__metaclass__ = PluginClassMeta

	# define signals we want to use - (closure type, return type and arg types)
	__gsignals__ = {
		'preferences-changed': (gobject.SIGNAL_RUN_LAST, None, ()),
	}

	plugin_info = {}

	plugin_preferences = ()

	is_profile_independent = False

	@classmethod
	def check_dependencies_ok(klass):
		'''Checks minimum dependencies are met

		@returns: C{True} if this plugin can be loaded
		'''
		check, dependencies = klass.check_dependencies()
		return check

	@classmethod
	def check_dependencies(klass):
		'''Checks what dependencies are met and gives details

		@returns: a boolean telling overall dependencies are met,
		followed by a list with details.

		This list consists of 3-tuples consisting of a (short)
		description of the dependency, a boolean for dependency being
		met, and a boolean for this dependency being optional or not.

		@implementation: must be implemented in sub-classes that have
		one or more (external) dependencies.
		'''
		return (True, [])

	def __init__(self, ui):
		'''Constructor

		@param ui: a L{NotebookInterface} object

		@implementation: sub-classes may override this constructor,
		but it is advised instead to do the work of initializing the
		plugin in the methods L{initialize_ui()}, L{initialize_notebook()},
		L{finalize_ui()} and L{finalize_notebook()} where apropriate.
		'''
		# NOTE: this method is decorated by the meta class
		gobject.GObject.__init__(self)
		self.ui = ui
		assert 'name' in self.plugin_info, 'Plugins should provide a name in the info dict'
		assert 'description' in self.plugin_info, 'Plugins should provide a description in the info dict'
		assert 'author' in self.plugin_info, 'Plugins should provide a author in the info dict'
		if self.plugin_preferences:
			assert isinstance(self.plugin_preferences[0], tuple), 'BUG: preferences should be defined as tuples'
		section = self.__class__.__name__
		self.preferences = self.ui.preferences[section]
		for pref in self.plugin_preferences:
				if len(pref) == 4:
					key, type, label, default = pref
					self.preferences.setdefault(key, default)
				else:
					key, type, label, default, check = pref
					self.preferences.setdefault(key, default, check=check)

		self._is_image_generator_plugin = False

		if self.ui.notebook:
			section = self.__class__.__name__
			self.uistate = self.ui.uistate[section]
		else:
			self.uistate = ListDict()

		# Find related extension classes in same module
		# any class with the "__extends__" field will be added
		# (Being subclass of Extension is optional)
		self.extension_classes = {}
		self._extensions = []
		module = get_module(self.__class__.__module__)
		for name, klass in inspect.getmembers(module, inspect.isclass):
			if hasattr(klass, '__extends__') and klass.__extends__:
				assert klass.__extends__ not in self.extension_classes, \
					'Extension point %s used multiple times in %s' % (klass.__extends__, module.__name__)
				self.extension_classes[klass.__extends__] = klass

	def _merge_uistate(self):
		# As a convenience we provide a uistate dict directly after
		# initialization of the plugin. However, in reality this
		# config file is only available after the notebook is opened.
		# Therefore we need to link the actual file and merge back
		# any defaults that were set during plugin intialization etc.
		if self.ui.uistate:
			section = self.__class__.__name__
			defaults = self.uistate
			self.uistate = self.ui.uistate[section]
			for key, value in defaults.items():
				self.uistate.setdefault(key, value)

	def _extension_point(self, obj):
		# TODO also check parent classes
		name = obj.__class__.__name__
		if name in self.extension_classes:
			ext = self.extension_classes[name](self, obj)
			ref = weakref.ref(obj, self._del_extension)
			self._extensions.append(ref)

	def _del_extension(self, ref):
		if ref in self._extensions:
			self._extensions.remove(ref)

	@property
	def extensions(self):
		extensions = [ref() for ref in self._extensions]
		return [e for e in extensions if e] # Filter out None values

	def initialize_ui(self, ui):
		'''Callback called during construction of the ui.

		Called after construction of the plugin when the application
		object is available. At this point the construction of the the
		interface itself does not yet need to be complete. Typically
		used to initialize any interface components of the plugin.

		@note: the plugin should check the C{ui_type} attribute of the
		application object to distinguish the Gtk from the WWW
		interface and only do something for the correct interface.

		@param ui: a L{NotebookInterface} object, e.g.
		L{zim.gui.GtkInterface}

		@implementation: optional, may be implemented by subclasses.
		'''
		pass

	def initialize_notebook(self, notebookuri):
		'''Callback called before construction of the notebook

		This callback is called before constructing the notebook object.
		It is intended for a fairly specific type of plugins that
		may want to do some manipulation of the notebook location
		before actually loading the notebook, e.g. auto-mounting
		a filesystem.

		Not called when plugin is constructed while notebook already
		exists.

		@param notebookuri: the URI of the notebook location

		@implementation: optional, may be implemented by subclasses.
		'''
		pass

	def finalize_notebook(self, notebook):
		'''Callback called once the notebook object is created

		This callback is called once the notebook object is constructed
		and loaded in the application object. This is a logical point
		to do any intialization that requires the notebook the be
		available.

		@param notebook: the L{Notebook} object

		@implementation: optional, may be implemented by subclasses.
		'''
		self._extension_point(notebook)

	def finalize_ui(self, ui):
		'''Callback called just before entering the main loop

		Called after the interface is fully initialized and has a
		notebook object loaded. Typically used for any initialization
		that needs the full application to be ready.

		@note: the plugin should check the C{ui_type} attribute of the
		application object to distinguish the Gtk from the WWW
		interface and only do something for the correct interface.

		@param ui: a L{NotebookInterface} object, e.g.
		L{zim.gui.GtkInterface}

		@implementation: optional, may be implemented by subclasses.
		'''
		# NOTE: this method is decorated by the meta class
		pass

	def do_decorate_window(self, window):
		'''Callback which is called for each window and dialog that
		opens in zim.
		May be overloaded by sub classes
		'''
		self._extension_point(window)

		# HACK
		if hasattr(window, 'pageview'):
			self._extension_point(window.pageview)

	def do_preferences_changed(self):
		'''Handler called when preferences are changed by the user

		@implementation: optional, may be implemented by subclasses.
		to apply relevant changes.
		'''
		pass

	def destroy(self):
		'''Destroy the plugin object and all extensions
		It is only called when a user actually disables the plugin,
		not when the application exits.

		Destroys all active extensions and disconnects all signals.
		This should revert any changes the plugin made to the
		application (although preferences etc. can be left in place).
		'''
		### TODO clean up this section when all plugins are ported
		if self.ui.ui_type == 'gtk':
			try:
				self.ui.remove_ui(self)
				self.ui.remove_actiongroup(self)
			except:
				logger.exception('Exception while disconnecting %s', self)

			if self._is_image_generator_plugin:
				try:
					self.ui.mainpage.pageview.unregister_image_generator_plugin(self)
				except:
					logger.exception('Exception while disconnecting %s', self)
		###

		while self._extensions:
			ref = self._extensions.pop()
			obj = ref()
			if obj:
				obj.destroy()

		try:
			self.disconnect_all()
		except:
			logger.exception('Exception while disconnecting %s', self)

	def toggle_action(self, action, active=None):
		'''Trigger a toggle action.

		This is a convenience method to help defining toggle actions
		in the menu or toolbar. It helps to keep the menu item
		or toolbar item in sync with your internal state.
		A typical usage to define a handler for a toggle action called
		'show_foo' would be::

			def show_foo(self, show=None):
				self.toggle_action('show_foo', active=show)

			def do_show_foo(self, show=None):
				if show is None:
					show = self.actiongroup.get_action('show_foo').get_active()

				# ... the actual logic for toggling on / off 'foo'

		This way you have a public method C{show_foo()} that can be
		called by anybody and a handler C{do_show_foo()} that is
		called when the user clicks the menu item. The trick is that
		when C{show_foo()} is called, the menu item is also updates.

		@param action: the name of the action item
		@param active: when C{None} the item is toggled with respect
		to it's current state, when C{True} or C{False} forces a state
		'''
		name = action
		action = self.actiongroup.get_action(name)
		if active is None or active != action.get_active():
			action.activate()
		else:
			method = getattr(self, 'do_'+name)
			method(active)

	#~ def remember_decorated_window(self, window):
		#~ import weakref
		#~ if not hasattr(self, '_decorated_windows'):
			#~ self._decorated_windows = []
		#~ ref = weakref.ref(window, self._clean_decorated_windows_list)
		#~ self._decorated_windows.append(ref)

	#~ def _clean_decorated_windows_list(self, *a):
		#~ self._decorated_windows = [
			#~ ref for ref in self._decorated_windows
				#~ if not ref() is None ]

	#~ def get_decorated_windows(self):
		#~ if not hasattr(self, '_decorated_windows'):
			#~ return []
		#~ else:
			#~ self._clean_decorated_windows_list()
			#~ return [ref() for ref in self._decorated_windows]

	def register_image_generator_plugin(self, type):
		'''Convenience method to register a plugin that adds a type
		of image objects

		@param type: the type of the objects (e.g. "equation")

		@todo: document image geneartor plugins
		'''
		self.ui.mainwindow.pageview.register_image_generator_plugin(self, type)
		self._is_image_generator_pluging = True
Example #4
0
class PluginClass(gobject.GObject):
	'''Base class for plugins. Every module containing a plugin should
	have exactly one class derived from this base class. That class
	will be initialized when the plugin is loaded.

	Plugins should define two class attributes. The first is a dict
	called 'plugin_info'. It can contain the following keys:

		* name - short name
		* description - one paragraph description
		* author - name of the author
		* help - page name in the manual (optional)

	This info will be used e.g. in the plugin tab of the preferences
	dialog.

	Secondly a tuple can be defined called 'plugin_preferences'.
	Each item in this list should in turn be tuple containing four items:

		* the key in the config file
		* an option type (see Dialog.add_fields() for more details)
		* a label to show in the dialog
		* a default value

	These preferences will be initialized if not set and the actual values
	can be found in the 'preferences' attribute. The type and label will
	be used to render a default configure dialog when triggered from
	the preferences dialog.
	'''

	plugin_info = {}

	plugin_preferences = ()

	# define signals we want to use - (closure type, return type and arg types)
	__gsignals__ = {
		'preferences-changed': (gobject.SIGNAL_RUN_LAST, None, ()),

	}

	def __init__(self, ui):
		gobject.GObject.__init__(self)
		self.ui = ui
		assert 'name' in self.plugin_info, 'Plugins should provide a name in the info dict'
		assert 'description' in self.plugin_info, 'Plugins should provide a description in the info dict'
		assert 'author' in self.plugin_info, 'Plugins should provide a author in the info dict'
		if self.plugin_preferences:
			assert isinstance(self.plugin_preferences[0], tuple), 'BUG: preferences should be defined as tupels'
		section = self.__class__.__name__
		self.preferences = self.ui.preferences[section]
		for key, type, label, default in self.plugin_preferences:
				self.preferences.setdefault(key, default)
		self.uistate = ListDict()
		self._is_image_generator_plugin = False
		self.ui.connect_after('open-notebook', self._merge_uistate)

	def _merge_uistate(self, *a):
		# As a convenience we provide a uistate dict directly after
		# initialization of the plugin. However, in reality this
		# config file is only available after the notebook is openend.
		# Therefore we need to link the actual file and merge back
		# any defaults that were set during plugin intialization etc.
		if self.ui.uistate:
			section = self.__class__.__name__
			defaults = self.uistate
			self.uistate = self.ui.uistate[section]
			for key, value in defaults.items():
				self.uistate.setdefault(key, value)

	def do_preferences_changed(self):
		'''Handler called when preferences are changed by the user.
		Can be overloaded by sub classes to apply relevant changes.
		'''
		pass

	def disconnect(self):
		'''Disconnect the plugin object from the ui, should revert
		any changes it made to the application. Default handler removes
		any GUI actions and menu items that were defined.
		'''
		if self.ui.ui_type == 'gtk':
			self.ui.remove_ui(self)
			self.ui.remove_actiongroup(self)
			if self._is_image_generator_plugin:
				self.ui.mainpage.pageview.unregister_image_generator_plugin(self)

	def toggle_action(self, action, active=None):
		'''Trigger a toggle action. If 'active' is None it is toggled, else it
		is forced to state of 'active'. This method helps to keep the menu item
		or toolbar item asociated with the action in sync with your internal
		state. A typical usage to define a handler for a toggle action called
		'show_foo' would be:

			def show_foo(self, show=None):
				self.toggle_action('show_foo', active=show)

			def do_show_foo(self, show=None):
				if show is None:
					show = self.actiongroup.get_action('show_foo').get_active()

				# ... the actual logic for toggling on / off 'foo'

		'''
		name = action
		action = self.actiongroup.get_action(name)
		if active is None or active != action.get_active():
			action.activate()
		else:
			method = getattr(self, 'do_'+name)
			method(active)

	def register_image_generator_plugin(self, type):
		self.ui.mainwindow.pageview.register_image_generator_plugin(self, type)
		self._is_image_generator_pluging = True