Difference between revisions of "Gramplets"

From Gramps
Jump to: navigation, search
(New docs for gramplets)
 
(Hello World)
 
(204 intermediate revisions by 11 users not shown)
Line 1: Line 1:
[[Category:Developers/General]][[Category:Developers/Tutorials]][[Category: Plugins]]
+
<!--{{languages|Gramplets}}-->
 +
{{man warn|Warning:|This section contains technical details about programming Gramplets and is intended for Developers. <br><br>'''If you are interested in using Gramplets, please see the [[Gramps_{{man version}}_Wiki_Manual_-_Gramplets|user manual section on gramplets]]'''.}}
 +
 
 +
A '''Gramplet''' is a [[Addon_list_legend#Type|type of Gramps plugin]]. Gramplets are mini-views designed to be composited with other Gramplets or Views to create a way to see your Family Tree that is just right for you. In fact, Gramplets can be made to do just about anything that you want.
 +
 
 +
[[File:DashboardCategory-DashboardView-default-gramplets-52.png|450px|thumb|right|Default Gramplets on Dashboard - With Example Family Tree Open]]
 +
[[File:Dashboard-category-view-first-open-no-family-tree-loaded-52.png|450px|thumb|right|Default Gramplets on Dashboard - No Family Tree Loaded]]
 +
 
  
 
= Gramplet Interface =
 
= Gramplet Interface =
  
[[Image:Gramplets.png|thumb|right|Gramplet]]
+
Gramplet only operate after being added to a view. Add (or remove and restore) to the [[Gramps_{{man version}}_Wiki_Manual_-_Main_Window#Bottombar_and_Sidebar|sidebar or bottombar]] of a view by clicking the {{man button|&or;}} (''Down Arrowhead'' button) also known as the '''Gramplet Bar Menu''' at the far top right of the bars titles, and then using one of the options from the drop-down menu. In the Dashboard view, this menu is accessed by right-clicking in-between gramplets in the main view.
  
A Gramplet is a type of GRAMPS plugin. A Gramplet is a mini-view that can be made
+
Gramplet can dramatically degrade the performance of Gramps. Some Gramplets consume huge amounts of memory, processor power and move a lot of data through storage. Don't keep a Gramplet active when it isn't being used!
to do just about anything that you want. There are 6 kinds of plugins:
 
  
# Reports: output for printing or display
+
When the view is not active or Gramplet tab is not the foremost in the splitbar, the Gramplet is inactive. A detached (undocked) Gramplet is always active.
# Tools: a method for processing data
 
# Quick View: a list of details based on the current object
 
# Importer: reads a file into your current tree
 
# Exporter writes a file from your current tree
 
# Gramplets: interactive views for moving, analysing, displaying, etc.
 
  
There are two plugin directories: a global/system one, and a
+
The user interface for a Gramplet is left completely to the discretion of the developer. Detach/undock a Gramplet to reveal the "Help" button. Explore the Configure options after adding a Gramplet.
private/personal one.  You can easily create a plugin by simply
+
 
putting a file in your personal plugin directory (usually in
+
== Are Gramplets and 'plug-ins' the same thing? ==
.gramps/plugins).
+
There are many [[Addon_list_legend#Type|types of plugins]]. The 6 most common are:
 +
 
 +
# '''Reports''': output for printing or display
 +
# '''Tools''': a method for processing data
 +
# '''Quick View''': a list of details based on the current object
 +
# '''Importer''': reads a file into your current tree
 +
# '''Exporter''': writes a file from your current tree
 +
# '''Gramplets''': interactive views for moving, analysing, displaying, etc.
 +
 
 +
There are two plugin directories: a global/system one, and a private/personal one.  You can easily create a plugin by simply putting a file in plugins folder of your [[Gramps_{{man version}}_Wiki_Manual_-_User_Directory|User Directory]] (usually in ''<code>.gramps/grampsxx/plugins/gramplet/</code>'' ).
  
 
== Hello World ==
 
== Hello World ==
  
In teaching programming, a common "first program" is to write a program that
+
In teaching programming, a common "first program" is to write a program that says "Hello World".
says "Hello World". Let's jump right in and take a look at such a gramplet:
+
 
 +
Let us jump right in and take a look at such a gramplet named ''HelloWorld.py'':
  
 +
Create a python file named ''HelloWorld.py'' and add the following content:
 
<pre>
 
<pre>
 +
# File: HelloWorld.py
 
def init(gui):
 
def init(gui):
  gui.set_text("Hello world!")
+
    gui.set_text("Hello world!")
 +
</pre>
  
from DataViews import register
+
And create another python file named ''HelloWorld.gpr.py'' with the following content:
  
register(type="gramplet",  
+
<pre>
         name="Hello World Gramplet",  
+
# File: HelloWorld.gpr.py
 +
register(GRAMPLET,
 +
        id="Hello World Gramplet",  
 +
         name=_("Hello World Gramplet"),
 +
        description = _("a program that says 'Hello World'"),
 +
        status = STABLE,
 +
        version="0.0.1",
 +
        fname="HelloWorld.py",
 
         height = 20,
 
         height = 20,
         content = init,  
+
         gramplet = 'init',
         title="Sample Gramplet",  
+
         gramplet_title=_("Sample Gramplet"),
 +
        gramps_target_version="5.2",
 +
        help_url="Sample Gramplet"
 
         )
 
         )
 
</pre>
 
</pre>
  
If you copy this text into a file in your plugins directory, and then
+
You can read more about the gpr.py file in [[Addons_development#Create_a_Gramps_Plugin_Registration_file|Addons development]] and the [https://github.com/gramps-project/gramps/blob/master/gramps/gen/plug/_pluginreg.py source code implementation].
either restart GRAMPS or go to Help -> Plugin Status -> Reload, the
 
you'll be able to create this Gramplet. On the Gramplet View,
 
right-click in an open area and select the "Hello World Gramplet". You
 
should then see:
 
  
[[Media:HelloWorldGramplet.png]]
+
If you place these files in '''your personal [[Gramps_{{man version}}_Wiki_Manual_-_User_Directory|user plugins directory]]''' (usually in ''.gramps/grampsxx/plugins/gramplet/'' ), and then restart Gramps, you will be able to create this Gramplet. On the '''Dashboard Category View''', right-click in an open area and select the "Hello World Gramplet". You should then see:
  
=== Explaination ===
+
[[File:HelloWorldGramplet-example-shown-embeded-on-DashboardCategory-52.png|thumb|450px|left|Hello World Gramplet - example (shown embedded on Dashboard Category View / Context menu "Add a gramplet" also shown)]]
  
The main work of a Gramplet is performed in a function, or a class. In
+
{{-}}
this very simple example, a function '''init''' is defined that takes
 
a single argument, gui. The function simply sets the gui's text area
 
to be "Hello World!", and that's it. It does this just once, and never
 
changes.
 
  
Before a plugin can be used, it needs to be "registered". The Gramplet
+
=== Explanation ===
interfaces uses a slightly different method for registering than the
+
 
other plugins. Here, you import the "register" function from the
+
The main work of a Gramplet is performed in a function, or a class. In this very simple example, a function <code>init</code> is defined that takes a single argument, <code>gui</code>. The function simply sets the gui's text area to be "Hello World!", and that's it. It does this just once, and never changes.
DataViews module. Then you call the register function with a number of
+
 
named-arguments. There are a number of named-arguments that you can
+
Before a plugin can be used, it needs to be "registered". You call the register function with a number of named-arguments. There are a number of named-arguments that you can provide, including: name, height, content, title, expand, state, and data. We will explore those in detail, below.
provide, including: name, height, content, title, expand, state, and
 
data. We will explore those in detail, below.
 
  
 
== Hello World, with Class ==
 
== Hello World, with Class ==
Line 68: Line 81:
  
 
<pre>
 
<pre>
from DataViews import register, Gramplet
+
# File: HelloWorld2.py
 +
from gramps.gen.plug import Gramplet
  
 
class HelloWorldGramplet(Gramplet):
 
class HelloWorldGramplet(Gramplet):
 
     def init(self):
 
     def init(self):
 
         self.set_text("Hello world!")
 
         self.set_text("Hello world!")
 +
</pre>
  
register(type="gramplet",  
+
<pre>
         name="Hello World2 Gramplet",  
+
# File: HelloWorld2.gpr.py
         height = 20,
+
register(GRAMPLET,
         content = HelloWorldGramplet,  
+
        id="Hello World2 Gramplet",  
         title="Sample2 Gramplet",  
+
         name=_("Hello World2 Gramplet"),
 +
        description = _("a program that says 'Hello World'"),
 +
        version="0.0.1",
 +
        gramps_target_version="5.2",
 +
        status = STABLE,
 +
        fname="HelloWorld2.py",
 +
         height = 20,  
 +
         gramplet = 'HelloWorldGramplet',
 +
         gramplet_title=_("Sample Gramplet"),
 +
        help_url="5.2_Addons#Addon_List"
 
         )
 
         )
 
</pre>
 
</pre>
  
This is the recommended method of creating a Gramplet. The following details
+
This is the '''recommended method''' of creating a Gramplet. The following details describe the properties and methods of this class.
describe the properties and methods of these classes.
+
 
 +
== Register Options ==
 +
 
 +
* '''<code>GRAMPLET</code>''': the first argument is the keyword GRAMPLET
 +
* '''<code>id</code>''': the identifying name of the gramplet, unique among all plugins
 +
* '''<code>name</code>''': the translated gramplet's name
 +
* '''<code>height</code>''': the minimum (or maximum) height of the gramplet in normal mode
 +
* '''<code>fname</code>''': the name of your gramplet file
 +
* '''<code>gramplet</code>''': the name of the function or class in fname that creates the gramplet
 +
* '''<code>gramplet_title</code>''': the default gramplet title; user changeable in ''Configure View''
 +
* '''<code>status</code>''': STABLE or UNSTABLE
 +
* '''<code>version</code>''': a string with 2 dots (such as "1.23.14") representing the version number
 +
* '''<code>gramps_target_version</code>''': a string with 2 dots representing the version of Gramps for which this gramplet was written. Only gramplets matching the installed version will be available for installation.
 +
* '''<code>help_url</code>''': the title of the wiki page that describes the gramplet. <br />If the help_url starts with <code>http://</code> then that fully qualified URL will be used as is. Otherwise, the paths will be interpreted as relative to <code>http&#58;//gramps-project.org/wiki/index.php?title=</code> base URL. The base URL will be prepended to the '''help_url''' and may get a language extension (such as <code>/nl</code> ) appended at the end, if the operating language is one of '''nl''' '''fr''' '''sq''' '''mk''' '''de''' '''fi''' '''ru''' '''sk'''. You should '''''not''''' use the <code>_(</code> <code>)</code> translate function around the '''<code>help_url</code>''' string, unless you specifically intend to create web pages named with the translated string.
  
== Core Properties ==
+
At the bare minimum, you need to have the above 11 options when registering your Gramplets.
  
* type: the case-insenstitive keyword "gramplet"
+
In addition, you can use the following as well:
* name: the gramplet's name, unique among gramplets
+
 
* tname: the gramplet's name, translated
+
* '''<code>detached_width</code>''': the size in pixels of the minimum and default detached height
* height: the minimum or maximum height of the gramplet in normal mode
+
* '''<code>detached_height</code>''': the size in pixels of the minimum and default detached height
* content: the function or class name of your code
+
* '''<code>expand</code>''': whether or not the Gramplet should expand to fill the column, if it can
* title: the default gramplet title; user changeable
+
* '''<code>description</code>''': a description of the Gramplet
  
 
== Core Methods ==
 
== Core Methods ==
  
* init: run once, on construction
+
The class-based gramplet utilizes the following methods:
* main: run once per update
+
 
* update: don't change this, it calls main
+
* '''<code>init()</code>''': run once, on construction
* active_changed: run when active-changed is triggered
+
* '''<code>main()</code>''': run once per update
* db_changed: run when db-changed is triggered
+
* '''<code>update()</code>''': don't change this, it calls main
 +
* '''<code>active_changed()</code>''': run when active-changed is triggered
 +
* '''<code>db_changed()</code>''': run when db-changed is triggered
 +
* '''<code>set_tooltip(TEXT)</code>''' - tooltip for gramplet
 +
 
 +
 
  
 
Don't call main directly; use the update method.
 
Don't call main directly; use the update method.
  
In the db_changed method, you should connect all of the signals that
+
In the <code>db_changed</code> method, you should connect all of the signals that will trigger an update. That typically looks like:
will trigger an update. That typically looks like:
 
  
 
<pre>
 
<pre>
Line 115: Line 156:
 
         self.dbstate.db.connect('family-delete', self.update)
 
         self.dbstate.db.connect('family-delete', self.update)
 
         self.dbstate.db.connect('family-update', self.update)
 
         self.dbstate.db.connect('family-update', self.update)
</pre>  
+
</pre>
 +
 
 +
The method <code>main()</code> can be written as a normal Python method, or it can be written to run nicely in parallel with other Gramps code. To make it run nicely in parallel, you should issue a '''<code>yield True</code>''' every once in a while. For example:
 +
 
 +
<pre>
 +
    def main(self):
 +
        for i in range(5000):
 +
            if i % 500 == 0:
 +
                yield True
 +
        yield False
 +
</pre>
 +
 
 +
The '''True''' means that there is more to do; '''False''' means that there is nothing left to do.
 +
 
 +
=== See also ===
 +
* [https://github.com/gramps-project/gramps/blob/d84282c496385ffc874d90a692f7d2628d314be7/gramps/gui/displaystate.py#L422 '''<code>DisplayState(Callback)</code>'''] class for additional signals. (Including Custom Filters, Gramplet display and other updates.)
  
 
== Textual Output Methods ==
 
== Textual Output Methods ==
  
The most common kinds of Gramplets are text-based. There are a number of
+
The most common kinds of Gramplets are text-based. There are a number of methods to assist with handling this text.
methods to assist with handling this text.
 
  
* link(TEXT, LINK-TYPE, OBJECT-HANDLE)
+
* '''set_text(TEXT)''' - clear and set text to TEXT
* set_use_markup(BOOLEAN-VALUE)
+
* '''append_text(TEXT, scroll_to=POSITION)'''
* render_text(TEXT)
+
** POSITION is 'begin' (top), 'end' (bottom) or 'start' (start of append)
* append_text(TEXT, scroll_to=POSITION)
+
* '''clear_text()''' - clears all text
* clear_text()
+
* '''set_use_markup(BOOLEAN-VALUE)'''
* set_text(TEXT)
+
* '''render_text(TEXT)''' - for use with A, B, I, U, and TT tags
* no_wrap()
+
** A for creating links; use tag '''HREF="url"''' for URLs, and '''WIKI="name"''' for pages on the wiki
 +
** B for bold
 +
** I for italics
 +
** U for underlined
 +
** TT for a fixed-width, typewriter font
 +
* '''link(TEXT, LINK-TYPE, DATA)''' -
 +
** TEXT can be any text
 +
** LINK-TYPE is:
 +
*** 'Person' - and DATA is a person handle
 +
*** 'PersonList' - and DATA is a list of handles
 +
*** 'Family' - and DATA is a family handle
 +
*** 'Surname' - and DATA is a person
 +
*** 'Given' -
 +
*** 'Filter' - and DATA is either:
 +
**** 'all people' -
 +
**** 'males' - all males
 +
**** 'females' - all females
 +
**** 'people with unknown gender' - people marked as unknown
 +
**** 'people with incomplete names' - people who have a missing surname or given name
 +
**** 'people with missing birth dates' - people who have missing birth dates
 +
**** 'disconnected people' - people with no parents and no children
 +
**** 'all families' - all families
 +
**** 'unique surnames' - list of all unique surnames
 +
**** 'people with media' - people who have media
 +
**** 'media references' - all of the media
 +
**** 'unique media' -
 +
**** 'missing media' - media for which the file does not exist
 +
**** 'media by size' -
 +
**** 'list of people'-
 +
*** 'URL' - and DATA is a URL
 +
*** 'WIKI' - and DATA is a wiki page
 +
*** 'Attribute' - and DATA is an attribute (eg, 'Nickname')
 +
* '''no_wrap()''' - turn word wrap off DEPRECATED
 +
* '''set_wrap(BOOL)''' - change word wrap
  
 
=== Using Tags ===
 
=== Using Tags ===
Line 143: Line 231:
 
</pre>
 
</pre>
  
== GUI Interface ==
+
==GUI Interface==
 +
* [[Gramplets#Widget_Gramplet_example|Widget Gramplet example]]
 +
* [[Gramplets#Cairo_Clock_Example|Cairo Clock Example]]
 +
{{stub}}
  
Occasionally, you might have to dive down to the graphical objects that
+
===Widget Gramplet example===
compose a Gramplet.
+
Occasionally, you might have to dive down to the graphical objects that compose a Gramplet.
  
* tooltip = TEXT
 
 
* gui
 
* gui
 
** gui.buffer
 
** gui.buffer
 
** gui.textview
 
** gui.textview
** gui.get_container_widget()
+
** '''gui.get_container_widget()'''
 +
 
 +
If you wanted to put an arbitrary gtk object into the main area of a Gramplet, then you need to replace the standard textview object with your own. Here is the basic structure:
 +
 
 +
<pre>
 +
# File: Widget.py
 +
from gettext import gettext as _
 +
from gramps.gen.plug import Gramplet
 +
from gi.repository import Gtk
 +
 
 +
class WidgetGramplet(Gramplet):
 +
    def init(self):
 +
        self.gui.WIDGET = ### Some Widget Constructor ###
 +
        self.gui.get_container_widget().remove(self.gui.textview)
 +
        self.gui.get_container_widget().add_with_viewport(self.gui.WIDGET)
 +
        self.gui.WIDGET.show()
 +
</pre>
 +
 
 +
<pre>
 +
# File: Widget.gpr.py
 +
 
 +
register(GRAMPLET,
 +
        id= "Widget Gramplet",
 +
        name=_("Widget Gramplet"),
 +
        description = _("Widget Gramplet example"),
 +
        height=100,
 +
        expand=False,
 +
        gramplet = 'WidgetGramplet',
 +
        gramplet_title=_("Widget"),
 +
        version = '0.0.1',
 +
        gramps_target_version = "5.2",
 +
        fname="Widget.py",
 +
        )
 +
</pre>
 +
 
 +
====Gramps 3.x and older====
 +
<pre>
 +
# File: Widget.py
 +
from gettext import gettext as _
 +
from gramps.gen.plug import Gramplet
 +
import gtk
 +
 
 +
class WidgetGramplet(Gramplet):
 +
    def init(self):
 +
        self.gui.WIDGET = ### Some Widget Constructor ###
 +
        self.gui.get_container_widget().remove(self.gui.textview)
 +
        self.gui.get_container_widget().add_with_viewport(self.gui.WIDGET)
 +
        self.gui.WIDGET.show()
 +
</pre>
 +
 
 +
<pre>
 +
# File: Widget.gpr.py
 +
register(GRAMPLET,
 +
        id= "Widget Gramplet",
 +
        name=_("Widget Gramplet"),
 +
        height=100,
 +
        expand=False,
 +
        fname="Widget.py",
 +
        gramplet = "WidgetGramplet",
 +
        gramps_target_version = "3.4",
 +
        gramplet_title=_("Widget"),
 +
        )
 +
</pre>
 +
 
 +
===Cairo Clock Example===
 +
 
 +
[[File:ClockGramplet-addon-example-50.png|thumb|400px|right|Clock Gramplet - shown detached (Gramps 5.0.0)]]
 +
 
 +
In fact, with Python, GTK, and cairo, you can make your own widgets that do pretty much anything and look very nice.
 +
 
 +
Here is an example adding a Cairo Clock (which really keeps time)
 +
* With GTK+3 & Gramps 5.1
 +
** Github code: [https://github.com/gramps-project/addons-source/tree/maintenance/gramps51/ClockGramplet ClockGramplet]
 +
** Manual Download: [https://github.com/gramps-project/addons/blob/master/gramps51/download/ClockGramplet.addon.tgz?raw=true ClockGramplet.addon.tgz]
 +
{{-}}
  
 
== GUI Options ==
 
== GUI Options ==
  
* add_option(OPTION)
+
* '''add_option(OPTION)'''
* build_options()
+
** OPTION is one of the menu options in [https://github.com/gramps-project/gramps/tree/master/gramps/gen/plug/menu gramps/gen/plug/menu] eg:
* save_options()
+
*** NumberOption
* get_option_widget(TEXT)
+
*** EnumeratedListOption
* get_option(TEXT)
+
*** StringOption
 +
*** ColorOption
 +
*** TextOption
 +
*** BooleanOption
 +
*** FilterOption
 +
*** PersonOption
 +
*** FamilyOption
 +
*** NoteOption
 +
*** MediaOption
 +
*** PersonListOption
 +
*** PlaceListOption
 +
*** SurnameColorOption
 +
*** DestinationOption
 +
*** StyleOption
 +
*** BooleanListOption
 +
* '''build_options()'''
 +
* '''save_options()'''
 +
* '''get_option_widget(TEXT)'''
 +
* '''get_option(TEXT)'''
  
== Predifined Properties ==
+
== Predefined Properties ==
  
There are a number of preset properies:
+
There are a number of preset properties:
  
* gui
 
** gui.data
 
 
* dbstate
 
* dbstate
 
** dbstate.db
 
** dbstate.db
Line 179: Line 359:
 
* gui
 
* gui
 
** gui.data
 
** gui.data
* on_load
+
* '''on_load()'''
* on_save
+
* '''on_save()'''
* save_text_to_data()
+
* '''save_text_to_data()'''
* load_data_to_text()
+
* '''load_data_to_text()'''
 +
 
 +
== Advanced Settings ==
 +
 
 +
* '''force_update''': set True to have the gramplet update, even when minimized
 +
 
 +
Calling the update() method of a gramplet causes its main() method to be called repeatedly in the background until it returns False.
 +
 
 +
Most gramplets will contain a main() method that runs quickly and always returns False.
 +
 
 +
Gramplets that have more intensive processing will use the main() method as a generator which will be run multiple times in the background. It will return True until it has finished processing then it will return False.
 +
 
 +
This background task can be controlled with the pause() and resume() methods. Additionally there is an interrupt() method which will cancel execution of the background task, and update_all() which will run the entire task in the foreground immediately.
 +
 
 +
A background task will continue to run '''even if the gramplet is no longer active'''.
 +
 
 +
Gramplets that should be updated in response to changes in the database should connect to the appropriate signals.
 +
 
 +
Gramplets such as a news gramplet could be updated by adding a refresh button or running the update() method at regular intervals.
 +
 
 +
== Learning More ==
 +
 
 +
To learn more about writing a Gramplet, it is suggested to look at the existing Gramplets. You can see a complete list of the Gramplet source code here:
 +
 
 +
* [https://github.com/gramps-project/gramps/tree/master/gramps/plugins/gramplet Gramps Master (development fork) Gramplets]
 +
* '''[https://github.com/gramps-project/gramps/tree/maintenance/gramps52/gramps/plugins/gramplet Gramps 5.2 Gramplets]'''
 +
* [https://github.com/gramps-project/gramps/tree/maintenance/gramps51/gramps/plugins/gramplet Gramps 5.1 Gramplets]
 +
* [https://github.com/gramps-project/gramps/tree/maintenance/gramps50/gramps/plugins/gramplet Gramps 5.0 Gramplets]
 +
* [https://github.com/gramps-project/gramps/tree/maintenance/gramps42/gramps/plugins/gramplet Gramps 4.2 Gramplets]
 +
* [https://github.com/gramps-project/gramps/tree/maintenance/gramps34/src/plugins Gramps 3.4 Gramplets]
  
 +
Click on a filename, to view the source code of that Gramplet.
  
 +
* [https://github.com/gramps-project/gramps/blob/master/gramps/gen/plug/_gramplet.py master/gramps/gen/plug/_gramplet.py] - Base class for non-graphical gramplet code.
  
 +
= See also =
 +
* [https://www.gramps-project.org/docs/gen/gen_utils.html?module-gramps.gen.utils.callback#gramps.gen.utils.callman.CallbackManager CallbackManager] - a key tool that enables event handling and function execution based on specific triggers or events of a tree database.
 +
* [https://www.gramps-project.org/docs/gen/gen_utils.html?highlight=signals#module-gramps.gen.utils.callback Callback] in <code>signals#module-gramps.gen.utils</code>
 +
* [[Addons development]] - for Gramps 4.2 and later
 +
** [[Addons development old]] - for Gramps 3.2 to 4.1
 +
* [[Writing a plugin]] - for Gramps version 3.2 and earlier
  
*{{man label|name}} = a unique name: a name GRAMPS identifies the plugin with, don't use spaces or strange symbols.
+
[[Category:Addons]]
*{{man label|category}} = a special constant indicating where the report will be shown. You can use '''CATEGORY_QR_PERSON''' to see the report on person editor and view, or '''CATEGORY_QR_FAMILY''' to see the report on family editor or view.
+
[[Category:Developers/General]]
*{{man label|run_func}} = the function you create in this plugin and that GRAMPS will execute when the user selects your quick report in the menu
+
[[Category:Developers/Tutorials]]
* {{man label|translated_name}} = the name of the report as it will appear in the menu
+
[[Category: Plugins]]
* {{man label|status}} = is your report '''Stable''' or '''Unstable'''. Not used at the moment. On distributing GRAMPS we only include stable reports.
+
[[Category:Gramplets]]
*{{man label|description}} = a description of what your plugin does. This appears in a tooltip over the menu
 
*{{man label|author_name}} = your name
 
*{{man label|author_email}}= your email, so people can congratulate you with your work, or ask for bug fixes...
 

Latest revision as of 22:07, 9 March 2024

Gnome-important.png
Warning:

This section contains technical details about programming Gramplets and is intended for Developers.

If you are interested in using Gramplets, please see the user manual section on gramplets.

A Gramplet is a type of Gramps plugin. Gramplets are mini-views designed to be composited with other Gramplets or Views to create a way to see your Family Tree that is just right for you. In fact, Gramplets can be made to do just about anything that you want.

Default Gramplets on Dashboard - With Example Family Tree Open
Default Gramplets on Dashboard - No Family Tree Loaded


Gramplet Interface

Gramplet only operate after being added to a view. Add (or remove and restore) to the sidebar or bottombar of a view by clicking the (Down Arrowhead button) also known as the Gramplet Bar Menu at the far top right of the bars titles, and then using one of the options from the drop-down menu. In the Dashboard view, this menu is accessed by right-clicking in-between gramplets in the main view.

Gramplet can dramatically degrade the performance of Gramps. Some Gramplets consume huge amounts of memory, processor power and move a lot of data through storage. Don't keep a Gramplet active when it isn't being used!

When the view is not active or Gramplet tab is not the foremost in the splitbar, the Gramplet is inactive. A detached (undocked) Gramplet is always active.

The user interface for a Gramplet is left completely to the discretion of the developer. Detach/undock a Gramplet to reveal the "Help" button. Explore the Configure options after adding a Gramplet.

Are Gramplets and 'plug-ins' the same thing?

There are many types of plugins. The 6 most common are:

  1. Reports: output for printing or display
  2. Tools: a method for processing data
  3. Quick View: a list of details based on the current object
  4. Importer: reads a file into your current tree
  5. Exporter: writes a file from your current tree
  6. Gramplets: interactive views for moving, analysing, displaying, etc.

There are two plugin directories: a global/system one, and a private/personal one. You can easily create a plugin by simply putting a file in plugins folder of your User Directory (usually in .gramps/grampsxx/plugins/gramplet/ ).

Hello World

In teaching programming, a common "first program" is to write a program that says "Hello World".

Let us jump right in and take a look at such a gramplet named HelloWorld.py:

Create a python file named HelloWorld.py and add the following content:

# File: HelloWorld.py
def init(gui):
    gui.set_text("Hello world!")

And create another python file named HelloWorld.gpr.py with the following content:

# File: HelloWorld.gpr.py
register(GRAMPLET,
         id="Hello World Gramplet", 
         name=_("Hello World Gramplet"),
         description = _("a program that says 'Hello World'"),
         status = STABLE,
         version="0.0.1",
         fname="HelloWorld.py",
         height = 20,
         gramplet = 'init',
         gramplet_title=_("Sample Gramplet"),
         gramps_target_version="5.2",
         help_url="Sample Gramplet"
         )

You can read more about the gpr.py file in Addons development and the source code implementation.

If you place these files in your personal user plugins directory (usually in .gramps/grampsxx/plugins/gramplet/ ), and then restart Gramps, you will be able to create this Gramplet. On the Dashboard Category View, right-click in an open area and select the "Hello World Gramplet". You should then see:

Hello World Gramplet - example (shown embedded on Dashboard Category View / Context menu "Add a gramplet" also shown)


Explanation

The main work of a Gramplet is performed in a function, or a class. In this very simple example, a function init is defined that takes a single argument, gui. The function simply sets the gui's text area to be "Hello World!", and that's it. It does this just once, and never changes.

Before a plugin can be used, it needs to be "registered". You call the register function with a number of named-arguments. There are a number of named-arguments that you can provide, including: name, height, content, title, expand, state, and data. We will explore those in detail, below.

Hello World, with Class

Here is the same functionality again, but this time as a class:

# File: HelloWorld2.py
from gramps.gen.plug import Gramplet

class HelloWorldGramplet(Gramplet):
    def init(self):
        self.set_text("Hello world!")
# File: HelloWorld2.gpr.py
register(GRAMPLET,
         id="Hello World2 Gramplet", 
         name=_("Hello World2 Gramplet"),
         description = _("a program that says 'Hello World'"),
         version="0.0.1",
         gramps_target_version="5.2",
         status = STABLE,
         fname="HelloWorld2.py",
         height = 20, 
         gramplet = 'HelloWorldGramplet',
         gramplet_title=_("Sample Gramplet"),
         help_url="5.2_Addons#Addon_List"
         )

This is the recommended method of creating a Gramplet. The following details describe the properties and methods of this class.

Register Options

  • GRAMPLET: the first argument is the keyword GRAMPLET
  • id: the identifying name of the gramplet, unique among all plugins
  • name: the translated gramplet's name
  • height: the minimum (or maximum) height of the gramplet in normal mode
  • fname: the name of your gramplet file
  • gramplet: the name of the function or class in fname that creates the gramplet
  • gramplet_title: the default gramplet title; user changeable in Configure View
  • status: STABLE or UNSTABLE
  • version: a string with 2 dots (such as "1.23.14") representing the version number
  • gramps_target_version: a string with 2 dots representing the version of Gramps for which this gramplet was written. Only gramplets matching the installed version will be available for installation.
  • help_url: the title of the wiki page that describes the gramplet.
    If the help_url starts with http:// then that fully qualified URL will be used as is. Otherwise, the paths will be interpreted as relative to http://gramps-project.org/wiki/index.php?title= base URL. The base URL will be prepended to the help_url and may get a language extension (such as /nl ) appended at the end, if the operating language is one of nl fr sq mk de fi ru sk. You should not use the _( ) translate function around the help_url string, unless you specifically intend to create web pages named with the translated string.

At the bare minimum, you need to have the above 11 options when registering your Gramplets.

In addition, you can use the following as well:

  • detached_width: the size in pixels of the minimum and default detached height
  • detached_height: the size in pixels of the minimum and default detached height
  • expand: whether or not the Gramplet should expand to fill the column, if it can
  • description: a description of the Gramplet

Core Methods

The class-based gramplet utilizes the following methods:

  • init(): run once, on construction
  • main(): run once per update
  • update(): don't change this, it calls main
  • active_changed(): run when active-changed is triggered
  • db_changed(): run when db-changed is triggered
  • set_tooltip(TEXT) - tooltip for gramplet


Don't call main directly; use the update method.

In the db_changed method, you should connect all of the signals that will trigger an update. That typically looks like:

    def db_changed(self):
        self.dbstate.db.connect('person-add', self.update)
        self.dbstate.db.connect('person-delete', self.update)
        self.dbstate.db.connect('person-update', self.update)
        self.dbstate.db.connect('family-add', self.update)
        self.dbstate.db.connect('family-delete', self.update)
        self.dbstate.db.connect('family-update', self.update)

The method main() can be written as a normal Python method, or it can be written to run nicely in parallel with other Gramps code. To make it run nicely in parallel, you should issue a yield True every once in a while. For example:

    def main(self):
        for i in range(5000):
            if i % 500 == 0:
                yield True
        yield False

The True means that there is more to do; False means that there is nothing left to do.

See also

  • DisplayState(Callback) class for additional signals. (Including Custom Filters, Gramplet display and other updates.)

Textual Output Methods

The most common kinds of Gramplets are text-based. There are a number of methods to assist with handling this text.

  • set_text(TEXT) - clear and set text to TEXT
  • append_text(TEXT, scroll_to=POSITION)
    • POSITION is 'begin' (top), 'end' (bottom) or 'start' (start of append)
  • clear_text() - clears all text
  • set_use_markup(BOOLEAN-VALUE)
  • render_text(TEXT) - for use with A, B, I, U, and TT tags
    • A for creating links; use tag HREF="url" for URLs, and WIKI="name" for pages on the wiki
    • B for bold
    • I for italics
    • U for underlined
    • TT for a fixed-width, typewriter font
  • link(TEXT, LINK-TYPE, DATA) -
    • TEXT can be any text
    • LINK-TYPE is:
      • 'Person' - and DATA is a person handle
      • 'PersonList' - and DATA is a list of handles
      • 'Family' - and DATA is a family handle
      • 'Surname' - and DATA is a person
      • 'Given' -
      • 'Filter' - and DATA is either:
        • 'all people' -
        • 'males' - all males
        • 'females' - all females
        • 'people with unknown gender' - people marked as unknown
        • 'people with incomplete names' - people who have a missing surname or given name
        • 'people with missing birth dates' - people who have missing birth dates
        • 'disconnected people' - people with no parents and no children
        • 'all families' - all families
        • 'unique surnames' - list of all unique surnames
        • 'people with media' - people who have media
        • 'media references' - all of the media
        • 'unique media' -
        • 'missing media' - media for which the file does not exist
        • 'media by size' -
        • 'list of people'-
      • 'URL' - and DATA is a URL
      • 'WIKI' - and DATA is a wiki page
      • 'Attribute' - and DATA is an attribute (eg, 'Nickname')
  • no_wrap() - turn word wrap off DEPRECATED
  • set_wrap(BOOL) - change word wrap

Using Tags

Tags are the manner in which you format the text.

   tag = self.gui.buffer.create_tag("fixed")
   tag.set_property("font", "Courier 8")
   ...
   start, end = self.gui.buffer.get_bounds()
   self.gui.buffer.apply_tag_by_name("fixed", start, end)
   self.append_text("", scroll_to="begin")

GUI Interface

Gramps-notes.png

This article's content is incomplete or a placeholder stub.
Please update or expand this section.


Widget Gramplet example

Occasionally, you might have to dive down to the graphical objects that compose a Gramplet.

  • gui
    • gui.buffer
    • gui.textview
    • gui.get_container_widget()

If you wanted to put an arbitrary gtk object into the main area of a Gramplet, then you need to replace the standard textview object with your own. Here is the basic structure:

# File: Widget.py
from gettext import gettext as _
from gramps.gen.plug import Gramplet
from gi.repository import Gtk

class WidgetGramplet(Gramplet):
    def init(self):
        self.gui.WIDGET = ### Some Widget Constructor ###
        self.gui.get_container_widget().remove(self.gui.textview)
        self.gui.get_container_widget().add_with_viewport(self.gui.WIDGET)
        self.gui.WIDGET.show()
# File: Widget.gpr.py

register(GRAMPLET,
         id= "Widget Gramplet",
         name=_("Widget Gramplet"),
         description = _("Widget Gramplet example"),
         height=100,
         expand=False,
         gramplet = 'WidgetGramplet',
         gramplet_title=_("Widget"),
         version = '0.0.1',
         gramps_target_version = "5.2",
         fname="Widget.py",
         )

Gramps 3.x and older

# File: Widget.py
from gettext import gettext as _
from gramps.gen.plug import Gramplet
import gtk

class WidgetGramplet(Gramplet):
    def init(self):
        self.gui.WIDGET = ### Some Widget Constructor ###
        self.gui.get_container_widget().remove(self.gui.textview)
        self.gui.get_container_widget().add_with_viewport(self.gui.WIDGET)
        self.gui.WIDGET.show()
# File: Widget.gpr.py
register(GRAMPLET, 
         id= "Widget Gramplet", 
         name=_("Widget Gramplet"), 
         height=100,
         expand=False,
         fname="Widget.py",
         gramplet = "WidgetGramplet",
         gramps_target_version = "3.4",
         gramplet_title=_("Widget"),
         )

Cairo Clock Example

Clock Gramplet - shown detached (Gramps 5.0.0)

In fact, with Python, GTK, and cairo, you can make your own widgets that do pretty much anything and look very nice.

Here is an example adding a Cairo Clock (which really keeps time)


GUI Options

  • add_option(OPTION)
    • OPTION is one of the menu options in gramps/gen/plug/menu eg:
      • NumberOption
      • EnumeratedListOption
      • StringOption
      • ColorOption
      • TextOption
      • BooleanOption
      • FilterOption
      • PersonOption
      • FamilyOption
      • NoteOption
      • MediaOption
      • PersonListOption
      • PlaceListOption
      • SurnameColorOption
      • DestinationOption
      • StyleOption
      • BooleanListOption
  • build_options()
  • save_options()
  • get_option_widget(TEXT)
  • get_option(TEXT)

Predefined Properties

There are a number of preset properties:

  • dbstate
    • dbstate.db
      • dbstate.db.connect(SIGNAL, METHOD)
      • dbstate.db.get_person_from_handle(HANDLE)
      • dbstate.get_active_person()
  • uistate

Persistance

  • gui
    • gui.data
  • on_load()
  • on_save()
  • save_text_to_data()
  • load_data_to_text()

Advanced Settings

  • force_update: set True to have the gramplet update, even when minimized

Calling the update() method of a gramplet causes its main() method to be called repeatedly in the background until it returns False.

Most gramplets will contain a main() method that runs quickly and always returns False.

Gramplets that have more intensive processing will use the main() method as a generator which will be run multiple times in the background. It will return True until it has finished processing then it will return False.

This background task can be controlled with the pause() and resume() methods. Additionally there is an interrupt() method which will cancel execution of the background task, and update_all() which will run the entire task in the foreground immediately.

A background task will continue to run even if the gramplet is no longer active.

Gramplets that should be updated in response to changes in the database should connect to the appropriate signals.

Gramplets such as a news gramplet could be updated by adding a refresh button or running the update() method at regular intervals.

Learning More

To learn more about writing a Gramplet, it is suggested to look at the existing Gramplets. You can see a complete list of the Gramplet source code here:

Click on a filename, to view the source code of that Gramplet.

See also