1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
3 # This file is part of Hooke.
5 # Hooke is free software: you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation, either
8 # version 3 of the License, or (at your option) any later version.
10 # Hooke is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with Hooke. If not, see
17 # <http://www.gnu.org/licenses/>.
19 """The `ui` module provides :class:`UserInterface` and various subclasses.
22 import ConfigParser as configparser
24 from .. import version
25 from ..compat.odict import odict
26 from ..config import Setting
27 from ..util.pluggable import IsSubclass
30 USER_INTERFACE_MODULES = [
34 """List of user interface modules. TODO: autodiscovery
37 USER_INTERFACE_SETTING_SECTION = 'user interfaces'
38 """Name of the config section which controls UI selection.
42 class QueueMessage (object):
44 return self.__class__.__name__
46 class CloseEngine (QueueMessage):
49 class CommandMessage (QueueMessage):
50 """A message storing a command to run, `command` should be a
51 :class:`hooke.command.Command` instance, and `arguments` should be
52 a :class:`dict` with `argname` keys and `value` values to be
53 passed to the command.
55 def __init__(self, command, arguments):
56 self.command = command
57 self.arguments = arguments
59 class UserInterface (object):
60 """A user interface to drive the :class:`hooke.engine.CommandEngine`.
62 def __init__(self, name):
64 self.setting_section = '%s user interface' % (self.name)
67 def default_settings(self):
68 """Return a list of :class:`hooke.config.Setting`\s for any
69 configurable UI settings.
71 The suggested section setting is::
73 Setting(section=self.setting_section, help=self.__doc__)
77 def reload_config(self, config):
78 """Update the user interface for new config settings.
80 Should be called with the new `config` upon recipt of
81 `ReloadUserInterfaceConfig` from the `CommandEngine` or when
82 loading the initial configuration.
85 self.config = dict(config.items(self.setting_section))
86 except configparser.NoSectionError:
89 def run(self, hooke, ui_to_command_queue, command_to_ui_queue):
92 # Assorted useful tidbits for subclasses
94 def _splash_text(self):
100 """ % version()).strip()
102 def _playlist_status(self, playlist):
103 if len(playlist) > 0:
104 return '%s (%s/%s)' % (playlist.name, playlist._index + 1,
106 return 'The playlist %s does not contain any valid force curve data.' \
109 def construct_odict(this_modname, submodnames, class_selector):
110 """Search the submodules `submodnames` of a module `this_modname`
111 for class objects for which `class_selector(class)` returns
112 `True`. These classes are instantiated and stored in the returned
113 :class:`hooke.compat.odict.odict` in the order in which they were
117 for submodname in submodnames:
118 count = len([s for s in submodnames if s == submodname])
119 assert count > 0, 'No %s entries: %s' % (submodname, submodnames)
120 assert count == 1, 'Multiple (%d) %s entries: %s' \
121 % (count, submodname, submodnames)
122 this_mod = __import__(this_modname, fromlist=[submodname])
123 submod = getattr(this_mod, submodname)
124 for objname in dir(submod):
125 obj = getattr(submod, objname)
126 if class_selector(obj):
128 instances[instance.name] = instance
131 USER_INTERFACES = construct_odict(
132 this_modname=__name__,
133 submodnames=USER_INTERFACE_MODULES,
134 class_selector=IsSubclass(UserInterface, blacklist=[UserInterface]))
135 """:class:`hooke.compat.odict.odict` of :class:`UserInterface`
136 instances keyed by `.name`.
139 def default_settings():
140 settings = [Setting(USER_INTERFACE_SETTING_SECTION,
141 help='Select the user interface (only one).')]
142 for i,ui in enumerate(USER_INTERFACES.values()):
143 help = ui.__doc__.split('\n', 1)[0]
144 settings.append(Setting(USER_INTERFACE_SETTING_SECTION,
145 ui.name, str(i==0), help=help))
146 # i==0 to enable the first by default
147 for ui in USER_INTERFACES.values():
148 settings.extend(ui.default_settings())
151 def load_ui(config, name=None):
153 uis = [c for c,v in config.items(USER_INTERFACE_SETTING_SECTION) if v == 'True']
154 assert len(uis) == 1, 'Can only select one UI, not %d: %s' % (len(uis),uis)
156 ui = USER_INTERFACES[name]
157 ui.reload_config(config)