コード例 #1
0
ファイル: sourceview.py プロジェクト: robla/zim-desktop-wiki
class SourceViewObjectType(InsertedObjectTypeExtension):

	name = 'code'

	label = _('Code Block') # T: menu item

	object_attr = {
		'lang': String(None),
		'linenumbers': Boolean(True),
	}

	def __init__(self, plugin, objmap):
		self._widgets = WeakSet()
		self.preferences = plugin.preferences
		InsertedObjectTypeExtension.__init__(self, plugin, objmap)
		self.connectto(self.preferences, 'changed', self.on_preferences_changed)

	def new_model_interactive(self, parent, notebook, page):
		lang = InsertCodeBlockDialog(parent).run()
		if lang is None:
			raise ValueError # dialog cancelled
		else:
			attrib = self.parse_attrib({'lang': lang})
			return SourceViewBuffer(attrib, '')

	def model_from_data(self, notebook, page, attrib, text):
		return SourceViewBuffer(attrib, text)

	def data_from_model(self, buffer):
		return buffer.get_object_data()

	def create_widget(self, buffer):
		widget = SourceViewWidget(buffer)
		widget.set_preferences(self.preferences)
		self._widgets.add(widget)
		return widget

	def on_preferences_changed(self, preferences):
		for widget in self._widgets:
			widget.set_preferences(preferences)

	def format_html(self, dumper, attrib, data):
		# to use highlight.js add the following to your template:
		#<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.5.0/styles/default.min.css">
		#<script src="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.5.0/highlight.min.js"></script>
		#<script>hljs.initHighlightingOnLoad();</script>
		#Map GtkSourceView language ids match with Highlight.js language ids.
		#http://packages.ubuntu.com/precise/all/libGtkSource.0-common/filelist
		#http://highlightjs.readthedocs.io/en/latest/css-classes-reference.html
		sh_map = {'dosbatch': 'dos'}
		sh_lang = sh_map[attrib['lang']] if attrib['lang'] in sh_map else attrib['lang']
		# TODO: some template instruction to be able to use other highlighters as well?
		output = ['<pre><code class="%s">' % html_encode(sh_lang)] # for syntaxhigligther
		#class="brush: language;" works with SyntaxHighlighter 2.0.278, 3 & 4
		#output = ['<pre class="brush: %s;">' % html_encode(sh_lang)] # for syntaxhigligther

		output.append(html_encode(data))
		output.append('</code></pre>\n')

		return output
コード例 #2
0
ファイル: __init__.py プロジェクト: tmhorne/simplewiki
    def __init__(self, config=None):
        '''Constructor
		@param config: a L{ConfigManager} object that is used to load
		plugin preferences.
		Defaults to a L{VirtualConfigManager} for testing.
		'''
        assert 'name' in self.plugin_info, 'Missing "name" in plugin_info'
        assert 'description' in self.plugin_info, 'Missing "description" in plugin_info'
        assert 'author' in self.plugin_info, 'Missing "author" in plugin_info'
        self.extensions = WeakSet()

        if self.plugin_preferences:
            assert isinstance(
                self.plugin_preferences[0],
                tuple), 'BUG: preferences should be defined as tuples'

        self.config = config or VirtualConfigManager()
        self.preferences = self.config.get_config_dict(
            '<profile>/preferences.conf')[self.config_key]

        for pref in self.plugin_preferences:
            if len(pref) == 4:
                key, type, label, default = pref
                self.preferences.setdefault(key, default)
                #~ print ">>>>", key, default, '--', self.preferences[key]
            else:
                key, type, label, default, check = pref
                self.preferences.setdefault(key, default, check=check)
                #~ print ">>>>", key, default, check, '--', self.preferences[key]

        self.load_extensions_classes()
コード例 #3
0
ファイル: __init__.py プロジェクト: rayleyva/zim-desktop-wiki
    def __init__(self, config=None):
        '''Constructor
		@param config: a L{ConfigManager} object that is used to load
		plugin preferences.
		Defaults to a L{VirtualConfigManager} for testing.
		'''
        assert 'name' in self.plugin_info, 'Missing "name" in plugin_info'
        assert 'description' in self.plugin_info, 'Missing "description" in plugin_info'
        assert 'author' in self.plugin_info, 'Missing "author" in plugin_info'
        self.extensions = WeakSet()

        if self.plugin_preferences:
            assert isinstance(
                self.plugin_preferences[0],
                tuple), 'BUG: preferences should be defined as tuples'

        self.config = config or VirtualConfigManager()
        self.preferences = self.config.get_config_dict(
            '<profile>/preferences.conf')[self.config_key]
        self._init_config(self.preferences, self.plugin_preferences)
        self._init_config(self.preferences, self.plugin_notebook_properties
                          )  # defaults for the properties are preferences

        self.load_insertedobject_types()
        self.load_extensions_classes()
コード例 #4
0
    def __init__(self, plugin, objmap):

        #		InsertSymbolDialog(self.plugi).run()
        self._widgets = WeakSet()
        self.preferences = plugin.preferences
        InsertedObjectTypeExtension.__init__(self, plugin, objmap)
        self.connectto(self.preferences, 'changed',
                       self.on_preferences_changed)
コード例 #5
0
    def _reset(self):
        self._preferences = ConfigManager.preferences['General']
        self._preferences.setdefault('plugins', [])

        self._plugins = {}
        self._extendables = WeakSet()
        self.failed = set()

        self.insertedobjects = InsertedObjectTypeMap()
コード例 #6
0
ファイル: sourceview.py プロジェクト: hjq300/zim-wiki
 def __init__(self, attrib, data, preferences):
     if data.endswith('\n'):
         data = data[:-1]
         # If we have trailing \n it looks like an extra empty line
         # in the buffer, so we default remove one
     CustomObjectClass.__init__(self, attrib, data)
     self.preferences = preferences
     self.buffer = None
     self._widgets = WeakSet()
コード例 #7
0
ファイル: __init__.py プロジェクト: pombredanne/zim
	def __init__(self, config=None):
		self.config = config or VirtualConfigManager()
		self._preferences = \
			self.config.get_config_dict('<profile>/preferences.conf')
		self.general_preferences = self._preferences['General']
		self.general_preferences.setdefault('plugins', [])

		self._plugins = {}
		self._extendables = WeakSet()

		self._load_plugins()

		self.connectto(self._preferences, 'changed',
			self.on_preferences_changed)
コード例 #8
0
class TableViewObjectType(InsertedObjectTypeExtension):

    name = 'runnable'

    label = _('Runnable')

    object_attr = {
        'program': String('grep'),
        'arguments': String('-r "hello!"')
    }

    def __init__(self, plugin, objmap):

        #		InsertSymbolDialog(self.plugi).run()
        self._widgets = WeakSet()
        self.preferences = plugin.preferences
        InsertedObjectTypeExtension.__init__(self, plugin, objmap)
        self.connectto(self.preferences, 'changed',
                       self.on_preferences_changed)

    def data_from_model(self, buffer):
        return buffer.get_object_data()

    def model_from_data(self, notebook, page, attrib, data):
        return TableModel(attrib)

    def model_from_element(self, attrib, element):
        assert ElementTree.iselement(element)
        attrib = self.parse_attrib(attrib)
        return TableModel(attrib)

    def create_widget(self, model):
        widget = TableViewWidget(model)
        self._widgets.add(widget)
        return widget

    def on_preferences_changed(self, preferences):
        for widget in self._widgets:
            widget.set_preferences(preferences)

    def dump(self, builder):

        builder.start("start")
        builder.data("data")
        builder.end("end")
コード例 #9
0
ファイル: sourceview.py プロジェクト: hjq300/zim-wiki
	def __init__(self, attrib, data, preferences):
		if data.endswith('\n'):
			data = data[:-1]
			# If we have trailing \n it looks like an extra empty line
			# in the buffer, so we default remove one
		CustomObjectClass.__init__(self, attrib, data)
		self.preferences = preferences
		self.buffer = None
		self._widgets = WeakSet()
コード例 #10
0
	def __init__(self):
		assert 'name' in self.plugin_info, 'Missing "name" in plugin_info'
		assert 'description' in self.plugin_info, 'Missing "description" in plugin_info'
		assert 'author' in self.plugin_info, 'Missing "author" in plugin_info'
		self.extensions = WeakSet()

		if self.plugin_preferences:
			assert isinstance(self.plugin_preferences[0], tuple), 'BUG: preferences should be defined as tuples'

		self.preferences = ConfigManager.preferences[self.config_key]
		self._init_config(self.preferences, self.plugin_preferences)
		self._init_config(self.preferences, self.plugin_notebook_properties) # defaults for the properties are preferences

		self.extension_classes = list(self.discover_classes(ExtensionBase))
