Skip to content

Commit

Permalink
Add LoaderConfig plugin to the Reference Viewer (#1076)
Browse files Browse the repository at this point in the history
* Add LoaderConfig plugin to the Reference Viewer
- documentation added and change log updated
  • Loading branch information
ejeschke authored Jan 4, 2024
1 parent 7e75347 commit 5868816
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 3 deletions.
2 changes: 2 additions & 0 deletions doc/WhatsNew.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Ver 5.0.0 (unreleased)
same as before) to better reflect what the mode does
- Changed icons and cursors from PNG (bitmap) to SVG (vector) format
- Added color distribution ("stretch") control to Info plugin
- Added LoaderConfig plugin; allows setting of loader priorities for
various MIME types

Ver 4.1.0 (2022-06-30)
======================
Expand Down
1 change: 1 addition & 0 deletions doc/manual/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Global plugins
plugins_global/command
plugins_global/saveimage
plugins_global/downloads
plugins_global/loaderconfig


.. _sec-localplugins:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions doc/manual/plugins_global/loaderconfig.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.. _sec-plugins-LoaderConfig:

LoaderConfig
============

.. image:: figures/loaderconfig-plugin.png
:align: center
:width: 800px
:alt: LoaderConfig plugin

.. automodapi:: ginga.rv.plugins.LoaderConfig
:no-heading:
:skip: LoaderConfig
25 changes: 24 additions & 1 deletion ginga/rv/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from ginga.misc import Task, ModuleManager, Settings, log
import ginga.version as version
import ginga.toolkit as ginga_toolkit
from ginga.util import paths, rgb_cms, json, compat
from ginga.util import paths, rgb_cms, json, compat, loader

# Catch warnings
logging.captureWarnings(True)
Expand Down Expand Up @@ -168,6 +168,9 @@
menu="Header [G]", hidden=False, category='Utils', ptype='global'),
Bunch(module='Zoom', tab='Zoom', workspace='left', start=False,
menu="Zoom [G]", category='Utils', ptype='global'),
Bunch(module='LoaderConfig', tab='Loaders', workspace='channels',
start=False, menu="LoaderConfig [G]", category='Debug',
ptype='global'),
]


Expand Down Expand Up @@ -518,6 +521,26 @@ def main(self, options, args):
guiHdlr.setFormatter(fmt)
logger.addHandler(guiHdlr)

# Set loader priorities, if user has saved any
# (see LoaderConfig plugin)
path = os.path.join(self.basedir, 'loaders.json')
if os.path.exists(path):
try:
with open(path, 'r') as in_f:
loader_dct = json.loads(in_f.read())

# set saved priorities for openers
for mimetype, m_dct in loader_dct.items():
for name, l_dct in m_dct.items():
opener = loader.get_opener(name)
loader.add_opener(opener, [mimetype],
priority=l_dct['priority'],
note=opener.__doc__)

except Exception as e:
logger.error(f"failed to process loader file '{path}': {e}",
exc_info=True)

# Load any custom modules
if options.modules is not None:
modules = options.modules.split(',')
Expand Down
173 changes: 173 additions & 0 deletions ginga/rv/plugins/LoaderConfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# This is open-source software licensed under a BSD license.
# Please see the file LICENSE.txt for details.
"""
The ``LoaderConfig`` plugin allows you to configure the file openers that
can be used to load various content into Ginga.
Registered file openers are associated with file MIME types, and there can
be several openers for a single MIME type. A priority associated
with a MIME type/opener pairing determines which opener will be used
for each type--the lowest priority value will determine which opener will
be used. If there are more than one opener with the same low priority
then the user will be prompted for which opener to use, when opening a
file in Ginga. This plugin can be used to set the opener preferences
and save it to the user's $HOME/.ginga configuration area.
**Plugin Type: Global**
``LoaderConfig`` is a global plugin. Only one instance can be opened.
**Usage**
After starting the plugin, the display will show all the registered MIME
types and the openers registered for those types, with an associated
priority for each MIME type/opener pairing.
Select one or more lines and type a priority for them in the box labeled
"Priority:"; press "Set" (or ENTER) to set the priority of those items.
.. note:: The lower the number, the higher the priority. Negative numbers
are fine and the default priority for a loader is usually 0.
So, for example, if there are two loaders available for a MIME
type and one priority is set to -1 and the other to 0, the one
with -1 will be used without asking the user to choose.
Click "Save" to save the priorities to $HOME/.ginga/loaders.json so that
they will be reloaded and used on subsequent restarts of the program.
"""
import os.path

from ginga import GingaPlugin
from ginga.util.paths import ginga_home
from ginga.util import loader, json
from ginga.gw import Widgets

__all__ = ['LoaderConfig']


