source: orange/Orange/OrangeCanvas/registry/qt.py @ 11440:cb2508213612

Revision 11440:cb2508213612, 11.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

Fixed default colors for nodes in the canvas.

RevLine 
[11098]1"""
2Qt Model classes for widget registry.
3
4"""
5import bisect
6
7from xml.sax.saxutils import escape
[11252]8from urllib import urlencode
[11098]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
[11440]23from . import cache, NAMED_COLORS, DEFAULT_COLOR
[11098]24
25
26class QtWidgetDiscovery(QObject, WidgetDiscovery):
[11286]27    """
28    Qt interface class for widget discovery.
[11098]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
[11285]45    def run(self, entry_points_iter):
[11098]46        self.discovery_start.emit()
[11285]47        WidgetDiscovery.run(self, entry_points_iter)
[11098]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):
[11286]59    """
60    A QObject wrapper for `WidgetRegistry`
[11098]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
[11133]81    BACKGROUND_ROLE = Qt.UserRole + 4
82    """Background color for widget/category in the canvas
83    (different from Qt.BackgroundRole)
84    """
85
[11098]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):
[11286]117        """
118        Return the widget descriptions in a Qt Item Model instance
[11098]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):
[11286]138        """
139        Return the QAction instance for the widget (can be a string or
140        a WidgetDescription instance).
[11098]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):
[11286]147        """
148        Create a QAction instance for the widget description item.
[11098]149        """
150        name = item.text()
151        tooltip = item.toolTip()
[11243]152        whatsThis = item.whatsThis()
[11098]153        icon = item.icon()
154        if icon:
[11243]155            action = QAction(icon, name, self, toolTip=tooltip,
156                             whatsThis=whatsThis,
157                             statusTip=name)
[11098]158        else:
[11243]159            action = QAction(name, self, toolTip=tooltip,
160                             whatsThis=whatsThis,
161                             statusTip=name)
[11098]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):
[11286]169        """
170        Override to update the item model and emit the signals.
[11098]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):
[11286]184        """
185        Override to update the item model and emit the signals.
[11098]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):
[11286]204        """
205        Create a QStandardItem for the category description.
[11098]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"
[11252]214
[11098]215        icon = icon_loader.from_description(desc).get(icon)
216        item.setIcon(icon)
217
218        if desc.background:
219            background = desc.background
220        else:
[11440]221            background = DEFAULT_COLOR
[11098]222
223        background = NAMED_COLORS.get(background, background)
224
225        brush = QBrush(QColor(background))
[11133]226        item.setData(brush, self.BACKGROUND_ROLE)
[11098]227
228        tooltip = desc.description if desc.description else desc.name
[11252]229
[11098]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):
[11286]236        """
237        Create a QStandardItem for the widget description.
[11098]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.
[11393]251        background = None
[11098]252        if desc.background:
[11393]253            background = desc.background
[11098]254        elif category.background:
[11393]255            background = category.background
[11440]256        else:
257            background = DEFAULT_COLOR
[11098]258
[11393]259        if background is not None:
260            background = NAMED_COLORS.get(background, background)
261            brush = QBrush(QColor(background))
[11133]262            item.setData(brush, self.BACKGROUND_ROLE)
[11098]263
264        tooltip = tooltip_helper(desc)
265        style = "ul { margin-top: 1px; margin-bottom: 1px; }"
266        tooltip = TOOLTIP_TEMPLATE.format(style=style, tooltip=tooltip)
267        item.setToolTip(tooltip)
[11243]268        item.setWhatsThis(whats_this_helper(desc))
[11098]269        item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
270        item.setData(QVariant(desc), self.WIDGET_DESC_ROLE)
271
272        # Create the action for the widget_item
273        action = self.create_action_for_item(item)
274        item.setData(QVariant(action), self.WIDGET_ACTION_ROLE)
275        return item
276
277
278TOOLTIP_TEMPLATE = """\
279<html>
280<head>
281<style type="text/css">
282{style}
283</style>
284</head>
285<body>
286{tooltip}
287</body>
288</html>
289"""
290
291
292def tooltip_helper(desc):
293    """Widget tooltip construction helper.
294
295    """
296    tooltip = []
297    tooltip.append("<b>{name}</b>".format(name=escape(desc.name)))
298
299    if desc.project_name:
300        tooltip[0] += " (from {0})".format(desc.project_name)
301
302    if desc.description:
303        tooltip.append("<b>Description</b><br/>{0}".format(
304                            escape(desc.description)))
305
306    inputs_fmt = "<li>{name} ({class_name})</li>"
307
308    if desc.inputs:
309        inputs = "".join(inputs_fmt.format(name=inp.name, class_name=inp.type)
310                         for inp in desc.inputs)
311    else:
312        inputs = "<ul>None</ul>"
313
314    tooltip.append("<b>Inputs</b><ul>{0}</ul>".format(inputs))
315
316    if desc.outputs:
317        outputs = "".join(inputs_fmt.format(name=out.name, class_name=out.type)
318                          for out in desc.outputs)
319    else:
320        outputs = "<ul>None</ul>"
321
322    tooltip.append("<b>Outputs</b><ul>{0}</ul>".format(outputs))
323
324    return "<hr/>".join(tooltip)
325
326
[11341]327def whats_this_helper(desc, include_more_link=False):
328    """
329    A `What's this` text construction helper. If `include_more_link` is
330    True then the text will include a `more...` link.
331
[11243]332    """
333    title = desc.name
334    help_url = desc.help
[11252]335
336    if not help_url:
337        help_url = "help://search?" + urlencode({"id": desc.id})
338
[11243]339    description = desc.description
[11252]340    long_description = desc.long_description
[11243]341
[11252]342    template = ["<h3>{0}</h3>".format(escape(title))]
343
344    if description:
345        template.append("<p>{0}</p>".format(escape(description)))
346
347    if long_description:
348        template.append("<p>{0}</p>".format(escape(long_description[:100])))
349
[11341]350    if help_url and include_more_link:
[11252]351        template.append("<a href='{0}'>more...</a>".format(escape(help_url)))
352
353    return "\n".join(template)
[11243]354
355
[11285]356def run_discovery(entry_points_iter, cached=False):
357    """
358    Run the default discovery and return an instance of
359    :class:`QtWidgetRegistry`.
[11098]360
361    """
362    reg_cache = {}
363    if cached:
364        reg_cache = cache.registry_cache()
365
366    discovery = QtWidgetDiscovery(cached_descriptions=reg_cache)
367    registry = QtWidgetRegistry()
368    discovery.found_category.connect(registry.register_category)
369    discovery.found_widget.connect(registry.register_widget)
370    discovery.run()
371    if cached:
372        cache.save_registry_cache(reg_cache)
373    return registry
Note: See TracBrowser for help on using the repository browser.