12. Contributing: Developer’s Guide

Everybody is encouraged to contribute to the RAFCON project. However, collaboration needs some guidelines. We try to collect all this information in this document, so please stick to this.

When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.

Please note that we have a code of conduct, please follow it in all your interactions with the project.

Please check our code style, before making any changes to the code. Please read the commit guidelines, before submitting any commit.

12.1. Getting Started

For an introduction of how to install RAFCON as a user, have a look at the website. Also, to get the correct dependencies, follow the instruction given on this site.

The following describes how to get the latest RAFCON version per GitHub.

First, change to the directory in which you want to clone RAFCON:

$ cd /some/personal/path/

Next, clone the RAFCON repository. You can either use the HTTPS URL:

$ git clone https://github.com/DLR-RM/RAFCON.git

or the SSH URL:

$ git clone git@github.com:DLR-RM/RAFCON.git

This must of course only be done once. If you want to get the latest commits after you have cloned the repository, use

$ cd /some/personal/path/rafcon
$ git pull

In order to run RAFCON from the local code base, you have to setup the environment:

$ export PYTHONPATH=/some/personal/path/rafcon/source:$PYTHONPATH
$ export PATH=/some/personal/path/rafcon/bin:$PATH

Now you can run rafcon to start the RAFCON-GUI or just run rafcon_core to only launch the core. Hereby, rafcon just links to the file /some/personal/path/rafcon/source/rafcon/gui/start.py and rafcon_core points to /some/personal/path/rafcon/source/rafcon/core/start.py, so you could also call these files directly.

IMPORTANT: If you start rafcon for the first time start it this way:

$ RAFCON_CHECK_INSTALLATION=True rafcon

This will install all fonts and gtk-styles.

12.2. Install RAFCON from Sources

RAFCON can be also installed from our GitHub releases.

If you don’t want to edit the source code of RAFCON, it can be installed directly from source:

pip install /install/directory/rafcon/ --user

If you want to be able to change the source code, you can install RAFCON in editable mode.

pip install --editable /install/directory/rafcon/ --user

Any changes in /install/directory/rafcon/source will take effect when launching RAFCON.

12.3. Running and writing tests

Running tests with tox

The simplest and most reliable way of running the tests is using tox. If you have not installed tox, do so using

$ pip install tox

Then, in the simplest case you just call tox in the root directory of the RAFCON repository:

$ tox

This will run the following environments:

  • py27, py3[4-7]: Runs the test using the according Python interpreter

  • coverage: Runs the tests using Python 2.7 with a coverage report

  • docs: Builds the documentation and verifies all links

  • check: Verifies the sdist and wheel file

Specific environments can be run with the -e option:

$ tox -e 2.7,3.4
$ tox -e docs

When running the tests (py27, py3[4-7] or coverage), you can pass custom options to pytest by listing them after tox [tox options] --. The default pytest options are -vx -m "(core or gui or share_elements) and not unstable".

$ tox -e 2.7 -- -x -m "core"
$ tox -- -s -k "test__destruct"

Tox creates a virtualenv for each environment, based on the dependencies defined in pyproject.toml and tox.ini. These are only generated on the first run of an environment. If the dependencies change, you need to tell tox to recreate the environments using the -r/--recreate option:

$ tox -re py2.7

The RAFCON tests are annotated with a number of markers, allowing you to select specific tests:

  • core, gui, share_elements, network: Tests located in a folder with that name

  • unstable: GUI tests that do not run very reliable (e.g. because of the window manager)

Pytest allows you to select tests based on markers using the -m option. Markers can be combined using not, and, or and paranthesis:

$ tox -e 2.7 -- -x -m "gui and not unstable"

Writing tests

RAFCON provides a lot of tests in the tests/ folder. Many of these tests are integration tests, unit tests are unfortunately often missing. If a test only uses imports from rafcon.core, it is to be placed in tests/core/, otherwise in tests/gui/.