コード例 #11
0
ファイル: __init__.py プロジェクト: tmhorne/simplewiki
    def __init__(self, config=None):
        '''Constructor
		Constructor will directly load a list of default plugins
		based on the preferences in the config. Failures while loading
		these plugins will be logged but not raise errors.

		@param config: a L{ConfigManager} object that is passed along
		to the plugins and is used to load plugin preferences.
		Defaults to a L{VirtualConfigManager} for testing.
		'''
        self.config = config or VirtualConfigManager()
        self._preferences = \
         self.config.get_config_dict('<profile>/preferences.conf')
        self.general_preferences = self._preferences['General']
        self.general_preferences.setdefault('plugins', [])

        self._plugins = {}
        self._extendables = WeakSet()

        self._load_plugins()

        self.connectto(self._preferences, 'changed',
                       self.on_preferences_changed)
コード例 #12
0
ファイル: tableeditor.py プロジェクト: hjq300/zim-wiki
    def __init__(self, attrib, header, rows, preferences):
        '''
		Creates a new object which can displayed within the page
		:param attrib: aligns, wraps
		:param header: titles of the table as list
		:param rows: body-rows of the table as list of lists
		:param preferences: optionally some preferences
		'''
        _attrib = {}
        for k, v in attrib.iteritems():
            if isinstance(v, list):
                v = ','.join(map(str, v))
            _attrib[k] = v
        CustomObjectClass.__init__(self, _attrib, [header] + rows)
        self.attrib = {'type': OBJECT_TYPE}  # just to be sure

        self._tableattrib = attrib
        self._header = header
        self._rows = rows
        self._widgets = WeakSet()
        self._liststore = None  # shared model between widgets

        self.preferences = preferences
コード例 #13
0
ファイル: __init__.py プロジェクト: pombredanne/zim
	def __init__(self, config=None):
		assert 'name' in self.plugin_info
		assert 'description' in self.plugin_info
		assert 'author' in self.plugin_info
		self.extensions = WeakSet()

		if self.plugin_preferences:
			assert isinstance(self.plugin_preferences[0], tuple), 'BUG: preferences should be defined as tuples'

		self.config = config or VirtualConfigManager()
		self.preferences = self.config.get_config_dict('<profile>/preferences.conf')[self.config_key]

		for pref in self.plugin_preferences:
				if len(pref) == 4:
					key, type, label, default = pref
					self.preferences.setdefault(key, default)
					#~ print ">>>>", key, default, '--', self.preferences[key]
				else:
					key, type, label, default, check = pref
					self.preferences.setdefault(key, default, check=check)
					#~ print ">>>>", key, default, check, '--', self.preferences[key]

		self.load_extensions_classes()
コード例 #14
0
    def register_object(self, type, factory):
        '''Register a factory method or class for a specific object type.
		@param type: the object type as string (unique name)
		@param factory: can be either an object class or a method,
		should callable and return objects. When constructing objects
		this factory will be called as::

			factory(attrib, text)

		Where:
		  - C{attrib} is a dict with attributes
		  - C{text} is the main text source of the object

		@returns: a previously set factory for C{type} or C{None}
		'''
        logger.debug('Registered object %s', type)
        type = type.lower()
        old = self.factories.get(type)
        self.factories[type] = factory
        self.objects[type] = WeakSet()
        return old
コード例 #15
0
ファイル: sourceview.py プロジェクト: zrf1/zim-desktop-wiki
class SourceViewObject(CustomObjectClass):

	OBJECT_ATTR = {
		'type': String('code'),
		'lang': String(None),
		'linenumbers': Boolean(True),
	}

	def __init__(self, attrib, data, preferences):
		if data.endswith('\n'):
			data = data[:-1]
			# If we have trailing \n it looks like an extra empty line
			# in the buffer, so we default remove one
		CustomObjectClass.__init__(self, attrib, data)
		self.preferences = preferences
		self.buffer = None
		self._widgets = WeakSet()

	def get_widget(self):
		if not self.buffer:
			self.buffer = gtksourceview2.Buffer()
			self.buffer.set_text(self._data)
			self.buffer.connect('modified-changed', self.on_modified_changed)
			self.buffer.set_highlight_matching_brackets(True)
			self.buffer.set_modified(False)
			self._data = None

			try:
				if self._attrib['lang']:
					self.buffer.set_language(lm.get_language(self._attrib['lang']))
			except:
				logger.exception('Could not set language for sourceview: %s', lang)

		widget = SourceViewWidget(self, self.buffer)
		self._widgets.add(widget)

		widget.view.set_show_line_numbers(self._attrib['linenumbers'])
		widget.set_preferences(self.preferences)
		return widget

	def preferences_changed(self):
		for widget in self._widgets:
			widget.set_preferences(self.preferences)

	def on_modified_changed(self, buffer):
		# Sourceview changed, set change on oject, reset state of
		# sourceview buffer so we get a new signal with next change
		if buffer.get_modified():
			self.set_modified(True)
			buffer.set_modified(False)

	def get_data(self):
		'''Returns data as text.'''
		if self.buffer:
			bounds = self.buffer.get_bounds()
			text = self.buffer.get_text(bounds[0], bounds[1])
			text += '\n' # Make sure we always have a trailing \n
			return text
		else:
			return self._data

	def dump(self, format, dumper, linker=None):
		if format == "html":
			if self._attrib['lang']:
				''' to use highlight.js add the following to your template:
				<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.5.0/styles/default.min.css">
				<script src="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.5.0/highlight.min.js"></script>
				<script>hljs.initHighlightingOnLoad();</script>
				Map GtkSourceView language ids match with Highlight.js language ids.
				http://packages.ubuntu.com/precise/all/libgtksourceview2.0-common/filelist
				http://highlightjs.readthedocs.io/en/latest/css-classes-reference.html
                '''
				sh_map = {'dosbatch': 'dos'}
				sh_lang = sh_map[self._attrib['lang']] if self._attrib['lang'] in sh_map else self._attrib['lang']
				# TODO: some template instruction to be able to use other highlighters as well?
				output = ['<pre><code class="%s">' % html_encode(sh_lang)] # for syntaxhigligther
				'''' class="brush: language;" works with SyntaxHighlighter 2.0.278, 3 & 4
				output = ['<pre class="brush: %s;">' % html_encode(sh_lang)] # for syntaxhigligther
				'''
			else:
				output = ['<pre>\n']
			data = self.get_data()
			data = html_encode(data) # XXX currently dumper gives encoded lines - NOK
			#if self._attrib['linenumbers']:
			#	for i, l in enumerate(data.splitlines(1)):
			#		output.append('%i&nbsp;' % (i+1) + l)
			#else:
			output.append(data) # ignoring numbering for html - syntaxhighlighter takes care of that
			if self._attrib['lang']:
				output.append('</code></pre>\n')
			else:
				output.append('</pre>\n')
			return output
		return CustomObjectClass.dump(self, format, dumper, linker)

	def set_language(self, lang):
		'''Set language in SourceView.'''
		self._attrib['lang'] = lang
		self.set_modified(True)

		if self.buffer:
			if lang is None:
				self.buffer.set_language(None)
			else:
				self.buffer.set_language(lm.get_language(lang))

	def show_line_numbers(self, show):
		'''Toggles line numbers in SourceView.'''
		self._attrib['linenumbers'] = show
		self.set_modified(True)

		for widget in self._widgets:
			widget.view.set_show_line_numbers(show)
