QMenu (contextMenuEvent) not closing on click on action nor parent QWidget - python

I am trying to create a context menu (QMenu) for a right-click inside of PatternWidget(QWidget) with PySide6.
For this purpose I have overwritten contextMenuEvent as seen below.
For whatever reason, the context menu will NOT exit, when any of the actions in it or any space around it within PatternWidget is clicked, whether by left nor by right click.
The only way I can make the QMenu close is to click inside another widget or outside of the whole Application window.
I spent a long time on Google already and it seems that people usually have the opposite problem (keeping it open when clicking somewhere). I therefore think that something must be wrong in my code that prevents the default behavior to close the QMenu rather than me having to connect to some Signal and close it manually
I was also wondering if the Mouse Click event is not bubbling up until the QMenu but,the signals of the actions in the QMenu (Copy Row Up and Down) are firing as expected when clicked.
EDIT:
Removed QScrollArea
Added Code for PatternOverlay
class MainWindow(QMainWindow):
...
def __init_layout(self):
self.layout = QHBoxLayout()
self.layout.setContentsMargins(10, 10, 10, 10)
self.layout.setSpacing(1)
self.mainWidget = QWidget()
self.mainWidget.setLayout(self.layout)
self._scene = QtWidgets.QGraphicsScene(self)
self._view = QtWidgets.QGraphicsView(self._scene)
self._view.setStyleSheet("border: 0px; background-color: Gainsboro")
self.pattern_widget = PatternWidget()
self._scene.addWidget(self.pattern_widget)
self.layout.addWidget(self._view)
self.setCentralWidget(self.mainWidget)
class PatternOverlay(QWidget):
def __init__(self, parent):
super().__init__(parent=parent)
self.setAttribute(Qt.WA_NoSystemBackground)
self.setAttribute(Qt.WA_TransparentForMouseEvents)
self.rects = []
def paintEvent(self, _):
painter = QPainter(self)
painter.setPen(QPen(Qt.blue, 2, Qt.SolidLine))
for rect in self.rects:
painter.drawRect(rect)
painter.end()
self.rects.clear()
def addRect(self, rect):
self.rects.append(rect)
class PatternWidget(QWidget):
def __init__(self, cell_width, cell_height, stitches, rows):
super().__init__()
self.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
self.setMouseTracking(True)
# initialize overlay
self.__overlay = PatternOverlay(self)
self.__overlay.resize(self.size())
self.__overlay.show()
...
def contextMenuEvent(self, e):
copy_row_up_action = QAction("Copy Row Up", self)
copy_row_up_action.setStatusTip("Copy Row Up")
copy_row_up_action.triggered.connect(self.__handle_copy_row_up)
copy_row_down_action = QAction("Copy Row Down", self)
copy_row_down_action.setStatusTip("Copy Row Down")
copy_row_down_action.triggered.connect(self.__handle_copy_row_down)
context = QMenu(self)
context.addActions([copy_row_up_action, copy_row_down_action])
context.exec_(e.globalPos())

Related

How to let a LineEdit Widget pop up and let the user enter text at the place where a mouse clicks?

I want to create an application that:
at any place in the window, when the user clicks a mouse, a lineedit will immediately pop up
the LineEdit allows the user to enter text(has focus at once)
after the editing is finished, the LineEdit disappears, and the text remains.
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.text = []
def mousePressEvent(self, event):
newle = QLineEdit(self)
newle.setGeometry(event.pos().x(), event.pos().y(), 100, 200)
newle.setFocus()
self.text.append((event.pos().x(), event.pos().y(), "helloworld"))
self.update()
def paintEvent(self, event):
print("Pressed")
painter = QPainter(self)
painter.drawText(random.randint(50, 200), 100, "Ok")
for i in self.text:
painter.drawText(*i)
This is the class that I created. I defined a mousePressEvent - in that method, the first three lines are supposed to make the LineEdit pop up and get the focus. But acutally nothing happens! I don't see a place to enter text. I can't imagine how to solve this problem.
Can anyone help me?

