Search code examples
popupresizepysidedirection

PySide: Changing resize/growth direction of a window


I have popup being created along the edge of a window and I'd like it to expand the popup as the user types into its text field. This currently works, but the window is expanding to the right. Instead, I'd like the popup to expand to the left (and keep the right edge anchored in place).

My closest example that's kind of working is below. In it, I'm getting the size of the popup with every text input and then moving the popup based on its new size. I feel like this should work, but its not.

On the first text input the popup jumps to the left edge of my screen (x transformation only). On the second text input the popup jumps back to its original position. On a third text input the popup jumps back to the left edge of the screen. On the fourth input... You get the idea. I'd also like to mention that the overall growth of the window looks like it's growing from the center of the popup and not from the right edge.

I've noticed that after the button is clicked it remains highlighted until my mouse passes over it. Could this contributing to the problem?

Any thoughts or a better way to achieve this effect would be great, thanks!

from PySide import QtCore, QtGui
import maya.OpenMayaUI as mui
from shiboken import wrapInstance 

def get_parent():
    ptr = mui.MQtUtil.mainWindow()
    return wrapInstance( long( ptr ), QtGui.QWidget )

############################################
class Tool_Window(QtGui.QDialog):
    def __init__(self, parent = get_parent() ):
        super(Tool_Window, self).__init__(parent)

        # Commands
        self.move_UI()
        self.create_gui()
        self.create_layout()
        self.create_connections()

    #-------------------------------------------
    def create_gui(self):
        self.button1 = QtGui.QPushButton()
        self.button1.setMaximumWidth(50)
        self.button2 = QtGui.QPushButton()
        self.button2.setMaximumWidth(50)
        self.button3 = QtGui.QPushButton()
        self.button3.setMaximumWidth(50)

    #-------------------------------------------
    def create_layout(self):
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.button1)
        layout.addWidget(self.button2)
        layout.addWidget(self.button3)
        blank_layout = QtGui.QVBoxLayout()
        main_layout = QtGui.QHBoxLayout( self )
        main_layout.addLayout(blank_layout)
        main_layout.addLayout(layout)
        layout.addStretch()
        self.setLayout(layout)

    #-------------------------------------------
    def move_UI( self ):
        ''' Moves the UI to the cursor's position '''
        pos = QtGui.QCursor.pos()
        self.move(pos.x()+20, pos.y()+15)

    #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
    def create_connections(self):
        # Left click
        self.button1.clicked.connect( self.on_left_click1 )
        self.button2.clicked.connect( self.on_left_click2 )
        self.button3.clicked.connect( self.on_left_click3 )

        # Right click delete
        delete = QtGui.QAction(self)
        delete.setText("remove")
        delete.triggered.connect(self.remove_button)
        self.addAction(delete)

    #-----#-----#-----#-----#-----#-----#-----#-----#-----#
    def remove_button(self):
        self.deleteLater()

    def on_left_click1(self):
        self.popup = Popup_Window( self, self.button1 )                    # Passing button in so I can get it's position
        self.popup.show()

    def on_left_click2(self):
        self.popup = Popup_Window( self, self.button2 )      
        self.popup.show()

    def on_left_click3(self):
        self.popup = Popup_Window( self, self.button3 )      
        self.popup.show()

############################################
class Popup_Window( QtGui.QDialog ):
    def __init__( self, toolWindow, button ):
        super( Popup_Window, self ).__init__()

        self.__popup_filter = ClosePopupFilter()
        self.installEventFilter(self.__popup_filter) 
        self.setWindowFlags(QtCore.Qt.Popup)
        '''
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint |
                            QtCore.Qt.WindowStaysOnTopHint |
                            QtCore.Qt.CustomizeWindowHint |
                            QtCore.Qt.Tool)    
        '''
        self.button_pos = button       
        self.toolWindow = toolWindow                                        
        self.setAttribute( QtCore.Qt.WA_DeleteOnClose )
        self.resize(100, 100) 

        # Commands
        self.create_gui()
        self.create_layout()
        self.create_connections()
        self.move_UI()        

    #-------------------------------------------
    def move_UI( self ):                                                        # Method that I use to place the popup window initially  
        self.line_edit.setFocus() 
        # Get button position                                          
        self.btn_global_point = self.button_pos.mapToGlobal(self.button_pos.rect().topLeft())  
        print self.btn_global_point
        # Get window position  
        self.win_global_point = self.toolWindow.mapToGlobal(self.rect().topLeft()) 
        print self.win_global_point
        # Get popup Size
        self.popup_size = self.mapToGlobal(self.rect().topRight())
        print self.popup_size
        # Move the window
        self.move((self.win_global_point.x()-self.popup_size.x()), self.btn_global_point.y())

    #-------------------------------------------
    def create_gui( self ):
        ''' Visible GUI stuff '''
        self.my_label = QtGui.QLabel("default text")
        self.line_edit = QtGui.QLineEdit()
        self.line_edit.setMaxLength( 30 )
        self.push_btn = QtGui.QPushButton( "Hey" )
        self.push_btn.setMaximumWidth( 30 )

    #-------------------------------------------
    def create_layout( self ):
        self.button_layout = QtGui.QVBoxLayout()
        self.button_layout.addWidget( self.my_label )
        self.button_layout.addWidget( self.line_edit )
        self.button_layout.addWidget( self.push_btn )
        #self.button_layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(self.button_layout)

    #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
    def create_connections( self ):
        self.line_edit.textChanged.connect( self.on_text_changed )

    #-----#-----#-----#-----#-----#-----#-----#-----#-----#
    def on_text_changed( self ):
        typed_name = self.line_edit.text()
        self.my_label.setText(typed_name) 
        self.move_UI()                                                          # I reuse the move method to move the ui on text edit

