source: orange/Orange/OrangeCanvas/registry/qt.py @ 11098:b743937fe90a

Revision 11098:b743937fe90a, 9.7 KB checked in by Ales Erjavec <ales.erjavec@…>, 19 months ago (diff)

Added new widget/category description classes and new widget discovery.

This includes a Qt item model interface for the registry.

Line 
1"""
2Qt Model classes for widget registry.
3
4"""
5import bisect
6
7from xml.sax.saxutils import escape
8
9from PyQt4.QtGui import (
10    QStandardItemModel, QStandardItem, QColor, QBrush, QAction
11)
12
13from PyQt4.QtCore import QObject, Qt, QVariant
14from PyQt4.QtCore import pyqtSignal as Signal
15
16from .discovery import WidgetDiscovery
17from .description import WidgetDescription, CategoryDescription
18from .base import WidgetRegistry
19
20from ..resources import icon_loader
21
22from . import NAMED_COLORS
23
24
25class QtWidgetDiscovery(QObject, WidgetDiscovery):
26    """Qt interface class for widget discovery.
27    """
28    # Discovery has started
29    discovery_start = Signal()
30    # Discovery has finished
31    discovery_finished = Signal()
32    # Processing widget with name
33    discovery_process = Signal(str)
34    # Found a widget with description
35    found_widget = Signal(WidgetDescription)
36    # Found a category with description
37    found_category = Signal(CategoryDescription)
38
39    def __init__(self, parent=None, registry=None, cached_descriptions=None):
40        QObject.__init__(self, parent)
41        WidgetDiscovery.__init__(self, registry, cached_descriptions)
42
43    def run(self):
44        self.discovery_start.emit()
45        WidgetDiscovery.run(self)
46        self.discovery_finished.emit()
47
48    def handle_widget(self, description):
49        self.discovery_process.emit(description.name)
50        self.found_widget.emit(description)
51
52    def handle_category(self, description):
53        self.found_category.emit(description)
54
55
56class QtWidgetRegistry(QObject, WidgetRegistry):
57    """A QObject wrapper for `WidgetRegistry`
58
59    A QStandardItemModel instance containing the widgets in
60    a tree (of depth 2). The items in a model can be quaries using standard
61    roles (DisplayRole, BackgroundRole, DecorationRole ToolTipRole).
62    They also have QtWidgetRegistry.CATEGORY_DESC_ROLE,
63    QtWidgetRegistry.WIDGET_DESC_ROLE, which store Category/WidgetDescription
64    respectfully. Furthermore QtWidgetRegistry.WIDGET_ACTION_ROLE stores an
65    default QAction which can be used for widget creation action.
66
67    """
68
69    CATEGORY_DESC_ROLE = Qt.UserRole + 1
70    """Category Description Role"""
71
72    WIDGET_DESC_ROLE = Qt.UserRole + 2
73    """Widget Description Role"""
74
75    WIDGET_ACTION_ROLE = Qt.UserRole + 3
76    """Widget Action Role"""
77
78    category_added = Signal(str, CategoryDescription)
79    """signal: category_added(name: str, desc: CategoryDescription)
80    """
81
82    widget_added = Signal(str, str, WidgetDescription)
83    """signal widget_added(category_name: str, widget_name: str,
84                           desc: WidgetDescription)
85    """
86
87    reset = Signal()
88    """signal: reset()
89    """
90
91    def __init__(self, other_or_parent=None, parent=None):
92        if isinstance(other_or_parent, QObject) and parent is None:
93            parent, other_or_parent = other_or_parent, None
94        QObject.__init__(self, parent)
95        WidgetRegistry.__init__(self, other_or_parent)
96
97        # Should  the QStandardItemModel be subclassed?
98        self.__item_model = QStandardItemModel(self)
99
100        for i, desc in enumerate(self.categories()):
101            cat_item = self._cat_desc_to_std_item(desc)
102            self.__item_model.insertRow(i, cat_item)
103
104            for j, wdesc in enumerate(self.widgets(desc.name)):
105                widget_item = self._widget_desc_to_std_item(wdesc, desc)
106                cat_item.insertRow(j, widget_item)
107
108    def model(self):
109        """Return the widget descriptions in a Qt Item Model instance
110        (QStandardItemModel).
111
112        .. note:: The model should not be modified outside of the registry.
113
114        """
115        return self.__item_model
116
117    def item_for_widget(self, widget):
118        """Return the QStandardItem for the widget.
119        """
120        if isinstance(widget, basestring):
121            widget = self.widget(widget)
122        cat = self.category(widget.category)
123        cat_ind = self.categories().index(cat)
124        cat_item = self.model().item(cat_ind)
125        widget_ind = self.widgets(cat).index(widget)
126        return cat_item.child(widget_ind)
127
128    def action_for_widget(self, widget):
129        """Return the QAction instance for the widget (can
130        be a string or a WidgetDescription instance).
131
132        """
133        item = self.item_for_widget(widget)
134        return item.data(self.WIDGET_ACTION_ROLE).toPyObject()
135
136    def create_action_for_item(self, item):
137        """Create a QAction instance for the widget description item.
138        """
139        name = item.text()
140        tooltip = item.toolTip()
141        icon = item.icon()
142        if icon:
143            action = QAction(icon, name, self, toolTip=tooltip)
144        else:
145            action = QAction(name, self, toolTip=tooltip)
146
147        widget_desc = item.data(self.WIDGET_DESC_ROLE)
148        action.setData(widget_desc)
149        action.setProperty("item", QVariant(item))
150        return action
151
152    def _insert_category(self, desc):
153        """Override to update the item model and emit the signals.
154        """
155        priority = desc.priority
156        priorities = [c.priority for c, _ in self.registry]
157        insertion_i = bisect.bisect_right(priorities, priority)
158
159        WidgetRegistry._insert_category(self, desc)
160
161        cat_item = self._cat_desc_to_std_item(desc)
162        self.__item_model.insertRow(insertion_i, cat_item)
163
164        self.category_added.emit(desc.name, desc)
165
166    def _insert_widget(self, category, desc):
167        """Override to update the item model and emit the signals.
168        """
169        assert(isinstance(category, CategoryDescription))
170        categories = self.categories()
171        cat_i = categories.index(category)
172        _, widgets = self._categories_dict[category.name]
173        priorities = [w.priority for w in widgets]
174        insertion_i = bisect.bisect_right(priorities, desc.priority)
175
176        WidgetRegistry._insert_widget(self, category, desc)
177
178        cat_item = self.__item_model.item(cat_i)
179        widget_item = self._widget_desc_to_std_item(desc, category)
180
181        cat_item.insertRow(insertion_i, widget_item)
182
183        self.widget_added.emit(category.name, desc.name, desc)
184
185    def _cat_desc_to_std_item(self, desc):
186        """Create a QStandardItem for the category description.
187        """
188        item = QStandardItem()
189        item.setText(desc.name)
190
191        if desc.icon:
192            icon = desc.icon
193        else:
194            icon = "icons/default-category.svg"
195        icon = icon_loader.from_description(desc).get(icon)
196        item.setIcon(icon)
197
198        if desc.background:
199            background = desc.background
200        else:
201            background = "light-yellow"
202
203        background = NAMED_COLORS.get(background, background)
204
205        brush = QBrush(QColor(background))
206        item.setBackground(brush)
207
208        tooltip = desc.description if desc.description else desc.name
209        item.setToolTip(tooltip)
210        item.setFlags(Qt.ItemIsEnabled)
211        item.setData(QVariant(desc), self.CATEGORY_DESC_ROLE)
212        return item
213
214    def _widget_desc_to_std_item(self, desc, category):
215        """Create a QStandardItem for the widget description.
216        """
217        item = QStandardItem(desc.name)
218        item.setText(desc.name)
219
220        if desc.icon:
221            icon = desc.icon
222        else:
223            icon = "icons/default-widget.svg"
224
225        icon = icon_loader.from_description(desc).get(icon)
226        item.setIcon(icon)
227
228        # This should be inherited from the category.
229        if desc.background:
230            brush = QBrush(QColor(desc.background))
231        elif category.background:
232            brush = QBrush(QColor(category.background))
233        else:
234            brush = None
235
236        if brush is not None:
237            item.setBackground(brush)
238
239        tooltip = tooltip_helper(desc)
240        style = "ul { margin-top: 1px; margin-bottom: 1px; }"
241        tooltip = TOOLTIP_TEMPLATE.format(style=style, tooltip=tooltip)
242        item.setToolTip(tooltip)
243        item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
244        item.setData(QVariant(desc), self.WIDGET_DESC_ROLE)
245
246        # Create the action for the widget_item
247        action = self.create_action_for_item(item)
248        item.setData(QVariant(action), self.WIDGET_ACTION_ROLE)
249        return item
250
251
252TOOLTIP_TEMPLATE = """\
253<html>
254<head>
255<style type="text/css">
256{style}
257</style>
258</head>
259<body>
260{tooltip}
261</body>
262</html>
263"""
264
265
266def tooltip_helper(desc):
267    """Widget tooltip construction helper.
268
269    """
270    tooltip = []
271    tooltip.append("<b>{name}</b>".format(name=escape(desc.name)))
272
273    if desc.project_name:
274        tooltip[0] += " (from {0})".format(desc.project_name)
275
276    if desc.description:
277        tooltip.append("<b>Description</b><br/>{0}".format(
278                            escape(desc.description)))
279
280    inputs_fmt = "<li>{name} ({class_name})</li>"
281
282    if desc.inputs:
283        inputs = "".join(inputs_fmt.format(name=inp.name, class_name=inp.type)
284                         for inp in desc.inputs)
285    else:
286        inputs = "<ul>None</ul>"
287
288    tooltip.append("<b>Inputs</b><ul>{0}</ul>".format(inputs))
289
290    if desc.outputs:
291        outputs = "".join(inputs_fmt.format(name=out.name, class_name=out.type)
292                          for out in desc.outputs)
293    else:
294        outputs = "<ul>None</ul>"
295
296    tooltip.append("<b>Outputs</b><ul>{0}</ul>".format(outputs))
297
298    return "<hr/>".join(tooltip)
299
300
301def run_discovery(cached=False):
302    """Run the default discovery and return an instance
303    of :class:`QtWidgetRegistry`.
304
305    """
306    from . import cache
307    reg_cache = {}
308    if cached:
309        reg_cache = cache.registry_cache()
310
311    discovery = QtWidgetDiscovery(cached_descriptions=reg_cache)
312    registry = QtWidgetRegistry()
313    discovery.found_category.connect(registry.register_category)
314    discovery.found_widget.connect(registry.register_widget)
315    discovery.run()
316    if cached:
317        cache.save_registry_cache(reg_cache)
318    return registry
Note: See TracBrowser for help on using the repository browser.