Rafael_Lago | 2020-11-10 20:14:45 UTC | #1
Hey there,
I'm trying to develop some image viewer in the same vein as KDE's Gwenview, but with some very specific features for my actual application. I'll be using python 3.8 and PyQt5.
The question is: is there already any widget for browsing files in icon-mode, showing thumbnails of images, or would I have to implement that whole jazz from the scratch?
martin | 2020-11-24 10:28:12 UTC | #2
Hi @Rafael_Lago welcome to the forum, sorry for the delay in replying
Unfortunately, there isn't a ready-to-go widget for this, but it's fairly straightforward to implement. See a quick example below, which is used the model view framework to render thumbnails for image files in a folder. Here we're using a table view to layout the items, and a custom delegate to draw the thumbnails (you can add anything else you like).
PyQt6 Crash Course — a new tutorial in your Inbox every day
Beginner-focused crash course explaining the basics with hands-on examples.
import glob
import math
import sys
from collections import namedtuple
from PyQt5.QtCore import QAbstractTableModel, Qt, QSize
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableView, QStyledItemDelegate
# Create a custom namedtuple class to hold our data.
preview = namedtuple("preview", "id title image")
NUMBER_OF_COLUMNS = 4
CELL_PADDING = 20 # all sides
class PreviewDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
# data is our preview object
data = index.model().data(index, Qt.DisplayRole)
if data is None:
return
width = option.rect.width() - CELL_PADDING * 2
height = option.rect.height() - CELL_PADDING * 2
# option.rect holds the area we are painting on the widget (our table cell)
# scale our pixmap to fit
scaled = data.image.scaled(
width,
height,
aspectRatioMode=Qt.KeepAspectRatio,
)
# Position in the middle of the area.
x = CELL_PADDING + (width - scaled.width()) / 2
y = CELL_PADDING + (height - scaled.height()) / 2
painter.drawImage(option.rect.x() + x, option.rect.y() + y, scaled)
def sizeHint(self, option, index):
# All items the same size.
return QSize(300, 200)
class PreviewModel(QAbstractTableModel):
def __init__(self, todos=None):
super().__init__()
# .data holds our data for display, as a list of Preview objects.
self.previews = []
def data(self, index, role):
try:
data = self.previews[index.row() * 4 + index.column() ]
except IndexError:
# Incomplete last row.
return
if role == Qt.DisplayRole:
return data # Pass the data to our delegate to draw.
if role == Qt.ToolTipRole:
return data.title
def columnCount(self, index):
return NUMBER_OF_COLUMNS
def rowCount(self, index):
n_items = len(self.previews)
return math.ceil(n_items / NUMBER_OF_COLUMNS)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.view = QTableView()
self.view.horizontalHeader().hide()
self.view.verticalHeader().hide()
self.view.setGridStyle(Qt.NoPen)
delegate = PreviewDelegate()
self.view.setItemDelegate(delegate)
self.model = PreviewModel()
self.view.setModel(self.model)
self.setCentralWidget(self.view)
# Add a bunch of images.
for n, fn in enumerate(glob.glob("*.jpg")):
image = QImage(fn)
item = preview(n, fn, image)
self.model.previews.append(item)
self.model.layoutChanged.emit()
self.view.resizeRowsToContents()
self.view.resizeColumnsToContents()
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
If you drop this file in any folder containing jpeg files (extension "*.jpg") they'll be shown in a list.
Rafael_Lago | 2020-11-24 10:28:12 UTC | #3
Thank you very much! I was actually trying to develop it using QTableView, but I am not very familiar with this part of Qt5 (yet). Your code is very helpful!
Rafael_Lago | 2020-11-24 10:28:07 UTC | #4
So, I extended the concept, but using QFilesystemModel and QListView in IconMode with a given gridSize:
modUI.py: https://pastebin.com/q5jzz7S8 main.py: https://pastebin.com/Mes6cVZt
One of the remaining problems is that the position of the icons is not updated when I resize the window, which gives some funny results (see attached image).
I assume I have to connect some signal to some slot (i.e. resize of window to QListView.update?) but I'm not very familiar with Qt at this point to know which signal to what. Can anyone shed some light?
EDIT#1: nevermind, I just found about setResizeMode(QtWidgets.QListView.Adjust)
Packaging Python Applications with PyInstaller by Martin Fitzpatrick — This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.