コード例 #16
0
ファイル: __init__.py プロジェクト: tmhorne/simplewiki
class PluginManager(ConnectorMixin, collections.Mapping):
    '''Manager that maintains a set of active plugins

	This class is the interface towards the rest of the application to
	load/unload plugins and to let plugins extend specific application
	objects.

	All object that want to instantiate new objects that are extendable
	need a reference to the plugin manager object that is instantiated
	when the application starts. When you instatiate a new object and
	want to present it for plugin extension, call the L{extend()} method.

	This object behaves as a dictionary with plugin object names as
	keys and plugin objects as value
	'''
    def __init__(self, config=None):
        '''Constructor
		Constructor will directly load a list of default plugins
		based on the preferences in the config. Failures while loading
		these plugins will be logged but not raise errors.

		@param config: a L{ConfigManager} object that is passed along
		to the plugins and is used to load plugin preferences.
		Defaults to a L{VirtualConfigManager} for testing.
		'''
        self.config = config or VirtualConfigManager()
        self._preferences = \
         self.config.get_config_dict('<profile>/preferences.conf')
        self.general_preferences = self._preferences['General']
        self.general_preferences.setdefault('plugins', [])

        self._plugins = {}
        self._extendables = WeakSet()

        self._load_plugins()

        self.connectto(self._preferences, 'changed',
                       self.on_preferences_changed)

    def __getitem__(self, name):
        return self._plugins[name]

    def __iter__(self):
        return iter(sorted(self._plugins.keys()))
        # sort to make operation predictable - easier debugging

    def __len__(self):
        return len(self._plugins)

    def _load_plugins(self):
        '''Load plugins based on config'''
        for name in sorted(self.general_preferences['plugins']):
            try:
                self.load_plugin(name)
            except:
                logger.exception('Exception while loading plugin: %s', name)
                self.general_preferences['plugins'].remove(name)

    @classmethod
    def list_installed_plugins(klass):
        '''Lists plugin names for all installed plugins
		@returns: a set of plugin names
		'''
        # List "zim.plugins" sub modules based on __path__ because this
        # parameter determines what folders will considered when importing
        # sub-modules of the this package once this module is loaded.
        plugins = set()
        for dir in __path__:
            dir = Dir(dir)
            for candidate in dir.list():  # returns [] if dir does not exist
                if candidate.startswith('_') or candidate == 'base':
                    continue
                elif candidate.endswith('.py'):
                    plugins.add(candidate[:-3])
                elif zim.fs.isdir(dir.path+'/'+candidate) \
                and os.path.exists(dir.path+'/'+candidate+'/__init__.py'):
                    plugins.add(candidate)
                else:
                    pass

        return plugins

    @classmethod
    def get_plugin_class(klass, name):
        '''Get the plugin class for a given name

		@param name: the plugin name (e.g. "calendar")
		@returns: the plugin class object
		'''
        modname = 'zim.plugins.' + name
        mod = get_module(modname)
        return lookup_subclass(mod, PluginClass)

    @SignalHandler
    def on_preferences_changed(self, o):
        current = set(self._plugins.keys())
        new = set(self.general_preferences['plugins'])

        for name in current - new:
            try:
                self.remove_plugin(name)
            except:
                logger.exception('Exception while loading plugin: %s', name)

        for name in new - current:
            try:
                self.load_plugin(name)
            except:
                logger.exception('Exception while loading plugin: %s', name)
                self.general_preferences['plugins'].remove(name)

    def load_plugin(self, name):
        '''Load a single plugin by name

		When the plugin was loaded already the existing object
		will be returned. Thus for each plugin only one instance can be
		active.

		@param name: the plugin module name
		@returns: the plugin object
		@raises Exception: when loading the plugin failed
		'''
        assert isinstance(name, basestring)
        if name in self._plugins:
            return self._plugins[name]

        logger.debug('Loading plugin: %s', name)
        klass = self.get_plugin_class(name)
        if not klass.check_dependencies_ok():
            raise AssertionError, 'Dependencies failed for plugin %s' % name

        plugin = klass(self.config)
        self.connectto(plugin, 'extension-point-changed')
        self._plugins[name] = plugin

        for obj in self._extendables:
            try:
                plugin.extend(obj)
            except:
                logger.exception('Exception in plugin: %s', name)

        if not name in self.general_preferences['plugins']:
            with self.on_preferences_changed.blocked():
                self.general_preferences['plugins'].append(name)
                self.general_preferences.changed()

        return plugin

    def remove_plugin(self, name):
        '''Remove a plugin and it's extensions
		Fails silently if the plugin is not loaded.
		@param name: the plugin module name
		'''
        if name in self.general_preferences['plugins']:
            # Do this first regardless of exceptions etc.
            with self.on_preferences_changed.blocked():
                self.general_preferences['plugins'].remove(name)
                self.general_preferences.changed()

        try:
            plugin = self._plugins.pop(name)
            self.disconnect_from(plugin)
        except KeyError:
            pass
        else:
            logger.debug('Unloading plugin %s', name)
            plugin.destroy()

    def _foreach(self, func):
        # sort to make operation predictable - easier debugging
        for name, plugin in sorted(self._plugins.items()):
            try:
                func(plugin)
            except:
                logger.exception('Exception in plugin: %s', name)

    def extend(self, obj):
        '''Let any plugin extend the object instance C{obj}
		Will also remember the object (by a weak reference) such that
		plugins loaded after this call will also be called to extend
		C{obj} on their construction
		@param obj: arbitrary object that can be extended by plugins
		'''
        if not obj in self._extendables:
            self._foreach(lambda p: p.extend(obj))
            self._extendables.add(obj)

    def on_extension_point_changed(self, plugin, name):
        for obj in self._extendables:
            if obj.__class__.__name__ == name:
                try:
                    plugin.extend(obj)
                except:
                    logger.exception('Exception in plugin: %s', name)
コード例 #17
0
ファイル: sourceview.py プロジェクト: hjq300/zim-wiki
class SourceViewObject(CustomObjectClass):

    OBJECT_ATTR = {
        'type': String('code'),
        'lang': String(None),
        'linenumbers': Boolean(True),
    }

    def __init__(self, attrib, data, preferences):
        if data.endswith('\n'):
            data = data[:-1]
            # If we have trailing \n it looks like an extra empty line
            # in the buffer, so we default remove one
        CustomObjectClass.__init__(self, attrib, data)
        self.preferences = preferences
        self.buffer = None
        self._widgets = WeakSet()

    def get_widget(self):
        if not self.buffer:
            self.buffer = gtksourceview2.Buffer()
            self.buffer.set_text(self._data)
            self.buffer.connect('modified-changed', self.on_modified_changed)
            self.buffer.set_highlight_matching_brackets(True)
            self.buffer.set_modified(False)
            self._data = None

            try:
                if self._attrib['lang']:
                    self.buffer.set_language(
                        lm.get_language(self._attrib['lang']))
            except:
                logger.exception('Could not set language for sourceview: %s',
                                 lang)

        widget = SourceViewWidget(self, self.buffer)
        self._widgets.add(widget)

        widget.view.set_show_line_numbers(self._attrib['linenumbers'])
        widget.set_preferences(self.preferences)
        return widget

    def preferences_changed(self):
        for widget in self._widgets:
            widget.set_preferences(self.preferences)

    def on_modified_changed(self, buffer):
        # Sourceview changed, set change on oject, reset state of
        # sourceview buffer so we get a new signal with next change
        if buffer.get_modified():
            self.set_modified(True)
            buffer.set_modified(False)

    def get_data(self):
        '''Returns data as text.'''
        if self.buffer:
            bounds = self.buffer.get_bounds()
            text = self.buffer.get_text(bounds[0], bounds[1])
            text += '\n'  # Make sure we always have a trailing \n
            return text
        else:
            return self._data

    def dump(self, format, dumper, linker=None):
        if format == "html":
            if self._attrib['lang']:
                # class="brush: language;" works with SyntaxHighlighter 2.0.278
                # by Alex Gorbatchev <http://alexgorbatchev.com/SyntaxHighlighter/>
                # TODO: not all GtkSourceView language ids match with SyntaxHighlighter
                # language ids.
                # TODO: some template instruction to be able to use other highlighters as well?
                output = [
                    '<pre class="brush: %s;">\n' %
                    html_encode(self._attrib['lang'])
                ]
            else:
                output = ['<pre>\n']
            data = self.get_data()
            data = html_encode(
                data)  # XXX currently dumper gives encoded lines - NOK
            if self._attrib['linenumbers']:
                for i, l in enumerate(data.splitlines(1)):
                    output.append('%i&nbsp;' % (i + 1) + l)
            else:
                output.append(data)
            output.append('</pre>\n')
            return output
        return CustomObjectClass.dump(self, format, dumper, linker)

    def set_language(self, lang):
        '''Set language in SourceView.'''
        self._attrib['lang'] = lang
        self.set_modified(True)

        if self.buffer:
            if lang is None:
                self.buffer.set_language(None)
            else:
                self.buffer.set_language(lm.get_language(lang))

    def show_line_numbers(self, show):
        '''Toggles line numbers in SourceView.'''
        self._attrib['linenumbers'] = show
        self.set_modified(True)

        for widget in self._widgets:
            widget.view.set_show_line_numbers(show)
