This document describes Cardapio's latest plugin API.

Right now the plugin API is very basic and only allows for very simple plugins. The good news, though, is that it is also *really* easy to write your own plugin :)

If you'd like to submit a plugin to be distributed with Cardapio, see https://answers.launchpad.net/cardapio/+faq/1212 for information on how to do that.

Anyways, here is all you need to know about Cardapio plugins:

    ~

1) Plugins are written in Python, and are placed at one of the locations below:
  a) CARDAPIO_PATH/plugins (usually either /usr/lib/cardapio/plugins or /usr/local/lib/cardapio/plugins)
  b) ~/.config/Cardapio/plugins

    ~

2) Every plugin must contain a class called CardapioPlugin, which inherits from CardapioPluginInterface. However, you should not import the cardapio module yourself. Instead, Cardapio adds a few things to Python's built-ins to make your life easier. These are:
 a) the CardapioPluginInterface class
 b) the gettext function _  (underscore) for translations
 c) the dbus module
 d) the subprocess module
So you can just use these without ever importing them in any way. I repeat: no need to "import Cardapio" or "import dbus" and such.

    ~

3) Please follow these simple coding guidelines:
 a) Use tabs, not spaces.
 b) Use single quotes, except for strings that have single quotes in them, like "you're funny".
 c) Use gettext's underscore function _('some text') for every string that is displayed in the GUI.
 d) Do not use gettext for error messages or strings that are saved to the log. Also, these should be in English.
 e) Catch all exceptions and save them to the log with the write_to_log method.

    ~

4) That said, to create a plugin, you can just follow the boilerplate code below. You can name the plugin file however you like, so long as it has a .py extension and does not start with _ (underscore), which is used to hide files from the plugin menu.

Note that since Launchpad does not like tabs I had to replace them with spaces below. But in your code you should replace them back with tabs (4 spaces = 1 tab).

    ~

    ~ 

    ~

class CardapioPlugin (CardapioPluginInterface):

    author = 'plugin author'
    name = _('plugin name')
    description = _('plugin description')
	icon = '' # the icon to show when listing the plugin in the options window
    # (icons can be gtk icons, named icons from the icon theme, or even
    # filesystem paths)

    # not yet used:
    url = 'http://www.pluginurl.com'
    help_text = 'a larger description of the plugin'
    version = '0.1.2.3'

    plugin_api_version = 1.40 # must be 1.40 to work with the current version of Cardapio

    search_delay_type = 'local'
    # must be one of:
    #
    # - None - if there should be no delay between when the user types and when
    # the plugin is called. Use only for VERY lightweight and fast plugins.
    #
    # - 'local' - if the delay should be small (as set in the config.json, in 
    # milliseconds)
    #
    # - 'remote' - if the delay should be slightly larger (as set in the config.json, 
    # in milliseconds)

    # the category name under which the plugin search results will appear
    category_name = _('Plugin Category')

    # the icon that will be shown next to the plugin category name
    category_icon = '' 

    # the tooltip that will be shown when the cursor hovers the category button
    category_tooltip = ''

	# the number of sidebar categories defined by this plugin. Generally, this
	# should be 1.
	category_count = 1
	#
	# For plugins such as the Zeitgeist plugin, which creates several sidebar
	# categories (such as "today", "this week", etc), this number should be
	# greater than 1. In that case, category_name, category_icon, and
	# category_tooltip should all be defined as arrays of strings -- one string
	# for each sidebar category.

    # the icon that will be used for all search items for which Cardapio cannot
    # find the assigned icon
    fallback_icon = ''

    # set to True if the plugin results should not show up when there is no text
    # in the search field (like the tracker and google plugins, for example)
    hide_from_sidebar = True

    # the default keyword used to restrict the search to this plugin. Leaving this 
    # blank is the same as using the filename without the .py
    default_keyword = ''
    # (more info see: https://blueprints.launchpad.net/cardapio/+spec/letters-to-use-only-specific-search-method)

    # Finally, Cardapio may add some variables to the plugin instance, and so you
    # should not used these names in your plugin code. Right now, these are:
    #    self.__is_running
    #    self.__show_only_with_keyword
    #    (More will probably come, but they will always start with two underscores. 
    #    So avoid using this naming scheme.)

