Customizing Ginga¶
One of the primary guiding concepts behind the Ginga project is to provide convenient ways to build custom viewers. The reference viewer embodies this concept through the use of a flexible layout engine and the use of plugins to implement all the major user interface features. By modifying or replacing the layout and adding, subclassing or removing plugins you can completely change the look, feel and operation of the reference viewer.
This chapter explains how you can customize the Ginga reference viewer in various ways, as a user or a developer.
Configuration Options¶
Ginga examines a configuration directory on startup to check for any configuration files or customization of the default behavior.
Note
The configuration area is determined first by examining the
environment variable GINGA_HOME
. If that is not set, then
$HOME/.ginga
(Mac OS X, Linux) or
$HOMEDRIVE:$HOMEPATH\\.ginga
(Windows) will be used.
Examples of the types of configuration files with comments describing the
effects of the parameters can be found in .../ginga/examples/configs
.
Many of the plugins have their own configuration file, with preferences
that are only changed via that file. You can copy an example
configuration file to your Ginga settings area and change the settings
to your liking.
Usually it is sufficient to simply close the plugin and open it again to pick up any settings changes, but some changes may require a viewer restart to take effect.
Channels also use configuration files to store many different settings
for the channel viewer windows. When a channel is created, the
reference viewer looks to see if there is a configuration file for that
channel in the configuration area; if so, the settings therein are used
to configure it. If not, the settings for the generic startup channel
“Image” are used to configure the new channel. The “Preferences” plugin
can be used to set many of the channel settings. If you set these for
the “Image” channel and use the “Save” button, other channels will
inherit them. You can also manually copy the example file from
.../ginga/examples/configs/channel_Image.cfg
to your configuration
area and edit it if you prefer.
Saving the workspace layout between sessions¶
By default, Ginga will will write its window size, position and some layout
information to a “layout” file in the configuration directory when the
program is closed. Upon a subsequent startup Ginga will attempt to
restore the window to the saved configuration. If this behavior is not
desired you can add the option save_layout = False
to your
general.cfg
file in the Ginga configuration directory.
There is a sample general.cfg
file in .../ginga/examples/configs
.
Invoking the program with the --norestore
option also prevents it from
reading the saved layout file. This may be needed in some cases when
the layout changes in an incompatible way between when the program was
last stopped and when it was started again.
Rebinding Controls¶
One configuration file that many users will be interested in is the one
controlling how keyboard and mouse/touch bindings are assigned. This is
handled by the configuration file bindings.cfg
. Several examples
are stored in .../ginga/examples/bindings
, including an example for
users familiar with the ds9 mouse controls, and an example for users
using a touchpad without a mouse (pinch zoom and scroll panning).
Simply copy the appropriate file to your Ginga settings area as
bindings.cfg
.
Customizing the Reference Viewer During Initialization¶
The reference viewer can be customized during viewer initialization
using a module called ginga_config
, which can be anywhere in the
user’s Python import path, including in the Ginga configuration folder
described above (e.g. $HOME/.ginga/ginga_config.py
).
Specifically, this file will be imported and two methods will be run if
defined: pre_gui_config(ginga_shell)
and
post_gui_config(ginga_shell)
.
The parameter to each function is the main viewer shell. These functions
can be used to define a different viewer layout, add or remove plugins,
add menu entries, add custom image or star catalogs, etc. We will refer
back to these functions in the sections below.
Workspace configuration¶
Ginga has a flexible table-driven layout scheme for dynamically creating
workspaces and mapping the available plugins to workspaces. By changing
a couple of tables via ginga_config.pre_gui_config()
you can change
the way Ginga looks and presents its content.
If you examine the module ginga.rv.main
you will find a layout table
called default_layout
:
default_layout = ['seq', {},
['vbox', dict(name='top', width=1400, height=700),
dict(row=['hbox', dict(name='menu')],
stretch=0),
dict(row=['hpanel', dict(name='hpnl'),
['ws', dict(name='left', wstype='tabs',
width=300, height=-1, group=2),
# (tabname, layout), ...
[("Info", ['vpanel', {},
['ws', dict(name='uleft', wstype='stack',
height=250, group=3)],
['ws', dict(name='lleft', wstype='tabs',
height=330, group=3)],
]
)]],
['vbox', dict(name='main', width=600),
dict(row=['ws', dict(name='channels', wstype='tabs',
group=1, use_toolbar=True)],
stretch=1),
dict(row=['ws', dict(name='cbar', wstype='stack',
group=99)], stretch=0),
dict(row=['ws', dict(name='readout', wstype='stack',
group=99)], stretch=0),
dict(row=['ws', dict(name='operations', wstype='stack',
group=99)], stretch=0),
],
['ws', dict(name='right', wstype='tabs',
width=400, height=-1, group=2),
# (tabname, layout), ...
[("Dialogs", ['ws', dict(name='dialogs', wstype='tabs',
group=2)
]
)]
],
], stretch=1),
dict(row=['ws', dict(name='toolbar', wstype='stack',
height=40, group=2)],
stretch=0),
dict(row=['hbox', dict(name='status')], stretch=0),
]]
This rather arcane-looking table defines the precise layout of the reference viewer shell, including how many workspaces it will have, their characteristics, how they are organized, and their names.
The key point in this section is that you can modify this table or
replace it entirely with one of your own design and set it in the
pre_gui_config()
method described above:
my_layout = [
...
]
def pre_gui_config(ginga_shell):
...
ginga_shell.set_layout(my_layout)
If done in the pre_gui_config()
method (as shown) the new layout will
be the one that is used when the GUI is constructed.
Format of the Layout Table¶
The table consists of a nested list of sublists, tuples and/or dictionaries. The first item in a sublist indicates the type of the container to be constructed. The following types are available:
seq
: defines a sequence of top-level windows to be createdhpanel
: a horizontal panel of containers, with handles to size themvpanel
: a vertical panel of containers, with handles to size themhbox
: a horizontal panel of containers of fixed sizevbox
: a vertical panel of containers of fixed sizews
: a workspace that allows a plugin or a channel viewer to be loaded into it. A workspace can be configured in four ways: as a tabbed notebook (wstype="tabs"
), as a stack (wstype="stack"
), as an MDI (Multiple Document Interface,wstype="mdi"
) or a grid (wstype="grid"
).widget
: a preconstructed widget passed in.
In every case the second item in the sublist is a dictionary that provides some optional parameters that modify the characteristics of the container. If there is no need to override the default parameters the dictionary can simply be empty. The optional third and following items are specifications for nested content.
All types of containers honor the following parameters:
- width: can specify a desired width in pixels for the container.
- height: can specify a desired height in pixels for the container.
- name: specifies a mapping of a name to the created container widget. The name is important especially for workspaces, as they may be referred to as an output destination when registering plugins.
Note
In the above example, we define a top-level window consisting of a vbox (named “top”) with 4 layers: a hbox (“menu”), hpanel (“hpnl”), a workspace (“toolbar”) and another hbox (“status”). The main horizontal panel of three containers: a workspace (“left”) with a width of 300 pixels, a vbox (“main”, 700 pixels) and a workspace (“right”, 400 pixels). The “left” workspace is pre-populated with an “Info” tab containing a vertical panel of two workspaces: “uleft” and “lleft” with heights of 300 and 430 pixels, respectively. The “right” workspace is pre-populated with a “Dialogs” tab containing an empty workspace. The “main” vbox is configured with three rows of workspaces: “channels”, “cbar” and “readout”.
Ginga uses some container names in special ways. For example, Ginga looks for a “channels” workspace as the default workspace for creating channels, and the “dialogs” workspace is where most local plugins are instantiated (when activated), by default. These two names should at least be defined somewhere in default_layout.
Auto-Start Plugins¶
Not all plugins provided by Ginga are automatically started up by default.
A plugin can be started automatically in post_gui_config()
using the
start_global_plugin()
or start_local_plugin()
methods, as appropriate:
def post_gui_config(ginga_shell):
# Auto start global plugins
ginga_shell.start_global_plugin('Zoom')
ginga_shell.start_global_plugin('Header')
# Auto start local plugin
ginga_shell.add_channel('Image')
ginga_shell.start_local_plugin('Image', 'Histogram', None)
Alternately, you can also start plugins via the command line interface using
--plugins
and -modules
for local and global plugins, respectively.
To load multiple plugins at once, use a comma-separated list. For example:
ginga --plugins=MyLocalPlugin,Imexam --modules=MyGlobalPlugin
Adding Plugins¶
A plugin can be added to the reference viewer in pre_gui_config()
using the add_plugin()
method with a specification (“spec”) for
the plugin:
from ginga.misc.Bunch import Bunch
def pre_gui_config(ginga_shell):
...
spec = Bunch(module='DQCheck', klass='DQCheck', workspace='dialogs',
category='Utils', ptype='local')
ginga_shell.add_plugin(spec)
The above call would try to load a local plugin called “DQCheck” from a module called “DQCheck”. When invoked from the Operations menu it would occupy a spot in the “dialogs” workspace (see layout discussion above).
Other keywords that can be used in a spec:
- Global plugins use
ptype='global'
. - If a plugin should be hidden from the menus (e.g. it is started under
program control, not by the user), specify
hidden=True
. - If the plugin should be started when the program starts, specify
start=True
. - To use a different name in the menu for starting the plugin, specify
menu="Custom Name"
. - To use a different name in the tab that is showing the plugin GUI,
specify
tab="Tab Name"
. - To prevent a control icon from appearing in the Operations plugin
manager tray specify
optray=False
.
Disabling Plugins¶
Both local and global plugins can be disabled (thus, not shown in the
reference viewer) using the --disable-plugins
option in the
command line interface. To remove multiple plugins at once,
use a comma-separated list. For example:
ginga --disable-plugins=Zoom,Compose
Alternately, plugins can also be disabled via general.cfg
configuration
file. For example:
disable_plugins = "Zoom,Compose"
Some plugins, like Operations
, when disabled, may result in
inconvenient GUI experience.
Making a Custom Startup Script¶
You can make a custom startup script to make the same reference viewer
configuration available without relying on the ginga_config
module in
a personal settings area. To do this we make use of the main
module:
import sys
from ginga.rv.main import ReferenceViewer
from optparse import OptionParser
my_layout = [ ... ]
plugins = [ ... ]
if __name__ == "__main__":
viewer = ReferenceViewer(layout=my_layout)
# add plugins
for spec in plugins:
viewer.add_plugin(spec)
# Parse command line options with optparse module
usage = "usage: %prog [options] cmd [args]"
optprs = OptionParser(usage=usage)
viewer.add_default_options(optprs)
(options, args) = optprs.parse_args(sys_argv[1:])
viewer.main(options, args)