コード例 #18
0
class TableViewObjectType(InsertedObjectTypeExtension):

    name = 'table'

    label = _('Table')  # T: menu item
    verb_icon = 'zim-insert-table'

    object_attr = {
        'aligns': String(''),  # i.e. String(left,right,center)
        'wraps': String('')  # i.e. String(0,1,0)
    }

    def __init__(self, plugin, objmap):
        self._widgets = WeakSet()
        self.preferences = plugin.preferences
        InsertedObjectTypeExtension.__init__(self, plugin, objmap)
        self.connectto(self.preferences, 'changed',
                       self.on_preferences_changed)

    def new_model_interactive(self, parent, notebook, page):
        definition = EditTableDialog(parent).run()
        if definition is None:
            raise ValueError  # dialog cancelled

        ids, headers, wraps, aligns = definition
        attrib = self.parse_attrib({
            'aligns': ','.join(map(str, aligns)),
            'wraps': ','.join(map(str, wraps))
        })
        rows = [''] * len(headers)
        return TableModel(attrib, headers, rows)

    def model_from_data(self, notebook, page, attrib, data):
        tree = WikiParser().parse(data)
        element = tree._etree.getroot().find(
            'table')  # XXX - should use token interface instead
        if element is not None:
            return self.model_from_element(element.attrib, element)
        else:
            return TableModel(attrib, [data.strip()], [''])

    def model_from_element(self, attrib, element):
        assert ElementTree.iselement(element)
        attrib = self.parse_attrib(attrib)
        headers, rows = self._tabledom_to_list(element)
        return TableModel(attrib, headers, rows)

    def _tabledom_to_list(self, tabledata):
        '''
		Extracts necessary data out of a xml-table into a list structure

		:param tabledata: XML - formated as a zim-tree table-object
		:return: tuple of header-list and list of row lists -  ([h1,h2],[[r11,r12],[r21,r22])
		'''
        headers = [head.text for head in tabledata.findall('thead/th')]
        headers = list(map(CellFormatReplacer.zim_to_cell, headers))

        rows = []
        for trow in tabledata.findall('trow'):
            row = trow.findall('td')
            row = [
                ElementTree.tostring(r, 'unicode').replace('<td>', '').replace(
                    '</td>', '') for r in row
            ]
            row = list(map(CellFormatReplacer.zim_to_cell, row))
            rows.append(row)
        return headers, rows

    def create_widget(self, model):
        widget = TableViewWidget(model)
        widget.set_preferences(self.preferences)
        self._widgets.add(widget)
        return widget

    def on_preferences_changed(self, preferences):
        for widget in self._widgets:
            widget.set_preferences(preferences)

    def dump(self, builder, model):
        headers, attrib, rows = model.get_object_data()

        def append(tag, text):
            builder.start(tag, {})
            builder.data(text)
            builder.end(tag)

        builder.start(TABLE, dict(attrib))
        builder.start(HEADROW)
        for header in headers:
            append(HEADDATA, header)
        builder.end(HEADROW)
        for row in rows:
            builder.start(TABLEROW, {})
            for cell in row:
                append(TABLEDATA, cell)
            builder.end(TABLEROW)
        builder.end(TABLE)
コード例 #19
0
ファイル: objectmanager.py プロジェクト: hjq300/zim-wiki
 def __init__(self):
     self.factories = {}
     self.objects = {'fallback': WeakSet()}
     self.window_extensions = {}
コード例 #20
0
ファイル: sourceview.py プロジェクト: hjq300/zim-wiki
class SourceViewObject(CustomObjectClass):

	OBJECT_ATTR = {
		'type': String('code'),
		'lang': String(None),
		'linenumbers': Boolean(True),
	}

	def __init__(self, attrib, data, preferences):
		if data.endswith('\n'):
			data = data[:-1]
			# If we have trailing \n it looks like an extra empty line
			# in the buffer, so we default remove one
		CustomObjectClass.__init__(self, attrib, data)
		self.preferences = preferences
		self.buffer = None
		self._widgets = WeakSet()

	def get_widget(self):
		if not self.buffer:
			self.buffer = gtksourceview2.Buffer()
			self.buffer.set_text(self._data)
			self.buffer.connect('modified-changed', self.on_modified_changed)
			self.buffer.set_highlight_matching_brackets(True)
			self.buffer.set_modified(False)
			self._data = None

			try:
				if self._attrib['lang']:
					self.buffer.set_language(lm.get_language(self._attrib['lang']))
			except:
				logger.exception('Could not set language for sourceview: %s', lang)

		widget = SourceViewWidget(self, self.buffer)
		self._widgets.add(widget)

		widget.view.set_show_line_numbers(self._attrib['linenumbers'])
		widget.set_preferences(self.preferences)
		return widget

	def preferences_changed(self):
		for widget in self._widgets:
			widget.set_preferences(self.preferences)

	def on_modified_changed(self, buffer):
		# Sourceview changed, set change on oject, reset state of
		# sourceview buffer so we get a new signal with next change
		if buffer.get_modified():
			self.set_modified(True)
			buffer.set_modified(False)

	def get_data(self):
		'''Returns data as text.'''
		if self.buffer:
			bounds = self.buffer.get_bounds()
			text = self.buffer.get_text(bounds[0], bounds[1])
			text += '\n' # Make sure we always have a trailing \n
			return text
		else:
			return self._data

	def dump(self, format, dumper, linker=None):
		if format == "html":
			if self._attrib['lang']:
				# class="brush: language;" works with SyntaxHighlighter 2.0.278
				# by Alex Gorbatchev <http://alexgorbatchev.com/SyntaxHighlighter/>
				# TODO: not all GtkSourceView language ids match with SyntaxHighlighter
				# language ids.
				# TODO: some template instruction to be able to use other highlighters as well?
				output = ['<pre class="brush: %s;">\n' % html_encode(self._attrib['lang'])]
			else:
				output = ['<pre>\n']
			data = self.get_data()
			data = html_encode(data) # XXX currently dumper gives encoded lines - NOK
			if self._attrib['linenumbers']:
				for i, l in enumerate(data.splitlines(1)):
					output.append('%i&nbsp;' % (i+1) + l)
			else:
				output.append(data)
			output.append('</pre>\n')
			return output
		return CustomObjectClass.dump(self, format, dumper, linker)

	def set_language(self, lang):
		'''Set language in SourceView.'''
		self._attrib['lang'] = lang
		self.set_modified(True)

		if self.buffer:
			if lang is None:
				self.buffer.set_language(None)
			else:
				self.buffer.set_language(lm.get_language(lang))

	def show_line_numbers(self, show):
		'''Toggles line numbers in SourceView.'''
		self._attrib['linenumbers'] = show
		self.set_modified(True)

		for widget in self._widgets:
			widget.view.set_show_line_numbers(show)
コード例 #21
0
 def __init__(self, plugin):
     InsertedObjectType.__init__(self, plugin)
     self._widgets = WeakSet()
     self.preferences = plugin.preferences
     self.connectto(self.preferences, 'changed',
                    self.on_preferences_changed)