RAFCON uses pytest as testing framework. It e.g. auto detects your test files starting with test_*. Please have a look at the documentation before writing tests: https://pytest.org/

GUI tests

When you want to write an integration test using the GUI, a custom fixture named gui is provided (tests/gui/conftest.py). Simply add gui as parameter to your test (no import is required for tests residing beneath test/gui/). The fixture automatically starts the GUI before the test and closes it thereafter.

Important: Do not import any module from rafcon.gui outside of a function! Otherwise, models might be created withing the wrong thread, which leads to gtkmvc forwarding observer notifications asynchronously (via idle_add) instead of synchronously.

When calling an operation that causes changes (in the core, models, GUI), you need to add the operation to the GTK queue and wait until the operation has finished. This is simply done by calling gui(function_reference, *args, **kwargs) instead of function_reference(*args, **kwargs).

If your test commands causes any logger.warning or logger.error, you need to specify the expected numbers. Do so by calling gui.expected_warnings += 1, respectively gui.expected_errors += 1, directly after the command that causes the warning/error.

The fixture will load the default core and gui config options and the libraries generic and unit_test_state_machines. If you want to override certain settings or add more libraries, use the following decorator:

@pytest.mark.parametrize('gui', [{
    "gui_config": {
        'AUTO_BACKUP_ENABLED': True,
        'HISTORY_ENABLED': True
    },
    "libraries": {
        "ros": os.path.join(testing_utils.EXAMPLES_PATH, "libraries", "ros_libraries"),
        "turtle_libraries": os.path.join(testing_utils.EXAMPLES_PATH, "libraries", "turtle_libraries")
    }
}], indirect=True, ids=["with history, auto backup, ros and turtle libraries"])
def test_name(gui):
    pass  # test code

Using the ids argument, you can specify a label for your configuration. Other possible keys are core_config (dict), runtime_config (dict) and with_gui (bool, for tests that operate on models but do not require the controllers and views). It is also possible to combine this with parameter sets:

config_options = {
    "gui_config": {
        'HISTORY_ENABLED': True
    }
}
@pytest.mark.parametrize("gui,state_path,recursive,rel_size", [
    (config_options, state_path_root, False, (40, 40)),
    (config_options, state_path_root, True, (40, 40)),
    (config_options, state_path_P, False, (20, 20)),
    (config_options, state_path_P, True, (20, 20)),
], indirect=["gui"])
def test_name(gui, state_path, recursive, rel_size, monkeypatch):
    pass  # test code

Note that in this case, you need to set the indirect parameter to ["gui"].

The gui fixture offers some features:

  • if you want to restart the GUI within a test, call gui.restart()

  • the fixture provides shorthand access the gui singletons via gui.singletons and core singletons via gui.core_singletons, without requiring any further imports.

  • if you want to run a test after the GUI was closed, you can set the function to be run via gui.post_test = functools.partial(function_reference, *args, **kwargs)

Tutorial - How to Write GUI Unit Tests for RAFCON

When you want to write a GUI unit test for RAFCON, you basically have two choices depending on what you are trying to test.

The first choice is a GTK widget test. This is the right pick when you want to test things like clicking buttons, toggling checkboxes, selecting items in a tree view, and generally anything where you are interacting with a specific GTK widget directly.

The second choice is a GTK event simulation test. This is the right pick when you are testing something that happens on the graphical canvas, like resizing a state, dragging something, or moving a handle. In this case you are not clicking a button widget, you are simulating the raw mouse events that GTK normally receives from the OS.

Both approaches use the same gui fixture and the same gui() wrapper, so they share a lot of common ground. The difference is just in how you interact with the UI.

The gui Fixture and the gui() Wrapper

Before jumping into examples, there is one thing you need to understand first, and that is the gui fixture.

