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:
- 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
- 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