コード例 #22
0
ファイル: tableeditor.py プロジェクト: hjq300/zim-wiki
class TableViewObject(CustomObjectClass):
    '''data presenter of an inserted table within a page'''

    OBJECT_ATTR = {
        'type': String('table'),
        'aligns': String(''),  # i.e. String(left,right,center)
        'wraps': String('')  # i.e. String(0,1,0)
    }

    def __init__(self, attrib, header, rows, preferences):
        '''
		Creates a new object which can displayed within the page
		:param attrib: aligns, wraps
		:param header: titles of the table as list
		:param rows: body-rows of the table as list of lists
		:param preferences: optionally some preferences
		'''
        _attrib = {}
        for k, v in attrib.iteritems():
            if isinstance(v, list):
                v = ','.join(map(str, v))
            _attrib[k] = v
        CustomObjectClass.__init__(self, _attrib, [header] + rows)
        self.attrib = {'type': OBJECT_TYPE}  # just to be sure

        self._tableattrib = attrib
        self._header = header
        self._rows = rows
        self._widgets = WeakSet()
        self._liststore = None  # shared model between widgets

        self.preferences = preferences

    # getters and setters for attributes
    def get_aligns(self):
        ''' get the list of align-attributes '''
        return self._attrib['aligns'].split(',')

    def set_aligns(self, data):
        ''' Set list of align attributes for the current table. Each item belongs to a column.'''
        assert (isinstance(data, list))
        self._attrib['aligns'] = ','.join(data)

    def get_wraps(self):
        ''' get the list of wrap-attributes '''
        return map(int, self._attrib['wraps'].split(','))

    def set_wraps(self, data):
        ''' Set list of wrap attributes for the current table. Each item belongs to a column.'''
        assert (isinstance(data, list))
        self._attrib['wraps'] = ','.join(str(item) for item in data)

    def _get_liststore(self, reset=False):
        if reset or not self._liststore:
            cols = [str] * len(self._header)
            self._liststore = gtk.ListStore(*cols)
            for trow in self._rows:
                self._liststore.append(trow)
            self._liststore.connect('row-changed', self.on_modified_changed)

        return self._liststore

    def get_widget(self):
        ''' Creates a new table-widget which can displayed on the wiki-page '''
        liststore = self._get_liststore()
        attrib = {'aligns': self.get_aligns(), 'wraps': self.get_wraps()}
        widget = TableViewWidget(self, liststore, self._header, attrib)
        self._widgets.add(widget)
        widget.set_preferences(self.preferences)
        return widget

    def preferences_changed(self):
        '''	Updates all created table-widgets, if preferences have changed '''
        for widget in self._widgets:
            widget.set_preferences(self.preferences)

    def on_sort_column_changed(self, liststore):
        ''' Trigger after a column-header is clicked and therefore its sort order has changed '''
        self.set_modified(True)

    def on_modified_changed(self, liststore, path, treeiter):
        ''' Trigger after a table cell content is changed by the user '''
        self.set_modified(True)

    def get_data(self):
        '''Returns table-object into textual data, for saving it as text.'''
        headers = self._header
        attrs = {
            'aligns': self._attrib['aligns'],
            'wraps': self._attrib['wraps']
        }

        if not self._liststore:
            rows = self._rows
        else:
            rows = []
            for treerow in self._liststore:
                rows.append(
                    map(
                        lambda cell: CellFormatReplacer.cell_to_input(
                            cell, True), treerow))

        return headers, rows, attrs

    def change_model(self, new_model):
        '''
		Replace liststore with new model and notify widgets to update
		their treeview.
		:param new_model: tuple of lists for ([id], [header], [warps], [aligns])
		'''
        # prepare results out of dialog-window
        id_mapping, headers, aligns, wraps = ({}, [], [], [])
        for i, model in enumerate(new_model):
            if model[0] != -1:
                id_mapping[i] = model[0]
            header = model[1] if model[1] else ' '
            headers.append(header)
            aligns.append(model[3])
            wraps.append(model[2])

        # update data
        if self._liststore:
            liststore = self._get_liststore()
            self._rows = self._update_rows(liststore, id_mapping, len(headers))
            liststore = self._get_liststore(reset=True)
        else:
            liststore = None
            self._rows = self._update_rows(self._rows, id_mapping,
                                           len(headers))

        self.set_aligns(aligns)
        self.set_wraps(wraps)
        self.set_modified(True)

        # notify widgets
        for widget in self._widgets:
            assert liststore is not None, 'Huh?'
            attrib = {'aligns': self.get_aligns(), 'wraps': self.get_wraps()}
            widget.on_model_changed(liststore, headers, attrib)

        self.preferences_changed()  # reset prefs on widgets

    def _update_rows(self, old_rows, id_mapping, nr_cols):
        ''' Old value of cells are used in the new table, but only if its column is not deleted '''
        new_rows = []
        for oldrow in old_rows:
            newrow = [' '] * nr_cols
            for v, k in id_mapping.iteritems():
                newrow[v] = oldrow[k]
            new_rows.append(newrow)
        return new_rows

    def build_parsetree_of_table(self, builder, iter):
        logger.debug("Anchor with TableObject: %s", self)

        # inserts a newline before and after table-object
        bound = iter.copy()
        bound.backward_char()
        char_before_table = bound.get_slice(iter)
        need_newline_infront = char_before_table.decode(
            'utf-8') != "\n".decode('utf-8')
        bound = iter.copy()
        bound.forward_char()
        iter2 = bound.copy()
        bound.forward_char()
        char_after_table = iter2.get_slice(bound)
        need_newline_behind = char_after_table.decode('utf-8') != "\n".decode(
            'utf-8')
        #

        headers, rows, attrib = self.get_data()
        #~ print "Table data:", headers, rows, attrib

        if need_newline_infront:
            builder.data('\n')

        builder.start(TABLE, attrib)
        builder.start(HEADROW)
        for header in headers:
            builder.append(HEADDATA, header)
        builder.end(HEADROW)
        for row in rows:
            builder.start(TABLEROW)
            for cell in row:
                builder.append(TABLEDATA, cell)
            builder.end(TABLEROW)
        builder.end(TABLE)

        if need_newline_behind:
            builder.data('\n')
コード例 #23
0
class TableViewObjectType(InsertedObjectType):

    name = 'table'

    label = _('Table')  # T: menu item
    verb_icon = 'zim-insert-table'

    object_attr = {
        'aligns': String(''),  # i.e. String(left,right,center)
        'wraps': String('')  # i.e. String(0,1,0)
    }

    def __init__(self, plugin):
        InsertedObjectType.__init__(self, plugin)
        self._widgets = WeakSet()
        self.preferences = plugin.preferences
        self.connectto(self.preferences, 'changed',
                       self.on_preferences_changed)

    def new_object_interactive(self, parent):
        definition = EditTableDialog(parent).run()
        if definition is None:
            raise ValueError  # dialog cancelled

        ids, headers, wraps, aligns = definition
        attrib = self.parse_attrib({
            'aligns': ','.join(map(str, aligns)),
            'wraps': ','.join(map(str, wraps))
        })
        rows = [''] * len(headers)
        data = ' | '.join(headers) + '\n' + ' | '.join(rows) + '\n'
        return attrib, data

    def model_from_data(self, attrib, data):
        rows = [line.split(' | ') for line in data.splitlines()]
        headers = rows.pop(0) if rows else []
        if not headers:
            headers = ['Column1', 'Column2']
        return TableModel(attrib, headers, rows)

    def model_from_element(self, attrib, element):
        assert ElementTree.iselement(element)
        attrib = self.parse_attrib(attrib)
        headers, rows = self._tabledom_to_list(element)
        return TableModel(attrib, headers, rows)

    def _tabledom_to_list(self, tabledata):
        '''
		Extracts necessary data out of a xml-table into a list structure

		:param tabledata: XML - formated as a zim-tree table-object
		:return: tuple of header-list and list of row lists -  ([h1,h2],[[r11,r12],[r21,r22])
		'''
        headers = [head.text for head in tabledata.findall('thead/th')]
        headers = list(map(CellFormatReplacer.zim_to_cell, headers))

        rows = []
        for trow in tabledata.findall('trow'):
            row = trow.findall('td')
            row = [
                ElementTree.tostring(r, 'unicode').replace('<td>', '').replace(
                    '</td>', '') for r in row
            ]
            row = list(map(CellFormatReplacer.zim_to_cell, row))
            rows.append(row)
        return headers, rows

    def create_widget(self, model):
        widget = TableViewWidget(model)
        widget.set_preferences(self.preferences)
        self._widgets.add(widget)
        return widget

    def on_preferences_changed(self, preferences):
        for widget in self._widgets:
            widget.set_preferences(preferences)

    def dump(self, builder, model):
        headers, attrib, rows = model.get_object_data()

        builder.start(TABLE, dict(attrib))
        builder.start(HEADROW)
        for header in headers:
            builder.append(HEADDATA, header)
        builder.end(HEADROW)
        for row in rows:
            builder.start(TABLEROW)
            for cell in row:
                builder.append(TABLEDATA, cell)
            builder.end(TABLEROW)
        builder.end(TABLE)
