def updateEditorGeometry(self, editor, option, index): if editor is None: return fm = editor.fontMetrics() # get the original size of the edit widget opt = QStyleOptionViewItem(option) self.initStyleOption(opt, index) opt.showDecorationSelected = True opt.decorationSize = QSize( 0, 0) # We want the editor to cover the decoration style = QApplication.style() initial_geometry = style.subElementRect( QStyle.SubElement.SE_ItemViewItemText, opt, None) orig_width = initial_geometry.width() # Compute the required width: the width that can show all of the current value if hasattr(self, 'get_required_width'): new_width = self.get_required_width(editor, style, fm) else: # The line edit box seems to extend by the space consumed by an 'M'. # So add that to the text text = self.displayText(index.data(Qt.ItemDataRole.DisplayRole), QLocale()) + 'M' srect = style.itemTextRect(fm, editor.geometry(), Qt.AlignmentFlag.AlignLeft, False, text) new_width = srect.width() # Now get the size of the combo/spinner arrows and add them to the needed width if isinstance(editor, (QComboBox, QDateTimeEdit)): r = style.subControlRect(QStyle.ComplexControl.CC_ComboBox, QStyleOptionComboBox(), QStyle.SubControl.SC_ComboBoxArrow, editor) new_width += r.width() elif isinstance(editor, (QSpinBox, QDoubleSpinBox)): r = style.subControlRect(QStyle.ComplexControl.CC_SpinBox, QStyleOptionSpinBox(), QStyle.SubControl.SC_SpinBoxUp, editor) new_width += r.width() # Compute the maximum we can show if we consume the entire viewport pin_view = self.table_widget.pin_view is_pin_view, p = False, editor.parent() while p is not None: if p is pin_view: is_pin_view = True break p = p.parent() if is_pin_view: max_width = pin_view.horizontalScrollBar().geometry().width() else: view = self.table_widget max_width = view.horizontalScrollBar().geometry().width( ) - view.verticalHeader().width() # What we have to display might not fit. If so, adjust down new_width = new_width if new_width < max_width else max_width # See if we need to change the editor's geometry if new_width <= orig_width: delta_x = 0 delta_width = 0 else: # Compute the space available from the left edge of the widget to # the right edge of the displayed table (the viewport) and the left # edge of the widget to the left edge of the viewport. These are # used to position the edit box space_left = initial_geometry.x() space_right = max_width - space_left if editor.layoutDirection() == Qt.LayoutDirection.RightToLeft: # If language is RtL, align to the cell's right edge if possible cw = initial_geometry.width() consume_on_left = min(space_left, new_width - cw) consume_on_right = max(0, new_width - (consume_on_left + cw)) delta_x = -consume_on_left delta_width = consume_on_right else: # If language is LtR, align to the left if possible consume_on_right = min(space_right, new_width) consume_on_left = max(0, new_width - consume_on_right) delta_x = -consume_on_left delta_width = consume_on_right - initial_geometry.width() initial_geometry.adjust(delta_x, 0, delta_width, 0) editor.setGeometry(initial_geometry)
def eventFilter(self, obj, e): 'Redirect key presses from the popup to the widget' widget = self.parent() if widget is None or sip.isdeleted(widget): return False etype = e.type() if obj is not self: return QObject.eventFilter(self, obj, e) # self.debug_event(e) if etype == QEvent.Type.KeyPress: try: key = e.key() except AttributeError: return QObject.eventFilter(self, obj, e) if key == Qt.Key.Key_Escape: self.hide() e.accept() return True if key == Qt.Key.Key_F4 and e.modifiers( ) & Qt.KeyboardModifier.AltModifier: self.hide() e.accept() return True if key in (Qt.Key.Key_Enter, Qt.Key.Key_Return): # We handle this explicitly because on OS X activated() is # not emitted on pressing Enter. idx = self.currentIndex() if idx.isValid(): self.item_chosen(idx) self.hide() e.accept() return True if key == Qt.Key.Key_Tab: idx = self.currentIndex() if idx.isValid(): self.item_chosen(idx) self.hide() elif self.model().rowCount() > 0: self.next_match() e.accept() return True if key in (Qt.Key.Key_PageUp, Qt.Key.Key_PageDown): # Let the list view handle these keys return False if key in (Qt.Key.Key_Up, Qt.Key.Key_Down): self.next_match(previous=key == Qt.Key.Key_Up) e.accept() return True # Send to widget widget.eat_focus_out = False widget.keyPressEvent(e) widget.eat_focus_out = True if not widget.hasFocus(): # Widget lost focus hide the popup self.hide() if e.isAccepted(): return True elif ismacos and etype == QEvent.Type.InputMethodQuery and e.queries( ) == (Qt.InputMethodQuery.ImHints | Qt.InputMethodQuery.ImEnabled) and self.isVisible(): # In Qt 5 the Esc key causes this event and the line edit does not # handle it, which causes the parent dialog to be closed # See https://bugreports.qt-project.org/browse/QTBUG-41806 e.accept() return True elif etype == QEvent.Type.MouseButtonPress and hasattr( e, 'globalPos') and not self.rect().contains( self.mapFromGlobal(e.globalPos())): # A click outside the popup, close it if isinstance(widget, QComboBox): # This workaround is needed to ensure clicking on the drop down # arrow of the combobox closes the popup opt = QStyleOptionComboBox() widget.initStyleOption(opt) sc = widget.style().hitTestComplexControl( QStyle.ComplexControl.CC_ComboBox, opt, widget.mapFromGlobal(e.globalPos()), widget) if sc == QStyle.SubControl.SC_ComboBoxArrow: QTimer.singleShot(0, self.hide) e.accept() return True self.hide() e.accept() return True elif etype in (QEvent.Type.InputMethod, QEvent.Type.ShortcutOverride): QApplication.sendEvent(widget, e) return False