Clickable Qlabel: Binding multiple labels to a single function - python

I have all this labels declared in a initUi
They are constructed like that:
ligne_1_left_cord = 300
self.label_ligne_1_1 = QtWidgets.QLabel(self)
pic_signe_tab =QPixmap('img/tab.png')
self.label_ligne_1_1.setPixmap(pic_signe_tab)
self.label_ligne_1_1.move(ligne_1_left_cord,300)
self.label_ligne_1_1.mousePressEvent = self.label_click
self.label_ligne_1_2 = QtWidgets.QLabel(self)
self.label_ligne_1_2.setPixmap(pic_signe_tab)
self.label_ligne_1_2.move( ligne_1_left_cord +85,300)
When I click label_ligne_1_1 the function label_click does this:
def label_click(self,event):
signe_pixmap = QPixmap('img/tabx.png')
self.label_ligne_1_1.setPixmap(signe_pixmap)
Is there anyway I could pass a variable when I'm calling label_click in order to bind it to all labels and use the same function?
Something like this?:
def label_click(name_of_the_lable):
name_of_the_lable.setPixmap(x)
Meaning of course that no matter on what square you click, the pixmap will change and an X will appear

Overriding functions in that way is not suggested, especially for what are considered protected functions in Qt, which is the case of any *Event() function of QObjects and QWidgets.
Also, in your case you're just overwriting the method with the same signature, which would never allow you to get the source of the function call.
A possible solution would be to use a lambda with the source as a keyword argument:
self.label_ligne_1_1.mousePressEvent = lambda ev, label=self.label_ligne_1_1: self.label_click(label)
But I wouldn't suggest you to do that.
A better approach, instead, would be to install an event filter on each label, and then set the pixmap each time a mouse press event is captured:
class Squares(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
layout.setSpacing(0)
for row in range(4):
for col in range(4):
square = QtWidgets.QLabel()
square.setPixmap(QtGui.QPixmap('tab.png'))
layout.addWidget(square, row, col)
setattr(self, 'label_ligne_{}_{}'.format(row + 1, col + 1), square)
square.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
source.setPixmap(QtGui.QPixmap('tabx.png'))
return super().eventFilter(source, event)

Related

How to show My Labels and activate respective Shortcut in Pyqt5?

Here is my code, How to show My Labels and activate respective QShortcut?. Want to show my labels(both instances) and assign respective shortcut keys to labels and activate it.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class Create_workingDict(QWidget):
def __init__(self,lblname,lblscut):
super(). __init__()
self.lblname = lblname
self.lblscut = lblscut
lbl_1 = QLabel()
lbl_1.setText(self.lblname)
lbl_2 = QLabel()
lbl_2.setText(self.lblscut)
vbox = QVBoxLayout()
vbox.addWidget(lbl_1)
vbox.addWidget(lbl_2)
self.setLayout(vbox)
self.msgSc = QShortcut(QKeySequence(f'{self.lblscut}'), self)
self.msgSc.activated.connect(lambda: QMessageBox.information(self,'Message', 'Ctrl + M initiated'))
class Mainclass(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Sample Window")
x = Create_workingDict("Accounts","Alt+A")
y = Create_workingDict("Inventory", "Ctrl+B")
def main():
app = QApplication(sys.argv)
ex = Mainclass()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Shortcuts work based on the context (and visibility) of the parent widget: as long as the parent is visible and the context is compatible, they will be triggered, otherwise they will not even be considered.
Be aware that the visibility is of the parent widgets (note the plural) is mandatory, no matter of the context: Qt has no API for a "global" shortcut, not only external to the application, but also within it: there is no shortcut that can automatically work anywhere in the app if (any of) its parent(s) is hidden. The context only ensures that the shortcut can only be activated if the active widget is part of the application (ApplicationShortcut), if the current active window is an ancestor of the shortcut parent (WindowShortcut), if any of the grand[...]parent widgets has focus (WidgetWithChildrenShortcut) or the current parent has it (WidgetShortcut).
Long story short: if the shortcut's parent is not visible (at any level), it will not be triggered.
Not only. In your code, both x and y are potentially garbage collected (they are not due to the fact that the lambda scope avoids destruction, but that's just "sheer dumb luck"), so that code would be actually prone to fail anyway if the activated signal would have been connected to an instance method.
If you want them to be available to the visible window, you must add their parent widgets to that window, even if they're not shown. Otherwise, just add the shortcuts to that window.
For instance:
class Mainclass(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Sample Window")
x = Create_workingDict("Accounts","Alt+A")
y = Create_workingDict("Inventory", "Ctrl+B")
layout = QVBoxLayout(self)
layout.addWidget(x)
layout.addWidget(y)
The only and automatic way to access a global application shortcut from any window of the application is to create a QKeySequence that is checked within an event filter installed on the application.
This is a possible, but crude implementation, so, take it as it is and consider its consequences:
class ShortCutFilter(QObject):
triggered = pyqtSignal(QKeySequence)
def __init__(self, shortcuts=None):
super().__init__()
self.shortcuts = {}
def addShortcut(self, shortcut, slot=None):
if isinstance(shortcut, str):
shortcut = QKeySequence(shortcut)
slots = self.shortcuts.get(shortcut)
if not slots:
self.shortcuts[shortcut] = slots = []
if slot is not None:
slots.append(slot)
return shortcut
def eventFilter(self, obj, event):
if event.type() == event.KeyPress:
keyCode = event.key()
mods = event.modifiers()
if mods & Qt.ShiftModifier:
keyCode += Qt.SHIFT
if mods & Qt.MetaModifier:
keyCode += Qt.META
if mods & Qt.ControlModifier:
keyCode += Qt.CTRL
if mods & Qt.ALT:
keyCode += Qt.ALT
for sc, slots in self.shortcuts.items():
if sc == QKeySequence(keyCode):
self.triggered.emit(sc)
for slot in slots:
try:
slot()
except Exception as e:
print(type(e), e)
return True
return super().eventFilter(obj, event)
def main():
app = QApplication(sys.argv)
shortcutFilter = ShortCutFilter()
app.installEventFilter(shortcutFilter)
shortcutFilter.addShortcut('alt+b', lambda:
QMessageBox.information(None, 'Hello', 'How are you'))
shortcutFilter.triggered.connect(lambda sc:
print('triggered', sc.toString())
ex = Mainclass()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This, obviously, means that any key press event will pass through the known python bottleneck. A better solution would be to create global QActions and addAction() to any possible top level window that could accept it.
While this approach might seem more complex, it has its benefits; for instance, you have more control on the context of the shortcut: in the case above, you could trigger Alt+B from any window, including the one shown after previously triggering it, which is clearly not a good thing.
Add the layout in main widget.
Then pass the layout to the function where you are creating labels and add them to layout.
Below is the modified code.
Find the details as comments in below code.
Your main window class
class Mainclass(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Sample Window")
#CREATE THE LAYOUT HERE IN MAIN WINDOW
vbox = QVBoxLayout()
x = Create_workingDict("Accounts","Alt+A",vbox) #PASS THE LAYOUT OBJECT (vbox) AS AN ARGUMENT
y = Create_workingDict("Inventory", "Ctrl+B",vbox) #PASS THE LAYOUT OBJECT (vbox) AS AN ARGUMENT
#SET THE LAYOUT TO MAIN WINDOW
self.setLayout(vbox)
Your function where labels created.
Observe that the functions vbox = QVBoxLayout() and self.setLayout(vbox) are moved out of this function to main window.
class Create_workingDict(QWidget):
def __init__(self,lblname,lblscut,vbox): #RECEIVE LAYOUT (vbox) ARGUMENT
super(). __init__()
self.lblname = lblname
self.lblscut = lblscut
lbl_1 = QLabel()
lbl_1.setText(self.lblname)
lbl_2 = QLabel()
lbl_2.setText(self.lblscut)
vbox.addWidget(lbl_1)
vbox.addWidget(lbl_2)
self.msgSc = QShortcut(QKeySequence(f'{self.lblscut}'), self)
self.msgSc.activated.connect(lambda: QMessageBox.information(self,'Message', 'Ctrl + M initiated'))

There is a problem with the operation of the Qlabel click function through the pyqt repetition [duplicate]

I am trying to connect slots with lambda functions, but it's not working the way I expect. In the code below, I succeed in connecting the first two buttons correctly. For the second two, which I connect in a loop, this goes wrong. Someone before me had the same question (Qt - Connect slot with argument using lambda), but this solution doesn't work for me. I've been staring at my screen for a half hour, but I can't figure out how my code is different.
class MainWindow(QtGui.QWidget):
def __init__(self):
super(QtGui.QWidget, self).__init__()
main_layout = QtGui.QVBoxLayout(self)
# Works:
self.button_1 = QtGui.QPushButton('Button 1 manual', self)
self.button_2 = QtGui.QPushButton('Button 2 manual', self)
main_layout.addWidget(self.button_1)
main_layout.addWidget(self.button_2)
self.button_1.clicked.connect(lambda x:self.button_pushed(1))
self.button_2.clicked.connect(lambda x:self.button_pushed(2))
# Doesn't work:
self.buttons = []
for idx in [3, 4]:
button = QtGui.QPushButton('Button {} auto'.format(idx), self)
button.clicked.connect(lambda x=idx: self.button_pushed(x))
self.buttons.append(button)
main_layout.addWidget(button)
def button_pushed(self, num):
print 'Pushed button {}'.format(num)
Pressing the first two buttons yields 'Pushed button 1' and 'Pushed button 2', the other two both yield 'Pushed button False', although I expected 3 and 4.
I also haven't understood the lambda mechanism completely. What exactly gets connected? A pointer to a function that is generated by lambda (with the parameter substituted in) or is the lambda function evaluated whenever the signal fires?
The QPushButton.clicked signal emits an argument that indicates the state of the button. When you connect to your lambda slot, the optional argument you assign idx to is being overwritten by the state of the button.
Instead, make your connection as
button.clicked.connect(lambda state, x=idx: self.button_pushed(x))
This way the button state is ignored and the correct value is passed to your method.
Beware! As soon as you connect your signal to a lambda slot with a reference to self, your widget will not be garbage-collected! That's because lambda creates a closure with yet another uncollectable reference to the widget.
Thus, self.someUIwidget.someSignal.connect(lambda p: self.someMethod(p)) is very evil :)
I'm not honestly sure what's going wrong with your use of lambda here either. I think it's because idx (your loop index when you set up the auto buttons) is going out of scope and doesn't contain the proper value anymore.
But I don't think you need to do it this way. It looks like the only reason you're using lambda is so that you can pass an argument to button_pushed(), identifying which button it was. There's a function sender() that can be called in the button_pushed() slot which identifies which button originated the signal.
Here's an example which I think does more or less what you were shooting for:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class MainWindow(QWidget):
def __init__(self):
super(QWidget, self).__init__()
main_layout = QVBoxLayout(self)
self.buttons = []
# Works:
self.button_1 = QPushButton('Button 1 manual', self)
main_layout.addWidget(self.button_1)
self.buttons.append(self.button_1)
self.button_1.clicked.connect(self.button_pushed)
self.button_2 = QPushButton('Button 2 manual', self)
main_layout.addWidget(self.button_2)
self.buttons.append(self.button_2)
self.button_2.clicked.connect(self.button_pushed)
# Doesn't work:
for idx in [3, 4]:
button = QPushButton('Button {} auto'.format(idx), self)
button.clicked.connect(self.button_pushed)
self.buttons.append(button)
main_layout.addWidget(button)
def button_pushed(self):
print('Pushed button {}'.format(self.buttons.index(self.sender())+1))
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
It's very simple. Check the working code and not working code. You have a syntax error.
Working code:
self.button_1.clicked.connect(lambda x:self.button_pushed(1))
Doesn't work:
button.clicked.connect(lambda x=idx: self.button_pushed(x))
Fix:
button.clicked.connect(lambda x: self.button_pushed(idx))
for lambda you are defining an "x" function and explaining the function to the program as "self.button_pushed(idx)" in order to use arguments with your function in this case the argument is (idx). Just try and let me know if it works.
The issue he has is that he is trying to get different outputs from a for loop creation. Unfortunately, it assigns the last value to any variable named button, so it gives 4 as a result. First two are working because they are not created in a for loop but created individually.
And the names of the button variables are different in the working ones as button_1 and button_2. In the for loop all buttons created will have the name button which results in the same function.
The solution for what he is trying to do is below and it works like a charm.
from sys import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
buttons = []
def newWin():
window = QWidget()
window.setWindowTitle("Lambda Loop")
window.setFixedWidth(1000)
window.move(175, 10)
window.setStyleSheet("background: #161219;")
grid = QGridLayout()
return window, grid
def newButton(text :str, margin_left, margin_right, x):
button = QPushButton(text)
button.setCursor(QCursor(Qt.PointingHandCursor))
button.setFixedWidth(485)
button.setStyleSheet(
"*{border: 4px solid '#BC006C';" +
"margin-left: " + str(margin_left) + "px;" +
"margin-right: " + str(margin_right) + "px;" +
"color: 'white';" +
"font-family: 'Comic Sans MS';" +
"font-size: 16px;" +
"border-radius: 25px;" +
"padding: 15px 0px;" +
"margin-top: 20px;}" +
"*:hover {background: '#BC006C'}"
)
def pushed():
val = x
text = QLabel(str(val))
text.setAlignment(Qt.AlignRight)
text.setStyleSheet(
"font-size: 35px;" +
"color: 'white';" +
"padding: 15px 15px 15px 25px;" +
"margin: 50px;" +
"background: '#64A314';" +
"border: 1px solid '#64A314';" +
"border-radius: 0px;"
)
grid.addWidget(text, 1, 0)
button.clicked.connect(pushed)
return button
app = QApplication(argv)
window, grid = newWin()
def frame1(grid):
for each in [3, 4]:
button = newButton('Button {}'.format(each), 150, 150, each)
buttons.append(button)
pass
b_idx = 0
for each in buttons:
grid.addWidget(each, 0, b_idx, 1, 2)
b_idx += 1
frame1(grid)
window.setLayout(grid)
window.show()
exit(app.exec())
I put all in one place so that all can see. Tell what you want to do is easier than speculations. (You can also add new variables in the list in Frame function and it will create more buttons for you with different values and functions.)

Script doesn't stop

This is the code
Entrypoint.py
def Data_For_MakePdf_Db(self):
mese = self.MakePdf_Strip_Month_select.currentText()
anno = self.MakePdf_Strip_Year_select.currentText()
MakeStrip(anno, mese)
return ### Breakpoint here
and MakeStrip.py
class SpecialStyledItemDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self._values = dict()
def add_text(self, text, row):
self._values[row] = text
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
row = index.row()
if row in self._values:
option.text = self._values[row]
option.displayAlignment = QtCore.Qt.AlignCenter
class MakeStrip(QtWidgets.QWidget):
def __init__(self, anno, mese):
super().__init__()
self.initUI(anno, mese)
def initUI(self, anno, mese):
self.title = "MAMbo - Strips di '%s' '%s' "%(mese, anno)
self.setWindowTitle(self.title)
self.setGeometry(50, 100, 900, 800)
self.callTable(anno, mese)
self.button = QtWidgets.QPushButton(self.tableWidget)
self.button.setGeometry(QtCore.QRect(440, 169, 70, 20))
self.button.setObjectName("button")
self.button.setText("close")
self.layout = QtWidgets.QHBoxLayout(self)
self.layout.addWidget(self.tableWidget)
self.show()
self.button.clicked.connect(self.on_click)
pippo = 0 ### Breakpoint here
def on_click(self):
print('Clicked')
# Functions.message_handler(message = "THIS WILL CLOSE THE APP")
return
def callTable(self, anno, mese):
# Create table
self.tableWidget = QtWidgets.QTableWidget()
self.tableWidget.move(100, 700)
self.tableWidget.setRowCount(33)
self.tableWidget.setColumnCount(3)
self.special_delegate = SpecialStyledItemDelegate()
self.tableWidget.setItemDelegate(self.special_delegate)
h_header = self.tableWidget.horizontalHeader()
h_header.hide()
for i in range(h_header.count()):
h_header.setSectionResizeMode(i, QtWidgets.QHeaderView.ResizeToContents)
v_header = self.tableWidget.verticalHeader()
v_header.hide()
v_header.setDefaultSectionSize(13)
self.tableWidget.setSpan(1, 0, 1, 3)
self.tableWidget.setSpan(0, 0, 1, 3)
...
pluto = 0
def on_click(self):
print('Clicked')
return
I use pycharm
Main problem
If I run the script I see for a fraction of second the result of Makescript even if there a push button
Alternative problem
If I debug it I need to put some breakpoints as shown in the scripts to see the result of the script
I know that the debugger keeps alive the connection but why do I need to put breakpoints in those positions to see the result?
The problem is in the following line:
MakeStrip(anno, mese)
What happens there is that you create an instance of MakeStrip which has absolutely no reference. The result is that it's created and immediately garbage collected as soon as its __init__ returns.
Imagine doing something like this:
def Data_For_MakePdf_Db(self):
list()
As soon as no reference to an object is left, python automatically destroys it. In the case above, you create an instance without any reference, so it gets immediately deleted.
Note that creating a local reference (a variable that exists only in the function) might be a solution, but since there is no locking, you'll have almost the same results, because the function will immediately returns, causing the instance to be destroyed as well:
def Data_For_MakePdf_Db(self):
strip = MakeStrip(anno, mese)
A possibility is to create a persistent reference, by making the object a member of the current instance:
def Data_For_MakePdf_Db(self):
self.strip = MakeStrip(anno, mese)
This has some, possibly unwanted, side effects: first of all, you could still switch between the current window to the new one (which will probably make things a bit confusing), and, most importantly, if you call Data_For_MakePdf_Db again while another MakeStrip window already exists, it will be probably destroyed and replaced by a new one.
A better and more consistent solution is to have MakeStrip a subclass of QDialog instead, and use its exec_(): it will make it a modal window and will return control only when the window is finally closed.
def Data_For_MakePdf_Db(self):
MakeStrip(self, anno, mese).exec_()
class MakeStrip(QtWidgets.QDialog):
def __init__(self, parent, anno, mese):
super().__init__(parent)
self.initUI(anno, mese)
def initUI(self, anno, mese):
# ...
self.button.clicked.connect(self.accept)
Note that in this specific case we can even create the dialog without the local or instance reference, thanks to the fact that exec_ creates an event loop that blocks the execution until the dialog is closed.
This is always a good idea, though, as you might need to know if the dialog was actually "accepted" or "rejected", or need access to some of the dialog's objects.
In that case, a local reference is required:
def Data_For_MakePdf_Db(self):
dialog = MakeStrip(<b>self</b>, anno, mese)
if dialog.exec_():
print(dialog.tableWidget.rowCount())
PS: capitalized names should always be used only for classes and constants, not for functions, so it should be data_for_MakePdf_Db; while you can name your function as you like, this standard convention is highly suggested, as it improves readability (it makes clear distinction between classes and functions). Read more on the official Style Guide for Python Code.

how to make an overriden QGraphicsTextItem editable & movable?

I am using PyQt and I'm trying to re-implement a QGraphicsTextItem, but it seems I'm missing something.
I would like to make the NodeTag item's text editable. I have tried setting flags such as Qt.TextEditorInteraction and QGraphicsItem.ItemIsMovable , but those seem to be ignored...
Here is a Minimal Reproducible Example :
import sys
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView, QMainWindow, QApplication, QGraphicsItem, QGraphicsTextItem
from PyQt5.QtCore import *
from PyQt5.QtGui import QPen
class NodeTag(QGraphicsTextItem):
def __init__(self,text):
QGraphicsTextItem.__init__(self,text)
self.text = text
self.setPos(0,0)
self.setTextInteractionFlags(Qt.TextEditorInteraction)
# self.setFlag(QGraphicsItem.ItemIsFocusable, True) # All these flags are ignored...
# self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
def boundingRect(self):
return QRectF(0,0,80,25)
def paint(self,painter,option,widget):
painter.setPen(QPen(Qt.blue, 2, Qt.SolidLine))
painter.drawRect(self.boundingRect())
painter.drawText(self.boundingRect(),self.text)
def mousePressEvent(self, event):
print("CLICK!")
# self.setTextInteractionFlags(Qt.TextEditorInteraction) # make text editable on click
# self.setFocus()
class GView(QGraphicsView):
def __init__(self, parent, *args, **kwargs):
super().__init__(*args, **kwargs)
self.parent = parent
self.setGeometry(100, 100, 700, 450)
self.show()
class Scene(QGraphicsScene):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
tagItem = NodeTag("myText") # create a NodeTag item
self.addItem(tagItem)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__() # create default constructor for QWidget
self.setGeometry(900, 70, 1000, 800)
self.createGraphicView()
self.show()
def createGraphicView(self):
self.scene = Scene(self)
gView = GView(self)
scene = Scene(gView)
gView.setScene(scene)
# Set the main window's central widget
self.setCentralWidget(gView)
# Run program
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
As you can see I have tried overriding the mousePressEvent and setting flags there too, but no luck so far.
Any help appreciated!
All QGraphicsItem subclasses have a paint method, and all items that paint some contents have that method overridden so that they can actually paint themselves.
The mechanism is the same as standard QWidgets, for which there is a paintEvent (the difference is that paint of QGraphicsItem receives an already instanciated QPainter), so if you want to do further painting other than what the class already provides, the base implementation must be called.
Consider that painting always happen from bottom to top, so everything that needs to be drawn behind the base painting has to be done before calling super().paint(), and everything that is going to be drawn in front of the default painting has to be placed after.
Depending on the situation, overriding might require that the default base implementation is called anyway, and that's important in your case for boundingRect too. QGraphicsTextItem automatically resizes itself when its contents change, so you should not always return a fixed QRect. If you need to have a minimum size, the solution is to merge a minimum rectangle with those provided by the default boundingRect() function.
Then, editing on a QGraphicsTextItem happens when the item gets focused, but since you also want to be able to move the item, things get trickier as both actions are based on mouse clicks. If you want to be able to edit the text with a single click, the solution is to make the item editable only when the mouse button has been released and has not been moved by some amount of pixels (the startDragDistance() property), otherwise the item is moved with the mouse. This obviously makes the ItemIsMovable flag useless, as we're going to take care of the movement internally.
Finally, since a minimum size is provided, we also need to override the shape() method in order to ensure that collision and clicks are correctly mapped, and return a QPainterPath that includes the whole bounding rect (for normal QGraphicsItem that should be the default behavior, but that doesn't happen with QGraphicsRectItem).
Here's a full implementation of what described above:
class NodeTag(QGraphicsTextItem):
def __init__(self, text):
QGraphicsTextItem.__init__(self, text)
self.startPos = None
self.isMoving = False
# the following is useless, not only because we are leaving the text
# painting to the base implementation, but also because the text is
# already accessible using toPlainText() or toHtml()
#self.text = text
# this is unnecessary too as all new items always have a (0, 0) position
#self.setPos(0, 0)
def boundingRect(self):
return super().boundingRect() | QRectF(0, 0, 80, 25)
def paint(self, painter, option, widget):
# draw the border *before* (as in "behind") the text
painter.setPen(QPen(Qt.blue, 2, Qt.SolidLine))
painter.drawRect(self.boundingRect())
super().paint(painter, option, widget)
def shape(self):
shape = QPainterPath()
shape.addRect(self.boundingRect())
return shape
def focusOutEvent(self, event):
# this is required in order to allow movement using the mouse
self.setTextInteractionFlags(Qt.NoTextInteraction)
def mousePressEvent(self, event):
if (event.button() == Qt.LeftButton and
self.textInteractionFlags() != Qt.TextEditorInteraction):
self.startPos = event.pos()
else:
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.startPos:
delta = event.pos() - self.startPos
if (self.isMoving or
delta.manhattanLength() >= QApplication.startDragDistance()):
self.setPos(self.pos() + delta)
self.isMoving = True
return
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if (not self.isMoving and
self.textInteractionFlags() != Qt.TextEditorInteraction):
self.setTextInteractionFlags(Qt.TextEditorInteraction)
self.setFocus()
# the following lines are used to correctly place the text
# cursor at the mouse cursor position
cursorPos = self.document().documentLayout().hitTest(
event.pos(), Qt.FuzzyHit)
textCursor = self.textCursor()
textCursor.setPosition(cursorPos)
self.setTextCursor(textCursor)
super().mouseReleaseEvent(event)
self.startPos = None
self.isMoving = False
As a side note, remember that QGraphicsTextItem supports rich text formatting, so even if you want more control on the text painting process you should not use QPainter.drawText(), because you'd only draw the plain text. In fact, QGraphicsTextItem draws its contents using the drawContents() function of the underlying text document.
Try it:
...
class NodeTag(QGraphicsTextItem):
def __init__(self, text, parent=None):
super(NodeTag, self).__init__(parent)
self.text = text
self.setPlainText(text)
self.setFlag(QGraphicsItem.ItemIsMovable)
self.setFlag(QGraphicsItem.ItemIsSelectable)
def focusOutEvent(self, event):
self.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
super(NodeTag, self).focusOutEvent(event)
def mouseDoubleClickEvent(self, event):
if self.textInteractionFlags() == QtCore.Qt.NoTextInteraction:
self.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction)
super(NodeTag, self).mouseDoubleClickEvent(event)
def paint(self,painter,option,widget):
painter.setPen(QPen(Qt.blue, 2, Qt.SolidLine))
painter.drawRect(self.boundingRect())
# painter.drawText(self.boundingRect(),self.text)
super().paint(painter, option, widget)
...

QTreeView, class, arguments, instance and attributes

I get to POO, python and PyQt slowly and I have a problem to understand something about passing argument and attributes.
I found online a code dealing with QTreeView (see below) and I don't understand how Index is passed into showPath(). Also why self.filename and self.filepath are not passed to the instance?
I hope I am clear enough ... Thank a lot.
from PyQt4 import QtGui
class TreeViewWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(TreeViewWidget, self).__init__(parent)
self.model = QtGui.QFileSystemModel(self)
self.model.setRootPath(rootpath)
self.indexRoot = self.model.index(self.model.rootPath())
self.treeView = QtGui.QTreeView(self)
self.treeView.setExpandsOnDoubleClick(False)
self.treeView.setModel(self.model)
self.treeView.setRootIndex(self.indexRoot)
self.treeView.setColumnWidth(0,220)
self.treeView.clicked.connect(self.showPath)
self.treeView.doubleClicked.connect(self.openQuickLook)
self.labelFileName = QtGui.QLabel(self)
self.labelFileName.setText("File Name:")
self.lineEditFileName = QtGui.QLineEdit(self)
self.labelFilePath = QtGui.QLabel(self)
self.labelFilePath.setText("File Path:")
self.lineEditFilePath = QtGui.QLineEdit(self)
self.gridLayout = QtGui.QGridLayout()
self.gridLayout.addWidget(self.labelFileName, 0, 0)
self.gridLayout.addWidget(self.lineEditFileName, 0, 1)
self.gridLayout.addWidget(self.labelFilePath, 1, 0)
self.gridLayout.addWidget(self.lineEditFilePath, 1, 1)
self.layout = QtGui.QVBoxLayout(self)
self.layout.addLayout(self.gridLayout)
self.layout.addWidget(self.treeView)
def givePathName(self, index):
indexItem = self.model.index(index.row(), 0, index.parent())
self.filename = self.model.fileName(indexItem)
self.filepath = self.model.filePath(indexItem)
def showPath(self, index):
self.givePathName(index)
self.lineEditFileName.setText(self.filename)
self.lineEditFilePath.setText(self.filepath)
I don't understand how index is passed into showPath()
You connect the widget's click signal to showPath explicitly:
self.treeView.clicked.connect(self.showPath)
part of this signal is the index of the specific item clicked on; this is passed as an argument to showPath automatically.
Also why self.filename and self.filepath are not passed to the instance?
They are instance attributes, they belong to the instance and are accessible to all methods of that instance. They are created in givePathName(), and are then part of the TreeViewWidget instance object. They start with self. because that is, by convention, the name given to the instance in instance methods (and the implicit first argument to those methods).
Putting that together:
def showPath(self, index):
# ^ the instance object, so you can access its attributes
# ^ the index of the specific item clicked
The clicked signal of your QTreeView pass the QModelIndex of the item clicked as an argument to any connected slots.
See Qt Documentation and PyQt signal and slots documentation.

Categories