source: orange/Orange/OrangeCanvas/registry/qt.py @ 11285:c0fc66213bfd

Revision 11285:c0fc66213bfd, 10.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 15 months ago (diff)

Changed discovery to run from arbitrary entry points iterator/group.

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