Allow hooke.ui to load with missing hooke.license submodule.
[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
24 from .. import version
25 from ..config import Setting
26 from ..util.pluggable import IsSubclass, construct_odict
27
28 try:
29     from ..license import short_license
30 except ImportError, e:
31     import logging
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 QueueMessage (object):
51     def __str__(self):
52         return self.__class__.__name__
53
54 class CloseEngine (QueueMessage):
55     pass
56
57 class CommandMessage (QueueMessage):
58     """A message storing a command to run, `command` should be a
59     :class:`hooke.command.Command` instance, and `arguments` should be
60     a :class:`dict` with `argname` keys and `value` values to be
61     passed to the command.
62     """
63     def __init__(self, command, arguments=None):
64         self.command = command
65         if arguments == None:
66             arguments = {}
67         self.arguments = arguments
68
69 class UserInterface (object):
70     """A user interface to drive the :class:`hooke.engine.CommandEngine`.
71     """
72     def __init__(self, name):
73         self.name = name
74         self.setting_section = '%s user interface' % (self.name)
75         self.config = {}
76
77     def default_settings(self):
78         """Return a list of :class:`hooke.config.Setting`\s for any
79         configurable UI settings.
80
81         The suggested section setting is::
82
83             Setting(section=self.setting_section, help=self.__doc__)
84         """
85         return []
86
87     def reload_config(self, config):
88         """Update the user interface for new config settings.
89
90         Should be called with the new `config` upon recipt of
91         `ReloadUserInterfaceConfig` from the `CommandEngine` or when
92         loading the initial configuration.
93         """
94         try:
95             self.config = dict(config.items(self.setting_section))
96         except configparser.NoSectionError:
97             self.config = {}
98
99     def run(self, commands, ui_to_command_queue, command_to_ui_queue):
100         return
101
102     # Assorted useful tidbits for subclasses
103
104     def _splash_text(self, extra_info, **kwargs):
105         return ("""
106 Hooke version %s
107
108 %s
109 ----
110 """ % (version(), short_license(extra_info, **kwargs))).strip()
111
112     def _playlist_status(self, playlist):
113         if len(playlist) > 0:
114             return '%s (%s/%s)' % (playlist.name, playlist.index() + 1,
115                                    len(playlist))
116         return 'The playlist %s does not contain any valid force curve data.' \
117             % self.name
118
119
120 USER_INTERFACES = construct_odict(
121     this_modname=__name__,
122     submodnames=USER_INTERFACE_MODULES,
123     class_selector=IsSubclass(UserInterface, blacklist=[UserInterface]))
124 """:class:`hooke.compat.odict.odict` of :class:`UserInterface`
125 instances keyed by `.name`.
126 """
127
128 def default_settings():
129     settings = [Setting(USER_INTERFACE_SETTING_SECTION,
130                         help='Select the user interface (only one).')]
131     for i,ui in enumerate(USER_INTERFACES.values()):
132         help = ui.__doc__.split('\n', 1)[0]
133         settings.append(Setting(USER_INTERFACE_SETTING_SECTION,
134                                 ui.name, str(i==0), help=help))
135         # i==0 to enable the first by default
136     for ui in USER_INTERFACES.values():
137         settings.extend(ui.default_settings())
138     return settings
139
140 def load_ui(config, name=None):
141     if name == None:
142         uis = [c for c,v in config.items(USER_INTERFACE_SETTING_SECTION) if v == 'True']
143         assert len(uis) == 1, 'Can only select one UI, not %d: %s' % (len(uis),uis)
144         name = uis[0]
145     ui = USER_INTERFACES[name]
146     ui.reload_config(config)
147     return ui