Cryptite
Cryptite

Reputation: 1476

Properly Positioning Popup Widgets in PyQt

his has plagued me for eons, mostly due to how many combinations of methodologies there are for moving widgets and whatnot. Essentially I have a simple widget that I'd like to be able to pop up in specific areas of my app. Problem is I can never seem to get it to pop up where I want it. Additionally, I'd like to set it up in a way where I can adjust the "pointer" side of it based on whether it's popping up to point at a widget in the top-left of the app versus, say, the bottom-right.

Ideally, I'd be able to place the popup nearly adjacent to the edges of the parent widget, and anchor it based on where it is. Here's what I've been trying.

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys

class popup(QWidget):
    def __init__(self, parent = None, widget=None):    
        QWidget.__init__(self, parent)
        layout = QGridLayout(self)
        button = QPushButton("Very Interesting Text Popup. Here's an arrow   ^")
        layout.addWidget(button)
        self.move(widget.rect().bottomLeft())

class Window(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.button = QPushButton('Hit this button to show a popup', self)
        self.button.clicked.connect(self.handleOpenDialog)
        self.button.move(250, 50)
        self.resize(600, 200)

    def handleOpenDialog(self):
        self.popup = popup(self, self.button)
        self.popup.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = Window()
    win.show()
    sys.exit(app.exec_())

This code generates a button that's randomly in the middle of the widget. What I'm trying to get is, in this example, the popup to show under the button with its "pivot" in the top right such that the arrow in the popup button would be pointing to the bottom right corner of the widget. However it's popping up in the top left of the Window instead. In all of my messing around with .move, .setGeometry, and playing with QRect, I can't for the life of me figure this out. Huge kudos to whoever can lend a hand. Thanks!

Upvotes: 5

Views: 13674

Answers (2)

jramm
jramm

Reputation: 6655

I know this is old, but I was searching for this recently and this is the best answer; I have a useful addition (for anyone else searching for this recipe!)

I implemented it as a mixin, which I think gives more flexibility to your dialogs:

class PopupDialogMixin(object):  # will not work (with PySide at least) unless implemented as 'new style' class. I.e inherit from object
    def makePopup(callWidget):
        """
        Turns the dialog into a popup dialog.
        callWidget is the widget responsible for calling the dialog (e.g. a toolbar button)
        """
        self.setContentsMargins(0,0,0,0)
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Popup)
        self.setObjectName('ImportDialog')

        # Move the dialog to the widget that called it
        point = callWidget.rect().bottomRight()
        global_point = callWidget.mapToGlobal(point)
        self.move(global_point - QtCore.QPoint(self.width(), 0))

Your custom dialog would then inherit from both QtCore.QDialog and PopupDialogMixin. This gives you the option to use your dialog in the 'normal' way or make it a popup dialog. e.g:

dlg = MyDialog(self)
dlg.makePopup(self.myButton)

I think implementing it as a mixin gives a number of benefits:

  • No need to write the 'popup' code for each custom dialog you want as a popup
  • 'Default' behaviour of the dialog is preserved - e.g. if you want to reuse it somewhere else as a 'regular' dialog, you just use it like normal
  • No need to pass anything extra to __init__ other than parent.

Upvotes: 5

Eric Hulser
Eric Hulser

Reputation: 4022

Here you go - the comments kind of explain the logic behind it - since the question is an example and about the positioning, I kept the rest of the code the same except the popup class, but just to mention cause its a pet peeve - you shouldn't import * (ever) but especially with something as big as PyQt4.QtCore/QtGui...

    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    import sys

    class popup(QWidget):
        def __init__(self, parent = None, widget=None):    
            QWidget.__init__(self, parent)
            layout = QGridLayout(self)
            button = QPushButton("Very Interesting Text Popup. Here's an arrow   ^")
            layout.addWidget(button)

            # adjust the margins or you will get an invisible, unintended border
            layout.setContentsMargins(0, 0, 0, 0)

            # need to set the layout
            self.setLayout(layout)
            self.adjustSize()

            # tag this widget as a popup
            self.setWindowFlags(Qt.Popup)

            # calculate the botoom right point from the parents rectangle
            point        = widget.rect().bottomRight()

            # map that point as a global position
            global_point = widget.mapToGlobal(point)

            # by default, a widget will be placed from its top-left corner, so
            # we need to move it to the left based on the widgets width
            self.move(global_point - QPoint(self.width(), 0))

    class Window(QWidget):
        def __init__(self):
            QWidget.__init__(self)
            self.button = QPushButton('Hit this button to show a popup', self)
            self.button.clicked.connect(self.handleOpenDialog)
            self.button.move(250, 50)
            self.resize(600, 200)

        def handleOpenDialog(self):
            self.popup = popup(self, self.button)
            self.popup.show()

    if __name__ == '__main__':
        app = QApplication(sys.argv)
        win = Window()
        win.show()
        sys.exit(app.exec_())

Upvotes: 2

Related Questions