source: orange/Orange/OrangeCanvas/registry/qt.py @ 11393:6dd5b2647d2d

Revision 11393:6dd5b2647d2d, 11.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Fixed named color mappings for Qt widget item model.

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