Search code examples
pythonpyqt6

Toggling character format with PyQt6


I'm writing a custom word processor as a hobby project. I'm using Python and PyQt6.

I wrote the following. The intent is that if I select some text and apply Bold formatting (for example, by hitting "ctrl-b"), it will toggle the formatting. Specifically, it should remove the bold formatting if all of the selected text is bold. Otherwise, it will apply the bold formatting.

class OvidFont:
    def __init__(self, ovid) -> None:
        self.textEditor = ovid.textEditor

    def setBoldText(self) -> None:
        fmt = QTextCharFormat()
        if self.textEditor.currentCharFormat().fontWeight() != QFont.Weight.Bold:
            print("    setting bold")   # for debugging
            fmt.setFontWeight(QFont.Weight.Bold)
        else:
            print("    setting normal") # for debugging
            fmt.setFontWeight(QFont.Weight.Normal)
        self.textEditor.textCursor().mergeCharFormat(fmt)

However, it won't remove the bold formatting.

For example, in the sentence "this is a test", if I select "is a" and apply the bold formatting, I get "this is a test", with the "is a" properly bold. However, with the selection in place, it still remains bold if I hit "ctrl-b". If I deselect either the first or last character, the toggling of bold works as expected. (I've tried reversing the if/else logic, but that fails, too).

What am I missing?

Update: I've added a working, minimal test case at https://gist.github.com/Ovid/65936985c6838c0220620cf40ba935fa


Solution

  • The issue with setBoldText function was that it checked the bold status using self.textEditor.currentCharFormat().fontWeight(), which only reflects the formatting of the character at the current cursor position, not the entire selected text. If your cursor was at the beginning or end of the selection, it might not accurately represent the formatting of the whole selected range.

    So I used the existing cursor and adjust it if necessary to check the formatting and directly apply the new font weight using setFontWeight() on the QTextEdit.

    Now it looks like that:

    enter image description here

    Updated code:

    import sys
    from PyQt6.QtWidgets import QTextEdit, QToolButton, QApplication, QMainWindow, QToolBar
    from PyQt6.QtGui import QFont, QShortcut, QKeySequence, QTextCharFormat, QTextCursor
    
    class OvidFont:
        def __init__(self, ovid) -> None:
            self.textEditor = ovid.textEditor
    
        def setBoldText(self):
            cursor = self.textEditor.textCursor()
    
            # If there's a selection, and the cursor is not at the block start and at the beginning of the selection,
            # move the cursor to the end of the selection
            if cursor.hasSelection() and not cursor.atBlockStart() and cursor.position() == cursor.selectionStart():
                cursor.setPosition(cursor.selectionEnd())
    
            # Check if the text (either selected or where the cursor is) is bold
            is_bold = cursor.charFormat().fontWeight() == QFont.Weight.Bold
    
            # Apply the new weight based on the current state
            new_weight = QFont.Weight.Normal if is_bold else QFont.Weight.Bold
            self.textEditor.setFontWeight(new_weight)
    
            print(f"Bold set to: {'Normal' if is_bold else 'Bold'}")
    
    class Ovid(QMainWindow):
        def __init__(self):
            super().__init__()
            self.initUI()
    
        def initUI(self):
            self.setWindowTitle("Ovid")
            self.setGeometry(100, 100, 200, 200)
            self.textEditor = QTextEdit()
            self.setCentralWidget(self.textEditor)
            self.fonts = OvidFont(self)
    
            self.toolbar = QToolBar("Main Toolbar")
            self.addToolBar(self.toolbar)
    
            bold_button = QToolButton()
            bold_button.setText("B")
            bold_button.setFont(QFont("Arial", 16, QFont.Weight.Bold))
            bold_button.setToolTip("Bold")
            bold_button.clicked.connect(self.fonts.setBoldText)
            self.toolbar.addWidget(bold_button)
    
            QShortcut(QKeySequence("Ctrl+B"), self, self.fonts.setBoldText)
    
    def main():
        app = QApplication(sys.argv)
        ex = Ovid()
        ex.show()
        sys.exit(app.exec())
    
    if __name__ == "__main__":
        main()