31fcae6fd9c859778b37c05819ef147235a22e91
[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 modify it
6 # under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 #
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
13 # 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 import logging
24
25 from .. import version
26 from ..config import Setting
27 from ..engine import CommandMessage
28 from ..util.pluggable import IsSubclass, construct_odict
29
30 try:
31     from ..license import short_license
32 except ImportError, e:
33     logging.warn('could not load short_license from hooke.license')
34     from .. import __license__
35     def short_license(extra_info, **kwargs):
36         return __license__
37
38
39 USER_INTERFACE_MODULES = [
40     'commandline',
41     'gui',
42     ]
43 """List of user interface modules.  TODO: autodiscovery
44 """
45
46 USER_INTERFACE_SETTING_SECTION = 'user interfaces'
47 """Name of the config section which controls UI selection.
48 """
49
50
51 class UserInterface (object):
52     """A user interface to drive the :class:`hooke.engine.CommandEngine`.
53     """
54     def __init__(self, name):
55         self.name = name
56         self.setting_section = '%s user interface' % (self.name)
57         self.config = {}
58
59     def default_settings(self):
60         """Return a list of :class:`hooke.config.Setting`\s for any
61         configurable UI settings.
62
63         The suggested section setting is::
64
65             Setting(section=self.setting_section, help=self.__doc__)
66         """
67         return []
68
69     def reload_config(self, config):
70         """Update the user interface for new config settings.
71
72         Should be called with the new `config` upon recipt of
73         `ReloadUserInterfaceConfig` from the `CommandEngine` or when
74         loading the initial configuration.
75         """
76         try:
77             self.config = dict(config.items(self.setting_section))
78         except configparser.NoSectionError:
79             self.config = {}
80
81     def run(self, commands, ui_to_command_queue, command_to_ui_queue):
82         return
83
84     # Assorted useful tidbits for subclasses
85
86     def _submit_command(self, command_message, ui_to_command_queue,
87                         explicit_user_call=True):
88         log = logging.getLogger('hooke')
89         if explicit_user_call == True:
90             executor = 'user'
91         else:
92             executor = 'UI'
93         command_message.explicit_user_call = explicit_user_call
94         log.debug('executing (for the %s) %s' % (executor, command_message))
95         ui_to_command_queue.put(command_message)
96
97     def _set_config(self, option, value, ui_to_command_queue, response_handler,
98                      section=None):
99         if section == None:
100             section = self.setting_section
101         if section in [self.setting_section, 'conditions']:
102             if self.config[option] == value:
103                 return  # No change, so no need to push the new value.
104             self.config[option] = value
105         cm = CommandMessage(
106             command='set config',
107             arguments={'section': section, 'option': option, 'value': value})
108         self._submit_command(command_message=cm,
109                              ui_to_command_queue=ui_to_command_queue,
110                              explicit_user_call=False)
111         response_handler(command_message=cm)
112
113     def _splash_text(self, extra_info, **kwargs):
114         return ("""
115 Hooke version %s
116
117 %s
118 ----
119 """ % (version(), short_license(extra_info, **kwargs))).strip()
120
121     def _playlist_status(self, playlist):
122         if len(playlist) > 0:
123             return '%s (%s/%s)' % (playlist.name, playlist.index() + 1,
124                                    len(playlist))
125         return 'The playlist %s does not contain any valid force curve data.' \
126             % self.name
127
128
129 USER_INTERFACES = construct_odict(
130     this_modname=__name__,
131     submodnames=USER_INTERFACE_MODULES,
132     class_selector=IsSubclass(UserInterface, blacklist=[UserInterface]))
133 """:class:`hooke.compat.odict.odict` of :class:`UserInterface`
134 instances keyed by `.name`.
135 """
136
137 def default_settings():
138     settings = [Setting(USER_INTERFACE_SETTING_SECTION,
139                         help='Select the user interface (only one).')]
140     for i,ui in enumerate(USER_INTERFACES.values()):
141         help = ui.__doc__.split('\n', 1)[0]
142         settings.append(Setting(USER_INTERFACE_SETTING_SECTION,
143                                 ui.name, str(i==0), help=help))
144         # i==0 to enable the first by default
145     for ui in USER_INTERFACES.values():
146         settings.extend(ui.default_settings())
147     return settings
148
149 def load_ui(config, name=None):
150     if name == None:
151         uis = [c for c,v in config.items(USER_INTERFACE_SETTING_SECTION) if v == 'True']
152         assert len(uis) == 1, 'Can only select one UI, not %d: %s' % (len(uis),uis)
153         name = uis[0]
154     ui = USER_INTERFACES[name]
155     ui.reload_config(config)
156     return ui