- Philosophy of KeepNote
- Getting started
- Basic use
- Editing your notes
- Backup and export
- Developer notes
This section contains notes that will most likely only be of interest to other developers working on KeepNote or making software compatiable with KeepNote.
For information on producing new translations of KeepNote, please see: README.translations.txt. You will need to be familiar with the unix/linux/mac command-line. Please email me for more information.
KeepNote uses extensions (i.e. plugins) to provide extra optional features. This allows the core application to remain simple and light-weight while allowing each user to enable different extra features depending on their needs. Also, features implemented in extensions can be developed at their own pace and maintained by many different developers. Extensions are written in the the python language, and can provide almost any feature that the core application can (which is also written in python). Here, we describe the details of developing an extension.
Extension file format
Extensions are distributed as a package with the file extension *.kne (see the extensions page for an example). By convention, the name of the package should be short and if it contains multiple words should delimit words with underscores (e.g. backup_tar.kne).
The package file is simply a zip file of a single directory with the following structure:
- optional data files...
extension_directory should be the same name as the package. Thus, backup_tar.kne would contain a single directory backup_tar.
info.xml provides a manifest of the contents of the extension. It has the following format:
<?xml version="1.0" encoding="UTF-8"?> <extension> <dict> <key>version</key> <string>1.0</string> <key>name</key> <string>Notebook Backup (tar)</string> <key>author</key> <string>Matt Rasmussen</string> <key>email</key> <string>firstname.lastname@example.org</string> <key>website</key> <string>http://keepnote.org</string> <key>description</key> <string>Backups a notebook to a gzip tar file (*.tar.gz)</string> </dict> </extension>
- version (string): version number of the extension (dot separated integers)
- name (string): display name of the extension
- author (string): name of the extension author
- email (string): email address of author
- website (string): optional website of author or extension
- description (string): short description of extension
Optional data files may be included in the extension directory. These can be GUI definitions (*.glade), images, or other static information (read-only).
Programing an extension (__init__.py)
The main source code of an extension is provided in the __init__.py file contained within the extension's main directory (extension file format). At a minimum, the python file should contain a single class named Extension that subclasses from the class keepnote.extension.Extension. If the extension has any graphical features (most do) then the class should subclass keepnote.gui.extension.Extension. The base class provides a number of methods that can be overwritten in order to implement different features of the extension.
Life time of an extension. Once an extension is installed it is by default enabled, however users can optionally disable an extension if they want to temporarily remove its features. The list of enabled and disabled extensions are recorded in the preferences of the user. When the application first starts, all enabled extensions are loaded automatically. Extensions are then notified of particular events throughout the lifetime of the application through a series of callback methods available within the Extension class.
- on_new_window(window): If the extension is a graphical extension (e.g. implements menus, interacts with the GUI) then the extension will receive the on_new_window(window) callback for every new window that is created. This gives the extension a chance to customize each window (keepnote.gui.main_window.KeepNoteWindow) when it first opens.
- on_close_window(window): This callback is called every time a window closes. Usually an extension implements this callback if cleanup is needed.
- on_add_ui(window): Most often the only thing an extension
needs to implement for each window is adding its own menus.
In this case, it is best to only override this callback.
For example, the backup_tar extension uses the following code
to add two new actions and attach them to the menu items in the
def on_add_ui(self, window): # add menu options self.add_action(window, "Backup Notebook", "_Backup Notebook...", lambda w: self.on_archive_notebook( window, window.get_notebook())) self.add_action(window, "Restore Notebook", "R_estore Notebook...", lambda w: self.on_restore_notebook(window)) # add menu items self.add_ui(window, """ <ui> <menubar name="main_menu_bar"> <menu action="File"> <placeholder name="Extensions"> <menuitem action="Backup Notebook"/> <menuitem action="Restore Notebook"/> </placeholder> </menu> </menubar> </ui> """)
- on_remove_ui(self, window): This callback is called every time a window closes. This gives the extension a chance to remove its UI elements from the window. By default, cleanup is done automatically and this callback does not need to be implemented.
- on_add_options_ui(self, dialog): This callback is called every time a window opens and it allows the extension to add its own configuration options to the ApplicationOptionsDialog (dialog).
- on_remove_options_ui(self, dialog): This callback is called every time a window closes and it allows the extension to remove its own configuration options from the ApplicationOptionsDialog (dialog).
Notebook file format
The data structure of the notebook is a directed graph of nodes. Each node contains an optional payload (typically a chunk of HTML) and a set of attributes. The most common graph (and the only one implemented as of 0.5.2) is a tree or hierachy.
KeepNote currently implements a file and directory format for writing notebooks to disk. The node hierarchy is implemented as a nested set of directories, one directory per node. Directory names are ignored. For user convenience, KeepNote happens to name directories similar to the node's title attribute. Care must be taken to strip invalid characters (such as "/") from node title's before using them as directory names.
Each node directory must contain a meta data file called node.xml. This file describes the attributes of the node. Here is an example:
<?xml version="1.0" encoding="UTF-8"?> <node> <version>3</version> <attr key="title">Title of my note</attr> <attr key="expanded">0</attr> <attr key="nodeid">fb4f99f5-e3d6-46d3-bf57-61ce46707cde</attr> <attr key="modified_time">1235451081</attr> <attr key="expanded2">0</attr> <attr key="content_type">text/xhtml+xml</attr> <attr key="created_time">1226172668</attr> <attr key="info_sort_dir">1</attr> <attr key="order">1</attr> <attr key="info_sort">order</attr> </node>
The root tag is node which contains a version tag and a list of attr tags which describe the node's attributes. The order of attr tags is insignificant. Attributes are key-value pairs. The key of an attribute is given in the html attribute key and the value is the data within the attr tags. Each attribute type has its own format for its data. Some attributes are more important than others. For example, the nodeid and content_type attributes are required but other attributes are optional and may be program specific.
In the future, I plan to allow attributes to be multi-valued, namely, they can occur multiple times in the node.xml file. The order in which attributes of the same type appear will be significant. An attribute type must be declared multi-valued in order to use this feature. This will allow implementing attributes that represent things such as "tags" or "labels".
- Required attributes
- nodeid (string) This is a universally unique identifier (uuid) that can be used to refer to this node. This can serve as a basis for node linking/syncing.
- content_type (string) This gives the mime type of the node's payload. For nodes without a payload (e.g. Folders) this attribute is used simply to define the type of the node.
- Application specific
- title (string) The display name of a node. This text will appear in the application.
- created_time (int) Node creation timestamp represented in seconds since Unix Epoc.
- modified_time (int) Node modification timestamp represented in seconds since Unix Epoc.
- expanded (bool) A bool (represented as a 1 or 0) that specifies whether the node is expanded in the treeview of KeepNote.
- expanded2 (bool) A bool that specifies whether the node is expanded in the listview of KeepNote.
- order (int) The position of a node in the list of its siblings in the hierarchy. This is used for display purposes only
- info_sort (string) This specifies the attribute on which to sort the children of this node.
- info_sort_dir (bool) A bool that specifies whether to sort the children of this node in ascending order (use 0 for desending).
Each node may optionally have a payload, i.e. additional data that is application specific. The "page" node in KeepNote (i.e. content_type="text/xhtml+xml") is the most common node with a payload, which is a XHTML document and its associated images. KeepNote requires this document to be named page.html. Images associated with the document can have any name (except node.xml and page.html) and are referred to within the XHTML document using relative filenames. Only a subset of XHTML is supported by KeepNote.
Lastly, a notebook has metadata that pertains to the entire notebook. This is stored in files and directories present at the base directory of the notebook. There is a XML file called notebook.nbk which contains simple preference information for the notebook (e.g. default font, custom icon names). There is also a directory called __NOTEBOOK__ that will contain support files for the notebook (e.g. custom icons, cached data in a database format, etc). Note, the filename "__NOTEBOOK__" is reserved and cannot be used by any nodes in the notebook.