RAFCON runs its GTK main loop in a separate thread. This means you cannot just call GTK functions directly from your test thread, because GTK is not thread safe and things will break or silently fail. Every time you want to do something to the GUI, you have to run it through the GTK thread.

That is exactly what the gui fixture gives you. When you do gui(some_function, arg1, arg2), it schedules some_function(arg1, arg2) to run in the GTK main thread, waits for it to finish, and returns the result. Simple as that.

Here is how you get it in your test:

# In your test file, just add `gui` as a parameter to your test function.
# pytest will inject the fixture automatically.

def test_something(gui):
    # `gui` is now your callable wrapper
    gui(some_gtk_widget.set_active, True)

If you want to configure things like window size or history settings, you can pass a config dict:

import pytest

GUI_CONFIG = {
    "gui_config": {
        "HISTORY_ENABLED": True,
    },
    "runtime_config": {
        "MAIN_WINDOW_SIZE": (1500, 800),
        "MAIN_WINDOW_POS": (0, 0),
    }
}

@pytest.mark.parametrize("gui", [GUI_CONFIG], indirect=True, ids=["with fixed config"])
def test_something(gui):
    ...

Choice 1: GTK Widget Tests

This approach is for testing panels and widgets in the RAFCON UI. The breakpoints panel test is a good example of this.

The general flow is:

  1. Load a state machine

  2. Get the controller for the panel you want to test

  3. Interact with the widgets on that controller’s view

  4. Check that the model or application state changed the way you expected

Example: The Breakpoints Panel Test

The test file is at tests/gui/test_breakpoints.py. Let us walk through it step by step.

Step 1: Import what you need
import pytest
import gui_singletons
from rafcon.core import interface
from rafcon.core.storage import storage
from rafcon.core.singleton import state_machine_manager

gui_singletons is where you find the main window controller and all the models after the GUI has started up. You will be using this a lot.

Step 2: Define the path to a test state machine
import os
from tests import utils as testing_utils

BOTTLES_PATH = os.path.join(testing_utils.TEST_ASSETS_PATH, "unit_test_state_machines", "99_bottles_of_beer_on_the_wall")

You need a real state machine to test with. RAFCON has a bunch of them under tests/assets/.

Step 3: Load the state machine and open it in the GUI
def test_breakpoints(gui):
    sm = gui(storage.load_state_machine_from_path, BOTTLES_PATH)
    sm_id = gui(state_machine_manager.add_state_machine, sm)
    testing_utils.wait_for_gui()

Notice that gui(storage.load_state_machine_from_path, BOTTLES_PATH) is just calling storage.load_state_machine_from_path(BOTTLES_PATH) but doing it safely in the GTK thread.

testing_utils.wait_for_gui() drains the GTK event queue after adding the state machine, giving the GUI time to finish rendering everything.

Step 4: Get references to what you need
sm_m = gui_singletons.state_machine_manager_model.state_machines[sm_id]
breakpoints_ctrl = gui_singletons.main_window_controller.get_controller("breakpoints_ctrl")
states_editor_ctrl = gui_singletons.main_window_controller.get_controller("states_editor_ctrl")

Here you are grabbing the model for your state machine and the controllers for the panels you want to interact with.

The get_controller("breakpoints_ctrl") call walks the controller tree and finds the breakpoints panel controller by its name. You can look up controller names by reading the view or controller source files, or by searching for register_view_callbacks in the controllers.

Step 5: Open a state in the states editor
count_state = sm.root_state.states["mRODp"]
count_state_m = sm_m.root_state.states["mRODp"]

gui(states_editor_ctrl.activate_state_tab, count_state_m)
testing_utils.wait_for_gui()

Some widgets only exist after their tab is opened. The breakpoint checkbox lives in the properties tab of a state, so you have to open that state first.

Step 6: Interact with the widget
count_tab_key = states_editor_ctrl.get_state_identifier(count_state_m)
props_ctrl = states_editor_ctrl.tabs[count_tab_key]["controller"].get_controller("properties_ctrl")

