Difference between revisions of "Addons development"

From Gramps
Jump to: navigation, search
(Package your addon)
(Resources)
(45 intermediate revisions by 7 users not shown)
Line 1: Line 1:
''This page documents the API, methods, and best practices for developing an 3rd-party addon for GRAMPS 3.2, due March 2010.''
+
{{man warn|Warning:|This page documents the API, methods, and best practices for developing a 3rd-party addon for Gramps 3.2 and later }}
  
= Addons development =
+
Addons for Gramps can extend the program in many different ways. You can add any of the following [http://svn.code.sf.net/p/gramps/code/trunk/gramps/gen/plug/_pluginreg.py types] of addons:
  
Addon's for GRAMPS can extend the program in many different ways. You can add any of the following types of addons:
+
#Report
 
+
#Quickreport
# Doc creator
+
#Tool
# Exporter
+
#Importer
# Gramplet
+
#Exporter
# Importer
+
#Doc creator
# Map service
+
#Plugin lib
# Plugin lib
+
#Map service
# Quickreport
+
#Gramps View
# Relationships
+
#Relationships
# Report
+
#Gramplet
# Tool
+
#Sidebar
# View
 
  
 
Writing an addon is fairly straightforward if you have just a little bit of Python experience. And sharing your addon is the right thing to do. The general steps to writing an addon and sharing your own addons are:
 
Writing an addon is fairly straightforward if you have just a little bit of Python experience. And sharing your addon is the right thing to do. The general steps to writing an addon and sharing your own addons are:
Line 25: Line 24:
 
# List and document your addon
 
# List and document your addon
 
# Support it through issue tracker
 
# Support it through issue tracker
# Maintain the code as GRAMPS continues to evolve
+
# Maintain the code as Gramps continues to evolve
  
 
We'll now look at each of these steps in detail.
 
We'll now look at each of these steps in detail.
Line 31: Line 30:
 
== Develop your addon ==
 
== Develop your addon ==
  
gramps-addons subversion repository has the following structure:
+
The [http://svn.code.sf.net/p/gramps-addons/code/ gramps-addons] subversion repository has the following structure:
  
 
* /gramps-addons
 
* /gramps-addons
Line 38: Line 37:
 
*** /download
 
*** /download
 
** /branches
 
** /branches
 +
*** /gramps{{stable_branch}}
 
*** /gramps32
 
*** /gramps32
 
**** /contrib
 
**** /contrib
 
**** /download
 
**** /download
  
The contrib subdirectories hold the source code for the addons for a particular version. If you are working on a addon for gramps32 then you should be working in gramps-addons/branches/gramps32/contrib. If you are working in gramps/trunk then you should use gramps-addons/trunk/contrib.
+
The contrib subdirectories hold the source code for the addons for a particular version. If you are working on a addon for gramps{{stable_branch}} then you should be working in gramps-addons/branches/gramps{{stable_branch}}/contrib. If you are working in gramps/trunk then you should use gramps-addons/trunk/contrib.
  
 
These steps show how to download and work with the addon development tools.
 
These steps show how to download and work with the addon development tools.
Line 50: Line 50:
 
### cd ~/gramps/trunk
 
### cd ~/gramps/trunk
 
## Checkout gramps-addons:
 
## Checkout gramps-addons:
### svn co https://gramps-addons.svn.sourceforge.net/svnroot/gramps-addons gramps-addons
+
### svn co https://svn.code.sf.net/p/gramps-addons/code gramps-addons
## Change to trunk or branches/gramps32 directory:
+
## Change to trunk or branches/gramps{{stable_branch}} directory:
### cd gramps-addons/branches/gramps32/contrib
+
### cd gramps-addons/branches/gramps{{stable_branch}}/contrib
# Make a new project directory in gramps-addon/branches/gramps32/contrib:
+
# Make a new project directory in gramps-addon/branches/gramps{{stable_branch}}/contrib:
 
## mkdir NewProjectName
 
## mkdir NewProjectName
 
# Initialize the addon:
 
# Initialize the addon:
Line 64: Line 64:
 
if the default ("../../..") is not correct.
 
if the default ("../../..") is not correct.
  
Follow the development API for your tool, [[Report-writing_tutorial|report]], view, or [[Gramplets]]. Place all of your associated .py, .glade, etc. files in this directory. For general information on GRAMPS development see [[Portal:Developers]] and [[Writing a Plugin]] specifically.
+
Follow the development API for your tool, [[Report-writing_tutorial|report]], view, or [[Gramplets]]. Place all of your associated .py, .glade, etc. files in this directory. For general information on Gramps development see [[Portal:Developers]] and [[Writing a Plugin]] specifically.
  
To test your addon as you develop it it is suggested that you replace your GRAMPS user plugin directory with a link to your addon development directory, like so:
+
To test your addon as you develop it it is suggested that you replace your Gramps user plugin directory with a link to your addon development directory, like so:
  
  cd ~/.gramps/gramps32/
+
  cd ~/.gramps/gramps{{stable_branch}}/
  mv plugins/* /wherever/trunk/gramps-addons/branches/gramps32/contrib/
+
  mv plugins/* /wherever/trunk/gramps-addons/branches/gramps{{stable_branch}}/contrib/
 
  rm -rf plugins
 
  rm -rf plugins
  ln -s /wherever/trunk/gramps-addons/branches/gramps32/contrib plugins
+
  ln -s /wherever/trunk/gramps-addons/branches/gramps{{stable_branch}}/contrib plugins
  
GRAMPS will search this folder (and subdirectories) for .grp.py files, and add them to the plugin list.
+
Gramps will search this folder (and subdirectories) for .grp.py files, and add them to the plugin list.
  
If you have code that you want to share between addons, you don't need to do anything special. Currently, GRAMPS adds each directory in which a .gpr.py is found onto the PYTHONPATH which is searched when you perform an import. Thus "import NewProjectName" will work from another addon. You should always make sure you name your addons with a name appropriate for Python imports.
+
If you have code that you want to share between addons, you don't need to do anything special. Currently, Gramps adds each directory in which a .gpr.py is found onto the PYTHONPATH which is searched when you perform an import. Thus "import NewProjectName" will work from another addon. You should always make sure you name your addons with a name appropriate for Python imports.
 +
 
 +
=== Commit your changes ===
  
 
To commit your changes so that others can use your addon, follow these steps:
 
To commit your changes so that others can use your addon, follow these steps:
  
 
# Get an http://sourceforge.net account if you don't already have one.
 
# Get an http://sourceforge.net account if you don't already have one.
# Request SVN write access for the gramps-addon project from https://sourceforge.net/project/memberlist.php?group_id=285429
+
# Request SVN write access for the gramps-addon project from https://sourceforge.net/project/memberlist.php?group_id=285429 [http://sourceforge.net/p/allura/tickets/5481/]
 
# Remove the files that should not be added to SVN:
 
# Remove the files that should not be added to SVN:
 
## ./make.py clean NewProjectName
 
## ./make.py clean NewProjectName
Line 133: Line 135:
  
 
For any addon which you have translations into other languages, you will need to add a way to retrieve the translation. You need to add this to the top of your NewProjectName.py file:
 
For any addon which you have translations into other languages, you will need to add a way to retrieve the translation. You need to add this to the top of your NewProjectName.py file:
 +
==== For Gramps 3: ====
 +
from TransUtils import get_addon_translator
 +
_ = get_addon_translator(__file__).gettext
 +
 +
==== For Gramps 4: ====
 +
 +
from gramps.gen.const import GRAMPS_LOCALE as glocale
 +
_ = glocale.get_addon_translator(__file__).gettext
  
<pre>
 
from TransUtils import get_addon_translator
 
_ = get_addon_translator().gettext
 
</pre>
 
  
Then you can use the standard "_()" function to translate phrases in your addon.
+
Then you can use the standard "_()" function to translate phrases in your addon.  
  
 
You can use one of a few different types of translation functions:
 
You can use one of a few different types of translation functions:
  
# gettext  
+
# gettext
 
# lgettext
 
# lgettext
 +
# ngettext
 
# lngettext
 
# lngettext
# ngettext
+
# sgettext
 +
 
 +
Gramps 3 also provides:
 +
 
 
# ugettext
 
# ugettext
 
# ungettext
 
# ungettext
  
NOTE: currently we don;t have a method of using a context in any of these functions.
+
These have become obsolete in Gramps 4; gettext, ngettext, and sgettext always return translated strings in unicode for consistent portability between Python 2 and Python3.
  
The translation functions that are supported are defined as follows.  
+
See the [http://docs.python.org/3/library/gettext.html#the-gnutranslations-class python documentation] for documentation of gettext and ngettext. The "l" versions return the string encoded according to the [http://docs.python.org/3/library/locale.html#locale.setlocale currently set locale]; the "u" versions return unicode strings in Python2 and are not available in Python 3.
  
==== gettext(message) ====
+
'''sgettext''' is a Gramps extension that filters out clarifying comments for translators, such as
 
+
_("Remaining names | rest")
If a fallback has been set, forward gettext() to the fallback. Otherwise, return the translated message. Overridden in derived classes.
+
Where "rest" is the English string that we want to present and "Remaining names" is a hint for translators.
 
 
==== lgettext(message) ====
 
 
 
If a fallback has been set, forward lgettext() to the fallback. Otherwise, return the translated message. Overridden in derived classes.
 
 
 
==== ugettext(message) ====
 
 
 
If a fallback has been set, forward ugettext() to the fallback. Otherwise, return the translated message as a Unicode string. Overridden in derived classes.
 
 
 
==== ngettext(singular, plural, n) ====
 
 
 
If a fallback has been set, forward ngettext() to the fallback. Otherwise, return the translated message. Overridden in derived classes.
 
 
 
==== lngettext(singular, plural, n) ====
 
 
 
If a fallback has been set, forward ngettext() to the fallback. Otherwise, return the translated message. Overridden in derived classes.
 
 
 
==== ungettext(singular, plural, n) ====
 
 
 
If a fallback has been set, forward ungettext() to the fallback. Otherwise, return the translated message as a Unicode string. Overridden in derived classes.
 
  
 
== Create a Gramps Plugin Registration file ==
 
== Create a Gramps Plugin Registration file ==
Line 184: Line 174:
 
<pre>
 
<pre>
 
register(PTYPE,
 
register(PTYPE,
     gramps_target_version = "3.2",
+
     gramps_target_version = "3.4",
 
     version = "1.0.0",
 
     version = "1.0.0",
 
     ATTR = value,
 
     ATTR = value,
Line 190: Line 180:
 
</pre>
 
</pre>
  
PTYPE is TOOL, GRAMPLET, REPORT, QUICKREPORT, IMPORT, EXPORT, DOCGEN, GENERAL, MAPSERVICE, VIEW, or RELCALC.
+
PTYPE is TOOL, GRAMPLET, REPORT, QUICKVIEW, IMPORT, EXPORT, DOCGEN, GENERAL, MAPSERVICE, VIEW, or RELCALC.
  
 
ATTR depends on the PTYPE. But you must have '''gramps_target_version''' and '''version'''. gramps_target_version should be a string of the form "X.Y" version number matching Gramps X major, Y minor integer. version is a string of the form "X.Y.Z" representing the version of your addon. X, Y, and Z should all be integers.
 
ATTR depends on the PTYPE. But you must have '''gramps_target_version''' and '''version'''. gramps_target_version should be a string of the form "X.Y" version number matching Gramps X major, Y minor integer. version is a string of the form "X.Y.Z" representing the version of your addon. X, Y, and Z should all be integers.
Line 202: Line 192:
 
         description =  _("Attaches a shared source to multiple objects."),
 
         description =  _("Attaches a shared source to multiple objects."),
 
         version = '1.0.0',
 
         version = '1.0.0',
         gramps_target_version = '3.2',
+
         gramps_target_version = '3.4',
 
         status = STABLE,
 
         status = STABLE,
 
         fname = 'AttachSourceTool.py',
 
         fname = 'AttachSourceTool.py',
Line 214: Line 204:
 
</pre>
 
</pre>
  
You can see examples of the kinds of addons [http://gramps.svn.sourceforge.net/viewvc/gramps/trunk/src/plugins/ here] (for example, see [http://gramps.svn.sourceforge.net/viewvc/gramps/trunk/src/plugins/drawreport/drawplugins.gpr.py?view=markup trunk/src/plugins/drawreport/drawreport.gpr.py]) and see the full documentation [http://gramps.svn.sourceforge.net/viewvc/gramps/trunk/src/gen/plug/_pluginreg.py?view=markup here] in the comments and docstrings.
+
You can see examples of the kinds of addons [http://svn.code.sf.net/p/gramps/code/trunk/gramps/plugins/ here] (for example, see [http://svn.code.sf.net/p/gramps/code/trunk/gramps/plugins/drawreport/drawplugins.gpr.py trunk/gramps/plugins/drawreport/drawplugins.gpr.py]) and see the full documentation [http://svn.code.sf.net/p/gramps/code/trunk/gramps/gen/plug/_pluginreg.py here] in the comments and docstrings.
  
 
Note that this .gpr.py will automatically use translations if you have them (see below). That is, the function "_" is predefined to use your locale translations; you only need to mark the text with _("TEXT") and include a translation of "TEXT" in your translation file. For example, in the above example, _("Attach Source") is marked for translation. If you have developed and packaged your addon with translation support, then that phrase will be converted into the user's language.
 
Note that this .gpr.py will automatically use translations if you have them (see below). That is, the function "_" is predefined to use your locale translations; you only need to mark the text with _("TEXT") and include a translation of "TEXT" in your translation file. For example, in the above example, _("Attach Source") is marked for translation. If you have developed and packaged your addon with translation support, then that phrase will be converted into the user's language.
  
=== General Plugins ===
+
=== Report plugins ===
 +
The possible report categories are (gen/plug/_pluginreg.py):
 +
<pre>
 +
#possible report categories
 +
CATEGORY_TEXT      = 0
 +
CATEGORY_DRAW      = 1
 +
CATEGORY_CODE      = 2
 +
CATEGORY_WEB        = 3
 +
CATEGORY_BOOK      = 4
 +
CATEGORY_GRAPHVIZ  = 5
 +
REPORT_CAT          = [ CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_CODE,
 +
                        CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ]
 +
</pre>
 +
 
 +
Each report category has a set of standards and interface. The categories CATEGORY_TEXT and CATEGORY_DRAW use the Document interface of Gramps. See also [[Report API]] for a draft view on this.
 +
The application programming interface or API for reports is treated at [[Report-writing_tutorial]]. For general information on Gramps development see [[Portal:Developers]] and [[Writing a plugin]] specifically.
 +
 
 +
=== General plugins ===
  
 
The plugin framework also allows you to create generic plugins for use. This includes the ability to create libraries of functions, and plugins of your own design.
 
The plugin framework also allows you to create generic plugins for use. This includes the ability to create libraries of functions, and plugins of your own design.
  
==== Example: A Library of Functions ====
+
==== Example: A library of functions ====
  
 
In this example, a file name library.py will be imported at time of registration (when Gramps starts):
 
In this example, a file name library.py will be imported at time of registration (when Gramps starts):
Line 234: Line 241:
 
   description =  _("Provides a library for doing something."),
 
   description =  _("Provides a library for doing something."),
 
   version = '1.0',
 
   version = '1.0',
   gramps_target_version = '3.3',
+
   gramps_target_version = '3.4',
 
   status = STABLE,
 
   status = STABLE,
 
   fname = 'library.py',
 
   fname = 'library.py',
Line 258: Line 265:
  
 
Here, you could connect signals to the dbstate, open windows, etc.
 
Here, you could connect signals to the dbstate, open windows, etc.
 
==== Example: A plugin for other plugins ====
 
  
 
Another example of what one can do with the plugin interface is to create a general purpose plugin framework for use by other plugins. Here is the basis for a plugin system that:
 
Another example of what one can do with the plugin interface is to create a general purpose plugin framework for use by other plugins. Here is the basis for a plugin system that:
Line 267: Line 272:
  
 
First, the gpr.py file:
 
First, the gpr.py file:
 +
 +
<pre>
 +
 +
register(GENERAL,
 +
  id    = "ID",
 +
  category = "CATEGORY",
 +
  load_on_reg = True,
 +
  process = "FUNCTION_NAME",
 +
  )
 +
</pre>
 +
 +
This example uses three new features:
 +
 +
# GENERAL plugins can have a category
 +
# GENERAL plugins can have a load_on_reg function that returns data
 +
# GENERAL plugins can have a function (called "process") which will process the data
 +
 +
If you (or someone else) create additional general plugins of this category, and they follow your load_on_reg data format API, then they could be used just like your original data. For example, here is an additional general plugin in the 'WEBSTUFF' category:
 +
 +
<pre>
 +
# anew.gpr.py
 +
 +
register(GENERAL,
 +
  id    = 'a new plugin',
 +
  category = "WEBSTUFF",
 +
  version = '1.0',
 +
  gramps_target_version = '3.4',
 +
  data = ["a", "b", "c"],
 +
  )
 +
</pre>
 +
 +
This doesn't have load_on_reg = True, nor does it have a fname or process, but it does set the data directly in the .gpr.py file. Then we have the following results:
 +
 +
<pre>
 +
>>> from gui.pluginmanager import GuiPluginManager
 +
>>> PLUGMAN = GuiPluginManager.get_instance()
 +
>>> PLUGMAN.get_plugin_data('WEBSTUFF')
 +
["a", "b", "c", "Stylesheet.css", "Another.css"]
 +
>>> PLUGMAN.process_plugin_data('WEBSTUFF')
 +
["A", "B", "C", "STYLESHEET.CSS", "ANOTHER.CSS"]
 +
</pre>
 +
 +
=== Registered GENERAL Categories ===
 +
 +
The following are the published secondary plugins API's (type GENERAL, with the following categories):
 +
 +
==== WEBSTUFF ====
 +
 +
A sample gpr.py file:
  
 
<pre>
 
<pre>
Line 273: Line 327:
 
register(GENERAL,  
 
register(GENERAL,  
 
   id    = 'system stylesheets',
 
   id    = 'system stylesheets',
   category = "CSS",
+
   category = "WEBSTUFF",
 
   name  = _("CSS Stylesheets"),
 
   name  = _("CSS Stylesheets"),
 
   description =  _("Provides a collection of stylesheets for the web"),
 
   description =  _("Provides a collection of stylesheets for the web"),
 
   version = '1.0',
 
   version = '1.0',
   gramps_target_version = '3.3',
+
   gramps_target_version = '3.4',
 
   fname = "stylesheet.py",
 
   fname = "stylesheet.py",
 
   load_on_reg = True,
 
   load_on_reg = True,
Line 283: Line 337:
 
   )
 
   )
 
</pre>
 
</pre>
 
This example uses three new features:
 
 
# GENERAL plugins can have a category
 
# GENERAL plugins can have a load_on_reg function that returns data
 
# GENERAL plugins can have a function (called "process") which will process the data
 
  
 
Here is the associated program:
 
Here is the associated program:
Line 305: Line 353:
 
</pre>
 
</pre>
  
These functions can be accessed through the PluginManager, by the name of the category given, 'CSS':
+
==== Filters ====
 +
 
 +
For example:
  
 
<pre>
 
<pre>
>>> from gui.pluginmanager import GuiPluginManager
+
register(GENERAL,
>>> PLUGMAN = GuiPluginManager.get_instance()
+
  category="Filters",
>>> PLUGMAN.get_plugin_data('CSS')
+
  ...
["Stylesheet.css", "Another.css"]
+
  load_on_reg = True
>>> PLUGMAN.process_plugin_data('CSS')
+
)
["STYLESHEET.CSS", "ANOTHER.CSS"]
 
 
</pre>
 
</pre>
 
If you (or someone else) create additional 'CSS' general plugins, and they follow your load_on_reg data format API, then they could be used just like your original data. For example, here is an additional general plugin in the 'CSS' category:
 
  
 
<pre>
 
<pre>
# anew.gpr.py
+
def load_on_reg(dbstate, uistate, plugin):
 
+
    # returns a function that takes a namespace, 'Person', 'Family', etc.
register(GENERAL,  
 
  id    = 'a new plugin',
 
  category = "CSS",
 
  version = '1.0',
 
  gramps_target_version = '3.3',
 
  data = ["a", "b", "c"],
 
  )
 
</pre>
 
 
 
This doesn't have load_on_reg = True, nor does it have a fname or process, but it does set the data directly in the .gpr.py file. Then we have the following results:
 
  
<pre>
+
    def filters(namespace):
>>> PLUGMAN.get_plugin_data('CSS')
+
        print "Ok...", plugin.category, namespace, uistate
["a", "b", "c", "Stylesheet.css", "Another.css"]
+
        # return a Filter object here
>>> PLUGMAN.process_plugin_data('CSS')
+
    return filters
["A", "B", "C", "STYLESHEET.CSS", "ANOTHER.CSS"]
 
 
</pre>
 
</pre>
  
Line 362: Line 398:
 
That will build and copy your addon to ../download
 
That will build and copy your addon to ../download
  
''New for Gramps 3.3'': You need to then make your addon available in listings of various languages. To do that:
+
NOTE: Running the '''make.py build''' will increment the third number in your dotted version number of all addons in the gpr.py file. Consider this number to be a "build number".
 +
 
 +
''New for Gramps 3.4'': You need to then make your addon available in listings of various languages. To do that:
  
 
  python make.py listing
 
  python make.py listing
Line 386: Line 424:
 
== List and document your addon ==
 
== List and document your addon ==
  
Edit [[Plugins3.2]] and describe your addon. You can point to the addon.tgz in SVN as the downloadable file. Document the addon in the wiki using the name "Addon:NewProjectName".
+
Edit [[Plugins3.4]] or [[Plugins4.0]] and describe your addon. You can point to the addon.tgz in SVN as the downloadable file.  
 +
 
 +
Document the addon in the wiki using the name '''"Addon:NewProjectName"'''.
  
 
== Support it through issue tracker ==
 
== Support it through issue tracker ==
  
== Maintain the code as GRAMPS continues to evolve ==
+
Visit http://www.gramps-project.org/bugs/view_all_bug_page.php and become a user. Suggest to check it regularly.
 +
 
 +
== Maintain the code as Gramps continues to evolve ==
  
 
= Resources =
 
= Resources =
  
* http://gramps-addons.svn.sourceforge.net/viewvc/gramps-addons/ - SVN browse
+
* https://sourceforge.net/p/gramps-addons/ - Gramps Addons site
  
 
[[Category:Developers/General]]
 
[[Category:Developers/General]]
 
[[Category:Developers/Tutorials]]
 
[[Category:Developers/Tutorials]]
[[Category: Plugins]]
+
[[Category:Plugins]]
 +
[[Category:Reports]]
 +
[[Category:Gramplets]]

Revision as of 13:01, 2 February 2013

Gnome-important.png
Warning:

This page documents the API, methods, and best practices for developing a 3rd-party addon for Gramps 3.2 and later

Addons for Gramps can extend the program in many different ways. You can add any of the following types of addons:

  1. Report
  2. Quickreport
  3. Tool
  4. Importer
  5. Exporter
  6. Doc creator
  7. Plugin lib
  8. Map service
  9. Gramps View
  10. Relationships
  11. Gramplet
  12. Sidebar

Writing an addon is fairly straightforward if you have just a little bit of Python experience. And sharing your addon is the right thing to do. The general steps to writing an addon and sharing your own addons are:

  1. Develop your addon
  2. Create a Gramps Plugin Registration file (.gpr.py)
  3. Get translators to translate your addon into multiple languages
  4. Package your addon
  5. List and document your addon
  6. Support it through issue tracker
  7. Maintain the code as Gramps continues to evolve

We'll now look at each of these steps in detail.

Develop your addon

The gramps-addons subversion repository has the following structure:

  • /gramps-addons
    • /trunk
      • /contrib
      • /download
    • /branches
      • /gramps52
      • /gramps32
        • /contrib
        • /download

The contrib subdirectories hold the source code for the addons for a particular version. If you are working on a addon for gramps52 then you should be working in gramps-addons/branches/gramps52/contrib. If you are working in gramps/trunk then you should use gramps-addons/trunk/contrib.

These steps show how to download and work with the addon development tools.

  1. Checkout the gramps-addons files from the gramps-addons project:
    1. cd into gramps trunk, for example:
      1. cd ~/gramps/trunk
    2. Checkout gramps-addons:
      1. svn co https://svn.code.sf.net/p/gramps-addons/code gramps-addons
    3. Change to trunk or branches/gramps52 directory:
      1. cd gramps-addons/branches/gramps52/contrib
  2. Make a new project directory in gramps-addon/branches/gramps52/contrib:
    1. mkdir NewProjectName
  3. Initialize the addon:
    1. ./make.py init NewProjectName

NOTE: to use make.py as shown throughout this document, you may have to use:

  GRAMPSPATH=/path/to/gramps python make.py ...

if the default ("../../..") is not correct.

Follow the development API for your tool, report, view, or Gramplets. Place all of your associated .py, .glade, etc. files in this directory. For general information on Gramps development see Portal:Developers and Writing a Plugin specifically.

To test your addon as you develop it it is suggested that you replace your Gramps user plugin directory with a link to your addon development directory, like so:

cd ~/.gramps/gramps52/
mv plugins/* /wherever/trunk/gramps-addons/branches/gramps52/contrib/
rm -rf plugins
ln -s /wherever/trunk/gramps-addons/branches/gramps52/contrib plugins

Gramps will search this folder (and subdirectories) for .grp.py files, and add them to the plugin list.

If you have code that you want to share between addons, you don't need to do anything special. Currently, Gramps adds each directory in which a .gpr.py is found onto the PYTHONPATH which is searched when you perform an import. Thus "import NewProjectName" will work from another addon. You should always make sure you name your addons with a name appropriate for Python imports.

Commit your changes

To commit your changes so that others can use your addon, follow these steps:

  1. Get an http://sourceforge.net account if you don't already have one.
  2. Request SVN write access for the gramps-addon project from https://sourceforge.net/project/memberlist.php?group_id=285429 [1]
  3. Remove the files that should not be added to SVN:
    1. ./make.py clean NewProjectName
  4. Add the project to the repository:
    1. svn add NewProjectName
    2. svn commit -m "A message describing what this addon is"

Before making additional edits to your addon, you should:

  1. svn update
  2. svn status
  3. svn commit -m "A message describing the changes"

Config

Some addons may want to have persistent data (data settings that remain between sessions). You can handle this yourself, or you can use Gramps' built-in configure system.

In the file that defines the settings, you would do this:

from config import config
cm = config.register_manager("view_placetreeview_0")
cm.register("section.variable1", value1)
cm.register("section.variable2", value2)
...
cm.init()

This will create the file "view_placetreeview_0.ini" and put in the same directory as the addon. In the addon, you can then:

x = cm.get("section.variable1")
cm.set("section.variable1", 3)

and when this code is exiting, you might want to save the config. In a Gramplet that would be:

def on_save(self):
    cm.save()

If your code is a system-level file, then you might want to save the config in the Gramps system folder:

cm = config.register_manager("system", use_config_path=True)

This, however, would be rare; most .ini files would go into the plugins directory.

In other code that might use this config file, you would do this:

from config import config
cm = config.get_manager("view_placetreeview_0")
x = cm.get("section.variable1")

Localization

For general help on translations in Gramps, see Coding for translation. However, that will only use translations that come with Gramps, or allows you to contribute translations to the Gramps core. To have your own managed translations that will be packaged with your addon, read the rest of this page.

For any addon which you have translations into other languages, you will need to add a way to retrieve the translation. You need to add this to the top of your NewProjectName.py file:

For Gramps 3:

from TransUtils import get_addon_translator
_ = get_addon_translator(__file__).gettext

For Gramps 4:

from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.get_addon_translator(__file__).gettext


Then you can use the standard "_()" function to translate phrases in your addon.

You can use one of a few different types of translation functions:

  1. gettext
  2. lgettext
  3. ngettext
  4. lngettext
  5. sgettext

Gramps 3 also provides:

  1. ugettext
  2. ungettext

These have become obsolete in Gramps 4; gettext, ngettext, and sgettext always return translated strings in unicode for consistent portability between Python 2 and Python3.

See the python documentation for documentation of gettext and ngettext. The "l" versions return the string encoded according to the currently set locale; the "u" versions return unicode strings in Python2 and are not available in Python 3.

sgettext is a Gramps extension that filters out clarifying comments for translators, such as

_("Remaining names | rest")

Where "rest" is the English string that we want to present and "Remaining names" is a hint for translators.

Create a Gramps Plugin Registration file

First, create the NewProjectName.gpr.py file. The registration takes this general form:

register(PTYPE,
     gramps_target_version = "3.4",
     version = "1.0.0",
     ATTR = value,
)

PTYPE is TOOL, GRAMPLET, REPORT, QUICKVIEW, IMPORT, EXPORT, DOCGEN, GENERAL, MAPSERVICE, VIEW, or RELCALC.

ATTR depends on the PTYPE. But you must have gramps_target_version and version. gramps_target_version should be a string of the form "X.Y" version number matching Gramps X major, Y minor integer. version is a string of the form "X.Y.Z" representing the version of your addon. X, Y, and Z should all be integers.

Here is a sample Tool GPR file:

register(TOOL, 
         id    = 'AttachSource',
         name  = _("Attach Source"),
         description =  _("Attaches a shared source to multiple objects."),
         version = '1.0.0',
         gramps_target_version = '3.4',
         status = STABLE,
         fname = 'AttachSourceTool.py',
         authors = ["Douglas S. Blank"],
         authors_email = ["[email protected]"],
         category = TOOL_DBPROC,
         toolclass = 'AttachSourceWindow',
         optionclass = 'AttachSourceOptions',
         tool_modes = [TOOL_MODE_GUI]
         )

You can see examples of the kinds of addons here (for example, see trunk/gramps/plugins/drawreport/drawplugins.gpr.py) and see the full documentation here in the comments and docstrings.

Note that this .gpr.py will automatically use translations if you have them (see below). That is, the function "_" is predefined to use your locale translations; you only need to mark the text with _("TEXT") and include a translation of "TEXT" in your translation file. For example, in the above example, _("Attach Source") is marked for translation. If you have developed and packaged your addon with translation support, then that phrase will be converted into the user's language.

Report plugins

The possible report categories are (gen/plug/_pluginreg.py):

#possible report categories
CATEGORY_TEXT       = 0
CATEGORY_DRAW       = 1
CATEGORY_CODE       = 2
CATEGORY_WEB        = 3
CATEGORY_BOOK       = 4
CATEGORY_GRAPHVIZ   = 5
REPORT_CAT          = [ CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_CODE,
                        CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ]

Each report category has a set of standards and interface. The categories CATEGORY_TEXT and CATEGORY_DRAW use the Document interface of Gramps. See also Report API for a draft view on this. The application programming interface or API for reports is treated at Report-writing_tutorial. For general information on Gramps development see Portal:Developers and Writing a plugin specifically.

General plugins

The plugin framework also allows you to create generic plugins for use. This includes the ability to create libraries of functions, and plugins of your own design.

Example: A library of functions

In this example, a file name library.py will be imported at time of registration (when Gramps starts):

# file: library.gpr.py

register(GENERAL, 
   id    = 'My Library',
   name  = _("My Library"),
   description =  _("Provides a library for doing something."),
   version = '1.0',
   gramps_target_version = '3.4',
   status = STABLE,
   fname = 'library.py',
   load_on_reg = True,
  )

The code in the file library.py will be imported when Gramps begins. You can access the loaded module in other code by issuing an "import library" as Python keeps track of files already imported. However, the amount of useful code that you can run when the program is imported is limited. You might like to have the code do something that requires a dbstate or uistate object, and neither of these is available when just importing a file.

If "load_on_reg" was not True, then this code would be unavailable until manually loaded. There is no automatic mechanism in Gramps to load GENERAL plugins automatically.

In addition to importing a file at startup, one can also run a single function inside a GENERAL plugin, and it will be passed the dbstate, the uistate, and the plugin data. The function must be called "load_on_reg", and take those three parameters, like this:

# file: library.py

def load_on_reg(dbstate, uistate, plugin):
    """
    Runs when plugin is registered.
    """
    print "Hello World!"

Here, you could connect signals to the dbstate, open windows, etc.

Another example of what one can do with the plugin interface is to create a general purpose plugin framework for use by other plugins. Here is the basis for a plugin system that:

  • allows plugins to list data files
  • allows the plugin to process all of the data files

First, the gpr.py file:


register(GENERAL, 
  id    = "ID",
  category = "CATEGORY",
  load_on_reg = True,
  process = "FUNCTION_NAME",
  )

This example uses three new features:

  1. GENERAL plugins can have a category
  2. GENERAL plugins can have a load_on_reg function that returns data
  3. GENERAL plugins can have a function (called "process") which will process the data

If you (or someone else) create additional general plugins of this category, and they follow your load_on_reg data format API, then they could be used just like your original data. For example, here is an additional general plugin in the 'WEBSTUFF' category:

# anew.gpr.py

register(GENERAL, 
  id    = 'a new plugin',
  category = "WEBSTUFF",
  version = '1.0',
  gramps_target_version = '3.4',
  data = ["a", "b", "c"],
  )

This doesn't have load_on_reg = True, nor does it have a fname or process, but it does set the data directly in the .gpr.py file. Then we have the following results:

>>> from gui.pluginmanager import GuiPluginManager
>>> PLUGMAN = GuiPluginManager.get_instance()
>>> PLUGMAN.get_plugin_data('WEBSTUFF')
["a", "b", "c", "Stylesheet.css", "Another.css"]
>>> PLUGMAN.process_plugin_data('WEBSTUFF')
["A", "B", "C", "STYLESHEET.CSS", "ANOTHER.CSS"]

Registered GENERAL Categories

The following are the published secondary plugins API's (type GENERAL, with the following categories):

WEBSTUFF

A sample gpr.py file:

# stylesheet.gpr.py

register(GENERAL, 
  id    = 'system stylesheets',
  category = "WEBSTUFF",
  name  = _("CSS Stylesheets"),
  description =  _("Provides a collection of stylesheets for the web"),
  version = '1.0',
  gramps_target_version = '3.4',
  fname = "stylesheet.py",
  load_on_reg = True,
  process = "process_list",
  )

Here is the associated program:

# file: stylesheet.py

def load_on_reg(dbstate, uistate, plugin):
    """
    Runs when plugin is registered.
    """
    return ["Stylesheet.css", "Another.css"]

def process_list(files):
    return [file.upper() for file in files]

Filters

For example:

register(GENERAL,
   category="Filters",
   ...
   load_on_reg = True
)
def load_on_reg(dbstate, uistate, plugin):
    # returns a function that takes a namespace, 'Person', 'Family', etc.

    def filters(namespace):
        print "Ok...", plugin.category, namespace, uistate
        # return a Filter object here
    return filters

Get translators to translate your addon into multiple languages

  1. Initialize and update the template.pot for your addon:
    1. ./make.py init NewProjectName
  2. Initialize a language for your addon (say French, fr):
    1. ./make.py init NewProjectName fr
  3. Update it from gramps and other addons:
    1. ./make.py update NewProjectName fr
  4. Edit contrib/NewProjectName/po/fr-local.po
  5. Compile the language:
    1. ./make.py compile NewProjectName
  6. Add or update your local language file, and commit changes:
    1. svn add NewProjectName/po/fr-local.po
    2. svn commit NewProjectName/po/fr-local.po -m "Added fr po file"

Package your addon

To create a downloadable package:

python make.py build NewProjectName

That will build and copy your addon to ../download

NOTE: Running the make.py build will increment the third number in your dotted version number of all addons in the gpr.py file. Consider this number to be a "build number".

New for Gramps 3.4: You need to then make your addon available in listings of various languages. To do that:

python make.py listing

That will create a series of files in the ../listings/

Then add the package to SVN:

svn add ../download/NewProjectName.addon.tgz ../listings/*
cd ..
svn commit -m "Message describing changes"

Miscellaneous commands

To build all projects:

python make.py build all

To compile all projects to their download/Addon.addon.tgz files:

python make.py compile all

List and document your addon

Edit Plugins3.4 or Plugins4.0 and describe your addon. You can point to the addon.tgz in SVN as the downloadable file.

Document the addon in the wiki using the name "Addon:NewProjectName".

Support it through issue tracker

Visit http://www.gramps-project.org/bugs/view_all_bug_page.php and become a user. Suggest to check it regularly.

Maintain the code as Gramps continues to evolve

Resources