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 interpretercoverage: Runs the tests using Python 2.7 with a coverage reportdocs: Builds the documentation and verifies all linkscheck: 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 nameunstable: 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.singletonsand core singletons viagui.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:
Load a state machine
Get the controller for the panel you want to test
Interact with the widgets on that controller’s view
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 (exceptrafcon/__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.debugoutputs (in commits) and never commitprintcommands
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 issueRelates 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 togit commitDo 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):
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.
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]
Fix relevant issues (optional)
Within your new branch, you shouldn’t integrate any new features, but only solve issues. The
developandfeature-xybranches should be used for new features. Take your time to ensure that the most critical issues are fixed and RAFCON is running stable.
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.
Check tests
Run all tests and verify that they do all pass. If not, fix them! Also check the BuildBot. Commit your changes.
Check the changelog
Open
[project directory]/doc/Changelog.rstand verify that all changes are included within the correct version number. Compare withgit logand the latest closed issues on GitHub. Commit your changes.
Build style files
Build
*.cssfiles from*.scssfiles.$ ./compile_scss.sh $ git add share/themes/RAFCON/gtk-3.0/*.css --force
Apply the version number
If the dev dependencies have not yet been installed via pdm, then run
pdm install --dev --no-editableUpdate the version number by running
pdm run bump2version [major / minor / or patch]Update the
date-releasedin[project directory]/CITATION.cff.Run
cffconvert --format zenodo --outfile .zenodo.json(see “Making software citable”, requires Python 3)Commit and push your changes.
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
Merge to develop
Merge all changes back into the develop branch:
$ git checkout develop $ git pull $ git merge release-[new version number]] $ git push
Publish new release to PyPi
Create a new distribution file and publish it on PyPi:
$ rm dist/* $ pdm build $ twine upload dist/*
Publish to GitHub
Publish the changes to GitHub and GitHub Enterprise (assuming
githubis your GitHub remote name):$ git push github $ git checkout master $ git push githubMake 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”.
Force build of GitHub pages
Push an empty commit to the
gh-pagesbranch:$ 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
Open the template with GIMP.
First select single window mode:
Windows -> Single Window Mode. This will save you a lot of time and struggle.Check if the Layer widget is present. It should contain five items, one layer
Backgroundand fourRAFCON_logolayers. If not present, pressCtrl + L.Now go to
File -> Open as Layersand select a picture of your choice.Check if the image is placed between
BackgroundandRAFCON_Logoin the Layer widget. If not, drag it in the correct position.Now select the layer with your picture in the Layer widget.
Go to the Tools widget and select the
Scaletool.Click on your picture with the
Scaletool and fit it in the 570x320px field.If your not satisfied with your result, try the “Move” tool and move the layer around.
Notice the small eye in the Layer widget next to a layer? Activate the visibility of a logo that serves your needs the best.
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.Voila, there is your normed splash screen!