source: orange/Orange/OrangeCanvas/preview/previewbrowser.py @ 11623:26ecc3a85f9c

Revision 11623:26ecc3a85f9c, 10.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 10 months ago (diff)

Refactored utility functions.

Line 
1"""
2Preview Browser Widget.
3
4"""
5
6from xml.sax.saxutils import escape
7
8from PyQt4.QtGui import (
9    QWidget, QLabel, QListView, QAction, QVBoxLayout, QHBoxLayout, QSizePolicy,
10    QStyleOption, QStylePainter
11)
12
13from PyQt4.QtSvg import QSvgWidget
14
15from PyQt4.QtCore import (
16    Qt, QSize, QByteArray, QModelIndex, QEvent
17)
18
19from PyQt4.QtCore import pyqtSignal as Signal
20
21from ..utils import check_type
22from ..gui.dropshadow import DropShadowFrame
23from . import previewmodel
24
25
26NO_PREVIEW_SVG = """
27
28"""
29
30
31# Default description template
32DESCRIPTION_TEMPLATE = u"""
33<h3 class=item-heading>{name}</h3>
34<p class=item-description>
35{description}
36</p>
37
38"""
39
40PREVIEW_SIZE = (440, 295)
41
42
43class LinearIconView(QListView):
44    def __init__(self, *args, **kwargs):
45        QListView.__init__(self, *args, **kwargs)
46
47        self.setViewMode(QListView.IconMode)
48        self.setWrapping(False)
49        self.setWordWrap(True)
50
51        self.setSelectionMode(QListView.SingleSelection)
52        self.setEditTriggers(QListView.NoEditTriggers)
53        self.setMovement(QListView.Static)
54        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
55        self.setSizePolicy(QSizePolicy.Expanding,
56                           QSizePolicy.Fixed)
57
58        self.setIconSize(QSize(120, 80))
59
60    def sizeHint(self):
61        if not self.model().rowCount():
62            return QSize(200, 140)
63        else:
64            scrollHint = self.horizontalScrollBar().sizeHint()
65            height = self.sizeHintForRow(0) + scrollHint.height()
66            _, top, _, bottom = self.getContentsMargins()
67            return QSize(200, height + top + bottom + self.verticalOffset())
68
69
70class TextLabel(QWidget):
71    """A plain text label widget with support for elided text.
72    """
73    def __init__(self, *args, **kwargs):
74        QWidget.__init__(self, *args, **kwargs)
75
76        self.setSizePolicy(QSizePolicy.Expanding,
77                           QSizePolicy.Preferred)
78
79        self.__text = ""
80        self.__textElideMode = Qt.ElideMiddle
81        self.__sizeHint = None
82        self.__alignment = Qt.AlignLeft | Qt.AlignVCenter
83
84    def setText(self, text):
85        """Set the `text` string to display.
86        """
87        check_type(text, basestring)
88        if self.__text != text:
89            self.__text = unicode(text)
90            self.__update()
91
92    def text(self):
93        """Return the text
94        """
95        return self.__text
96
97    def setTextElideMode(self, mode):
98        """Set elide mode (`Qt.TextElideMode`)
99        """
100        if self.__textElideMode != mode:
101            self.__textElideMode = mode
102            self.__update()
103
104    def elideMode(self):
105        return self.__elideMode
106
107    def setAlignment(self, align):
108        """Set text alignment (`Qt.Alignment`).
109        """
110        if self.__alignment != align:
111            self.__alignment = align
112            self.__update()
113
114    def sizeHint(self):
115        if self.__sizeHint is None:
116            option = QStyleOption()
117            option.initFrom(self)
118            metrics = option.fontMetrics
119
120            self.__sizeHint = QSize(200, metrics.height())
121
122        return self.__sizeHint
123
124    def paintEvent(self, event):
125        painter = QStylePainter(self)
126        option = QStyleOption()
127        option.initFrom(self)
128
129        rect = option.rect
130        metrics = option.fontMetrics
131        text = metrics.elidedText(self.__text, self.__textElideMode,
132                                  rect.width())
133        painter.drawItemText(rect, self.__alignment,
134                             option.palette, self.isEnabled(), text,
135                             self.foregroundRole())
136        painter.end()
137
138    def changeEvent(self, event):
139        if event.type() == QEvent.FontChange:
140            self.__update()
141
142        return QWidget.changeEvent(self, event)
143
144    def __update(self):
145        self.__sizeHint = None
146        self.updateGeometry()
147        self.update()
148
149
150class PreviewBrowser(QWidget):
151    """A Preview Browser for recent/premade scheme selection.
152    """
153    # Emitted when the current previewed item changes
154    currentIndexChanged = Signal(int)
155
156    # Emitted when an item is double clicked in the preview list.
157    activated = Signal(int)
158
159    def __init__(self, *args):
160        QWidget.__init__(self, *args)
161        self.__model = None
162        self.__currentIndex = -1
163        self.__template = DESCRIPTION_TEMPLATE
164        self.__setupUi()
165
166    def __setupUi(self):
167        vlayout = QVBoxLayout()
168        vlayout.setContentsMargins(0, 0, 0, 0)
169        top_layout = QHBoxLayout()
170        top_layout.setContentsMargins(12, 12, 12, 12)
171
172        # Top row with full text description and a large preview
173        # image.
174        self.__label = QLabel(self, objectName="description-label",
175                              wordWrap=True,
176                              alignment=Qt.AlignTop | Qt.AlignLeft)
177
178        self.__label.setWordWrap(True)
179        self.__label.setFixedSize(220, PREVIEW_SIZE[1])
180
181        self.__image = QSvgWidget(self, objectName="preview-image")
182        self.__image.setFixedSize(*PREVIEW_SIZE)
183
184        self.__imageFrame = DropShadowFrame(self)
185        self.__imageFrame.setWidget(self.__image)
186
187        # Path text below the description and image
188        path_layout = QHBoxLayout()
189        path_layout.setContentsMargins(12, 0, 12, 0)
190        path_label = QLabel("<b>{0!s}</b>".format(self.tr("Path:")), self,
191                            objectName="path-label")
192
193        self.__path = TextLabel(self, objectName="path-text")
194
195        path_layout.addWidget(path_label)
196        path_layout.addWidget(self.__path)
197
198        self.__selectAction = \
199            QAction(self.tr("Select"), self,
200                    objectName="select-action",
201                    )
202
203        top_layout.addWidget(self.__label, 1,
204                             alignment=Qt.AlignTop | Qt.AlignLeft)
205        top_layout.addWidget(self.__image, 1,
206                             alignment=Qt.AlignTop | Qt.AlignRight)
207
208        vlayout.addLayout(top_layout)
209        vlayout.addLayout(path_layout)
210
211        # An list view with small preview icons.
212        self.__previewList = LinearIconView(objectName="preview-list-view")
213        self.__previewList.doubleClicked.connect(self.__onDoubleClicked)
214
215        vlayout.addWidget(self.__previewList)
216        self.setLayout(vlayout)
217
218    def setModel(self, model):
219        """Set the item model for preview.
220        """
221        if self.__model != model:
222            if self.__model:
223                s_model = self.__previewList.selectionModel()
224                s_model.selectionChanged.disconnect(self.__onSelectionChanged)
225                self.__model.dataChanged.disconnect(self.__onDataChanged)
226
227            self.__model = model
228            self.__previewList.setModel(model)
229
230            if model:
231                s_model = self.__previewList.selectionModel()
232                s_model.selectionChanged.connect(self.__onSelectionChanged)
233                self.__model.dataChanged.connect(self.__onDataChanged)
234
235            if model and model.rowCount():
236                self.setCurrentIndex(0)
237
238    def model(self):
239        """Return the item model.
240        """
241        return self.__model
242
243    def setPreviewDelegate(self, delegate):
244        """Set the delegate to render the preview images.
245        """
246        raise NotImplementedError
247
248    def setDescriptionTemplate(self, template):
249        self.__template = template
250        self.__update()
251
252    def setCurrentIndex(self, index):
253        """Set the selected preview item index.
254        """
255        if self.__model is not None and self.__model.rowCount():
256            index = min(index, self.__model.rowCount() - 1)
257            index = self.__model.index(index, 0)
258            sel_model = self.__previewList.selectionModel()
259            # This emits selectionChanged signal and triggers
260            # __onSelectionChanged, currentIndex is updated there.
261            sel_model.select(index, sel_model.ClearAndSelect)
262
263        elif self.__currentIndex != -1:
264            self.__currentIndex = -1
265            self.__update()
266            self.currentIndexChanged.emit(-1)
267
268    def currentIndex(self):
269        """Return the current selected index.
270        """
271        return self.__currentIndex
272
273    def __onSelectionChanged(self, *args):
274        """Selected item in the preview list has changed.
275        Set the new description and large preview image.
276
277        """
278        rows = self.__previewList.selectedIndexes()
279        if rows:
280            index = rows[0]
281            self.__currentIndex = index.row()
282        else:
283            index = QModelIndex()
284            self.__currentIndex = -1
285
286        self.__update()
287        self.currentIndexChanged.emit(self.__currentIndex)
288
289    def __onDataChanged(self, topleft, bottomRight):
290        """Data changed, update the preview if current index in the changed
291        range.
292
293        """
294        if self.__currentIndex <= topleft.row() and \
295                self.__currentIndex >= bottomRight.row():
296            self.__update()
297
298    def __onDoubleClicked(self, index):
299        """Double click on an item in the preview item list.
300        """
301        self.activated.emit(index.row())
302
303    def __update(self):
304        """Update the current description.
305        """
306        if self.__currentIndex != -1:
307            index = self.model().index(self.__currentIndex, 0)
308        else:
309            index = QModelIndex()
310
311        if not index.isValid():
312            description = ""
313            name = ""
314            path = ""
315            svg = NO_PREVIEW_SVG
316        else:
317            description = unicode(index.data(Qt.WhatsThisRole).toString())
318            if not description:
319                description = "No description."
320
321            description = escape(description)
322            description = description.replace("\n", "<br/>")
323
324            name = unicode(index.data(Qt.DisplayRole).toString())
325            if not name:
326                name = "Untitled"
327
328            name = escape(name)
329            path = unicode(index.data(Qt.StatusTipRole).toString())
330
331            svg = unicode(index.data(previewmodel.ThumbnailSVGRole).toString())
332
333        desc_text = self.__template.format(description=description, name=name)
334
335        self.__label.setText(desc_text)
336
337        self.__path.setText(path)
338
339        if not svg:
340            svg = NO_PREVIEW_SVG
341
342        if svg:
343            self.__image.load(QByteArray(svg.encode("utf-8")))
Note: See TracBrowser for help on using the repository browser.