############################################
class  ClosePopupFilter(QtCore.QObject):
    ''' Close popup window '''
    def  eventFilter(self, target, event):
        if event.type() == QtCore.QEvent.WindowDeactivate:
            target.close()
        return  False



if __name__ == '__main__':
    # Things to fix PySide Maya bug
    try:
        test_ui.close()
        test_ui.deleteLater()
    except:
        pass

    test_ui = Tool_Window()
    test_ui.show()

    try:
        test_ui.show()
    except:
        test_ui.close()
        test_ui.deleteLater()

Solution

  • I've trimmed a little bit the code you provided in the OP and tried to put together an example that address your particular issue.

    Some of the modifications I've done to the code:

    • trigger the move_UI within the resizeEvent of the Popup_Window ;
    • corrected the method move_UI in Popup_Window so that the popup does not blink on and off to one location to the other ;
    • moved the eventFilter directly within the Popup_Window ;
    • merged the functions handling the events for the button.clicked.connect in Tool_Window.

    The solution is not perfect, there is still a little 'flicker' of the popup window when it is expanding and moving in Ubuntu. This is not very apparent in Windows7 however. If I think of another solution, I'll do an update.

    from PySide import QtCore, QtGui
    import sys
    
    class Tool_Window(QtGui.QDialog): #=============================================
        def __init__(self, parent=None): 
            super(Tool_Window, self).__init__(parent)
    
            self.create_gui()
            self.create_layout()
            self.create_connections()
    
        def create_gui(self): #=====================================================
    
            self.button1 = QtGui.QPushButton('Button 1')
            self.button2 = QtGui.QPushButton('Button 2')
            self.button3 = QtGui.QPushButton('Button 3')
    
        def create_layout(self): #==================================================
    
            layout = QtGui.QVBoxLayout()
            layout.addWidget(self.button1)
            layout.addWidget(self.button2)
            layout.addWidget(self.button3)
            self.setLayout(layout)
    
        def create_connections(self): #=============================================
    
            self.button1.clicked.connect(self.on_lef_click )
            self.button2.clicked.connect(self.on_lef_click )
            self.button3.clicked.connect(self.on_lef_click )
    
        def on_lef_click(self): #===================================================
    
            button = self.sender()
    
            self.popup = Popup_Window(self, button)
            self.popup.show()
    
    
    class Popup_Window( QtGui.QWidget ): #==========================================
        def __init__( self, parent, button):
            super(Popup_Window, self ).__init__(parent)
    
            self.setWindowFlags(QtCore.Qt.Popup)
    
            self.button = button       
            self.parent = parent
    
            self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
    
            #---- set layout key dimension ----
    
            self.min_width = 100
            self.frame_thick = 10
            self.setFixedWidth(self.min_width)
    
            #---- init GUI ----
    
            self.installEventFilter(self)
    
            self.create_gui()
            self.create_layout()
            self.create_connections()
            self.move_UI()
    
            self.line_edit.setFocus()
    
    
        def create_gui( self ): #===================================================
    
            self.my_label = QtGui.QLabel("default text")
            self.my_label.setAlignment(QtCore.Qt.AlignRight)
            self.line_edit = QtGui.QLineEdit()
            self.line_edit.setAlignment(QtCore.Qt.AlignRight)
            self.line_edit.setMaxLength( 50 )
    
    
        def create_layout( self ): #================================================
    
            button_layout = QtGui.QGridLayout()
    
            button_layout.addWidget(self.my_label, 0, 0)
            button_layout.addWidget(self.line_edit, 1, 0)
    
            button_layout.setContentsMargins(self.frame_thick, self.frame_thick,
                                             self.frame_thick, self.frame_thick)
    
            self.setLayout(button_layout)
    
    
        def create_connections(self): #=============================================
            self.line_edit.textChanged.connect(self.line_edit_text_changed)
    
    
        def line_edit_text_changed(self, text):  #==================================
    
            #---- set the text in label ----
    
            self.my_label.setText(text)
    
            #---- determine new width of popup ----
    
            fm = self.line_edit.fontMetrics()
            txtWidth = fm.boundingRect(text).width() + 10 # Padding to the left.
                                                          # Value is arbitrary.
    
            newWidth = max(txtWidth + self.frame_thick * 2, self.min_width)
    
            self.setFixedWidth(newWidth)
    
    
        def eventFilter(self, source, event): #=====================================
    
            if event.type() == QtCore.QEvent.WindowDeactivate:
                self.close()
    
            return QtGui.QWidget.eventFilter(self, source, event)
    
        def resizeEvent(self, event): #=============================================
    
            self.move_UI()        
            QtGui.QWidget.resizeEvent(self, event)
    
    
        def move_UI(self): # =======================================================
    
            y_btn = self.button.mapToGlobal(QtCore.QPoint(0, 0)).y()        
            x_win = self.parent.mapToGlobal(QtCore.QPoint(0, 0)).x()
    
            w_pop = self.frameGeometry().width()
    
            x = x_win - w_pop - 12 # 12 is an arbitrary value.
            y = y_btn
    
            self.move(QtCore.QPoint(x, y))
    
    
    if __name__ == '__main__':
    
        app = QtGui.QApplication(sys.argv)
        instance_1 = Tool_Window()
        instance_1.show()
        sys.exit(app.exec_())
    

    This results in:

    enter image description here

    Update (2015-07-30): Popup shrink to the width of the content

    I've extended the example so that now the popup starts with a minimum with of 100 that extends when the width of the text goes over this value. Also, the width of the popup will shrink, down to the minimum value of 100, if text is removed.

    This is done by calculating the width of the text in QLineEdit when its content change and using the value to assign a fixed width to the popup window in the method line_edit_text_changed.