#          ~

    def __init__(self, cardapio_proxy, category):
		"""
		REQUIRED

		This constructor gets called whenever a plugin is activated.
		(Typically once per session, unless the user is turning plugins on/off)

		The constructor *must* set the instance variable self.loaded to True of False.
		For example, the Tracker plugin sets self.loaded to False if Tracker is not
		installed in the system.

		The constructor is given a single parameter, which is an object used to
		communicate with Cardapio. This object has the following members:

		   - write_to_log - this is a function that lets you write to Cardapio's
		     log file, like this: write_to_log(self, 'hi there')

		   - handle_search_result - a function to which you should pass the
		     search results when you have them (see more info below, in the
			 search() method)

		   - handle_search_error - a function to which you should pass an error
		     message if the search fails (see more info below, in the
			 search() method)

		   - ask_for_reload_permission - a function that should be used whenever
			 the plugin wants to reload its database. Not all plugins have
			 internal databases, though, so this is not always applicable. This
			 is used, for example, with the software_center plugin. (see
 		     on_reload_permission_granted below for more info)
		"""

        # Do stuff here. For example:

        self.loaded         = False
        self.cardapio_proxy = cardapio_proxy
        self.category       = category

        # Do some other initialization
        
        # If all goes well, set loaded to True:
        self.loaded = True 

        # For an example of how self.loaded is used, the tracker plugin will set
        # self.loaded = False if the user does not have Tracker installed in
        # his/her system. This way, Cardapio can just deactivate the plugin
        # instead of crashing.

#          ~

    def __del__(self):
        """
        NOT REQUIRED

        This destructor gets called whenever a plugin is deactivated
        (Typically once per session, unless the user is turning plugins on/off)
        """
        pass

#          ~

    def search(self, text, result_limit):
		"""
		REQUIRED

		This method gets called when a new text string is entered in the search
		field. It also takes an argument indicating the maximum number of
		results Cardapio's expecting. The plugin should always provide as many
		results as it can but their number cannot exceed the given limit!

		One of the following functions should be called from this method
		(of from a thread spawned by this method):

		   * if all goes well:
		   --> handle_search_result(plugin, results, original_query)

		   * if there is an error
		   --> handle_search_error(plugin, text)

		The arguments to these functions are:

		   * plugin          - this plugin instance (that is, it should always
		                       be "self", without quotes)
		   * text            - some text to be inserted in Cardapio's log.
		   * results         - an array of dict items as described below.
		   * original_query  - the search query that this corresponds to. The
		                       plugin should save the query received by the
							   search() method and pass it back to Cardapio.

		item = {
		  'name'         : _('Music'),
		  'tooltip'      : _('Show your Music folder'),
		  'icon name'    : 'text-x-generic',
		  'type'         : 'xdg',
		  'command'      : '~/Music',
		  'context menu' : None
		  }

		Where setting 'type' to 'xdg' means that 'command' should be opened
		using xdg-open (you should give it a try it in the terminal, first!).
		Meanwhile, setting 'type' to 'callback' means that 'command' is a
		function that should be called when the item is clicked. This function
		will receive as an argument the current search string.

		Note that you can set item['file name'] to None if you want Cardapio
		to guess the icon from the 'command'. This only works for 'xdg' commands,
		though.

		To change what is shown in the context menu for the search results, set
		the 'context menu' field to a list [] of dictionary items exactly like
		the ones above.
		"""

        self.current_query = text

        # Do stuff here
        # Build an array full of items like the ones explained above

        # Let's say that array is called my_items.
		# You should make sure that len(my_items) <= result_limit

        # Then, if the search worked (even if len(my_items) == 0)
        if (search_worked_without_problems):

            self.cardapio_proxy.handle_search_result(self, my_items, self.current_query)

        else:
            # or, if the search failed entirely for some reason (like when there is 
            # no internet connection and we're doing a google search)

            self.cardapio_proxy.handle_search_error(self, 'error message')
            # all errors are automatically added to Cardapio's log file, by the way


#          ~

    def cancel(self):
        """
        NOT REQUIRED

        This function should cancel the search operation. This is useful if the search is
        done in a separate thread (which it should, as much as possible)
        """
        pass


#          ~


    def on_reload_permission_granted(self):
        """
        NOT REQUIRED
        
        Whenever a plugin wishes to rebuild some sort of internal database,
        if this takes more than a couple of milliseconds it is advisable to 
        first ask Cardapio for permission. This is how this works:

        1) Plugin calls cardapio_proxy.ask_for_reload_permission(self)

        Cardapio then decides at what time it is best to give the plugin the
        reload permission. Usually this can take up to 10s, to allow several
        plugins to reload at the same time. Then, Cardapio shows the "Data has
        changed" window.
        
        2) Cardapio calls on_reload_permission_granted to tell the plugin that
        it can reload its database 
        
        When done, the "Data has changed" window is hidden.
        """
        pass



    ~

    ~ 

    ~


That's it!
