source: orange/Orange/OrangeCanvas/registry/qt.py @ 11525:a6619bfdc8e4

Revision 11525:a6619bfdc8e4, 11.2 KB checked in by markotoplak, 11 months ago (diff)

Made menu tooltips "lighter" (like those on canvas).

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, DEFAULT_COLOR
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 = DEFAULT_COLOR
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        else:
257            background = DEFAULT_COLOR
258
259        if background is not None:
260            background = NAMED_COLORS.get(background, background)
261            brush = QBrush(QColor(background))
262            item.setData(brush, self.BACKGROUND_ROLE)
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)
268        item.setWhatsThis(whats_this_helper(desc))
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 and desc.project_name != "Orange":
300        tooltip[0] += " (from {0})".format(desc.project_name)
301
302    if desc.description:
303        tooltip.append("{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        tooltip.append("Inputs:<ul>{0}</ul>".format(inputs))
312    else:
313        tooltip.append("No inputs")
314
315    if desc.outputs:
316        outputs = "".join(inputs_fmt.format(name=out.name, class_name=out.type)
317                          for out in desc.outputs)
318        tooltip.append("Outputs:<ul>{0}</ul>".format(outputs))
319    else:
320        tooltip.append("No 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.