Push GUI config setting changes to the engine process' Hooke.
[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         log = logging.getLogger('hooke')
88         log.debug('executing %s' % command_message)
89         ui_to_command_queue.put(command_message)
90
91     def _set_config(self, option, value, ui_to_command_queue, response_handler,
92                      section=None):
93         if section == None:
94             section = self.setting_section
95         if section in [self.setting_section, 'conditions']:
96             if self.config[option] == value:
97                 return  # No change, so no need to push the new value.
98             self.config[option] = value
99         cm = CommandMessage(
100             command='set config',
101             arguments={'section': section, 'option': option, 'value': value})
102         self._submit_command(command_message=cm,
103                              ui_to_command_queue=ui_to_command_queue)
104         response_handler(command_message=cm)
105
106     def _splash_text(self, extra_info, **kwargs):
107         return ("""
108 Hooke version %s
109
110 %s
111 ----
112 """ % (version(), short_license(extra_info, **kwargs))).strip()
113
114     def _playlist_status(self, playlist):
115         if len(playlist) > 0:
116             return '%s (%s/%s)' % (playlist.name, playlist.index() + 1,
117                                    len(playlist))
118         return 'The playlist %s does not contain any valid force curve data.' \
119             % self.name
120
121
122 USER_INTERFACES = construct_odict(
123     this_modname=__name__,
124     submodnames=USER_INTERFACE_MODULES,
125     class_selector=IsSubclass(UserInterface, blacklist=[UserInterface]))
126 """:class:`hooke.compat.odict.odict` of :class:`UserInterface`
127 instances keyed by `.name`.
128 """
129
130 def default_settings():
131     settings = [Setting(USER_INTERFACE_SETTING_SECTION,
132                         help='Select the user interface (only one).')]
133     for i,ui in enumerate(USER_INTERFACES.values()):
134         help = ui.__doc__.split('\n', 1)[0]
135         settings.append(Setting(USER_INTERFACE_SETTING_SECTION,
136                                 ui.name, str(i==0), help=help))
137         # i==0 to enable the first by default
138     for ui in USER_INTERFACES.values():
139         settings.extend(ui.default_settings())
140     return settings
141
142 def load_ui(config, name=None):
143     if name == None:
144         uis = [c for c,v in config.items(USER_INTERFACE_SETTING_SECTION) if v == 'True']
145         assert len(uis) == 1, 'Can only select one UI, not %d: %s' % (len(uis),uis)
146         name = uis[0]
147     ui = USER_INTERFACES[name]
148     ui.reload_config(config)
149     return ui