コード例 #24
0
ファイル: __init__.py プロジェクト: pombredanne/zim
class PluginManager(ConnectorMixin, collections.Mapping):
	'''Manager that maintains a set of active plugins
	Handles loading and destroying plugins and is the entry point
	for extending application components.

	This object behaves as a dictionary with plugin object names as
	keys and plugin objects as value
	'''

	def __init__(self, config=None):
		self.config = config or VirtualConfigManager()
		self._preferences = \
			self.config.get_config_dict('<profile>/preferences.conf')
		self.general_preferences = self._preferences['General']
		self.general_preferences.setdefault('plugins', [])

		self._plugins = {}
		self._extendables = WeakSet()

		self._load_plugins()

		self.connectto(self._preferences, 'changed',
			self.on_preferences_changed)

	def __getitem__(self, name):
		return self._plugins[name]

	def __iter__(self):
		return iter(sorted(self._plugins.keys()))
			# sort to make operation predictable - easier debugging

	def __len__(self):
		return len(self._plugins)

	def _load_plugins(self):
		'''Load plugins based on config'''
		for name in sorted(self.general_preferences['plugins']):
			try:
				self.load_plugin(name)
			except:
				logger.exception('Exception while loading plugin: %s', name)
				self.general_preferences['plugins'].remove(name)

	@SignalHandler
	def on_preferences_changed(self, o):
		current = set(self._plugins.keys())
		new = set(self.general_preferences['plugins'])

		for name in current - new:
			try:
				self.remove_plugin(name)
			except:
				logger.exception('Exception while loading plugin: %s', name)

		for name in new - current:
			try:
				self.load_plugin(name)
			except:
				logger.exception('Exception while loading plugin: %s', name)
				self.general_preferences['plugins'].remove(name)

	def load_plugin(self, name):
		'''Load a single plugin by name

		When the plugin was loaded already the existing object
		will be returned. Thus for each plugin only one instance can be
		active.

		@param name: the plugin module name
		@returns: the plugin object
		@raises Exception: when loading the plugin failed
		'''
		assert isinstance(name, basestring)
		if name in self._plugins:
			return self._plugins[name]

		logger.debug('Loading plugin: %s', name)
		klass = get_plugin_class(name)
		if not klass.check_dependencies_ok():
			raise AssertionError, 'Dependencies failed for plugin %s' % name

		plugin = klass(self.config)
		self.connectto(plugin, 'extension-point-changed')
		self._plugins[name] = plugin

		for obj in self._extendables:
			try:
				plugin.extend(obj)
			except:
				logger.exception('Exception in plugin: %s', name)

		if not name in self.general_preferences['plugins']:
			with self.on_preferences_changed.blocked():
				self.general_preferences['plugins'].append(name)
				self.general_preferences.changed()

		return plugin

	def remove_plugin(self, name):
		'''Remove a plugin and it's extensions
		Fails silently if the plugin is not loaded.
		@param name: the plugin module name
		'''
		if name in self.general_preferences['plugins']:
			# Do this first regardless of exceptions etc.
			with self.on_preferences_changed.blocked():
				self.general_preferences['plugins'].remove(name)
				self.general_preferences.changed()

		try:
			plugin = self._plugins.pop(name)
			self.disconnect_from(plugin)
		except KeyError:
			pass
		else:
			logger.debug('Unloading plugin %s', name)
			plugin.destroy()

	def _foreach(self, func):
		# sort to make operation predictable - easier debugging
		for name, plugin in sorted(self._plugins.items()):
			try:
				func(plugin)
			except:
				logger.exception('Exception in plugin: %s', name)

	def extend(self, obj):
		'''Let any plugin extend the object instance C{obj}
		Will also remember object (by a weak reference) such that
		plugins loaded after this call will also be called to extend
		C{obj} on their construction
		@param obj: arbitrary object that can be extended by plugins
		'''
		if not obj in self._extendables:
			self._foreach(lambda p: p.extend(obj))
			self._extendables.add(obj)

	def on_extension_point_changed(self, plugin, name):
		for obj in self._extendables:
			if obj.__class__.__name__ == name:
				try:
					plugin.extend(obj)
				except:
					logger.exception('Exception in plugin: %s', name)
コード例 #25
0
 def __init__(self):
     self.factories = {}
     self.objects = {'fallback': WeakSet()}
コード例 #26
0
ファイル: __init__.py プロジェクト: tmhorne/simplewiki
class PluginClass(ConnectorMixin, SignalEmitter):
    '''Base class for plugins objects.

	To be recognized as a plugin, a submodule of "zim.plugins" needs to
	define one (and only one) sub-class of L{PluginClass}. This class
	will define the main plugin object and contains meta data about the
	plugin and e.g. plugin preferences.

	The plugin object itself doesn't directly interact with the rest of the
	zim application. To actually add functionality to zim, the plugin module
	will also need to define one or more "extension" classes. These classes
	act as decorators for specific objects that appear in the application.

	All extension classes defined in the same module
	file as the plugin object are automatically linked to the plugin.

	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.

	Plugin classes should at minimum define two class attributes:
	C{plugin_info} and C{plugin_preferences}. When these are defined
	no other code is needed to have a basic plugin up and running.

	@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 (if any). Each preference is defined
	by a 4-tuple containing the following items:

		1. the dict key of the option (used in the config file and in
		   the preferences dict)
		2. an option type (see L{InputForm.add_inputs(){} for more details)
		3. a (translatable) label to show in the preferences dialog for
		   this option
		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 of the plugin object. The type and label will be
	used to render a default config dialog when triggered from the
	preferences dialog.
	Changes to these preferences will be stored in a config file so
	they are persistent.

	@ivar preferences: a L{ConfigDict} with plugin preferences

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

	@ivar config: a L{ConfigManager} object that can be used to lookup
	additional config files for the plugin

	@ivar extension_classes: a dictionary with extension classes found
	in the plugin module

	@ivar extensions: a set with extension objects loaded by this plugin.
	The lookup extensions objects it is usually better to use the methods
	L{get_extension()} or L{get_extensions()} rather than using this
	set directly.

	@signal: C{extension-point-changed (name)}: emitted when extension
	point C{name} changes
	'''

    # define signals we want to use - (closure type, return type and arg types)
    __signals__ = {'extension-point-changed': (None, None, (basestring, ))}

    plugin_info = {}

    plugin_preferences = ()

    @classproperty
    def config_key(klass):
        '''The name of section used in the config files to store the
		preferences for this plugin.
		'''
        return klass.__name__

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

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

    @classmethod
    def check_dependencies(klass):
        '''Checks what dependencies are met and gives details for
		display in the preferences dialog

		@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. Default always returns
		C{True} with an empty list.
		'''
        return (True, [])

    def __init__(self, config=None):
        '''Constructor
		@param config: a L{ConfigManager} object that is used to load
		plugin preferences.
		Defaults to a L{VirtualConfigManager} for testing.
		'''
        assert 'name' in self.plugin_info, 'Missing "name" in plugin_info'
        assert 'description' in self.plugin_info, 'Missing "description" in plugin_info'
        assert 'author' in self.plugin_info, 'Missing "author" in plugin_info'
        self.extensions = WeakSet()

        if self.plugin_preferences:
            assert isinstance(
                self.plugin_preferences[0],
                tuple), 'BUG: preferences should be defined as tuples'

        self.config = config or VirtualConfigManager()
        self.preferences = self.config.get_config_dict(
            '<profile>/preferences.conf')[self.config_key]

        for pref in self.plugin_preferences:
            if len(pref) == 4:
                key, type, label, default = pref
                self.preferences.setdefault(key, default)
                #~ print ">>>>", key, default, '--', self.preferences[key]
            else:
                key, type, label, default, check = pref
                self.preferences.setdefault(key, default, check=check)
                #~ print ">>>>", key, default, check, '--', self.preferences[key]

        self.load_extensions_classes()

    @classmethod
    def lookup_subclass(pluginklass, klass):
        '''Returns first subclass of C{klass} found in the module of
		this plugin. (Similar to L{zim.utils.lookup_subclass}).
		@param pluginklass: plugin class
		@param klass: base class of the wanted class
		'''
        module = get_module(pluginklass.__module__)
        return lookup_subclass(module, klass)

    def load_extensions_classes(self):
        '''Instantiates the C{extension_classes} dictionary with classes
		found in the same module as the plugin object.
		Called directly by the constructor.
		'''
        self.extension_classes = {}
        for extends, klass in self.discover_extensions_classes():
            self.add_extension_class(extends, klass)

    @classmethod
    def discover_extensions_classes(pluginklass):
        '''Find extension classes in same module as the plugin
		object class.
		@returns: yields 2-tuple of the name of the object class to be
		extended (as set by the L{extends} decorator) and the extension
		class object
		'''
        # Any class with the "__extends__" field will be added
        # (Being subclass of ObjectExtension is optional)
        module = get_module(pluginklass.__module__)
        for n, klass in inspect.getmembers(module, inspect.isclass):
            if hasattr(klass, '__extends__') and klass.__extends__:
                yield klass.__extends__, klass

    def set_extension_class(self, extends, klass):
        '''Set the extension class for a specific target object class

		This method can be used to dynamically set extension classes
		on run time. E.g. of the extension class depends on a preference.
		If another extension class was already defined for the same
		target object, it is removed.

		When the plugin is managed by a L{PluginManager} and that
		manager is aware of objects of the target class, extensions
		will immediatly be instantiated for those objects.

		@param extends: class name of the to-be-extended object
		@param klass: the extension class

		@emits: extension-point-changed
		'''
        if extends in self.extension_classes:
            if self.extension_classes[extends] == klass:
                pass
            else:
                self.remove_extension_class(extends)
                self.add_extension_class(extends, klass)
        else:
            self.add_extension_class(extends, klass)

    def add_extension_class(self, extends, klass):
        '''Add an extension class for a specific target object class

		When the plugin is managed by a L{PluginManager} and that
		manager is aware of objects of the target class, extensions
		will immediatly be instantiated for those objects.

		@param extends: class name of the to-be-extended object
		@param klass: the extension class

		@emits: extension-point-changed
		'''
        if extends in self.extension_classes:
            raise AssertionError, 'Extension point %s already in use' % name
        self.extension_classes[extends] = klass
        self.emit('extension-point-changed', extends)

    def remove_extension_class(self, extends):
        '''Remove the extension class for a specific target object class
		Will result in all extension objects for this object class to be destroyed.
		@param extends: class name of the to-be-extended object
		'''
        klass = self.extension_classes.pop(extends)
        for obj in self.get_extensions(klass):
            obj.destroy()

    def extend(self, obj, _name=None):
        '''This method will look through the extensions defined for this
		plugin and construct a new extension object if a match is found
		for C{obj}.
		@param obj: the obejct to be extended
		@param _name: lookup name to use when extending the object.
		To be used for testing only. Normally the class name of C{obj}
		is used.
		'''
        name = _name or obj.__class__.__name__
        if name in self.extension_classes:
            ext = self.extension_classes[name](self, obj)
            self.extensions.add(ext)

    def get_extension(self, klass, **attr):
        '''Look up an extension object instatiation
		@param klass: the class of the extention object (_not_ the to-be-extended
		klass)
		@param attr: any object attributes that should match
		@returns: a single extension object or C{None}
		'''
        ext = self.get_extensions(klass)
        for key, value in attr.items():
            ext = filter(lambda e: getattr(e, key) == value, ext)

        if len(ext) > 1:
            raise AssertionError, 'BUG: multiple extensions of class %s found' % klass
        elif ext:
            return ext[0]
        else:
            return None

    def get_extensions(self, klass):
        '''Look up extension object instatiations
		@param klass: the class of the extention object (_not_ the to-be-extended
		klass)
		@returns: a list of extension objects (if any)
		'''
        return [e for e in self.extensions if isinstance(e, klass)]

    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).
		'''
        for obj in self.extensions:
            obj.destroy()

        try:
            self.disconnect_all()
        except:
            logger.exception('Exception while disconnecting %s', self)
コード例 #27
0
ファイル: __init__.py プロジェクト: pombredanne/zim
class PluginClass(ConnectorMixin, SignalEmitter):
	'''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}.

	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.

	@ivar ui: the main application object, e.g. an instance of
	L{zim.gui.GtkInterface} or L{zim.www.WWWInterface}
	@ivar preferences: a C{ConfigDict()} 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{ConfigDict()} 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{extension-point-changed (name)}: emitted when extension
	point C{name} changes
	'''

	# define signals we want to use - (closure type, return type and arg types)
	__signals__ = {
		'extension-point-changed': (None, None, (basestring,))
	}

	plugin_info = {}

	plugin_preferences = ()

	@classproperty
	def config_key(klass):
		return klass.__name__

	@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, config=None):
		assert 'name' in self.plugin_info
		assert 'description' in self.plugin_info
		assert 'author' in self.plugin_info
		self.extensions = WeakSet()

		if self.plugin_preferences:
			assert isinstance(self.plugin_preferences[0], tuple), 'BUG: preferences should be defined as tuples'

		self.config = config or VirtualConfigManager()
		self.preferences = self.config.get_config_dict('<profile>/preferences.conf')[self.config_key]

		for pref in self.plugin_preferences:
				if len(pref) == 4:
					key, type, label, default = pref
					self.preferences.setdefault(key, default)
					#~ print ">>>>", key, default, '--', self.preferences[key]
				else:
					key, type, label, default, check = pref
					self.preferences.setdefault(key, default, check=check)
					#~ print ">>>>", key, default, check, '--', self.preferences[key]

		self.load_extensions_classes()

	@classmethod
	def lookup_subclass(pluginklass, klass):
		'''Returns first subclass of C{klass} found in the module of
		this plugin. (Similar to L{zim.utils.lookup_subclass})
		@param pluginklass: plugin class
		@param klass: base class of the wanted class
		'''
		module = get_module(pluginklass.__module__)
		return lookup_subclass(module, klass)

	def load_extensions_classes(self):
		self.extension_classes = {}
		for name, klass in self.discover_extensions_classes():
			self.add_extension_class(name, klass)

	@classmethod
	def discover_extensions_classes(pluginklass):
		# Find related extension classes in same module
		# any class with the "__extends__" field will be added
		# (Being subclass of ObjectExtension is optional)
		module = get_module(pluginklass.__module__)
		for n, klass in inspect.getmembers(module, inspect.isclass):
			if hasattr(klass, '__extends__') and klass.__extends__:
				yield klass.__extends__, klass

	def set_extension_class(self, name, klass):
		if name in self.extension_classes:
			if self.extension_classes[name] == klass:
				pass
			else:
				self.remove_extension_class(name)
				self.add_extension_class(name, klass)
		else:
			self.add_extension_class(name, klass)

	def add_extension_class(self, name, klass):
		if name in self.extension_classes:
			raise AssertionError, 'Extension point %s already in use' % name
		self.extension_classes[name] = klass
		self.emit('extension-point-changed', name)

	def remove_extension_class(self, name):
		klass = self.extension_classes.pop(name)
		for obj in self.get_extensions(klass):
			obj.destroy()

	def extend(self, obj, name=None):
		# TODO also check parent classes
		# name should only be used for testing
		name = name or obj.__class__.__name__
		if name in self.extension_classes:
			ext = self.extension_classes[name](self, obj)
			self.extensions.add(ext)

	def get_extension(self, klass, **attr):
		ext = self.get_extensions(klass)
		for key, value in attr.items():
			ext = filter(lambda e: getattr(e, key) == value, ext)

		if len(ext) > 1:
			raise AssertionError, 'BUG: multiple extensions of class %s found' % klass
		elif ext:
			return ext[0]
		else:
			return None

	def get_extensions(self, klass):
		return [e for e in self.extensions if isinstance(e, klass)]

	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).
		'''
		for obj in self.extensions:
			obj.destroy()

		try:
			self.disconnect_all()
		except:
			logger.exception('Exception while disconnecting %s', self)
