PyQt5: QAction -- QTextEdit.paste not working with QTabWidget?

Proposed Solution:

Intro:

Okay, finally, after four days of research and testing, I have a solution that seems to work

While the suggestion by @musicamante was interesting, it only allowed pasting into the first tab created. This, in fact, was a recurrent problem for the many "solutions" and "examples" I found during my research: They only pasted text into either the first or last tab created.

However, it did highlight the basic problem.

The problem:

  1. The textArea needs to be defined in the addTab function, but this means it is not directly addressable by the editMenu actions in either the mainMenu or ToolBar
  2. As originally written, the textArea for each tab is not uniquely identifiable.

The solution:

Part 1:

To address the first part of the problem, I created a function to handle All the editMenu triggered.connect signals:

def editFunctions(self, Fnct):
        """ edit Menu/ToolBar clickHandlers"""
        print(self.tabs.currentIndex())
        match Fnct:
            case "selectall": self.currentEditor.selectAll()
            case "paste": self.currentEditor.paste()
            case "copy": self.currentEditor.copy()
            case "undo": self.currentEditor.undo()
            case "redo": self.currentEditor.redo()
            case "cut": self.currentEditor.cut()

And to call the above function I used, triggered.connect signals of the following form:

Paste.triggered.connect(partial(self.editFunctions, "paste"))

Part 2:

This still left the problem of the textArea for each tab not being uniquely identifiable.

Firstly, I added a new signal to the *tabwidget" and created a list of available text areas, called editors

    def __init__(self):
        super().__init__()

        # Define initial window geometry
        self.setWindowTitle('Mq Editor')
        self.setWindowIcon(QIcon(c.Quill))
        self.left = 0
        self.top = 0
        self.width = 1024
        self.height = 768
        self.setGeometry(self.left, self.top, self.width, self.height)

        # Setup a TabBar with movable closable tabs
        self.tabs = QTabWidget()
        self.tabs.tabCloseRequested.connect(self.closeTab)
        self.tabs.currentChanged.connect(self.changeTextEditor)
        self.tabs.setTabsClosable(True)
        self.tabs.setMovable(True)
        self.editors = []

        # Define the Layout
        widget = QWidget(self)
        self.setCentralWidget(widget)
        self.layout = QVBoxLayout(widget)

        self.initUi()

This required a new function to handle the signal and a new attribute instance:

    def changeTextEditor(self, index):
        """ Set currentEditor to index of current tab """
        self.currentEditor = self.editors[index]

It also meant editing the addTab and closeTab functions to udate the new attributes

    def addTab(self):
        """ Create Tabs """

        # Add tab with new editor instance
        textArea = QTextEdit()
        textArea.textChanged.connect(self.textChanged)
        textArea.createStandardContextMenu()

        # Update list of editors
        self.currentEditor = textArea
        self.editors.append(self.currentEditor)

        self.tabs.addTab(textArea, "Untitled")

        # Switch to new tab
        index = self.tabs.count() - 1
        self.tabs.setCurrentIndex(index)
        self.tabs.setTabToolTip(index, "Untitled")
    def closeTab(self, index):
        """ Close Tab Handler """
        self.tabs.removeTab(index)

        # Update editors list
        del self.editors[index]

Finally, I needed to replace any instances of self.textArea with self.currentEditor

Summary:

My solution probably needs some further refinement, but, as things stand at the moment, it has passed all my initial tests and seems too work without error.

Irvine

Edit:

As I said it could do with some refinement, specifically: Use the QTabWidget.currentWidget() method.

    def editFunctions(self, Fnct):
        """ 'edit' Menu/ToolBar clickHandlers"""
        
        currentEditor = self.tabs.currentWidget()
        
        match Fnct:
            case "selectall": currentEditor.selectAll()
            case "paste": currentEditor.paste()
            case "copy": currentEditor.copy()
            case "undo": currentEditor.undo()
            case "redo": currentEditor.redo()
            case "cut": currentEditor.cut()

It is:

  • More flexible
  • Handles movable tabs
  • It only requires adding the new function to the original code without any further edits