wxPython Solitaire GUI - button

I'm writing a Solitaire GUI using wxPython, and I'm on Windows 7. I've only written one GUI before (in Java Swing), so I'm not as familiar as I could be with all the different types of widgets and controls. I'm faced with the challenge of having resizable, cascading piles of cards in the Tableaux of the Solitaire board. To me, using BitmapButtons for each card (or at least for face-up cards) and having a panel contain a pile of cards seemed natural, since it is legal to move sub-piles of cards in the Tableau from pile to pile in Solitaire. I'm sure there is a better way to do this, but for now I've been fiddling with a smaller GUI (not my main GUI) to try and achieve this. I've attached the code for the test GUI below.
Note: My main GUI uses a GridBagSizer with 14 cells. I haven't tried using the following panel/buttons in the GridBagSizer, or even know if a GridBagSizer is the best way to go about this.
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, id_, title):
wx.Frame.__init__(self, parent, id_, title, size=(810, 580))
self.panel = wx.Panel(self, size=(72, 320), pos=(20,155))
self.buttons = []
self.init_buttons()
def init_buttons(self):
for i in range(6):
face_down = wx.Image('img/cardback.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap()
wid = face_down.GetWidth()
hgt = face_down.GetHeight()
bmpbtn = wx.BitmapButton(self.panel, -1, bitmap=face_down, pos=(20,155+7*i), size=(wid, hgt))
bmpbtn.Bind(wx.EVT_ENTER_WINDOW, self.onMouseOver)
self.buttons.append(bmpbtn)
for i in range(1,14):
rank = 14 - i
if i % 2 == 0:
filename = 'img/%sC.png' % rank
else:
filename = 'img/%sH.png' % rank
img = wx.Image(filename, wx.BITMAP_TYPE_PNG).ConvertToBitmap()
wid = img.GetWidth()
hgt = img.GetHeight()
bmpbtn = wx.BitmapButton(self.panel, -1, bitmap=img, pos=(20, 177+20*i), size=(wid, hgt))
bmpbtn.Bind(wx.EVT_ENTER_WINDOW, self.onMouseOver)
self.buttons.append(bmpbtn)
def onMouseOver(self, event):
#event.Skip()
pass
class MyApp(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
self.frame = MyFrame(None, -1, "Solitaire")
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
app = MyApp(0)
app.MainLoop()
This is what results from running:
http://oi44.tinypic.com/1zv4swj.jpg
Which I was satisfied with, until I moved my mouse over some of the buttons:
http://oi44.tinypic.com/2rdupmq.jpg
This must have to do with the EVT_ENTER_WINDOW event. I attempted to write an event handler, but realized I didn't really know how to achieve what I need. According to the docs, a BitmapButton has different bitmaps for each of its states - hover, focus, selected, inactive, etc. However, I do not want to change the Bitmap on a mouseover event. I simply want the button to stay put, and to not display itself on top of other buttons.
Any help would be greatly appreciated. Incidentally, if anybody has advice for a better way (than GridBagSizer and these panels of buttons) to implement this GUI, I would love that!

I would recommend against using actual window controls for each of the cards. I would instead have a single canvas upon which you render the card bitmaps in their appropriate locations. You'll have to do a little extra math to determine what cards are being clicked on, but this is definitely the way to go.
Use a wx.Panel with a EVT_PAINT handler to do your drawing.
Here's a starting point that is written to use double-buffering to avoid flickering.
P.S. You can use bitmap = wx.Bitmap(path) to load an image, instead of bothering with wx.Image and converting it to a bitmap object.
import wx
class Panel(wx.Panel):
def __init__(self, parent):
super(Panel, self).__init__(parent)
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
self.Bind(wx.EVT_PAINT, self.on_paint)
self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
self.Bind(wx.EVT_LEFT_UP, self.on_left_up)
def on_left_down(self, event):
print 'on_left_down', event.GetPosition()
def on_left_up(self, event):
print 'on_left_up', event.GetPosition()
def on_paint(self, event):
dc = wx.AutoBufferedPaintDC(self)
# Use dc.DrawBitmap(bitmap, x, y) to draw the cards here
class Frame(wx.Frame):
def __init__(self):
super(Frame, self).__init__(None)
self.SetTitle('My Title')
Panel(self)
def main():
app = wx.App()
frame = Frame()
frame.Center()
frame.Show()
app.MainLoop()
if __name__ == '__main__':
main()

Related

Should I use QGraphicsView to display an image and some decorated text side by side?

I want to create a "details" view for books I have downloaded.
With the attached image as an example, imagine the red block to the left is the book's cover page, and metadata related to it is displayed to the right.
With the way I have it done right now:
from PySide6 import QtWidgets as qtw
from PySide6 import QtGui as qtg
from PySide6 import QtCore as qtc
class Details:
def __init__(self):
self.location = "/home/user/Desktop/Untitled.png"
self.title = "Some title"
self.subtitle = "Sub title"
self.id = 123124
def to_html(self):
return """
<p>
<b>Author =</b> author<br/>
<b>Published Date =</b> 2000-1-1<br/>
<b>Pages =</b> 500<br/>
</p>
"""
class DetailsWidget(qtw.QWidget):
_title_font = qtg.QFont()
_title_font.setBold(True)
_title_font.setPixelSize(24)
_subtitle_font = qtg.QFont()
_subtitle_font.setBold(True)
_subtitle_font.setPixelSize(19)
_id_font = qtg.QFont()
_id_font.setBold(True)
_id_font.setPixelSize(15)
_redacted_details_font = qtg.QFont()
_redacted_details_font.setPixelSize(12)
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.setFixedSize(1000, 500)
self.setWindowFlag(qtc.Qt.WindowType.Dialog, True)
self.setLayout(qtw.QGridLayout())
self.layout().setContentsMargins(0, 0, 0, 0)
self._details: Details = Details()
self._thumbnail_image = qtg.QImage(self._details.location)
self._thumbnail_image = self._thumbnail_image.scaled(
500,
500,
qtc.Qt.AspectRatioMode.KeepAspectRatio,
qtc.Qt.TransformationMode.SmoothTransformation,
)
self._details_rect = qtc.QRect(
self._get_actual_geometry().left() + self._thumbnail_image.width() + 10,
self._get_actual_geometry().top(),
self._get_actual_geometry().width() - self._thumbnail_image.width() - 20,
self._get_actual_geometry().height(),
)
height = 0
self._title_rects = []
font_metrics_rect = qtg.QFontMetrics(self._title_font).boundingRect(
self._details_rect, qtc.Qt.TextFlag.TextWordWrap, self._details.title, 0
)
drawing_rect = qtc.QRect(self._details_rect)
self._title_rects.append(drawing_rect)
height += font_metrics_rect.height() + 10
drawing_rect = qtc.QRect(self._details_rect)
drawing_rect.moveTop(height)
self._title_rects.append(drawing_rect)
font_metrics_rect = qtg.QFontMetrics(self._title_font).boundingRect(
self._details_rect, qtc.Qt.TextFlag.TextWordWrap, self._details.subtitle, 0
)
drawing_rect = qtc.QRect(self._details_rect)
height += font_metrics_rect.height() - 3
drawing_rect.moveTop(height)
self._title_rects.append(drawing_rect)
font_metrics_rect = qtg.QFontMetrics(self._title_font).boundingRect(
self._details_rect,
qtc.Qt.TextFlag.TextWordWrap,
str(self._details.id),
0,
)
self._title_rects.append(drawing_rect)
height += font_metrics_rect.height() + 10
self._details_rect.moveTop(height)
self._redacted_details_text_document = qtg.QTextDocument()
self._redacted_details_text_document.setHtml(self._details.to_html())
# First set the width,
self._redacted_details_text_document.setTextWidth(self._details_rect.width())
# then get the height of the QTextDocument based on the given width and set
# that + the titles heights + bottom padding as the total height.
if (total_height:=height + self._redacted_details_text_document.size().height() + 10) > self.height():
self.setFixedHeight(total_height)
def _get_actual_geometry(self) -> qtc.QRect:
# Probably not needed for normal desktop environments with window
# managers but I'm an epik i3 user so self.geometry() does not work as
# intended when full screening the window with $mod + F. Or I'm just
# retarded and this is not even a problem.
geometry = self.geometry()
geometry.setTopLeft(qtc.QPoint(0, 0))
return geometry
def paintEvent(self, event: qtg.QPaintEvent) -> None:
total_height = 0
painter = qtg.QPainter(self)
painter.setRenderHint(qtg.QPainter.RenderHint.TextAntialiasing)
painter.drawImage(0, 0, self._thumbnail_image)
painter.save()
painter.setFont(self._title_font)
painter.drawText(
self._title_rects[0], qtc.Qt.TextFlag.TextWordWrap, self._details.title
)
painter.setFont(self._subtitle_font)
painter.drawText(
self._title_rects[1], qtc.Qt.TextFlag.TextWordWrap, self._details.subtitle
)
painter.setFont(self._id_font)
painter.drawText(
self._title_rects[2],
qtc.Qt.TextFlag.TextWordWrap,
str(self._details.id),
)
painter.translate(self._details_rect.topLeft())
painter.setFont(self._redacted_details_font)
self._redacted_details_text_document.drawContents(painter)
painter.restore()
app = qtw.QApplication()
widget = DetailsWidget()
widget.show()
app.exec()
I can display the text and the image next to each other just fine, but the text is not selectable. Looking around for a way to do so, I stumbled upon QGraphicsTextItem. Should I re-do the whole thing in a QGraphicsView instead of using the paintEvent on a QWidget? The reason I'm hesitant to do so is because I don't know of the cons of using a QGraphicsView, maybe it's a lot more resource heavy and not the best for this use case?
You're complicating things unnecessarily.
Just use a basic QHBoxLayout and two QLabels, with the one on the left for the image, and the one on the right for the details.
If you want to allow text selection, use QLabel.setTextInteractionFlags(Qt.TextSelectableByMouse).
An even better solution would be to use a QGraphicsView with a QGraphicsPixmapItem for the image (using fitInView() in the resizeEvent to always show it as large as possible) and a QTextEdit for the details, set in read only mode.
Note that your usage of _get_actual_geometry is wrong in principle (besides the fact that you're calling 4 times in a row, while you could just use a local variable instead), because when a widget has not been shown yet it always has a default size (100x30 for widgets created with a parent, otherwise 640x480), so not only you'll be getting a wrong geometry, but you're also changing it, since setTopLeft() will only move the corner, not translate the rectangle: if you want the basic rectangle of the widget, just use rect(). Obviously, if you properly use layouts as suggested above, this won't be necessary in the first place.

QGraphicsView shows a white screen after the update [duplicate]

This question already has answers here:
Updating GUI elements in MultiThreaded PyQT
(2 answers)
Closed 1 year ago.
I'm writing a program that should display a chessboard on request from the console, here's skeleton QGraphicsView
class TableView(QGraphicsView):
def __init__(self, item):
super().__init__(item)
self.num = 1
self.Size = self.size() - QtCore.QSize(2, 2)
scene = QtWidgets.QGraphicsScene()
self.setScene(scene)
self.RenderBoard()
def UpdateTable(self, **kwargs):
for key, val in kwargs.items():
setattr(self, key, val)
self.RenderBoard()
def LoadImage(self, path, size, pos=None, flip=False):
print(path, size, pos, flip)
picture = QtGui.QImage(path)
picture = picture.scaled(size)
if flip:
my_transform = QtGui.QTransform()
my_transform.rotate(180)
picture = picture.transformed(my_transform)
pic = QtWidgets.QGraphicsPixmapItem()
pic.setPixmap(QtGui.QPixmap.fromImage(picture))
if pos is not None:
pic.setPos(*pos)
self.scene().addItem(pic)
def RenderBoard(self):
self.scene().clear()
self.LoadImage('img/board.png', self.Size, flip=(self.num % 2 == 0))
def resizeEvent(self, event):
self.Size = self.size() - QtCore.QSize(2, 2)
self.RenderBoard()
super().resizeEvent(event)
The program works and outputs the board, but as soon as I call UpdateTable from QtCore.QThread, which accepts requests from the console, the screen turns white. But, if i change the size of the program, the resizeEvent event is called and the program comes to life again and shows exactly what I wanted.
Do you have any idea what the problem might be?
P.S. the update that I call changes one of the arrays from which the reading occurs during rendering, I printing it during the Update call and during the Resize call, in both cases, it is the same.
Other functions
class UpdaterThread(QtCore.QThread):
def __init__(self, table):
QtCore.QThread.__init__(self)
self.table = table
def run(self):
sleep(1)
self.table.UpdateTable(**{'num': 2})
class Ui_ChessHelper(object):
def setupUi(self, ChessHelper):
self.table = TableView(self.centralwidget)
self.table.setMinimumSize(QtCore.QSize(350, 350))
self.table.setObjectName("table")
self.thread = UpdaterThread(self.table)
self.thread.start()
QGraphicsView has a method scene() that returns the current scene. When you write
self.scene = QtWidgets.QGraphicsScene()
self.setScene(self.scene)
you are clobbering that method's name with your new scene object. Instead
scene = QtWidgets.QGraphicsScene()
self.setScene(scene)
then use self.scene() to access it later when needed.

PyQt listview with html rich text delegate moves text bit out of place(pic and code included)

Got a listview where items need to make use of bold text, so a html delegate is the way to go. I looked around and found several solutions and out of them I made this code, but it has the issue that you can see in the gif, which shows the results with the use of the delegate and without.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
class HTMLDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super(HTMLDelegate, self).__init__(parent)
self.doc = QTextDocument(self)
def paint(self, painter, option, index):
painter.save()
options = QStyleOptionViewItemV4(option)
self.initStyleOption(options, index)
self.doc.setHtml(options.text)
options.text = ""
style = QApplication.style() if options.widget is None \
else options.widget.style()
style.drawControl(QStyle.CE_ItemViewItem, options, painter)
ctx = QAbstractTextDocumentLayout.PaintContext()
if option.state & QStyle.State_Selected:
ctx.palette.setColor(QPalette.Text, option.palette.color(
QPalette.Active, QPalette.HighlightedText))
textRect = style.subElementRect(QStyle.SE_ItemViewItemText, options)
painter.translate(textRect.topLeft())
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
if __name__ == '__main__':
app = QApplication(sys.argv)
data = ['1','2','3','4','5','6','7','8','9']
main_list = QListView()
main_list.setItemDelegate(HTMLDelegate())
main_list.setModel(QStringListModel(data))
main_list.show()
sys.exit(app.exec_())
Here are PyQt5 and PySide versions
I dont use sizeHint, since through my testing it didnt feel like it was affecting this behavior.
Some attribute is positioning the text wrongly, I have been able to partly counter it with:
self.doc.setDocumentMargin(0)
the default margin is 4, and then move the whole item bit to the right
rect = options.rect
options.rect = QRect(rect.x()+3, rect.y(), rect.width(), rect.height())
But I dont think it addresses the cause of this issue, it just masks it and it becomes a problem again once you try add icons for example
this one is now the one I use
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import sys
class HTMLDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__()
self.doc = QTextDocument(self)
def paint(self, painter, option, index):
painter.save()
options = QStyleOptionViewItem(option)
self.initStyleOption(options, index)
self.doc.setHtml(options.text)
options.text = ""
style = QApplication.style() if options.widget is None \
else options.widget.style()
style.drawControl(QStyle.CE_ItemViewItem, options, painter)
ctx = QAbstractTextDocumentLayout.PaintContext()
print(QStyle.State_Selected)
if option.state & QStyle.State_Selected:
ctx.palette.setColor(QPalette.Text, option.palette.color(
QPalette.Active, QPalette.HighlightedText))
else:
ctx.palette.setColor(QPalette.Text, option.palette.color(
QPalette.Active, QPalette.Text))
textRect = style.subElementRect(
QStyle.SE_ItemViewItemText, options)
if index.column() != 0:
textRect.adjust(5, 0, 0, 0)
thefuckyourshitup_constant = 4
margin = (option.rect.height() - options.fontMetrics.height()) // 2
margin = margin - thefuckyourshitup_constant
textRect.setTop(textRect.top() + margin)
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
def sizeHint(self, option, index):
return QSize(self.doc.idealWidth(), self.doc.size().height())
if __name__ == '__main__':
app = QApplication(sys.argv)
data = ['1','2','3','4','5','6','7','8','9']
main_list = QListView()
main_list.setItemDelegate(HTMLDelegate())
main_list.setModel(QStringListModel(data))
main_list.show()
sys.exit(app.exec_())
Well after some more digging and trying, adjusting the text rectangle seems to give the feel that everything is allright and seems to behave consistently in various DEs
textRect.adjust(-1, -4, 0, 0)
I am not sure if its a bug, but one would expect that SE_ItemViewItemText should have give the correct position on its own
An old question, but since I ran across this with a similar problem I thought I'd address some problems with your code. Mostly, it is the self.doc member. It is not going to work as you had hoped. The sizeHint() method is called (multiple times) before paint() is called (multiple times) and will be getting an empty document (which is why you "didnt feel like it was affecting this behavior".
You might think well, just initialize the document in sizeHint() vs paint() then, but that would also be wrong. There is just one delegate and document for your list, so the document would always be based on the last one sized or painted, so having self.doc as a member only saves you the document construction which is probably not worth it considering the confusion it causes.

DIV tag equivalent in Qt Graphics Framework

I am working on a simple desktop application where I have to show a tree structure of folders and files along with other diagrams. For this I chose Qt and python (PySide). I need a structure like below (Forgive me for the bad drawing. But you get the idea):
The folders can be double clicked to expand/shrink. When a folder expands, new child elements need to take more space, and the folders below the current folder must move down. Similarly when the folder is shrunk, the folders below the current folder must come up; just like a standard folder system.
Hence I am in search of a <div> equivalent element in Qt where I can place each directory and all of its children inside that div and the div can expand and shrink. This way I don't have to write code for a re-draw every time the folder is opened/closed. Currently I have to calculate each item's position and place the child items respective to that position. That is a lot of calculation and no of items are > 1000. With a div, I will just re-calculate positions of child items and resize the div. Other divs can then automatically re-draw themselves.
I am not using QTreeView because as I said earlier, I have to draw other diagrams and connect these folders with them. QTreeView will live in its own space (with scroll bar and stuff), and I won't be able to draw lines to connect items in QTreeView and QGraphicsScene.
You can view my current work here in github. Here is the file that has my work.
I'm not sure what you're thinking of "<div>". It's just the most simple HTML container, and it seems to have nothing to do with your goal.
You can use graphics layouts to align items in the scene automatically. Here's how it can be implemented:
from PySide import QtGui, QtCore
class Leaf(QtGui.QGraphicsProxyWidget):
def __init__(self, path, folder = None):
QtGui.QGraphicsProxyWidget.__init__(self)
self.folder = folder
label = QtGui.QLabel()
label.setText(QtCore.QFileInfo(path).fileName())
self.setWidget(label)
self.setToolTip(path)
self.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
def mousePressEvent(self, event):
if self.folder:
self.folder.toggleChildren()
class Folder(QtGui.QGraphicsWidget):
def __init__(self, path, isTopLevel = False):
QtGui.QGraphicsWidget.__init__(self)
self.offset = 32
childrenLayout = QtGui.QGraphicsLinearLayout(QtCore.Qt.Vertical)
childrenLayout.setContentsMargins(self.offset, 0, 0, 0)
flags = QtCore.QDir.AllEntries | QtCore.QDir.NoDotAndDotDot
for info in QtCore.QDir(path).entryInfoList(flags):
if info.isDir():
childrenLayout.addItem(Folder(info.filePath()))
else:
childrenLayout.addItem(Leaf(info.filePath()))
self.childrenWidget = QtGui.QGraphicsWidget()
self.childrenWidget.setLayout(childrenLayout)
mainLayout = QtGui.QGraphicsLinearLayout(QtCore.Qt.Vertical)
mainLayout.setContentsMargins(0, 0, 0, 0)
self.leaf = Leaf(path, self)
mainLayout.addItem(self.leaf)
mainLayout.addItem(self.childrenWidget)
if isTopLevel:
mainLayout.addStretch()
self.setLayout(mainLayout)
def paint(self, painter, option, widget):
QtGui.QGraphicsWidget.paint(self, painter, option, widget)
if self.childrenWidget.isVisible() and self.childrenWidget.layout().count() > 0:
lastChild = self.childrenWidget.layout().itemAt(self.childrenWidget.layout().count() - 1)
lastChildY = self.childrenWidget.geometry().top() + \
lastChild.geometry().top() + self.leaf.geometry().height() / 2;
painter.drawLine(self.offset / 2, self.leaf.geometry().bottom(), self.offset / 2, lastChildY)
for i in range(0, self.childrenWidget.layout().count()):
child = self.childrenWidget.layout().itemAt(i)
childY = self.childrenWidget.geometry().top() + \
child.geometry().top() + self.leaf.geometry().height() / 2
painter.drawLine(self.offset / 2, childY, self.offset, childY)
def toggleChildren(self):
if self.childrenWidget.isVisible():
self.layout().removeItem(self.childrenWidget)
self.childrenWidget.hide()
self.leaf.widget().setStyleSheet("QLabel { color : blue; }")
print "hide"
else:
self.childrenWidget.show()
self.layout().insertItem(1, self.childrenWidget)
self.leaf.widget().setStyleSheet("")
self.update()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
scene = QtGui.QGraphicsScene()
view = QtGui.QGraphicsView(scene)
# put your root path here
scene.addItem(Folder("/usr/share/alsa", True))
view.show()
view.resize(400, 400)
sys.exit(app.exec_())

Clickable elements or child widgets inside custom-painted delegate

I have a QListView, where I display items using a custom delegate with custom painting. Within each item (i.e. each list row) I want to be able to show a couple of "hyperlinks" which the user could click on and which would then call on some functions.
I have already tried to check the official documentation (e.g. Model/View Programming) as well as quite a lot of googling, but haven't been able to figure out how to accomplish this.
I have two ideas, each with their own problems:
I could draw them using child widgets, like a flat QPushButton. How do I then position and display these widgets?
I could also draw them as text strings. How do I then make them clickable? Or can I capture click events on the parent QListView and somehow determine coordinates from those? I could then match coordinates to these clickable elements and act accordingly.
My initial approach was to use QListWidget with .setItemWidget(), where I had a proper widget with a layout and child widgets. Unfortunately this was too slow when my list grew to hundreds or thousands of items. That's why I changed to QListView with a delegate.
I seem to be closing in on a solution.
I can receive clicks on the elements by overriding the delegate's .editorEvent(event, model, option, index). I can then find out the event.type(), the clicked row from index.row() and the actual coordinates from event.x() and event.y() (since, if the event type is MouseButtonRelease, the event is a QMouseEvent).
From these, I think I can correlate the coordinates to my elements on screen and act accordingly.
I will update this answer once I have working code.
EDIT
A simple working example, using PySide:
class MyModel(QtGui.QStandardItemModel):
def __init__(self):
super(MyModel, self).__init__()
for i in range(10): self.appendRow(QtGui.QStandardItem("Row %d" % i))
class MyDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent=None):
super(MyDelegate, self).__init__(parent)
self.links = {}
def makeLinkFunc(self, row, text):
def linkFunc(): print("Clicked on %s in row %d" % (text, row))
return linkFunc
def paint(self, painter, option, index):
painter.save()
textHeight = QtGui.QFontMetrics(painter.font()).height()
painter.drawText(option.rect.x()+2, option.rect.y()+2+textHeight, index.data())
rowLinks = {}
for i in range(3):
text = "Link %d" % (3-i)
linkWidth = QtGui.QFontMetrics(font).width(text)
x = option.rect.right() - (i+1) * (linkWidth + 10)
painter.drawText(x, y, text)
rect = QtCore.QRect(x, y - textHeight, linkWidth, textHeight)
rowLinks[rect] = self.makeLinkFunc(index.row(), text)
self.links[index.row()] = rowLinks
painter.restore()
def sizeHint(self, option, index):
hint = super().sizeHint(option, index)
hint.setHeight(30)
return hint
def editorEvent(self, event, model, option, index):
if event.type() == QtCore.QEvent.MouseButtonRelease:
for rect, link in self.links[index.row()].items():
if rect.contains(event.pos()):
link()
return True
return False
listmodel = MyModel()
listview = QtGui.QListView()
listview.setModel(listmodel)
listview.setItemDelegate(MyDelegate(parent=listview))
listview.setSelectionMode(QtGui.QAbstractItemView.NoSelection)

Resources