コード例 #28
0
class PluginManagerClass(ConnectorMixin, collections.Mapping):
    '''Manager that maintains a set of active plugins

	This class is the interface towards the rest of the application to
	load/unload plugins. It behaves as a dictionary with plugin object names as
	keys and plugin objects as value
	'''
    def __init__(self):
        '''Constructor
		Constructor will directly load a list of default plugins
		based on the preferences in the config. Failures while loading
		these plugins will be logged but not raise errors.

		@param config: a L{ConfigManager} object that is passed along
		to the plugins and is used to load plugin preferences.
		Defaults to a L{VirtualConfigManager} for testing.
		'''
        self._reset()

    def _reset(self):
        self._preferences = ConfigManager.preferences['General']
        self._preferences.setdefault('plugins', [])

        self._plugins = {}
        self._extendables = WeakSet()
        self.failed = set()

        self.insertedobjects = InsertedObjectTypeMap()

    def load_plugins_from_preferences(self, names):
        '''Calls L{load_plugin()} for each plugin in C{names} but does not
		raise an exception when loading fails.
		'''
        for name in names:
            try:
                self.load_plugin(name)
            except Exception as exc:
                if isinstance(exc, ImportError):
                    logger.info('No such plugin: %s', name)
                else:
                    logger.exception('Exception while loading plugin: %s',
                                     name)
                if name in self._preferences['plugins']:
                    self._preferences['plugins'].remove(name)
                self.failed.add(name)

    def __call__(self):
        return self  # singleton behavior if called as class

    def __getitem__(self, name):
        return self._plugins[name]

    def __iter__(self):
        return iter(sorted(self._plugins.keys()))
        # sort to make operation predictable - easier debugging

    def __len__(self):
        return len(self._plugins)

    @classmethod
    def list_installed_plugins(klass):
        '''Lists plugin names for all installed plugins
		@returns: a set of plugin names
		'''
        # List "zim.plugins" sub modules based on __path__ because this
        # parameter determines what folders will considered when importing
        # sub-modules of the this package once this module is loaded.
        plugins = set()  # THIS LINE IS REPLACED BY SETUP.PY - DON'T CHANGE IT
        for folder in [f for f in map(LocalFolder, __path__) if f.exists()]:
            for child in folder:
                name = child.basename
                if name.startswith('_') or name == 'base':
                    continue
                elif isinstance(child, LocalFile) and name.endswith('.py'):
                    plugins.add(name[:-3])
                elif isinstance(child, LocalFolder) \
                 and child.file('__init__.py').exists():
                    plugins.add(name)
                else:
                    pass

        return plugins

    @classmethod
    def get_plugin_class(klass, name):
        '''Get the plugin class for a given name

		@param name: the plugin module name
		@returns: the plugin class object
		'''
        modname = 'zim.plugins.' + name
        mod = get_module(modname)
        return lookup_subclass(mod, PluginClass)

    def _new_extendable(self, obj):
        '''Let any plugin extend the object instance C{obj}
		Will also remember the object (by a weak reference) such that
		plugins loaded after this call will also be called to extend
		C{obj} on their construction
		@param obj: arbitrary object that can be extended by plugins
		'''
        logger.debug("New extendable: %s", obj)
        assert not obj in self._extendables

        for name, plugin in sorted(self._plugins.items()):
            # sort to make operation predictable
            self._extend(plugin, obj)

        self._extendables.add(obj)

    def _extend(self, plugin, obj):
        for ext_class in plugin.extension_classes:
            if issubclass(ext_class, obj.__zim_extension_bases__):
                logger.debug("Load extension: %s", ext_class)
                try:
                    ext = ext_class(plugin, obj)
                except:
                    logger.exception(
                        'Failed loading extension %s for plugin %s', ext_class,
                        plugin)
                else:
                    plugin.extensions.add(ext)

    def load_plugin(self, name):
        '''Load a single plugin by name

		When the plugin was loaded already the existing object
		will be returned. Thus for each plugin only one instance can be
		active.

		@param name: the plugin module name
		@returns: the plugin object
		@raises Exception: when loading the plugin failed
		'''
        assert isinstance(name, str)
        if name in self._plugins:
            return self._plugins[name]

        logger.debug('Loading plugin: %s', name)
        klass = self.get_plugin_class(name)
        if not klass.check_dependencies_ok():
            raise AssertionError('Dependencies failed for plugin %s' % name)

        plugin = klass()
        self._plugins[name] = plugin

        for obj in self._extendables:
            self._extend(plugin, obj)

        if not name in self._preferences['plugins']:
            self._preferences['plugins'].append(name)
            self._preferences.changed()

        return plugin

    def remove_plugin(self, name):
        '''Remove a plugin and it's extensions
		Fails silently if the plugin is not loaded.
		@param name: the plugin module name
		'''
        if name in self._preferences['plugins']:
            # Do this first regardless of exceptions etc.
            self._preferences['plugins'].remove(name)
            self._preferences.changed()

        try:
            plugin = self._plugins.pop(name)
            self.disconnect_from(plugin)
        except KeyError:
            pass
        else:
            logger.debug('Unloading plugin %s', name)
            plugin.destroy()