gui(props_ctrl.view["breakpoint_checkbox"].set_active, True)
testing_utils.wait_for_gui()

This is the main interaction. You are getting the checkbox widget from the view and calling set_active(True) on it, which is exactly what would happen if a user clicked the checkbox.

The key pattern is: gui(widget.method, argument).

Step 7: Assert that the model changed
from rafcon.core.singleton import breakpoint_manager as bm

assert bm.should_pause(count_state)

After interacting with the widget, you check that the underlying model or application state is what you expect. The test does not care about the visual appearance, it cares about whether the logic worked.

Step 8: Test the panel controls
toggle_btn = breakpoints_ctrl.view["toggle_all_button"]
gui(toggle_btn.set_active, True)
testing_utils.wait_for_gui()

assert not bm.should_pause(count_state)

Same pattern again. Get the widget, call the method through gui(), then assert.

To test the remove button, you select a row in the tree first, then call the handler:

gui(breakpoints_ctrl.breakpoints_tree.get_selection().select_path, 0)
gui(breakpoints_ctrl.on_remove_selected, None)
testing_utils.wait_for_gui()

assert len(list(breakpoints_ctrl.breakpoints_store)) == 0

Choice 2: GTK Event Simulation Tests

This approach is for testing things that happen on the graphical canvas. Instead of clicking a widget, you are creating raw GTK events like BUTTON_PRESS, MOTION_NOTIFY, and BUTTON_RELEASE, and passing them directly to the tool or controller that would normally receive them.

The state resize test is a good example of this. The file is at tests/gui/test_state_resize.py.

Example: The State Resize Test

The idea here is to simulate a user clicking and dragging the resize handle of a state on the canvas.

Step 1: Set up the state machine and get the canvas
from gi.repository import Gdk
from rafcon.gui.mygaphas.tools import MoveHandleTool

def test_state_resize(gui, monkeypatch):
    # load and open the state machine (same as before)
    ...

    # get the graphical editor for the open state machine
    sm_m = gui_singletons.state_machine_manager_model.get_selected_state_machine_model()
    page = gui_singletons.main_window_controller.view["notebook"].get_nth_page(0)
    testing_utils.focus_graphical_editor_in_page(page)
Step 2: Use monkeypatch to make the test deterministic

When a real user drags a resize handle, GTK figures out which handle is under the cursor by checking pixel coordinates. In a test, pixel positions are hard to predict because the window may render slightly differently.

So instead of trying to hit the exact pixel, you monkeypatch the handle detection function to always return the handle you want:

from rafcon.gui.mygaphas import aspect
from gaphas.geometry import Rectangle

state_v = ...  # get the view for the state you want to resize

def get_resize_handle(x, y, distance=None):
    return state_v, state_v.handles()[-1]  # always return the bottom-right handle

monkeypatch.setattr(
    aspect.StateHandleFinder, "get_handle_at_point", get_resize_handle
)

This way the test always resizes the correct state from the correct handle, regardless of window position.

Step 3: Create and send the events
resize_tool = MoveHandleTool(page.canvas)

# Press down
button_press = Gdk.Event.new(Gdk.EventType.BUTTON_PRESS)
button_press.button = 1
button_press.x = 300.0
button_press.y = 300.0
gui(resize_tool.on_button_press, button_press)

# Move the mouse (simulate dragging)
for i in range(10):
    motion = Gdk.Event.new(Gdk.EventType.MOTION_NOTIFY)
    motion.x = 300.0 + (i + 1) * 5
    motion.y = 300.0 + (i + 1) * 5
    gui(resize_tool.on_motion_notify, motion)
    testing_utils.wait_for_gui()

# Release
button_release = Gdk.Event.new(Gdk.EventType.BUTTON_RELEASE)
button_release.button = 1
gui(resize_tool.on_button_release, button_release)
testing_utils.wait_for_gui()

