Finished off hooke.hooke and fleshed out hooke.ui.
[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 ..compat.odict import odict
7 from ..config import Setting
8 from ..plugin import IsSubclass
9
10
11 USER_INTERFACE_MODULES = [
12     'commandline',
13     #'gui',
14     ]
15 """List of user interface modules.  TODO: autodiscovery
16 """
17
18 class QueueMessage (object):
19     def __str__(self):
20         return self.__class__.__name__
21
22 class CloseEngine (QueueMessage):
23     pass
24
25 class CommandMessage (QueueMessage):
26     """A message storing a command to run, `command` should be a
27     :class:`hooke.command.Command` instance, and `arguments` should be
28     a :class:`dict` with `argname` keys and `value` values to be
29     passed to the command.
30     """
31     def __init__(self, command, arguments):
32         self.command = command
33         self.arguments = arguments
34
35 class UserInterface (object):
36     """A user interface to drive the :class:`hooke.engine.CommandEngine`.
37     """
38     def __init__(self, name):
39         self.name = name
40         self.setting_section = '%s ui' % self.name
41         self.config = {}
42     
43     def default_settings(self):
44         """Return a list of :class:`hooke.config.Setting`\s for any
45         configurable UI settings.
46
47         The suggested section setting is::
48
49             Setting(section=self.setting_section, help=self.__doc__)
50         """
51         return []
52
53     def run(self, hooke, ui_to_command_queue, command_to_ui_queue):
54         return
55
56     def playlist_status(self, playlist):
57         if playlist.has_curves():
58             return '%s (%s/%s)' % (playlist.name, playlist._index + 1,
59                                    len(playlist))
60         return 'The playlist %s does not contain any valid force curve data.' \
61             % self.name
62
63 def construct_odict(this_modname, submodnames, class_selector):
64     """Search the submodules `submodnames` of a module `this_modname`
65     for class objects for which `class_selector(class)` returns
66     `True`.  These classes are instantiated and stored in the returned
67     :class:`hooke.compat.odict.odict` in the order in which they were
68     discovered.
69     """
70     instances = odict()
71     for submodname in submodnames:
72         count = len([s for s in submodnames if s == submodname])
73         assert count > 0, 'No %s entries: %s' % (submodname, submodnames)
74         assert count == 1, 'Multiple (%d) %s entries: %s' \
75             % (count, submodname, submodnames)
76         this_mod = __import__(this_modname, fromlist=[submodname])
77         submod = getattr(this_mod, submodname)
78         for objname in dir(submod):
79             obj = getattr(submod, objname)
80             if class_selector(obj):
81                 instance = obj()
82                 instances[instance.name] = instance
83     return instances
84
85 USER_INTERFACES = construct_odict(
86     this_modname=__name__,
87     submodnames=USER_INTERFACE_MODULES,
88     class_selector=IsSubclass(UserInterface, blacklist=[UserInterface]))
89 """:class:`hooke.compat.odict.odict` of :class:`UserInterface`
90 instances keyed by `.name`.
91 """
92
93 def default_settings():
94     settings = [Setting('UIs', help='Select the user interface (only one).')]
95     for i,ui in enumerate(USER_INTERFACES.values()):
96         help = ui.__doc__.split('\n', 1)[0]
97         settings.append(Setting('UIs', ui.name, str(i==0), help=help))
98         # i==0 to enable the first by default
99     for ui in USER_INTERFACES.values():
100         settings.extend(ui.default_settings())
101     return settings
102
103 def load_ui(config):
104     uis = [c for c,v in config.items('UIs') if v == 'True']
105     assert len(uis) == 1, 'Can only select one UI, not %d: %s' % (len(uis),uis)
106     ui_name = uis[0]
107     ui = USER_INTERFACES[ui_name]
108     try:
109         ui.config = dict(config.items(ui.setting_section))
110     except configparser.NoSectionError:
111         pass
112     return ui