Drawing Line from QLabel to QLabel in PyQt

I'm fairly new to PyQt
I'm trying to drawing a line from 1 QLabel to another.
My 2 QLabel are located on another QLabel which acts as an image in my GUI.
I've managed to track the mouse event and move the label around, but I cannot draw the line between them using QPainter.
Thank you in advance :)
This is my MouseTracking class
class MouseTracker(QtCore.QObject):
positionChanged = QtCore.pyqtSignal(QtCore.QPoint)
def __init__(self, widget):
super().__init__(widget)
self._widget = widget
self.widget.setMouseTracking(True)
self.widget.installEventFilter(self)
#property
def widget(self):
return self._widget
def eventFilter(self, o, e):
if e.type() == QtCore.QEvent.MouseMove:
self.positionChanged.emit(e.pos())
return super().eventFilter(o, e)
This is my DraggableLabel class:
class DraggableLabel(QLabel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.LabelIsMoving = False
self.setStyleSheet("border-color: rgb(238, 0, 0); border-width : 2.0px; border-style:inset; background: transparent;")
self.origin = None
# self.setDragEnabled(True)
def mousePressEvent(self, event):
if not self.origin:
# update the origin point, we'll need that later
self.origin = self.pos()
if event.button() == Qt.LeftButton:
self.LabelIsMoving = True
self.mousePos = event.pos()
# print(event.pos())
def mouseMoveEvent(self, event):
if event.buttons() == Qt.LeftButton:
# move the box
self.move(self.pos() + event.pos() - self.mousePos)
# print(event.pos())
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
print(event.pos())
def paintEvent(self, event):
painter = QPainter()
painter.setBrush(Qt.red)
# painter.setPen(qRgb(200,0,0))
painter.drawLine(10, 10, 200, 200)
This is my custom class for the QTabwigdet (since I need to control and track the position of 2 QLabels whenever the user add/insert a new Tab)
class DynamicTab(QWidget):
def __init__(self):
super(DynamicTab, self).__init__()
# self.count = 0
self.setMouseTracking(True)
self.setAcceptDrops(True)
self.bool = True
self.layout = QVBoxLayout(self)
self.label = QLabel()
self.layout.addChildWidget(self.label)
self.icon1 = DraggableLabel(parent=self)
#pixmap for icon 1
pixmap = QPixmap('icon1.png')
# currentTab.setLayout(QVBoxLayout())
# currentTab.layout.setWidget(QRadioButton())
self.icon1.setPixmap(pixmap)
self.icon1.setScaledContents(True)
self.icon1.setFixedSize(20, 20)
self.icon2 = DraggableLabel(parent=self)
pixmap = QPixmap('icon1.png')
# currentTab.setLayout(QVBoxLayout())
# currentTab.layout.setWidget(QRadioButton())
self.icon2.setPixmap(pixmap)
self.icon2.setScaledContents(True)
self.icon2.setFixedSize(20, 20)
#self.label.move(event.x() - self.label_pos.x(), event.y() - self.label_pos.y())
MainWindow and main method:
class UI_MainWindow(QMainWindow):
def __init__(self):
super(UI_MainWindow, self).__init__()
self.setWindowTitle("QHBoxLayout")
self.PictureTab = QTabWidget
def __setupUI__(self):
# super(UI_MainWindow, self).__init__()
self.setWindowTitle("QHBoxLayout")
loadUi("IIML_test2.ui", self)
self.tabChanged(self.PictureTab)
# self.tabChanged(self.tabWidget)
self.changeTabText(self.PictureTab, index=0, TabText="Patient1")
self.Button_ImportNew.clicked.connect(lambda: self.insertTab(self.PictureTab))
# self.PictureTab.currentChanged.connect(lambda: self.tabChanged(QtabWidget=self.PictureTab))
# self.tabWidget.currentChanged.connect(lambda: self.tabChanged(QtabWidget=self.tabWidget))
def tabChanged(self, QtabWidget):
QtabWidget.currentChanged.connect(lambda : print("Tab was changed to ", QtabWidget.currentIndex()))
def changeTabText(self, QTabWidget, index, TabText):
QTabWidget.setTabText(index, TabText)
def insertTab(self, QtabWidget):
# QFileDialog.getOpenFileNames(self, 'Open File', '.')
QtabWidget.addTab(DynamicTab(), "New Tab")
# get number of active tab
count = QtabWidget.count()
# change the view to the last added tab
currentTab = QtabWidget.widget(count-1)
QtabWidget.setCurrentWidget(currentTab)
pixmap = QPixmap('cat.jpg')
#currentTab.setLayout(QVBoxLayout())
#currentTab.layout.setWidget(QRadioButton())
# currentTab.setImage("cat.jpg")
currentTab.label.setPixmap(pixmap)
currentTab.label.setScaledContents(True)
currentTab.label.setFixedSize(self.label.width(), self.label.height())
tracker = MouseTracker(currentTab.label)
tracker.positionChanged.connect(self.on_positionChanged)
self.label_position = QtWidgets.QLabel(currentTab.label, alignment=QtCore.Qt.AlignCenter)
self.label_position.setStyleSheet('background-color: white; border: 1px solid black')
currentTab.label.show()
# print(currentTab.label)
#QtCore.pyqtSlot(QtCore.QPoint)
def on_positionChanged(self, pos):
delta = QtCore.QPoint(30, -15)
self.label_position.show()
self.label_position.move(pos + delta)
self.label_position.setText("(%d, %d)" % (pos.x(), pos.y()))
self.label_position.adjustSize()
# def SetupUI(self, MainWindow):
#
# self.setLayout(self.MainLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
UI_MainWindow = UI_MainWindow()
UI_MainWindow.__setupUI__()
widget = QtWidgets.QStackedWidget()
widget.addWidget(UI_MainWindow)
widget.setFixedHeight(900)
widget.setFixedWidth(1173)
widget.show()
try:
sys.exit(app.exec_())
except:
print("Exiting")
My concept: I have a DynamicTab (QTabWidget) which acts as a picture opener (whenever the user press Import Now). The child of this Widget are 3 Qlabels: self.label is the picture it self and two other Qlabels are the icon1 and icon2 which I'm trying to interact/drag with (Draggable Label)
My Problem: I'm trying to track my mouse movement and custom the painter to paint accordingly. I'm trying that out by telling the painter class to paint whenever I grab the label and move it with my mouse (Hence, draggable). However, I can only track the mouse position inside the main QLabel (the main picture) whenever I'm not holding or clicking my left mouse.
Any help will be appreciated here.
Thank you guys.
Painting can only happen within the widget rectangle, so you cannot draw outside the boundaries of DraggableLabel.
The solution is to create a further custom widget that shares the same parent, and then draw the line that connects the center of the other two.
In the following example I install an event filter on the two draggable labels which will update the size of the custom widget based on them (so that its geometry will always include those two geometries) and call self.update() which schedules a repainting. Note that since the widget is created above the other two, it might capture mouse events that are intended for the others; to prevent that, the Qt.WA_TransparentForMouseEvents attribute must be set.
class Line(QWidget):
def __init__(self, obj1, obj2, parent):
super().__init__(parent)
self.obj1 = obj1
self.obj2 = obj2
self.obj1.installEventFilter(self)
self.obj2.installEventFilter(self)
self.setAttribute(Qt.WA_TransparentForMouseEvents)
def eventFilter(self, obj, event):
if event.type() in (event.Move, event.Resize):
rect = self.obj1.geometry() | self.obj2.geometry()
corner = rect.bottomRight()
self.resize(corner.x(), corner.y())
self.update()
return super().eventFilter(obj, event)
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(painter.Antialiasing)
painter.setPen(QColor(200, 0, 0))
painter.drawLine(
self.obj1.geometry().center(),
self.obj2.geometry().center()
)
class DynamicTab(QWidget):
def __init__(self):
# ...
self.line = Line(self.icon1, self.icon2, self)
Notes:
to simplify things, I only use resize() (not setGeometry()), in this way the widget will always be placed on the top left corner of the parent and we can directly get the other widget's coordinates without any conversion;
the custom widget is placed above the other two because it is added after them; if you want to place it under them, use self.line.lower();
the painter must always be initialized with the paint device argument, either by using QPainter(obj) or painter.begin(obj), otherwise no painting will happen (and you'll get lots of errors in the output);
do not use layout.addChildWidget() (which is used internally by the layout), but the proper addWidget() function of the layout;
the stylesheet border syntax can be shortened with border: 2px inset rgb(238, 0, 0);;
the first lines of insertTab could be simpler: currentTab = DynamicTab() QtabWidget.addTab(currentTab, "New Tab");
currentTab.label.setFixedSize(self.label.size());
QMainWindow is generally intended as a top level widget, it's normally discouraged to add it to a QStackedWidget; note that if you did that because of a Youtube tutorial, that tutorial is known for suggesting terrible practices (like the final try/except block) which should not be followed;
only classes and constants should have capitalized names, not variables and functions which should always start with a lowercase letter;

Widget dragging not working when hovering over the widget itself

I'm having an issue where I have implemented a dragging feature for widgets using eventFilter(), but it seems when I drag towards the right and my cursor hovers over the widget which in my case is a QPushButton it seems to stop tracking until I hover out of the widget
How would I fix this?
class widget1(QWidget):
def __init__(self):
super().__init__()
self.widget = QPushButton("button0", self)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.createbutton = QPushButton('+', self)
self.createbutton.setGeometry(5, 5, 15, 15)
self.createbutton.clicked.connect(self.createWidget)
self.show()
def createWidget(self):
self.new_widget = widget1()
self.new_widget.setParent(self)
self.new_widget.show()
self.new_widget.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QEvent.MouseMove:
MousePos = QPoint(event.pos())
if event.buttons() == Qt.LeftButton:
source.move(source.x() + MousePos.x(), source.y() + MousePos.y())
return super(MainWindow, self).eventFilter(source, event)
Basically I have a class with a widget in it called widget1() (This will be my custom widget in the future) I am then adding it dynamically to the window every time I press self.createbutton using .show() instead of using a layout, then it installs the event filter to it so that it can allow for dragging.
The event filter is receiving events for the widget1 instance but not for its child widgets (i.e. the QPushButton). A quick fix is to invoke QMouseEvent.ignore for the QPushButton's mouseMoveEvent so the mouse event will be propagated up to the parent widget and received in the event filter.
class widget1(QWidget):
def __init__(self):
super().__init__()
self.widget = QPushButton("button0", self)
self.widget.mouseMoveEvent = lambda event: event.ignore()

A toggle button which once clicked initiates full screen window and if clicked again goes back to the normal state

My code in pyqt is simple:
QtCore.QObject.connect(self.pushButton, QtCore.SIGNAL("clicked()"), self.add_entry)
def add_entry(self):
if QtCore.Qt.WindowFullScreen:
MainWindow.showNormal()
else :
MainWindow.showMaximized()
The toggle button when clicked however does its job it's showing full screen but on clicking again it's not reverting back to normal screen mode.
You are mixing things together. QtCore.Qt.WindowFullScreen is constant value - therefore your condition is always true. And at second - .showMaximized window method switch window object to the Qt.WindowMaximized state.
Here is how you should change it:
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
cb = QtGui.QPushButton('Switch', self)
cb.move(20, 20)
cb.clicked.connect(self.add_entry)
self.setGeometry(300, 300, 250, 150)
self.show()
def add_entry(self):
if self.windowState() & QtCore.Qt.WindowFullScreen:
self.showNormal()
else:
self.showFullScreen()

PyQT4 - Replacing widgets on main window

I`m new in PyQT4 and I've faced the problem below.
Some application must collect users' data on one screen
and show next screen on button click.
main.py
app = QtGui.QApplication(sys.argv)
#here I create main window with inherited class
w = SAN_MainWindow()
#and apply some basic geometry
w.SetUI()
#here first screen rendered and button event applyed
w.ApplyWidget( SAN_Intro() )
w.show()
sys.exit(app.exec_())
Rendering of first widget occurs correctly.
SAN_MainWindow.py:
class SAN_MainWindow(QtGui.QMainWindow):
def __init__(self ):
super(SAN_MainWindow, self).__init__()
def SetUI(self):
self.setObjectName(_fromUtf8("MainWindow"))
self.resize(789, 602)
self.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
self.central_widget = QtGui.QWidget(self)
self.central_widget.setObjectName(_fromUtf8("central_widget"))
self.setCentralWidget(self.central_widget)
def ApplyWidget( self, widget ):
self.active_widget = widget
self.active_widget.Setup(self.central_widget)
self.active_widget.SetControls( self )
def RemoveActiveWidget( self ):
self.active_widget.widget().setParent(None)
def ShowInstruction( self ):
self.RemoveActiveWidget()
self.ApplyWidget( SAN_Instruction() )
#self.centralWidget().update() <- Problem
SAN_Intro.py:
class SAN_Intro(object):
def __init__(self):
super(SAN_Intro, self).__init__()
def Setup(self, widget):
self.gridLayoutWidget = QtGui.QWidget(widget)
self.gridLayoutWidget.setGeometry(QtCore.QRect(80, 30, 638, 451))
self.gridLayoutWidget.setObjectName(_fromUtf8("gridLayoutWidget"))
self.gridLayout = QtGui.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setContentsMargins(-1, 16, 16, -1)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
#a lot of form inputs instructions....
def SetControls( self, main_window ):
QtCore.QObject.connect(self.pushButton, QtCore.SIGNAL("clicked()"), main_window.ShowInstruction)
def widget(self):
return self.gridLayoutWidget
SAN_Instruction is almost the same as SAN_Intro, but with different commands in Setup:
class SAN_Instruction(object):
def __init__(self):
super(SAN_Instruction, self).__init__()
def Setup(self, widget):
self.verticalLayoutWidget = QtGui.QWidget(widget)
self.verticalLayoutWidget.setGeometry(QtCore.QRect(40, 20, 591, 521))
self.verticalLayoutWidget.setObjectName(_fromUtf8("verticalLayoutWidget"))
self.verticalLayout = QtGui.QVBoxLayout(self.verticalLayoutWidget)
self.verticalLayout.setMargin(0)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
#a lot of form inputs instructions....
def SetControls( self, main_window ):
pass
def widget(self):
return self.verticalLayoutWidget
AND HERE IS QUESTION:
How must I show the second (generated on fly with SAN_Instruction) widget?
I tried to update/show/repaint mainwindow/centralwidget/parent.
But all I've got on button click is self.RemoveActiveWidget().
Maybe my concept is incorrect?
Thanx in advance
Well, your code isn't complete but i would advise you to take advantage of the hide() method.
You can define this in your button function, so that all the widgets you do not want to appear get hidden, and on the same function call the widgets you want to appear on their place. I use to do that way because it makes my projects more organized and easy to track and debug. Furthermore, this also allows you to make that widgets visible again if you need, with the show() method.
You can use a "QtGui.QStackedWidget()". You Split your Window to many Frames, add each frame to the QStackedWidget() and use function setCurrentWidget() to choose which frame to display (switch between frames).
self.central_widget.addWidget(self.mainFrame)
self.central_widget.setCurrentWidget(self.mainFrame)

Categories