The idea is you are playing the role of the OS. Normally the OS sends these events to GTK and GTK forwards them to the tool. Here you are just cutting out the middleman and sending them directly to the tool yourself.

Step 4: Assert on the state size
new_size = (state_v.model.state.width, state_v.model.state.height)
assert new_size[0] > original_size[0]
assert new_size[1] > original_size[1]

Utility Functions Reference

  • Run anything in the GTK thread: gui(function, arg1, arg2)

  • Wait for GTK to finish processing: testing_utils.wait_for_gui()

  • Give focus to the graphical canvas: testing_utils.focus_graphical_editor_in_page(page)

  • Get any controller by name: gui_singletons.main_window_controller.get_controller(...)

  • Get selected state machine model: gui_singletons.state_machine_manager_model.get_selected_state_machine_model()

  • Load a state machine from disk: gui(storage.load_state_machine_from_path, path)

Quick Decision Guide

Use the GTK widget approach when you are testing:

  • A panel with buttons, checkboxes, labels, or tree views

  • The properties editor for a state

  • The breakpoints panel, the execution history panel, and similar side panels

Use the GTK event simulation approach when you are testing:

  • Something that happens directly on the graphical canvas

  • Resizing, moving, or connecting states

  • Drag and drop operations on the canvas

If you are not sure, ask yourself: is there a GTK widget I can grab and call a method on? If yes, use the widget approach. If you are dealing with the canvas, use the event simulation approach.

12.4. Helper Function: Convert pixels to cairo units

This function can be used when developing with cairo:

def pixel_to_cairo(self, pixel):
    """Helper function to convert pixels to cairo units

    The conversion is depending on the view. The critical parameter is the current zooming factor. The equation is:
    cairo units = pixels / zoom factor

    :param float pixel: Number of pixels to convert
    :return: Number of cairo units corresponding to given number of pixels
    :rtype: float
    """
    zoom = self.get_zoom_factor()
    return pixel / zoom

12.5. Code of Conduct

Our Pledge

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

Our Standards

Examples of behavior that contributes to creating a positive environment include:

  • Using welcoming and inclusive language

  • Being respectful of differing viewpoints and experiences

  • Gracefully accepting constructive criticism

  • Focusing on what is best for the community

  • Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

  • The use of sexualized language or imagery and unwelcome sexual attention or advances

  • Trolling, insulting/derogatory comments, and personal or political attacks

  • Public or private harassment

  • Publishing others’ private information, such as a physical or electronic address, without explicit permission

  • Other conduct which could reasonably be considered inappropriate in a professional setting

Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at rafcon@dlr.de. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership.

Attribution

This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/ <https://www.contributor-covenant.org/version/1/4/>`__

12.6. Bugs & Feature request

Please use GitHub Issues to report bugs. This page can also be used for feature requests.

12.7. Code style

Style guides

We follow the rules PEP 8 (Style Guide for Python Code) and the DLR Coding Conventions with some minor exceptions:

  • Line length is limited to 120 characters (instead of 79)

  • no __version__ in header (except rafcon/__init__.py)

For docstrings (and generally for the documentation), we format using reStructuredText for Sphinx. More information on this is available in another PEP 287.

PyCharm Coding Style/Conventions

