source: orange/Orange/OrangeCanvas/preview/previewbrowser.py @ 11215:a55eaa72e6ef

Revision 11215:a55eaa72e6ef, 10.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 17 months ago (diff)

Select and accept a scheme with a double click on an item in the preview list.

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