Rose GTK library¶
The Rose/Rosie GUIs (such as the config editor) are written using the Python
bindings for the GTK GUI toolkit (PyGTK). You can write your own custom GTK
widgets and use them within Rose. They should live with the metadata under
the lib/python/widget/
directory.
Value Widgets¶
Value widgets are used for operating on the values of settings. In the config editor, they appear next to the menu button and name label. There are builtin value widgets in Rose such as text entry boxes, radio buttons, and drop-down menus. These are chosen by the config editor based on metadata - for example, if a setting has an integer type, the value widget will be a spin button.
The config editor supports adding user-defined custom widgets which replace the default widgets. These have the same API, but live in the metadata directories rather than the Rose source code.
For example, you may wish to add widgets that deal with dates (e.g. using something based on a calendar widget) or use a slider widget for numbers. You may even want something that uses an image-based interface such as a latitude-longitude chooser based on a map.
Normally, widgets will be placed within the metadata directory for the suite
or application. Widgets going into the Rose core should be added to the
lib/python/rose/config_editor/valuewidget/
directory in a Rose
distribution.
Example¶
See the Advanced Tutorial.
API Reference¶
All value widgets, custom or core, use the same API. This means that a good
practical reference is the set of existing value widgets in the package
rose.config_editor.valuewidget
.
The procedure for implementing a custom value widget is as follows:
Assign a widget[rose-config-edit]
attribute to the relevant variable in the
metadata configuration, e.g.
[namelist:VerifConNL/ScalarAreaCodes]
widget[rose-config-edit]=module_name.AreaCodeChooser
where the widget class lives in the module module_name
under
lib/python/widget/
in the metadata directory for the application or suite.
Modules are imported by the config editor on demand.
This class should have a constructor of the form
class AreaCodeChooser(gtk.HBox):
def __init__(self, value, metadata, set_value, hook, arg_str=None)
with the following arguments:
value
- a string that represents the value that the widget should display.
metadata
a map or dictionary of configuration metadata properties for this value, such as
{'type': 'integer', 'help': 'This is used to count something'}
Note
You may not need to use this information.
set_value
a function that should be called with a new string value of this widget, e.g.
set_value("20")
hook
- An instance of a class
rose.config_editor.valuewidget.ValueWidgetHook
containing callback functions that you should connect some of your widgets to. arg_str
a keyword argument that stores extra text given to the
widget
option in the metadata, if any:widget[rose-config-edit]=modulename.ClassName arg1 arg2 arg3 ...
would give a
arg_str
of"arg1 arg2 arg3 ..."
. This could help configure your widget - for example, for a table based widget, you might give the column names:widget[rose-config-edit]=table.TableValueWidget NAME ID WEIGHTING
This means that you can write a generic widget and then configure it for different cases.
hook
contains some callback functions that you should implement:
hook.get_focus(widget) -> None
which you should connect your top-level widget (
self
) to as follows:self.grab_focus = lambda: hook.get_focus(my_favourite_focus_widget)
or define a method in your class
def grab_focus(self): """Override the focus method, so we can scroll to a particular widget.""" return hook.get_focus(my_favourite_focus_widget)
which allows the correct widget (
my_favourite_focus_widget
) in your container to receive the focus such as a gtk.Entry (my_favourite_focus_widget
) and will also trigger a scroll action on a config editor page. This is important to implement to get the proper global find functionality.hook.trigger_scroll(widget) -> None
accessed by
hook.trigger_scroll(my_favourite_focus_widget)
This should be connected to the
focus-in-event
GTK signal of your top-level widget (self
):self.entry.connect('focus-in-event', hook.trigger_scroll)
This also is used to trigger a config editor page scroll to your widget.
You may implement the following optional methods for your widget, which help to preserve cursor position when a widget is refreshed:
set_focus_index(focus_index) -> None
A method that takes a number as an argument, which is the current cursor position relative to the characters in the variable value:
def set_focus_index(self, focus_index): """Set the cursor position to focus_index.""" self.entry.set_position(focus_index)
For example, a
focus_index
of0
means that your widget should set the cursor position to the beginning of the value. Afocus_index
of4
for a variable value ofOperational
means that the cursor should be placed between ther
and thea
.Note
This has no real meaning or importance for widgets that don’t display editable text. If you do not supply this method, the config editor will attempt to do the right thing anyway.
get_focus_index() -> focus_index
A method that takes no arguments and returns a number which is the current cursor position relative to the characters in the variable value:
def get_focus_index(self): """Return the cursor position.""" return self.entry.get_position()
Note
This has no real meaning or importance for widgets that don’t display editable text. If you do not supply this method, the config editor will guess the cursor position anyway, based on the last change to the variable value.
handle_type_error(is_in_error) -> None
The default behaviour when a variable error is added or removed is to re-instantiate the widget (refresh and redraw it). This can be overridden by defining this method in your value widget class. It takes a boolean
is_in_error
which isTrue
if there is a value (type) error andFalse
otherwise:def handle_type_error(self, is_in_error): """Change behaviour based on whether the variable is_in_error.""" icon_id = gtk.STOCK_DIALOG_ERROR if is_in_error else None self.entry.set_icon_from_stock(0, gtk.STOCK_DIALOG_ERROR)
For example, this is used in a built-in widget for the quoted string types
string
andcharacter
. The quotes around the text are normally hidden, but thehandle_type_error
shows them if there is an error. The method also keeps the keyboard focus, which is the main purpose.Tip
You may not have much need for this method, as the default error flagging and cursor focus handling is normally sufficient.
Tip
All the existing variable value widgets are implemented using this
API, so a good resource is the modules within the
lib/python/rose/config_editor/valuewidget
package.
Config Editor Custom Pages¶
A ‘page’ in the config editor is the container inside a tab or detached tab that (by default) contains a table of variable widgets. The config editor allows custom ‘pages’ to be defined that may or may not use the standard set of variable widgets (menu button, name, value widget). This allows any presentation of the underlying variable information.
For example, you may wish to present the variables in a more structured, two-dimensional form rather than as a simple list. You may want to strip down or add to the information presented by default - e.g. hiding names or embedding widgets within a block of help text.
You may even wish to do something off-the-wall such as an xdot-based widget set!
API Reference¶
The procedure for generating a custom page widget is as follows:
Assign a widget
option to the relevant namespace in the metadata
configuration, e.g.
[ns:namelist/STASHNUM]
widget[rose-config-edit]=module_name.MyGreatBigTable
The widget class should have a constructor of the form
class MyGreatBigTable(gtk.Table):
def __init__(self, real_variable_list, missing_variable_list,
variable_functions_inst, show_modes_dict,
arg_str=None):
The class can inherit from any gtk.Container
-derived class.
The constructor arguments are
real_variable_list
- a list of the Variable objects (
x.name
,x.value
,x.metadata
, etc from therose.variable
module). These are the objects you will need to generate your widgets around. missing_variable_list
- a list of ‘missing’ Variable objects that could be added to the container.
You will only need to worry about these if you plan to show them by
implementing the
'View Latent'
menu functionality that we’ll discuss further on. variable_functions_inst
- an instance of the class
rose.config_editor.ops.variable.VariableOperations
. This contains methods to operate on the variables. These will update the undo stack and take care of any errors. These methods are the only ways that you should write to the variable states or values. For documentation, see the modulelib/python/rose/config_editor/ops/variable.py
. show_modes_dict
a dictionary that looks like this:
show_modes_dict = {'latent': False, 'fixed': False, 'ignored': True, 'user-ignored': False, 'title': False, 'flag:optional': False, 'flag:no-meta': False}
which could be ignored for most custom pages, as you need. The meaning of the different keys in a non-custom page is:
'latent'
- False means don’t display widgets for variables in the metadata or
that have been deleted (the
variable_list.ghosts
variables) 'fixed'
- False means don’t display widgets for variables if they only have
one value set in the metadata
values
option. 'ignored'
- False means don’t display widgets for variables if they’re ignored (in the configuration, but commented out).
'user-ignored'
- (If
ignored
is False) False means don’t display widgets for user-ignored variables. True means always show user-ignored variables. 'title'
- Short for ‘View with no title’, False means show the title of a variable, True means show the variable name instead.
'flag:optional'
- True means indicate if a variable is
optional
, and False means do not show an indicator. 'flag:no-meta'
- True means indicate if a variable has any metadata content, and False means do not show an indicator.
If you wish to implement actions based on changes in these properties (e.g. displaying and hiding fixed variables depending on the ‘fixed’ setting), the custom page widget should expose a method named
'show_mode_change'
followed by the key. However,'ignored'
is handled separately (more below). These methods should take a single boolean that indicates the display status. For example:def show_fixed(self, should_show)
The argument
should_show
is a boolean. If True, fixed variables should be shown. If False, they should be hidden by your custom container.arg_str
a keyword argument that stores extra text given to the
widget
option in the metadata, if any:widget[rose-config-edit] = modulename.ClassName arg1 arg2 arg3 ...
would give a
arg_str
of"arg1 arg2 arg3 ..."
. This could help configure your widget - for example, for a table based widget, you might give the column names:widget[rose-config-edit] = table.TableValueWidget NAME ID WEIGHTING
This means that you can write a generic widget and then configure it for different cases.
Refreshing the whole page in order to display a small change to a variable
(the default) can be undesirable. To deal with this, custom page widgets can
optionally expose some variable-change specific methods that do this
themselves. These take a single rose.variable.Variable
instance as an
argument.
-
add_variable_widget
(self, variable) → None¶ Will be called when a variable is created.
-
reload_variable_widget
(self, variable) → None¶ Will be called when a variable’s status is changed, e.g. it goes into an error state.
-
remove_variable_widget
(self, variable) → None¶ Will be called when a variable is removed.
-
update_ignored
(self) → None¶ Will be called to allow you to update ignored widget display, if (for example) you show/hide ignored variables. If you don’t have any custom behaviour for ignored variables, it’s worth writing a method that does nothing - e.g. one that contains just
pass
).
If you take the step of using your own variable widgets, rather than the
VariableWidget
class in lib/python/rose/config_editor/variable.py
(the default for normal config-edit pages), each variable-specific widget
should have an attribute variable
set to their rose.variable.Variable
instance. You can implement ‘ignored’ status display by giving the widget a
method set_ignored
which takes no arguments. This should examine the
ignored_reason
dictionary attribute of the widget’s variable
instance - the variable is ignored if this is not empty. If the variable is
ignored, the widget should indicate this e.g. by greying out part of it.
Tip
All existing page widgets use this API, so a good resource is the
modules in lib/python/rose/config_editor/pagewidget/
.
Generally speaking, a visible change, click, or key press in the custom page
widget should make instant changes to variable value(s), and the value that
the user sees. Pages are treated as temporary, superficial views of variable
data, and changes are always assumed to be made directly to the main copy
of the configuration in memory (this is automatic when the
rose.config_editor.ops.variable.VariableOperations
methods are used, as
they should be). Closing the page shouldn’t change, or lose, any data!
The custom class should return a gtk object to be packed into the page
framework, so it’s best to subclass from an existing gtk Container type
such as gtk.VBox
(or gtk.Table
, in the example above).
Note
In line with the general philosophy, metadata should not be critical to page operations - it should be capable of displaying variables even when they have no or very little metadata, and still make sense if some variables are missing or new.
Config Editor Custom Sub Panels¶
A ‘sub panel’ or ‘summary panel’ in the config editor is a panel that appears at the bottom of a page and is intended to display some summarised information about sub-pages (sub-namespaces) underneath the page. For example, the top-level file page, by default, has a sub panel to summarise the individual file sections.
Any actual data belonging to the page will appear above the sub panel in a separate representation.
Sub panels are capable of using quite a lot of functionality such as modifying the sections and options in the sub-pages directly.
API Reference¶
The procedure for generating a custom sub panel widget is as follows:
Assign a widget[rose-config-edit:sub-ns]
option to the relevant
namespace in the metadata configuration, e.g.
[ns:namelist/all_the_foo_namelists]
widget[rose-config-edit:sub-ns]=module_name.MySubPanelForFoos
Note that because the actual data on the page has a separate representation,
you need to write [rose-config-edit:sub-ns]
rather than just
[rose-config-edit]
.
The widget class should have a constructor of the form
class MySubPanelForFoos(gtk.VBox):
def __init__(self, section_dict, variable_dict,
section_functions_inst, variable_functions_inst,
search_for_id_function, sub_functions_inst,
is_duplicate_boolean, arg_str=None):
The class can inherit from any gtk.Container
-derived class.
The constructor arguments are:
section_dict
- a dictionary (map, hash) of section name keys and section data object
values (instances of the
rose.section.Section
class). These contain some of the data such as section ignored status and comments that you may want to present. These objects can usually be used by thesection_functions_inst
methods as arguments - for example, passed in in order to ignore or enable a section. variable_dict
- a dictionary (map, hash) of section name keys and lists of variable data
objects (instances of the
rose.variable.Variable
class). These contain useful information for the variable (option) such as state, value, and comments. Like section data objects, these can usually be used as arguments to thevariable_functions_inst
methods to accomplish things like changing a variable value or adding or removing a variable. section_functions_inst
- an instance of the class rose.config_editor.ops.section.SectionOperations.
This contains methods to operate on the variables. These will update the
undo stack and take care of any errors. Together with
sub_functions_inst
, these methods are the only ways that you should write to the section states or other attributes. For documentation, see the modulelib/python/rose/config_editor/ops/section.py
. variable_functions_inst
- an instance of the class
rose.config_editor.ops.variable.VariableOperations
. This contains methods to operate on the variables. These will update the undo stack and take care of any errors. These methods are the only ways that you should write to the variable states or values. For documentation, see the modulelib/python/rose/config_editor/ops/variable.py
. search_for_id_function
- a function that accepts a setting id (a section name, or a variable id) as an argument and asks the config editor to navigate to the page for that setting. You could use this to allow a click on a section name in your widget to launch the page for the section.
sub_functions_inst
- an instance of the class
rose.config_editor.ops.group.SubDataOperations
. This contains some convenience methods specifically for sub panels, such as operating on many sections at once in an optimised way. For documentation, see the modulelib/python/rose/config_editor/ops/group.py
. is_duplicate_boolean
- a boolean that denotes whether or not the sub-namespaces in the summary
data consist only of duplicate sections (e.g. only
namelist:foo(1)
,namelist:foo(2)
, …). For example, this could be used by your widget to decide whether to implement a “Copy section” user option. arg_str
a keyword argument that stores extra text given to the
widget
option in the metadata, if any - e.g.:widget[rose-config-edit:sub-ns] = modulename.ClassName arg1 arg2 arg3 ...
would give a
arg_str
of"arg1 arg2 arg3 ..."
. You can use this to help configure your widget.
Tip
All existing sub panel widgets use this API, so a good resource is the
modules in lib/python/rose/config_editor/panelwidget/
.