Our recommended tool for development is PyCharm. This IDE makes it easy for you to follow our style guides. We prepared a settings file for “Code styles” and “Inspection profiles”, which you just need to import: File > Import Settings > [project root/.idea/recommended_code_style_and_inspections.jar.

12.8. Git commit guidelines

General Git commit conventions

Git is used as our version control system. Please keep the following points in mind when committing to the repository:

  • if you are not a core developer, all changes must be bundled in pull requests

  • therefore, a new branch is required for new features and bug fixes

  • try to keep pull requests small, this eases the review and will speed up the merge of the branch

  • before submitting a pull request, make sure that all tests are passed

  • new features require new unit tests

  • each functional/logical change gets its own commit (try to keep them small)

  • no excessive use of logger.debug outputs (in commits) and never commit print commands

Git commit messages

When looking at the Git commit history, it should be easy to see what changes have been performed. This is why it is important to have good commit messages.

What to do:

  • Use imperative (Add file …)

  • First line is the caption of the commit (should be less than 72 chars)

    • summarizes the commit

    • mentions which code is affected (e.g. by stating the changes module/class)

  • Second line is blank

  • Following lines give a longer description and list changes in detail (use “- ” or “* ” as bullet points)

    • Why is the change necessary

    • How does it address the issue

    • Possible side effects

    • Give Issue/Feature-ID of GitHub Issues

      • Clos[e[s]]|Fix[e[s]]|Resolve[e[s]] #1234567 – use one of the keywords to automatically close an issue

      • Relates to issue #1234567 – state issue id also when the issue is not supposed to be closed

What not to do:

  • Try to avoid using the -m <msg>/--message=<msg> flag to git commit

  • Do not assume that the reader knows the details about bugs, features or previous commits

  • Neither assume that the reader looks at the commited code

Setting up your system

Add this line to your ~/.vimrc to add spell checking and automatic wrapping to your commit messages.

autocmd Filetype gitcommit setlocal spell textwidth=72

Explaining examples

This is a good example for a commit message:

Example: Capitalized, short (<72 chars) summary

More detailed explanatory text. Wrap it to about 72 characters. Think
of first line as the subject of an email and the rest of the text as
the body. The blank line separating the summary from the body is
critical (unless you omit the body entirely); tools like rebase can get
confused if you run the two together.

Further paragraphs come after blank lines.

- Dash '-' and asterisk '*' are both fine, followed by a space
- Use a hanging indent

Th following link shows some bad examples: https://wiki.openstack.org/wiki/GitCommitMessages#Some_examples_of_bad_practice

Sources

12.9. Steps for releasing

Steps to perform, when releasing a new version of RAFCON (this section is only for releasing the tool inside our institute):

  1. Decide about the new version number

Should the release be a patch or minor release? Or even a major release? Check the latest version number and adjust it appropriately.

  1. Create a release Branch

Create a new branch from the latest commit you want to include into the new release. Optionally, if there are new untested feature, which you don’t want to include into the release, first go back to a previous commit:

$ git checkout [hash]

Then, create the new branch and check it out:

$ git checkout -b release-[new version number]
  1. Fix relevant issues (optional)

Within your new branch, you shouldn’t integrate any new features, but only solve issues. The develop and feature-xy branches should be used for new features. Take your time to ensure that the most critical issues are fixed and RAFCON is running stable.

  1. Create a test state machine for the new version (only minor/major releases)

For each minor release, a state machine must be created to ensure backwards compatibility using a special test.

Therefore, open the state machine in [project directory]/tests/assets/unit_test_state_machines/backward_compatibility/[latest version number] and save it to the same folder with the correct version as library name. Commit your changes.

  1. Check tests

Run all tests and verify that they do all pass. If not, fix them! Also check the BuildBot. Commit your changes.

  1. Check the changelog

Open [project directory]/doc/Changelog.rst and verify that all changes are included within the correct version number. Compare with git log and the latest closed issues on GitHub. Commit your changes.

  1. Build style files

Build *.css files from *.scss files.

$ ./compile_scss.sh
$ git add share/themes/RAFCON/gtk-3.0/*.css --force
  1. Apply the version number

  1. If the dev dependencies have not yet been installed via pdm, then run pdm install --dev --no-editable

  2. Update the version number by running pdm run bump2version [major / minor / or patch]

  3. Update the date-released in [project directory]/CITATION.cff.

  4. Run cffconvert --format zenodo --outfile .zenodo.json (see “Making software citable”, requires Python 3)

  5. Commit and push your changes.

  1. Merge to master

When everything is prepared, you can merge the release branch into the master branch:

$ git push release-[new version number]
$ git checkout master
$ git pull master
$ git merge release-[new version number]
$ git push
  1. Merge to develop

Merge all changes back into the develop branch:

$ git checkout develop
$ git pull
$ git merge release-[new version number]]
$ git push
  1. Publish new release to PyPi

Create a new distribution file and publish it on PyPi:

$ rm dist/*
$ pdm build
$ twine upload dist/*
  1. Publish to GitHub

Publish the changes to GitHub and GitHub Enterprise (assuming github is your GitHub remote name):

$ git push github
$ git checkout master
$ git push github

Make a release on GitHub by navigating to https://github.com/DLR-RM/RAFCON/releases/new. Enter the new version number in the “Tag version” field. Optioanlly add a release title and decription. Click “Publish release”.

  1. Force build of GitHub pages

Push an empty commit to the gh-pages branch:

$ git checkout gh-pages
$ git commit -m 'rebuild pages' --allow-empty
$ git push
$ git push github

12.10. Translating RAFCON

RAFCON is prepared for internationalization (i18n) and in fact ships with a basic German translation. This translation can be extended and new translations can be added. We are happy about your support here!

Add translatable strings

In order to allow for translating strings in RAFCON, they need to be marked in the source code. All you need to do for this is wrapping it in the gettext function _(), e.g. _("Hello world").

Strings in glade files do not need to be marked, but the label property needs an attribute translatable="yes":

<object class="GtkButton">
    <property name="label" translatable="yes">Translatable button label</property>
    ...
</object>

Generating a translation template

If new translatable strings have been added or you want to create translations for a new language, a POT file (Portable Object Template) has to be created:

cd /dir/to/rafcon/repository
./bin/i18n-gen-msg

This will create a rafcon.pot file in the source/rafcon/locale folder.

Updating an existing translation

If you want to update a translation, open the according PO file, located in source/rafcon/locale (e.g. de.po) with Poedit. Then go to Catalog => Update from POT file and select the generated rafcon.pot file. This step is optional, if no new translatable strings have been added.

With Poedit, you can add new translations for strings comfortably. Alternatively, you can directly edit the PO files with the text editor of your choice.

Creating a new translation

Open Poedit. Go to File => New From POT/PO File… and enter the name of the new language. Start adding your translations. When you are done, store the file in source/rafcon/locale with the language’ locale as name, e.g. de.po for the German translation.

12.11. HowTo on creating a splashscreen

This is a guide on creating a splash screen picture using the GIMP template.

Location The template file is located in source/rafcon/gui/assets/themes/templates. The standard place for images to be loaded into the startup splashscreen is source/rafcon/gui/assets/splashscreens.

File The template is a .xcf which is the native project format of GIMP. This guide is also using GIMP.

Process

  1. Open the template with GIMP.

  2. First select single window mode: Windows -> Single Window Mode. This will save you a lot of time and struggle.

  3. Check if the Layer widget is present. It should contain five items, one layer Background and four RAFCON_logo layers. If not present, press Ctrl + L.

  4. Now go to File -> Open as Layers and select a picture of your choice.

  5. Check if the image is placed between Background and RAFCON_Logo in the Layer widget. If not, drag it in the correct position.

  6. Now select the layer with your picture in the Layer widget.

    • Go to the Tools widget and select the Scale tool.

    • Click on your picture with the Scale tool and fit it in the 570x320px field.

    • If your not satisfied with your result, try the “Move” tool and move the layer around.

  7. Notice the small eye in the Layer widget next to a layer? Activate the visibility of a logo that serves your needs the best.

  8. If everything is done, ignore the invisible layers and click on File -> Export As. Export the picture as whatever python is able to load into a pixelbuffer, .jpg and .png work fine.

  9. Voila, there is your normed splash screen!