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