source: orange/Orange/OrangeCanvas/registry/qt.py @ 11243:e788addef69c

Revision 11243:e788addef69c, 10.6 KB checked in by Ales Erjavec <ales.erjavec@…>, 16 months ago (diff)

Implemented a different way to handle toolbox/canvas hover/selection help text.

Now using a QStatusTip subclass to notify the top level window.

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    BACKGROUND_ROLE = Qt.UserRole + 4
79    """Background color for widget/category in the canvas
80    (different from Qt.BackgroundRole)
81    """
82
83    category_added = Signal(str, CategoryDescription)
84    """signal: category_added(name: str, desc: CategoryDescription)
85    """
86
87    widget_added = Signal(str, str, WidgetDescription)
88    """signal widget_added(category_name: str, widget_name: str,
89                           desc: WidgetDescription)
90    """
91
92    reset = Signal()
93    """signal: reset()
94    """
95
96    def __init__(self, other_or_parent=None, parent=None):
97        if isinstance(other_or_parent, QObject) and parent is None:
98            parent, other_or_parent = other_or_parent, None
99        QObject.__init__(self, parent)
100        WidgetRegistry.__init__(self, other_or_parent)
101
102        # Should  the QStandardItemModel be subclassed?
103        self.__item_model = QStandardItemModel(self)
104
105        for i, desc in enumerate(self.categories()):
106            cat_item = self._cat_desc_to_std_item(desc)
107            self.__item_model.insertRow(i, cat_item)
108
109            for j, wdesc in enumerate(self.widgets(desc.name)):
110                widget_item = self._widget_desc_to_std_item(wdesc, desc)
111                cat_item.insertRow(j, widget_item)
112
113    def model(self):
114        """Return the widget descriptions in a Qt Item Model instance
115        (QStandardItemModel).
116
117        .. note:: The model should not be modified outside of the registry.
118
119        """
120        return self.__item_model
121
122    def item_for_widget(self, widget):
123        """Return the QStandardItem for the widget.
124        """
125        if isinstance(widget, basestring):
126            widget = self.widget(widget)
127        cat = self.category(widget.category)
128        cat_ind = self.categories().index(cat)
129        cat_item = self.model().item(cat_ind)
130        widget_ind = self.widgets(cat).index(widget)
131        return cat_item.child(widget_ind)
132
133    def action_for_widget(self, widget):
134        """Return the QAction instance for the widget (can
135        be a string or a WidgetDescription instance).
136
137        """
138        item = self.item_for_widget(widget)
139        return item.data(self.WIDGET_ACTION_ROLE).toPyObject()
140
141    def create_action_for_item(self, item):
142        """Create a QAction instance for the widget description item.
143        """
144        name = item.text()
145        tooltip = item.toolTip()
146        whatsThis = item.whatsThis()
147        icon = item.icon()
148        if icon:
149            action = QAction(icon, name, self, toolTip=tooltip,
150                             whatsThis=whatsThis,
151                             statusTip=name)
152        else:
153            action = QAction(name, self, toolTip=tooltip,
154                             whatsThis=whatsThis,
155                             statusTip=name)
156
157        widget_desc = item.data(self.WIDGET_DESC_ROLE)
158        action.setData(widget_desc)
159        action.setProperty("item", QVariant(item))
160        return action
161
162    def _insert_category(self, desc):
163        """Override to update the item model and emit the signals.
164        """
165        priority = desc.priority
166        priorities = [c.priority for c, _ in self.registry]
167        insertion_i = bisect.bisect_right(priorities, priority)
168
169        WidgetRegistry._insert_category(self, desc)
170
171        cat_item = self._cat_desc_to_std_item(desc)
172        self.__item_model.insertRow(insertion_i, cat_item)
173
174        self.category_added.emit(desc.name, desc)
175
176    def _insert_widget(self, category, desc):
177        """Override to update the item model and emit the signals.
178        """
179        assert(isinstance(category, CategoryDescription))
180        categories = self.categories()
181        cat_i = categories.index(category)
182        _, widgets = self._categories_dict[category.name]
183        priorities = [w.priority for w in widgets]
184        insertion_i = bisect.bisect_right(priorities, desc.priority)
185
186        WidgetRegistry._insert_widget(self, category, desc)
187
188        cat_item = self.__item_model.item(cat_i)
189        widget_item = self._widget_desc_to_std_item(desc, category)
190
191        cat_item.insertRow(insertion_i, widget_item)
192
193        self.widget_added.emit(category.name, desc.name, desc)
194
195    def _cat_desc_to_std_item(self, desc):
196        """Create a QStandardItem for the category description.
197        """
198        item = QStandardItem()
199        item.setText(desc.name)
200
201        if desc.icon:
202            icon = desc.icon
203        else:
204            icon = "icons/default-category.svg"
205        icon = icon_loader.from_description(desc).get(icon)
206        item.setIcon(icon)
207
208        if desc.background:
209            background = desc.background
210        else:
211            background = "light-yellow"
212
213        background = NAMED_COLORS.get(background, background)
214
215        brush = QBrush(QColor(background))
216        item.setData(brush, self.BACKGROUND_ROLE)
217
218        tooltip = desc.description if desc.description else desc.name
219        item.setToolTip(tooltip)
220        item.setFlags(Qt.ItemIsEnabled)
221        item.setData(QVariant(desc), self.CATEGORY_DESC_ROLE)
222        return item
223
224    def _widget_desc_to_std_item(self, desc, category):
225        """Create a QStandardItem for the widget description.
226        """
227        item = QStandardItem(desc.name)
228        item.setText(desc.name)
229
230        if desc.icon:
231            icon = desc.icon
232        else:
233            icon = "icons/default-widget.svg"
234
235        icon = icon_loader.from_description(desc).get(icon)
236        item.setIcon(icon)
237
238        # This should be inherited from the category.
239        if desc.background:
240            brush = QBrush(QColor(desc.background))
241        elif category.background:
242            brush = QBrush(QColor(category.background))
243        else:
244            brush = None
245
246        if brush is not None:
247            item.setData(brush, self.BACKGROUND_ROLE)
248
249        tooltip = tooltip_helper(desc)
250        style = "ul { margin-top: 1px; margin-bottom: 1px; }"
251        tooltip = TOOLTIP_TEMPLATE.format(style=style, tooltip=tooltip)
252        item.setToolTip(tooltip)
253        item.setWhatsThis(whats_this_helper(desc))
254        item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
255        item.setData(QVariant(desc), self.WIDGET_DESC_ROLE)
256
257        # Create the action for the widget_item
258        action = self.create_action_for_item(item)
259        item.setData(QVariant(action), self.WIDGET_ACTION_ROLE)
260        return item
261
262
263TOOLTIP_TEMPLATE = """\
264<html>
265<head>
266<style type="text/css">
267{style}
268</style>
269</head>
270<body>
271{tooltip}
272</body>
273</html>
274"""
275
276
277def tooltip_helper(desc):
278    """Widget tooltip construction helper.
279
280    """
281    tooltip = []
282    tooltip.append("<b>{name}</b>".format(name=escape(desc.name)))
283
284    if desc.project_name:
285        tooltip[0] += " (from {0})".format(desc.project_name)
286
287    if desc.description:
288        tooltip.append("<b>Description</b><br/>{0}".format(
289                            escape(desc.description)))
290
291    inputs_fmt = "<li>{name} ({class_name})</li>"
292
293    if desc.inputs:
294        inputs = "".join(inputs_fmt.format(name=inp.name, class_name=inp.type)
295                         for inp in desc.inputs)
296    else:
297        inputs = "<ul>None</ul>"
298
299    tooltip.append("<b>Inputs</b><ul>{0}</ul>".format(inputs))
300
301    if desc.outputs:
302        outputs = "".join(inputs_fmt.format(name=out.name, class_name=out.type)
303                          for out in desc.outputs)
304    else:
305        outputs = "<ul>None</ul>"
306
307    tooltip.append("<b>Outputs</b><ul>{0}</ul>".format(outputs))
308
309    return "<hr/>".join(tooltip)
310
311
312def whats_this_helper(desc):
313    """What's this construction helper.
314    """
315    title = desc.name
316    help_url = desc.help
317    description = desc.description
318
319    template = "<h3>{title}</h3>" + \
320               "<p>{description}</p>" + \
321               ("<a href='{url}'>more...</a>" if help_url else "")
322    help_text = template.format(title=title, description=description,
323                                url=help_url)
324    return help_text
325
326
327def run_discovery(cached=False):
328    """Run the default discovery and return an instance
329    of :class:`QtWidgetRegistry`.
330
331    """
332    from . import cache
333    reg_cache = {}
334    if cached:
335        reg_cache = cache.registry_cache()
336
337    discovery = QtWidgetDiscovery(cached_descriptions=reg_cache)
338    registry = QtWidgetRegistry()
339    discovery.found_category.connect(registry.register_category)
340    discovery.found_widget.connect(registry.register_widget)
341    discovery.run()
342    if cached:
343        cache.save_registry_cache(reg_cache)
344    return registry
Note: See TracBrowser for help on using the repository browser.