4

I have a Qt5 application mainly driven by context menu.

Right now I have the standard structure with menu(s), submenu(s) and actions.

I would like to add, in place of a submenu, a small dialog with a few input widgets, something like this:

Context-menu mockup

Is there any (possibly simple) way to get this?

I know I can open a normal dialog from popup, but that is not what I mean. I would like to have normal submenu behavior, with chance to go back to parent menu... if possible.

Note: I'm actually using PyQt5, but I think this is a more general Qt question.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
ZioByte
  • 2,690
  • 1
  • 32
  • 68
  • [`QWidgetAction`](https://doc.qt.io/qt-5/qwidgetaction.html) might be what you're looking for. – G.M. May 18 '19 at 19:10

1 Answers1

2

Following @G.M. advice I was able to partially solve my problem.

My code current code looks like:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *


class ActionFont(QWidgetAction):
    def __init__(self, parent: QWidget, target: QPlainTextEdit):
        super(ActionFont, self).__init__(parent)
        self.setIcon(QIcon("font-face.svg"))
        self.setText("Face")
        w = QFontComboBox()
        w.currentFontChanged.connect(self.doit)
        self.setDefaultWidget(w)
        self.cursor = target.textCursor()
        self.char_format = self.cursor.charFormat()
        font = self.char_format.font()
        w.setCurrentFont(font)
        # self.triggered.connect(self.doit)

    def doit(self, font):
        self.char_format.setFont(font)
        self.cursor.setCharFormat(self.char_format)


class ActionSize(QWidgetAction):
    def __init__(self, parent: QWidget, target: QPlainTextEdit):
        super(ActionSize, self).__init__(parent)
        self.setIcon(QIcon("font-size.svg"))
        self.setText("Size")
        self.has_changed = False
        w = QSpinBox()
        self.setDefaultWidget(w)
        self.cursor = target.textCursor()
        self.char_format = self.cursor.charFormat()
        font = self.char_format.font()
        size = font.pointSize()
        w.setRange(6, 100)
        w.setValue(size)
        w.valueChanged.connect(self.doit)
        w.editingFinished.connect(self.quit)

    def doit(self, size):
        print(f'ActionSize.doit({size})')
        self.char_format.setFontPointSize(size)
        self.cursor.setCharFormat(self.char_format)
        self.has_changed = True

    def quit(self):
        print(f'ActionSize.quit()')
        if self.has_changed:
            print(f'ActionSize.quit(quitting)')


class Window(QMainWindow):
    def __init__(self, parent=None):
        from lorem import text
        super().__init__(parent)
        self.text = QPlainTextEdit(self)
        self.setCentralWidget(self.text)

        self.text.setContextMenuPolicy(Qt.CustomContextMenu)
        self.text.customContextMenuRequested.connect(self.context)

        self.text.appendPlainText(text())

        self.setGeometry(100, 100, 1030, 800)
        self.setWindowTitle("Writer")

    def context(self, pos):
        m = QMenu(self)
        w = QComboBox()
        w.addItems(list('ABCDE'))
        wa = QWidgetAction(self)
        wa.setDefaultWidget(w)
        m.addAction('Some action')
        m.addAction(wa)
        m.addAction('Some other action')
        sub = QMenu(m)
        sub.setTitle('Font')
        sub.addAction(ActionFont(self, self.text))
        sub.addAction(ActionSize(self, self.text))
        m.addMenu(sub)

        pos = self.mapToGlobal(pos)
        m.move(pos)
        m.show()


app = QApplication([])

w = Window()
w.show()

app.exec()

This works with a few limitations:

  • I have been able to add just a single widget using setDefaultWidget(), if I try to add a fill QWidget or a container (e.g.: QFrame) nothing appears in menu.
  • I have therefore not been able to prepend an icon (or a QLabel) to the widget.
  • Widget does not behave like a menu item (it does not close when activated); I tried to overcome that as implemented in ActionSize, but looks rather kludgy and I'm unsure if it's the right way to go.

I will therefore not accept my own answer in hope someone can refine it enough to be generally useful.

ZioByte
  • 2,690
  • 1
  • 32
  • 68