コード例 #29
0
ファイル: __init__.py プロジェクト: rayleyva/zim-desktop-wiki
class PluginClass(ConnectorMixin):
    '''Base class for plugins objects.

	To be recognized as a plugin, a submodule of "zim.plugins" needs to
	define one (and only one) sub-class of L{PluginClass}. This class
	will define the main plugin object and contains meta data about the
	plugin and e.g. plugin preferences.

	The plugin object itself doesn't directly interact with the rest of the
	zim application. To actually add functionality to zim, the plugin module
	will also need to define one or more "extension" classes. These classes
	act as decorators for specific objects that appear in the application.

	All extension classes defined in the same module
	file as the plugin object are automatically linked to the plugin.

	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.

	Plugin classes should at minimum define two class attributes:
	C{plugin_info} and C{plugin_preferences}. When these are defined
	no other code is needed to have a basic plugin up and running.

	@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 (if any). Each preference is defined
	by a 4-tuple containing the following items:

		1. the dict key of the option (used in the config file and in
		   the preferences dict)
		2. an option type (see L{InputForm.add_inputs(){} for more details)
		3. a (translatable) label to show in the preferences dialog for
		   this option
		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 of the plugin object. The type and label will be
	used to render a default config dialog when triggered from the
	preferences dialog.
	Changes to these preferences will be stored in a config file so
	they are persistent.

	@ivar preferences: a L{ConfigDict} with plugin preferences

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

	@ivar config: a L{ConfigManager} object that can be used to lookup
	additional config files for the plugin

	@ivar extension_classes: a dictionary with extension classes found
	in the plugin module

	@ivar extensions: a set with extension objects loaded by this plugin.
	'''

    # define signals we want to use - (closure type, return type and arg types)
    plugin_info = {}

    plugin_preferences = ()
    plugin_notebook_properties = ()

    @classproperty
    def config_key(klass):
        '''The name of section used in the config files to store the
		preferences for this plugin.
		'''
        return klass.__name__

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

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

    @classmethod
    def check_dependencies(klass):
        '''Checks what dependencies are met and gives details for
		display in the preferences dialog

		@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. Default always returns
		C{True} with an empty list.
		'''
        return (True, [])

    def __init__(self, config=None):
        '''Constructor
		@param config: a L{ConfigManager} object that is used to load
		plugin preferences.
		Defaults to a L{VirtualConfigManager} for testing.
		'''
        assert 'name' in self.plugin_info, 'Missing "name" in plugin_info'
        assert 'description' in self.plugin_info, 'Missing "description" in plugin_info'
        assert 'author' in self.plugin_info, 'Missing "author" in plugin_info'
        self.extensions = WeakSet()

        if self.plugin_preferences:
            assert isinstance(
                self.plugin_preferences[0],
                tuple), 'BUG: preferences should be defined as tuples'

        self.config = config or VirtualConfigManager()
        self.preferences = self.config.get_config_dict(
            '<profile>/preferences.conf')[self.config_key]
        self._init_config(self.preferences, self.plugin_preferences)
        self._init_config(self.preferences, self.plugin_notebook_properties
                          )  # defaults for the properties are preferences

        self.load_insertedobject_types()
        self.load_extensions_classes()

    @staticmethod
    def _init_config(config, definitions):
        for pref in definitions:
            if len(pref) == 4:
                key, type, label, default = pref
                config.setdefault(key, default)
            else:
                key, type, label, default, check = pref
                config.setdefault(key, default, check=check)

    @staticmethod
    def form_fields(definitions):
        fields = []
        for pref in definitions:
            if len(pref) == 4:
                key, type, label, default = pref
            else:
                key, type, label, default, check = pref

            if type in ('int', 'choice'):
                fields.append((key, type, label, check))
            else:
                fields.append((key, type, label))

        return fields

    def notebook_properties(self, notebook):
        properties = notebook.config[self.config_key]
        if not properties:
            self._init_config(properties, self.plugin_notebook_properties)

            # update defaults based on preference
            for key, definition in properties.definitions.items():
                try:
                    definition.default = definition.check(
                        self.preferences[key])
                except ValueError:
                    pass

        return properties

    @classmethod
    def lookup_subclass(pluginklass, klass):
        '''Returns first subclass of C{klass} found in the module of
		this plugin. (Similar to L{zim.utils.lookup_subclass}).
		@param pluginklass: plugin class
		@param klass: base class of the wanted class
		'''
        module = get_module(pluginklass.__module__)
        return lookup_subclass(module, klass)

    def load_insertedobject_types(self):
        '''Loads L{InsertedObjectType} classes defined in the same modul
		as the plugin.
		'''
        from zim.objectmanager import ObjectManager
        self._objecttypes = [
            objtype(self)
            for objtype in self.discover_classes(InsertedObjectType)
        ]
        for obj in self._objecttypes:
            ObjectManager.register_object(obj)

    def load_extensions_classes(self):
        '''Instantiates the C{extension_classes} dictionary with classes
		found in the same module as the plugin object.
		Called directly by the constructor.
		'''
        self.extension_classes = {}
        for klass in self.discover_classes(ExtensionBase):
            extends = klass.__extends__
            if extends in self.extension_classes:
                raise AssertionError('Extension point %s already in use' %
                                     name)
            self.extension_classes[extends] = klass

    @classmethod
    def discover_classes(pluginklass, baseclass):
        '''Yields a list of classes derived from C{baseclass} and
		defined in the same module as the plugin
		'''
        module = get_module(pluginklass.__module__)
        for klass in lookup_subclasses(module, baseclass):
            yield klass

    def extend(self, obj):
        '''This method will look through the extensions defined for this
		plugin and construct a new extension object if a match is found
		for C{obj}.
		@param obj: the object to be extended
		'''
        name = obj.__class__.__name__
        if name in self.extension_classes:
            try:
                ext = self.extension_classes[name](self, obj)
            except ExtensionNotApplicable:
                pass
            except:
                logger.exception('Failed loading extension %s for plugin %s',
                                 self.extension_classes[name], self)
            else:
                self.extensions.add(ext)

    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).
		'''
        from zim.objectmanager import ObjectManager

        for obj in self.extensions:
            obj.destroy()

        for obj in self._objecttypes:
            ObjectManager.unregister_object(obj)

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

    def teardown(self):
        '''Cleanup method called by C{destroy()}.
		Can be implemented by sub-classes.
		'''
        pass