1f2bd4e2cf536fabb10c5e24d741441011f6e7b0
[hooke.git] / hooke / ui / __init__.py
1 """The `ui` module provides :class:`UserInterface` and various subclasses.
2 """
3
4 import ConfigParser as configparser
5
6 from .. import version
7 from ..compat.odict import odict
8 from ..config import Setting
9 from ..util.pluggable import IsSubclass
10
11
12 USER_INTERFACE_MODULES = [
13     'commandline',
14     #'gui',
15     ]
16 """List of user interface modules.  TODO: autodiscovery
17 """
18
19 USER_INTERFACE_SETTING_SECTION = 'user interfaces'
20 """Name of the config section which controls UI selection.
21 """
22
23
24 class QueueMessage (object):
25     def __str__(self):
26         return self.__class__.__name__
27
28 class CloseEngine (QueueMessage):
29     pass
30
31 class CommandMessage (QueueMessage):
32     """A message storing a command to run, `command` should be a
33     :class:`hooke.command.Command` instance, and `arguments` should be
34     a :class:`dict` with `argname` keys and `value` values to be
35     passed to the command.
36     """
37     def __init__(self, command, arguments):
38         self.command = command
39         self.arguments = arguments
40
41 class UserInterface (object):
42     """A user interface to drive the :class:`hooke.engine.CommandEngine`.
43     """
44     def __init__(self, name):
45         self.name = name
46         self.setting_section = '%s user interface' % (self.name)
47         self.config = {}
48
49     def default_settings(self):
50         """Return a list of :class:`hooke.config.Setting`\s for any
51         configurable UI settings.
52
53         The suggested section setting is::
54
55             Setting(section=self.setting_section, help=self.__doc__)
56         """
57         return []
58
59     def reload_config(self, config):
60         """Update the user interface for new config settings.
61
62         Should be called with the new `config` upon recipt of
63         `ReloadUserInterfaceConfig` from the `CommandEngine`.
64         """
65         try:
66             self.config = dict(config.items(self.setting_section))
67         except configparser.NoSectionError:
68             self.config = {}
69
70     def run(self, hooke, ui_to_command_queue, command_to_ui_queue):
71         return
72
73     # Assorted useful tidbits for subclasses
74
75     def _splash_text(self):
76         return ("""
77 Hooke version %s
78
79 COPYRIGHT
80 ----
81 """ % version()).strip()
82
83     def _playlist_status(self, playlist):
84         if len(playlist) > 0:
85             return '%s (%s/%s)' % (playlist.name, playlist._index + 1,
86                                    len(playlist))
87         return 'The playlist %s does not contain any valid force curve data.' \
88             % self.name
89
90 def construct_odict(this_modname, submodnames, class_selector):
91     """Search the submodules `submodnames` of a module `this_modname`
92     for class objects for which `class_selector(class)` returns
93     `True`.  These classes are instantiated and stored in the returned
94     :class:`hooke.compat.odict.odict` in the order in which they were
95     discovered.
96     """
97     instances = odict()
98     for submodname in submodnames:
99         count = len([s for s in submodnames if s == submodname])
100         assert count > 0, 'No %s entries: %s' % (submodname, submodnames)
101         assert count == 1, 'Multiple (%d) %s entries: %s' \
102             % (count, submodname, submodnames)
103         this_mod = __import__(this_modname, fromlist=[submodname])
104         submod = getattr(this_mod, submodname)
105         for objname in dir(submod):
106             obj = getattr(submod, objname)
107             if class_selector(obj):
108                 instance = obj()
109                 instances[instance.name] = instance
110     return instances
111
112 USER_INTERFACES = construct_odict(
113     this_modname=__name__,
114     submodnames=USER_INTERFACE_MODULES,
115     class_selector=IsSubclass(UserInterface, blacklist=[UserInterface]))
116 """:class:`hooke.compat.odict.odict` of :class:`UserInterface`
117 instances keyed by `.name`.
118 """
119
120 def default_settings():
121     settings = [Setting(USER_INTERFACE_SETTING_SECTION,
122                         help='Select the user interface (only one).')]
123     for i,ui in enumerate(USER_INTERFACES.values()):
124         help = ui.__doc__.split('\n', 1)[0]
125         settings.append(Setting(USER_INTERFACE_SETTING_SECTION,
126                                 ui.name, str(i==0), help=help))
127         # i==0 to enable the first by default
128     for ui in USER_INTERFACES.values():
129         settings.extend(ui.default_settings())
130     return settings
131
132 def load_ui(config, name=None):
133     if name == None:
134         uis = [c for c,v in config.items(USER_INTERFACE_SETTING_SECTION) if v == 'True']
135         assert len(uis) == 1, 'Can only select one UI, not %d: %s' % (len(uis),uis)
136         name = uis[0]
137     ui = USER_INTERFACES[name]
138     try:
139         ui.config = dict(config.items(ui.setting_section))
140     except configparser.NoSectionError:
141         pass
142     return ui