source: orange/Orange/OrangeCanvas/registry/qt.py @ 11341:4843f2e987d9

Revision 11341:4843f2e987d9, 11.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Exclude the 'more...' link from the default 'Whats this' text.

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        if desc.background:
252            brush = QBrush(QColor(desc.background))
253        elif category.background:
254            brush = QBrush(QColor(category.background))
255        else:
256            brush = None
257
258        if brush is not None:
259            item.setData(brush, self.BACKGROUND_ROLE)
260
261        tooltip = tooltip_helper(desc)
262        style = "ul { margin-top: 1px; margin-bottom: 1px; }"
263        tooltip = TOOLTIP_TEMPLATE.format(style=style, tooltip=tooltip)
264        item.setToolTip(tooltip)
265        item.setWhatsThis(whats_this_helper(desc))
266        item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
267        item.setData(QVariant(desc), self.WIDGET_DESC_ROLE)
268
269        # Create the action for the widget_item
270        action = self.create_action_for_item(item)
271        item.setData(QVariant(action), self.WIDGET_ACTION_ROLE)
272        return item
273
274
275TOOLTIP_TEMPLATE = """\
276<html>
277<head>
278<style type="text/css">
279{style}
280</style>
281</head>
282<body>
283{tooltip}
284</body>
285</html>
286"""
287
288
289def tooltip_helper(desc):
290    """Widget tooltip construction helper.
291
292    """
293    tooltip = []
294    tooltip.append("<b>{name}</b>".format(name=escape(desc.name)))
295
296    if desc.project_name:
297        tooltip[0] += " (from {0})".format(desc.project_name)
298
299    if desc.description:
300        tooltip.append("<b>Description</b><br/>{0}".format(
301                            escape(desc.description)))
302
303    inputs_fmt = "<li>{name} ({class_name})</li>"
304
305    if desc.inputs:
306        inputs = "".join(inputs_fmt.format(name=inp.name, class_name=inp.type)
307                         for inp in desc.inputs)
308    else:
309        inputs = "<ul>None</ul>"
310
311    tooltip.append("<b>Inputs</b><ul>{0}</ul>".format(inputs))
312
313    if desc.outputs:
314        outputs = "".join(inputs_fmt.format(name=out.name, class_name=out.type)
315                          for out in desc.outputs)
316    else:
317        outputs = "<ul>None</ul>"
318
319    tooltip.append("<b>Outputs</b><ul>{0}</ul>".format(outputs))
320
321    return "<hr/>".join(tooltip)
322
323
324def whats_this_helper(desc, include_more_link=False):
325    """
326    A `What's this` text construction helper. If `include_more_link` is
327    True then the text will include a `more...` link.
328
329    """
330    title = desc.name
331    help_url = desc.help
332
333    if not help_url:
334        help_url = "help://search?" + urlencode({"id": desc.id})
335
336    description = desc.description
337    long_description = desc.long_description
338
339    template = ["<h3>{0}</h3>".format(escape(title))]
340
341    if description:
342        template.append("<p>{0}</p>".format(escape(description)))
343
344    if long_description:
345        template.append("<p>{0}</p>".format(escape(long_description[:100])))
346
347    if help_url and include_more_link:
348        template.append("<a href='{0}'>more...</a>".format(escape(help_url)))
349
350    return "\n".join(template)
351
352
353def run_discovery(entry_points_iter, cached=False):
354    """
355    Run the default discovery and return an instance of
356    :class:`QtWidgetRegistry`.
357
358    """
359    reg_cache = {}
360    if cached:
361        reg_cache = cache.registry_cache()
362
363    discovery = QtWidgetDiscovery(cached_descriptions=reg_cache)
364    registry = QtWidgetRegistry()
365    discovery.found_category.connect(registry.register_category)
366    discovery.found_widget.connect(registry.register_widget)
367    discovery.run()
368    if cached:
369        cache.save_registry_cache(reg_cache)
370    return registry
Note: See TracBrowser for help on using the repository browser.