Merged my unitary FFT wrappers (FFT_tools) as hooke.util.fft.
[hooke.git] / hooke / ui / __init__.py
1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of Hooke.
4 #
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.
9 #
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.
14 #
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/>.
18
19 """The `ui` module provides :class:`UserInterface` and various subclasses.
20 """
21
22 import ConfigParser as configparser
23
24 from .. import version
25 from ..compat.odict import odict
26 from ..config import Setting
27 from ..util.pluggable import IsSubclass
28
29
30 USER_INTERFACE_MODULES = [
31     'commandline',
32     #'gui',
33     ]
34 """List of user interface modules.  TODO: autodiscovery
35 """
36
37 USER_INTERFACE_SETTING_SECTION = 'user interfaces'
38 """Name of the config section which controls UI selection.
39 """
40
41
42 class QueueMessage (object):
43     def __str__(self):
44         return self.__class__.__name__
45
46 class CloseEngine (QueueMessage):
47     pass
48
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.
54     """
55     def __init__(self, command, arguments):
56         self.command = command
57         self.arguments = arguments
58
59 class UserInterface (object):
60     """A user interface to drive the :class:`hooke.engine.CommandEngine`.
61     """
62     def __init__(self, name):
63         self.name = name
64         self.setting_section = '%s user interface' % (self.name)
65         self.config = {}
66
67     def default_settings(self):
68         """Return a list of :class:`hooke.config.Setting`\s for any
69         configurable UI settings.
70
71         The suggested section setting is::
72
73             Setting(section=self.setting_section, help=self.__doc__)
74         """
75         return []
76
77     def reload_config(self, config):
78         """Update the user interface for new config settings.
79
80         Should be called with the new `config` upon recipt of
81         `ReloadUserInterfaceConfig` from the `CommandEngine`.
82         """
83         try:
84             self.config = dict(config.items(self.setting_section))
85         except configparser.NoSectionError:
86             self.config = {}
87
88     def run(self, hooke, ui_to_command_queue, command_to_ui_queue):
89         return
90
91     # Assorted useful tidbits for subclasses
92
93     def _splash_text(self):
94         return ("""
95 Hooke version %s
96
97 COPYRIGHT
98 ----
99 """ % version()).strip()
100
101     def _playlist_status(self, playlist):
102         if len(playlist) > 0:
103             return '%s (%s/%s)' % (playlist.name, playlist._index + 1,
104                                    len(playlist))
105         return 'The playlist %s does not contain any valid force curve data.' \
106             % self.name
107
108 def construct_odict(this_modname, submodnames, class_selector):
109     """Search the submodules `submodnames` of a module `this_modname`
110     for class objects for which `class_selector(class)` returns
111     `True`.  These classes are instantiated and stored in the returned
112     :class:`hooke.compat.odict.odict` in the order in which they were
113     discovered.
114     """
115     instances = odict()
116     for submodname in submodnames:
117         count = len([s for s in submodnames if s == submodname])
118         assert count > 0, 'No %s entries: %s' % (submodname, submodnames)
119         assert count == 1, 'Multiple (%d) %s entries: %s' \
120             % (count, submodname, submodnames)
121         this_mod = __import__(this_modname, fromlist=[submodname])
122         submod = getattr(this_mod, submodname)
123         for objname in dir(submod):
124             obj = getattr(submod, objname)
125             if class_selector(obj):
126                 instance = obj()
127                 instances[instance.name] = instance
128     return instances
129
130 USER_INTERFACES = construct_odict(
131     this_modname=__name__,
132     submodnames=USER_INTERFACE_MODULES,
133     class_selector=IsSubclass(UserInterface, blacklist=[UserInterface]))
134 """:class:`hooke.compat.odict.odict` of :class:`UserInterface`
135 instances keyed by `.name`.
136 """
137
138 def default_settings():
139     settings = [Setting(USER_INTERFACE_SETTING_SECTION,
140                         help='Select the user interface (only one).')]
141     for i,ui in enumerate(USER_INTERFACES.values()):
142         help = ui.__doc__.split('\n', 1)[0]
143         settings.append(Setting(USER_INTERFACE_SETTING_SECTION,
144                                 ui.name, str(i==0), help=help))
145         # i==0 to enable the first by default
146     for ui in USER_INTERFACES.values():
147         settings.extend(ui.default_settings())
148     return settings
149
150 def load_ui(config, name=None):
151     if name == None:
152         uis = [c for c,v in config.items(USER_INTERFACE_SETTING_SECTION) if v == 'True']
153         assert len(uis) == 1, 'Can only select one UI, not %d: %s' % (len(uis),uis)
154         name = uis[0]
155     ui = USER_INTERFACES[name]
156     try:
157         ui.config = dict(config.items(ui.setting_section))
158     except configparser.NoSectionError:
159         pass
160     return ui