Creating an extension
- What is an OpenSesame extension?
- Relevant files
- Extension information
- Writing the extension code
- Listening for events
- Building a package and uploading to pypi
- Examples
- Event overview
- change_experiment
- change_item
- close
- delete_item
- heartbeat
- ide_jump_to_line
- ide_new_file
- ide_save_current_file
- image_annotations_detect
- jupyter_exception_occurred
- jupyter_execute_finished
- jupyter_execute_result_text
- jupyter_execute_start
- jupyter_interrupt
- jupyter_restart
- jupyter_run_code
- jupyter_run_system_command
- jupyter_show_prompt
- jupyter_write
- new_item
- new_linked_copy
- notify
- open_experiment
- open_item
- pause_experiment
- prepare_change_experiment
- prepare_delete_item
- prepare_open_item
- prepare_purge_unused_items
- prepare_regenerate
- prepare_rename_item
- purge_unused_items
- pyqode_clear_breakpoints
- pyqode_resume_auto_backend_restart
- pyqode_select_indentation_mode
- pyqode_suspend_auto_backend_restart
- regenerate
- register_editor
- rename_item
- resume_experiment
- run_experiment_canceled
- save_experiment
- startup
- unregister_editor
What is an OpenSesame extension?
Extensions add arbitrary functionality to the OpenSesame user interface. For example, an extension can add a new entry to the main toolbar or the menubar. (To add functionality that you can use in experiments, you need a plugin.)
Relevant files
One or more extensions are put together in an extension package, which is always a subpackage of opensesame_extensions
(which is itself a so-called implicit namespace package, but that's a technical detail that is not very important). Let's say that your extension package is called example
, and that it contains a single extension (there can be more) called example_extension
. This would correspond to the following file-and-folder structure:
opensesame_extensions/
example/
__init__.py # can be empty but must exist
example_extension/
__init__.py # contains extension information
example_extension.py # contains extension class
Extension information
Extension information is defined in the __init__.py
of the extension module, so in our example this is opensesesame_extensions/example/example_extension/__init__.py
.
"""A docstring with a description of the extension"""
# A standard icon name
# - <https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html>
icon = 'applications-accessories'
# The label and the tooltip are used to create the default action, which is
# insert into the menu and/ or toolbar (or neither)
label = "Example extension"
tooltip = "Example tooltip"
menu = {
"index": -1,
"separator_before": True,
"separator_after": True,
"submenu": "Example"
}
toolbar = {
"index": -1,
"separator_before": True,
"separator_after": True
}
# Settings are perstistently stored in the cfg object
settings = {
"example_setting": "example value"
}
An extension can appear in the menu or main toolbar of OpenSesame. This requires that you define several fields in __init__.py
as shown above:
- The
label
is the text that will appear in the menu. - The
icon
is a freedesktop-compliant icon name that specifies the icon that will appear in the menu and/ or toolbar. - The
index
gives the position of the extension in the menu/ toolbar, and works like alist
index. That is, negative values are relative to the last entry, where -1 puts your extension at the end.
To have your extension respond to menu/ toolbar activation, implement the activate()
method as shown below in the extension code below.
Writing the extension code
The main extension code is placed in [extension_name].py
. This file generally contains only a single class named [ExtensionName].py
, that is, a class with the CamelCase equivalent of the plugin name, which inherits libqtopensesame.extensions.BaseExtension
. So a basic (non-functional) extension class looks like this:
from libopensesame.py3compat import *
from libopensesame.oslogging import oslogger
from libqtopensesame.extensions import BaseExtension
class ExampleExtension(BaseExtension):
"""An example extension that lists several common events. The class name
should be the CamelCase version of the folder_name and file_name. So in
this case both the extension folder (which is a Python package) and the
.py file (which is a Python module) are called example_extension, whereas
the class is called ExampleExtension.
"""
def activate(self):
oslogger.debug('example_extension extension activated')
def event_save_experiment(self, path):
oslogger.debug(f'Event fired: save_experiment(path={path})')
# See example_extension source code for more event listeners
Listening for events
OpenSesame fires events whenever something important happens. For example, the save_experiment
event is fired when an experiment is saved. To have your extension listen to an event, simply implement a method with the name event_[event name]
as shown above.
Note that some events take keyword arguments, such as path
in the case of save_experiment
. The keyword signature of your function must match the expected keyword signature. See the Event overview below for a full list of events and expected keywords.
Building a package and uploading to pypi
Building an extensin package and uploading it to pypi
works the same way as it does for plugins:
Examples
For a working example, see:
Other examples can be found in the opensesame_extensions
folder of the OpenSesame source code:
Event overview
This overview lists all events that are fired somewhere in the code, and that your extenstion can therefore listen to by implementing the corresponding event_[eventname]()
functions.
change_experiment
Fired in: general_properties.py
extension_manager.fire(u'change_experiment')
change_item
Fired in: sequence.py
extension_manager.fire(u'change_item', name=self.name)
Fired in: qtitem.py
extension_manager.fire(u'change_item', name=self.name)
Fired in: loop.py
extension_manager.fire(u'change_item', name=self.name)
close
Fired in: qtopensesame.py
extension_manager.fire(u'close')
delete_item
Fired in: qtitem_store.py
extension_manager.fire(u'delete_item', name=name)
heartbeat
Fired in: multiprocess_runner.py
extension_manager.fire(u'heartbeat')
ide_jump_to_line
Fired in: SymbolSelector.py
extension_manager.fire('ide_jump_to_line', lineno=lineno)
Fired in: OpenSesameIDE.py
extension_manager.fire('ide_jump_to_line', lineno=line_number)
ide_new_file
Fired in: JupyterNotebook.py
extension_manager.fire(u'ide_new_file', source=code, ext=ext)
ide_save_current_file
Fired in: OpenSesameIDE.py
extension_manager.fire('ide_save_current_file')
image_annotations_detect
Fired in: JupyterNotebook.py
extension_manager.fire(u'image_annotations_detect', code=code)
jupyter_exception_occurred
Fired in: transparent_jupyter_widget.py
extension_manager.fire('jupyter_exception_occurred')
jupyter_execute_finished
Fired in: transparent_jupyter_widget.py
extension_manager.fire('jupyter_execute_finished')
jupyter_execute_result_text
Fired in: transparent_jupyter_widget.py
extension_manager.fire('jupyter_execute_result_text', text=text)
jupyter_execute_start
Fired in: transparent_jupyter_widget.py
extension_manager.fire('jupyter_execute_start')
jupyter_interrupt
Fired in: OpenSesameIDE.py
extension_manager.fire(u'jupyter_interrupt')
jupyter_restart
Fired in: console_bridge.py
extension_manager.fire(u'jupyter_restart')
Fired in: OpenSesameIDE.py
extension_manager.fire(u'jupyter_restart')
jupyter_run_code
Fired in: menubar.py
extension_manager.fire('jupyter_run_code',
code=command)
jupyter_run_system_command
Fired in: JupyterNotebook.py
extension_manager.fire('jupyter_run_system_command', cmd=cmd)
jupyter_show_prompt
Fired in: console_bridge.py
extension_manager.fire(u'jupyter_show_prompt')
jupyter_write
Fired in: console_bridge.py
extension_manager.fire(u'jupyter_write', msg=s)
new_item
Fired in: tree_overview.py
extension_manager.fire(u'new_item', name=item.name,
_type=item.item_type)
new_linked_copy
Fired in: tree_overview.py
extension_manager.fire('new_linked_copy', name=item_name)
notify
Fired in: sequence.py
extension_manager.fire(u'notify',
message=_(u'Sequence contains non-existing item: %s')
Fired in: qtitem.py
extension_manager.fire(u'notify',
message=_(u'"%s" is set to a variable or '
u'unknown value and can only be edited through '
u'the script.')
Fired in: qtitem.py
extension_manager.fire(u'notify',
message=_(u'"%s" is defined using '
'variables and can only be edited through the '
'script.')
Fired in: qtitem.py
extension_manager.fire(u'notify',
message=_(u'"%s" is defined using '
u'variables or has an invalid value, and can only be '
u'edited through the script.')
Fired in: logger.py
extension_manager.fire(u'notify',
message=_(u'You have multiple unlinked loggers. This can lead to messy log files.')
Fired in: sketchpad_widget.py
extension_manager.fire(u'notify', message=notification,
category=u'info')
open_experiment
Fired in: qtopensesame.py
extension_manager.fire(u'open_experiment', path=path)
open_item
Fired in: qtitem.py
extension_manager.fire(u'open_item', name=self.name)
pause_experiment
Fired in: base_runner.py
extension_manager.fire(u'pause_experiment')
prepare_change_experiment
Fired in: general_properties.py
extension_manager.fire(u'prepare_change_experiment')
prepare_delete_item
Fired in: qtitem_store.py
extension_manager.fire(u'prepare_delete_item', name=name)
prepare_open_item
Fired in: qtitem.py
extension_manager.fire(u'prepare_open_item', name=self.name)
prepare_purge_unused_items
Fired in: unused_widget.py
extension_manager.fire(u'prepare_purge_unused_items')
prepare_regenerate
Fired in: qtopensesame.py
extension_manager.fire(u'prepare_regenerate')
prepare_rename_item
Fired in: qtitem_store.py
extension_manager.fire(u'prepare_rename_item', from_name=from_name,
to_name=to_name)
purge_unused_items
Fired in: unused_widget.py
extension_manager.fire(u'purge_unused_items')
pyqode_clear_breakpoints
Fired in: OpenSesameIDE.py
extension_manager.fire('pyqode_clear_breakpoints')
pyqode_resume_auto_backend_restart
Fired in: OpenSesameIDE.py
extension_manager.fire('pyqode_resume_auto_backend_restart')
Fired in: OpenSesameIDE.py
extension_manager.fire('pyqode_resume_auto_backend_restart')
Fired in: OpenSesameIDE.py
extension_manager.fire('pyqode_resume_auto_backend_restart')
Fired in: OpenSesameIDE.py
extension_manager.fire('pyqode_resume_auto_backend_restart')
pyqode_select_indentation_mode
Fired in: menubar.py
extension_manager.fire('pyqode_select_indentation_mode')
pyqode_suspend_auto_backend_restart
Fired in: OpenSesameIDE.py
extension_manager.fire('pyqode_suspend_auto_backend_restart')
Fired in: OpenSesameIDE.py
extension_manager.fire('pyqode_suspend_auto_backend_restart')
Fired in: OpenSesameIDE.py
extension_manager.fire('pyqode_suspend_auto_backend_restart')
Fired in: OpenSesameIDE.py
extension_manager.fire('pyqode_suspend_auto_backend_restart')
regenerate
Fired in: qtopensesame.py
extension_manager.fire(u'regenerate')
Fired in: qtopensesame.py
extension_manager.fire(u'regenerate')
register_editor
Fired in: qtplugin.py
extension_manager.fire(u'register_editor', editor=editor)
rename_item
Fired in: qtitem_store.py
extension_manager.fire(u'rename_item', from_name=from_name,
to_name=to_name)
resume_experiment
Fired in: base_runner.py
extension_manager.fire(u'resume_experiment')
run_experiment_canceled
Fired in: base_runner.py
extension_manager.fire('run_experiment_canceled')
save_experiment
Fired in: qtopensesame.py
extension_manager.fire(u'save_experiment', path=self.current_path)
startup
Fired in: qtopensesame.py
extension_manager.fire(u'startup')
unregister_editor
Fired in: OpenSesameIDE.py
extension_manager.fire('unregister_editor', editor=editor)