Skip to content

Commit 3df2437

Browse files
authored
Merge pull request #1381 from ales-erjavec/image-viewer-thumb-size
[ENH] Image viewer thumbnail size
2 parents 2929026 + e504ddc commit 3df2437

File tree

1 file changed

+83
-77
lines changed

1 file changed

+83
-77
lines changed

Orange/widgets/data/owimageviewer.py

Lines changed: 83 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
-------------------
44
55
"""
6+
import sys
67
import os
78
import weakref
89
import logging
@@ -43,6 +44,7 @@ def __init__(self, pixmap, parent=None):
4344
self.setCacheMode(QGraphicsItem.ItemCoordinateCache)
4445
self._pixmap = pixmap
4546
self._pixmapSize = QSizeF()
47+
self._keepAspect = True
4648
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
4749

4850
def setPixmap(self, pixmap):
@@ -53,30 +55,29 @@ def setPixmap(self, pixmap):
5355
def pixmap(self):
5456
return QPixmap(self._pixmap)
5557

56-
def setPixmapSize(self, size):
57-
if self._pixmapSize != size:
58-
self._pixmapSize = QSizeF(size)
59-
self.updateGeometry()
60-
61-
def pixmapSize(self):
62-
if self._pixmapSize.isValid():
63-
return QSizeF(self._pixmapSize)
64-
else:
65-
return QSizeF(self._pixmap.size())
66-
6758
def sizeHint(self, which, constraint=QSizeF()):
6859
if which == Qt.PreferredSize:
69-
return self.pixmapSize()
60+
return QSizeF(self._pixmap.size())
7061
else:
7162
return QGraphicsWidget.sizeHint(self, which, constraint)
7263

64+
def setKeepAspectRatio(self, keep):
65+
if self._keepAspect != keep:
66+
self._keepAspect = bool(keep)
67+
self.update()
68+
69+
def keepAspectRatio(self):
70+
return self._keepAspect
71+
7372
def paint(self, painter, option, widget=0):
7473
if self._pixmap.isNull():
7574
return
7675

7776
rect = self.contentsRect()
78-
79-
pixsize = self.pixmapSize()
77+
pixsize = QSizeF(self._pixmap.size())
78+
aspectmode = (Qt.KeepAspectRatio if self._keepAspect
79+
else Qt.IgnoreAspectRatio)
80+
pixsize.scale(rect.size(), aspectmode)
8081
pixrect = QRectF(QPointF(0, 0), pixsize)
8182
pixrect.moveCenter(rect.center())
8283

@@ -132,13 +133,13 @@ def __init__(self, pixmap, title="", parent=None):
132133

133134
layout.addItem(self.pixmapWidget)
134135
layout.addItem(self.labelWidget)
135-
136+
layout.addStretch()
136137
layout.setAlignment(self.pixmapWidget, Qt.AlignCenter)
137138
layout.setAlignment(self.labelWidget, Qt.AlignHCenter | Qt.AlignBottom)
139+
138140
self.setLayout(layout)
139141

140-
self.setSizePolicy(QSizePolicy.MinimumExpanding,
141-
QSizePolicy.MinimumExpanding)
142+
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
142143

143144
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
144145
self.setTitle(title)
@@ -178,18 +179,14 @@ def paint(self, painter, option, widget=0):
178179
painter.save()
179180
painter.setPen(QPen(QColor(125, 162, 206, 192)))
180181
painter.setBrush(QBrush(QColor(217, 232, 252, 192)))
181-
painter.drawRoundedRect(QRectF(contents.topLeft(),
182-
self.geometry().size()), 3, 3)
182+
painter.drawRoundedRect(
183+
QRectF(contents.topLeft(), self.geometry().size()), 3, 3)
183184
painter.restore()
184185

185186
def _updatePixmapSize(self):
186-
pixmap = self.pixmap()
187-
if not pixmap.isNull() and self._size.isValid():
188-
pixsize = QSizeF(self.pixmap().size())
189-
pixsize.scale(self._size, Qt.KeepAspectRatio)
190-
else:
191-
pixsize = QSizeF()
192-
self.pixmapWidget.setPixmapSize(pixsize)
187+
pixsize = QSizeF(self._size)
188+
self.pixmapWidget.setMinimumSize(pixsize)
189+
self.pixmapWidget.setMaximumSize(pixsize)
193190

194191

195192
class ThumbnailWidget(QGraphicsWidget):
@@ -206,6 +203,16 @@ def setGeometry(self, geom):
206203
super(ThumbnailWidget, self).setGeometry(geom)
207204
self.reflow(self.size().width())
208205

206+
def event(self, event):
207+
if event.type() == QEvent.LayoutRequest:
208+
sh = self.effectiveSizeHint(Qt.PreferredSize)
209+
self.resize(sh)
210+
self.layout().activate()
211+
event.accept()
212+
return True
213+
else:
214+
return super().event(event)
215+
209216
def reflow(self, width):
210217
if not self.layout():
211218
return
@@ -231,6 +238,8 @@ def reflow(self, width):
231238
for i, item in enumerate(items):
232239
layout.addItem(item, i // ncol, i % ncol)
233240

241+
layout.invalidate()
242+
234243
def items(self):
235244
layout = self.layout()
236245
if layout:
@@ -330,14 +339,27 @@ class OWImageViewer(widget.OWWidget):
330339
imageAttr = settings.ContextSetting(0)
331340
titleAttr = settings.ContextSetting(0)
332341

333-
zoom = settings.Setting(25)
342+
imageSize = settings.Setting(100)
334343
autoCommit = settings.Setting(False)
335344

336345
buttons_area_orientation = Qt.Vertical
337346
graph_name = "scene"
338347

339348
def __init__(self):
340349
super().__init__()
350+
self.data = None
351+
self.allAttrs = []
352+
self.stringAttrs = []
353+
354+
self.thumbnailWidget = None
355+
self.sceneLayout = None
356+
self.selectedIndices = []
357+
358+
#: List of _ImageItems
359+
self.items = []
360+
361+
self._errcount = 0
362+
self._successcount = 0
341363

342364
self.info = gui.widgetLabel(
343365
gui.vBox(self.controlArea, "Info"),
@@ -363,9 +385,9 @@ def __init__(self):
363385
)
364386

365387
gui.hSlider(
366-
self.controlArea, self, "zoom",
367-
box="Zoom", minValue=1, maxValue=100, step=1,
368-
callback=self.updateZoom,
388+
self.controlArea, self, "imageSize",
389+
box="Image Size", minValue=32, maxValue=1024, step=16,
390+
callback=self.updateSize,
369391
createLabel=False
370392
)
371393
gui.rubber(self.controlArea)
@@ -388,16 +410,6 @@ def __init__(self):
388410
)
389411
self.resize(800, 600)
390412

391-
self.thumbnailWidget = None
392-
self.sceneLayout = None
393-
self.selectedIndices = []
394-
395-
#: List of _ImageItems
396-
self.items = []
397-
398-
self._errcount = 0
399-
self._successcount = 0
400-
401413
self.loader = ImageLoader(self)
402414

403415
def setData(self, data):
@@ -452,7 +464,7 @@ def setupScene(self):
452464
if numpy.isfinite(inst[attr])]
453465
widget = ThumbnailWidget()
454466
layout = widget.layout()
455-
467+
size = QSizeF(self.imageSize, self.imageSize)
456468
self.scene.addItem(widget)
457469

458470
for i, inst in enumerate(instances):
@@ -462,7 +474,7 @@ def setupScene(self):
462474
thumbnail = GraphicsThumbnailWidget(
463475
QPixmap(), title=title, parent=widget
464476
)
465-
477+
thumbnail.setThumbnailSize(size)
466478
thumbnail.setToolTip(url.toString())
467479
thumbnail.instance = inst
468480
layout.addItem(thumbnail, i / 5, i % 5)
@@ -486,8 +498,6 @@ def set_pixmap(future, thumb=thumbnail):
486498
pixmap = QPixmap.fromImage(future.result())
487499

488500
thumb.setPixmap(pixmap)
489-
if not pixmap.isNull():
490-
thumb.setThumbnailSize(self.pixmapSize(pixmap))
491501

492502
self._updateStatus(future)
493503

@@ -503,11 +513,7 @@ def set_pixmap(future, thumb=thumbnail):
503513
self.sceneLayout = layout
504514

505515
if self.sceneLayout:
506-
width = (self.sceneView.width() -
507-
self.sceneView.verticalScrollBar().width())
508-
self.thumbnailWidget.reflow(width)
509-
self.thumbnailWidget.setPreferredWidth(width)
510-
self.sceneLayout.activate()
516+
self._updateGeometryConstraints()
511517

512518
def urlFromValue(self, value):
513519
variable = value.variable
@@ -530,14 +536,6 @@ def urlFromValue(self, value):
530536
url.setScheme("file")
531537
return url
532538

533-
def pixmapSize(self, pixmap):
534-
"""
535-
Return the preferred pixmap size based on the current `zoom` value.
536-
"""
537-
scale = 2 * self.zoom / 100.0
538-
size = QSizeF(pixmap.size()) * scale
539-
return size.expandedTo(QSizeF(16, 16))
540-
541539
def _cancelAllFutures(self):
542540
for item in self.items:
543541
if item.future is not None:
@@ -560,19 +558,12 @@ def clearScene(self):
560558
def thumbnailItems(self):
561559
return [item.widget for item in self.items]
562560

563-
def updateZoom(self):
561+
def updateSize(self):
562+
size = QSizeF(self.imageSize, self.imageSize)
564563
for item in self.thumbnailItems():
565-
item.setThumbnailSize(self.pixmapSize(item.pixmap()))
566-
567-
if self.thumbnailWidget:
568-
width = (self.sceneView.width() -
569-
self.sceneView.verticalScrollBar().width())
564+
item.setThumbnailSize(size)
570565

571-
self.thumbnailWidget.reflow(width)
572-
self.thumbnailWidget.setPreferredWidth(width)
573-
574-
if self.sceneLayout:
575-
self.sceneLayout.activate()
566+
self._updateGeometryConstraints()
576567

577568
def updateTitles(self):
578569
titleAttr = self.allAttrs[self.titleAttr]
@@ -632,24 +623,32 @@ def _updateStatus(self, future):
632623
def _updateSceneRect(self):
633624
self.scene.setSceneRect(self.scene.itemsBoundingRect())
634625

626+
def _updateGeometryConstraints(self):
627+
# Update the thumbnail grid widget's width constraint (derived from
628+
# the viewport's width
629+
if self.thumbnailWidget is not None:
630+
width = (self.sceneView.width() -
631+
self.sceneView.verticalScrollBar().width())
632+
width = width - 2
633+
self.thumbnailWidget.reflow(width)
634+
self.thumbnailWidget.setPreferredWidth(width)
635+
self.thumbnailWidget.layout().activate()
636+
635637
def onDeleteWidget(self):
636638
self._cancelAllFutures()
639+
self.clear()
637640

638641
def eventFilter(self, receiver, event):
639642
if receiver is self.sceneView and event.type() == QEvent.Resize \
640643
and self.thumbnailWidget:
641-
width = (self.sceneView.width() -
642-
self.sceneView.verticalScrollBar().width())
643-
644-
self.thumbnailWidget.reflow(width)
645-
self.thumbnailWidget.setPreferredWidth(width)
644+
self._updateGeometryConstraints()
646645

647646
return super(OWImageViewer, self).eventFilter(receiver, event)
648647

649648

650649
class ImageLoader(QObject):
651650
#: A weakref to a QNetworkAccessManager used for image retrieval.
652-
#: (we can only have only one QNetworkDiskCache opened on the same
651+
#: (we can only have one QNetworkDiskCache opened on the same
653652
#: directory)
654653
_NETMANAGER_REF = None
655654

@@ -740,17 +739,24 @@ def on_reply_ready(reply, future):
740739
return future
741740

742741

743-
def main():
742+
def main(argv=sys.argv):
744743
import sip
745744

746-
app = QApplication([])
745+
app = QApplication(argv)
746+
argv = app.arguments()
747747
w = OWImageViewer()
748748
w.show()
749749
w.raise_()
750-
data = Orange.data.Table('zoo-with-images')
750+
751+
if len(argv) > 1:
752+
data = Orange.data.Table(argv[1])
753+
else:
754+
data = Orange.data.Table('zoo-with-images')
755+
751756
w.setData(data)
752757
rval = app.exec_()
753758
w.saveSettings()
759+
w.onDeleteWidget()
754760
sip.delete(w)
755761
app.processEvents()
756762
return rval

0 commit comments

Comments
 (0)