42085c25a0b05b5216cb8263004ae4158a4e1b54
[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` or when
82         loading the initial configuration.
83         """
84         try:
85             self.config = dict(config.items(self.setting_section))
86         except configparser.NoSectionError:
87             self.config = {}
88
89     def run(self, commands, ui_to_command_queue, command_to_ui_queue):
90         return
91
92     # Assorted useful tidbits for subclasses
93
94     def _splash_text(self):
95         return ("""
96 Hooke version %s
97
98 COPYRIGHT
99 ----
100 """ % version()).strip()
101
102     def _playlist_status(self, playlist):
103         if len(playlist) > 0:
104             return '%s (%s/%s)' % (playlist.name, playlist._index + 1,
105                                    len(playlist))
106         return 'The playlist %s does not contain any valid force curve data.' \
107             % self.name
108
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
114     discovered.
115     """
116     instances = odict()
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):
127                 instance = obj()
128                 instances[instance.name] = instance
129     return instances
130
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`.
137 """
138
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())
149     return settings
150
151 def load_ui(config, name=None):
152     if 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)
155         name = uis[0]
156     ui = USER_INTERFACES[name]
157     ui.reload_config(config)
158     return ui