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