class LoaderConfig(GingaPlugin.GlobalPlugin):

def __init__(self, fv):
super().__init__(fv)

self.loader_dct = dict()

self.columns = [("Name", 'name'),
("Priority", 'priority'),
("Note", 'note')]

self.gui_up = False

def build_gui(self, container):
vbox = Widgets.VBox()
vbox.set_spacing(1)

tv = Widgets.TreeView(sortable=True, use_alt_row_color=True,
selection='multiple', auto_expand=True)
tv.add_callback('selected', self.select_cb)
tv.setup_table(self.columns, 2, 'name')
self.w.loader_tbl = tv

vbox.add_widget(tv, stretch=1)

tbar = Widgets.HBox()
tbar.set_border_width(4)
tbar.set_spacing(4)

tbar.add_widget(Widgets.Label('Priority:'))
pri = Widgets.TextEntrySet(editable=True)
pri.set_tooltip("Edit priority of loader (lower=better, negative numbers ok)")
pri.set_enabled(False)
pri.add_callback('activated', self.set_priority_cb)
self.w.pri_edit = pri
tbar.add_widget(pri)

tbar.add_widget(Widgets.Label(''), stretch=1)
vbox.add_widget(tbar, stretch=0)

btns = Widgets.HBox()
btns.set_border_width(4)
btns.set_spacing(4)

btn = Widgets.Button("Close")
btn.add_callback('activated', lambda w: self.close())
btns.add_widget(btn)
btn = Widgets.Button("Help")
btn.add_callback('activated', lambda w: self.help())
btns.add_widget(btn, stretch=0)
btn = Widgets.Button("Save")
btn.add_callback('activated', lambda w: self.save_loaders_cb())
btn.set_tooltip("Save configuration of loaders")
btns.add_widget(btn, stretch=0)

btns.add_widget(Widgets.Label(''), stretch=1)
vbox.add_widget(btns, stretch=0)
container.add_widget(vbox, stretch=1)

self.gui_up = True

def start(self):
# create loader table
tree_dct = dict()
for mimetype, dct in loader.loader_by_mimetype.items():
md = dict()
tree_dct[mimetype] = md
for name, bnch in dct.items():
md[name] = dict(name=bnch.opener.name,
priority=bnch.priority,
note=bnch.opener.note)
self.loader_dct = tree_dct

self.w.loader_tbl.set_tree(self.loader_dct)
self.w.loader_tbl.set_optimal_column_widths()

def stop(self):
self.gui_up = False

def set_priority_cb(self, w):
sel_dct = self.w.loader_tbl.get_selected()
priority = int(self.w.pri_edit.get_text())
for mimetype, ld_dct in sel_dct.items():
for name, m_dct in ld_dct.items():
self.loader_dct[mimetype][name]['priority'] = priority
# actually change it in the loader registration
opener = loader.get_opener(name)
loader.add_opener(opener, [mimetype], priority=priority,
note=opener.__doc__)
# update the UI table
self.w.loader_tbl.set_tree(self.loader_dct)

def save_loaders_cb(self):
path = os.path.join(ginga_home, 'loaders.json')
try:
with open(path, 'w') as out_f:
out_f.write(json.dumps(self.loader_dct, indent=4))

except Exception as e:
self.logger.error(f"failed to save loader file: {e}",
exc_info=True)
self.fv.show_error(str(e))

def select_cb(self, w, dct):
selected = len(dct) > 0
self.w.pri_edit.set_enabled(selected)
if not selected:
self.w.pri_edit.set_text('')
else:
# only set priority widget if all selected are the same
# unique value
priorities = set([l_dct['priority']
for m_dct in dct.values()
for l_dct in m_dct.values()])
if len(priorities) == 1:
self.w.pri_edit.set_text(str(priorities.pop()))
else:
self.w.pri_edit.set_text('')

def close(self):
self.fv.stop_global_plugin(str(self))
return True

def __str__(self):
return 'loaderconfig'
4 changes: 2 additions & 2 deletions ginga/util/io/io_fits.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ class AstropyFitsFileHandler(BaseFitsFileHandler):
"""

name = 'astropy.io.fits'
mimetypes = ['image/fits', 'image/x-fits']
mimetypes = ['image/fits', 'image/x-fits', 'application/fits']

@classmethod
def check_availability(cls):
Expand Down Expand Up @@ -562,7 +562,7 @@ class FitsioFileHandler(BaseFitsFileHandler):
"""For loading FITS (Flexible Image Transport System) data files.
"""
name = 'fitsio'
mimetypes = ['image/fits', 'image/x-fits']
mimetypes = ['image/fits', 'image/x-fits', 'application/fits']

@classmethod
def check_availability(cls):
Expand Down

0 comments on commit 5868816

Please sign in to comment.