pythonpyqt5qtableview

How to set selected text colour, style in Qtableview string


Below is a small working code to display what I am trying to achieve.

import sys
import os
import sqlite3

from PyQt5.QtSql import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *


# createDB()
# con_1 = sqlite3.connect("temp.db")

#  # create a table
# con_1.execute('''create table t1(
#    id INTEGER PRIMARY KEY, 'First Name' TEXT, 'Last Name' TEXT, 'Full Name' TEXT);''')

# # populate the table
# con_1.execute("insert into t1 ('First Name', 'Last Name', 'Full Name') values('Wade', 'Wilson', 'Wade Wilson');")
# con_1.execute("insert into t1 ('First Name', 'Last Name', 'Full Name') values('Norman', 'Osborn', 'Norman Osborn');")
# con_1.execute("insert into t1 ('First Name', 'Last Name', 'Full Name') values('Stephen', 'Strange', 'Stephen Strange');")
# con_1.commit()
# con_1.close()

class MultilineTextEditor(QStyledItemDelegate):

    def createEditor(self, parent, options, index):
        return QTextEdit(parent)

    def setEditorData(self, editor, index):
        editor.setText(index.data())

    def setModelData(self, editor, model, index):
        model.setData(index, editor.toPlainText()) 
    
    def paint(self, painter, option, index):
        value = index.data(Qt.DisplayRole)
        if  option.state & QStyle.State_Selected:
            text1 = index.data()
            doc = QTextDocument(text1)
            cursor = QTextCursor(doc)
            cursor.selectedTableCells()
            # cursor.selectedText()  <-- How to implement this?
            # Trying out to simply select the first line!
            cursor.movePosition(QTextCursor.Start)
            cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor)
            format = QTextCharFormat()

            format.setFontWeight(QFont.Bold)
            format.setFontPointSize(20)
            weight = QFont.DemiBold
            format.setFontWeight(weight)
            cursor.mergeCharFormat(format)
            print(cursor.selectedText())
            # painter commands??        
            QStyledItemDelegate.paint(self, painter, option, index)
        else:
            QStyledItemDelegate.paint(self, painter, option, index)
            
class TableViewer(QMainWindow):
    def __init__(self):
        super().__init__()
        working_dir = os.getcwd()
        db_path = f"{working_dir}\\temp.db"
        print(db_path)
        self.db = QSqlDatabase.addDatabase("QSQLITE")
        self.db.setDatabaseName(db_path)

        self.db.open()

        self.model = QSqlTableModel(self, self.db)   
        self.model.setTable('t1')
        self.model.select()

        self.tableView = QTableView(self)
        self.tableView.setModel(self.model)
        self.tableView.resizeColumnsToContents()
        layout = QVBoxLayout()
        layout.addWidget(self.tableView)
        
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        self.tableView.setItemDelegateForColumn(3, MultilineTextEditor())
        self.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    tv = TableViewer()
    sys.exit(app.exec_())
  1. A SQL Lite Database (temp.db) is created in the commented out sections
  2. This db is read and displayed in a Qtableview
  3. The column containing full names is editable

How would you set the format (bold, Italics, underline), size or colour of selected string? I would eventually bind the selection with say keyboard shorcuts (eg cntr + B for bold) or display a toolbar for user selection. Found an example here: https://forum.qt.io/topic/138318/how-do-you-bold-and-un-bold-a-selected-text-pyqt-or-pyside. I am unable to implement it, even rudimentarily in a Qtableview cell.

The rich text data should be saved back to be retrieved from the DB. Unfortunately I am unable to find any good explanitions on delegate --> paint.


Solution

  • I have found a solution to my question.

    Courtesy:

    1. Changing the font color of a QTableView after replacing the default editor with an ItemDelegate QTextEdit in PyQt5

    2. https://forum.qt.io/topic/138318/how-do-you-bold-and-un-bold-a-selected-text-pyqt-or-pyside

    import sys
    import os
    import sqlite3
    from functools import partial
    
    from PyQt5.QtSql import *
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    
    from wid_comments_editor_v1 import Ui_CommEdit
    
    # createDB()
    # con_1 = sqlite3.connect("temp.db")
    
    #  # create a table
    # con_1.execute('''create table t1(
    #    id INTEGER PRIMARY KEY, 'First Name' TEXT, 'Last Name' TEXT, 'Full Name' TEXT);''')
    
    # # populate the table
    # con_1.execute("insert into t1 ('First Name', 'Last Name', 'Full Name') values('Wade', 'Wilson', 'Wade Wilson');")
    # con_1.execute("insert into t1 ('First Name', 'Last Name', 'Full Name') values('Norman', 'Osborn', 'Norman Osborn');")
    # con_1.execute("insert into t1 ('First Name', 'Last Name', 'Full Name') values('Stephen', 'Strange', 'Stephen Strange');")
    # con_1.commit()
    # con_1.close()
    
    class CommentEditor(QWidget, Ui_CommEdit):
    
       """ 
       UI code (Ui_CommEdit) not attached.
       Contains a simple Qtextedit with 5 buttons for bold, italics, underline,
       strikeout and colour
       """    
       closed = pyqtSignal()
       def __init__(self, parent=None):
           QWidget.__init__(self, parent=parent)
           self.setupUi(self)
       
       def closeEvent(self, event):
           self.closed.emit()
    
       
    
    class MultilineTextEditor(QStyledItemDelegate):
    
       def createEditor(self, parent, options, index):
           return QTextEdit(parent)
    
       def setEditorData(self, editor, index):
           txt = index.data()
           editor.setHtml(txt)
           return editor.setHtml(txt)
    
    
       def setModelData(self, editor, model, index):
           model.setData(index, editor.toHtml())
    
       def paint(self, painter, option, index):
           painter.save()
           rich_txt = index.data()
           doc = QTextDocument()
           doc.setTextWidth(option.rect.width())
           doc.setHtml(rich_txt)
           option.text = ""
           option.widget.style().drawControl(
               QStyle.CE_ItemViewItem, option, painter
               )
           painter.translate(option.rect.left(), option.rect.top())
           doc.drawContents(painter)
           painter.restore()
    
    
    
    class TableViewer(QMainWindow):
    
       # close_comment_editor = pyqtSignal()
    
       def __init__(self):
           super().__init__()
           working_dir = os.getcwd()
           db_path = f"{working_dir}\\temp.db"
           self.db = QSqlDatabase.addDatabase("QSQLITE")
           self.db.setDatabaseName(db_path)
    
           self.db.open()
    
           self.model = QSqlTableModel(self, self.db)   
           self.model.setTable('t1')
           self.model.select()
    
           self.tableView = QTableView(self)
           self.tableView.setModel(self.model)
           self.tableView.resizeColumnsToContents()
           layout = QVBoxLayout()
           layout.addWidget(self.tableView)
           
           widget = QWidget()
           widget.setLayout(layout)
           self.setCentralWidget(widget)
           
           # Adjust width of columns and rows
           header_hor = self.tableView.horizontalHeader()
           header_hor.setSectionsMovable(True)
           header_hor.resizeSection(3, 800)
           self.tableView.resizeRowsToContents()
    
           self.tableView.setItemDelegateForColumn(3, MultilineTextEditor())
                   
          
           self.showMaximized()
       
       
    
       def contextMenuEvent(self, event):
           # Imp! Create new model from existing items on view
           # self.self.tableView != self.tableView.model
           model = self.tableView.model()
           # Indices
           col_indx = self.tableView.currentIndex().column()
           row_indx = self.tableView.currentIndex().row()
           index = model.index(row_indx, col_indx)
           # Get Value
           data = model.data(index)
           # header names
           header_name = []
           for x in range(self.model.columnCount()):
               header_name.append(self.model.headerData(x, Qt.Horizontal))
    
           selectionModel = self.tableView.selectionModel()
           selectionModel.isSelected(index)
    
           if header_name[col_indx] == "Full Name" and selectionModel.isSelected(
               index
           ):  
               self.menu = QMenu(self)
               edit = QAction("Edit", self)
               self.menu.addAction(edit)
               self.menu.popup(QCursor.pos())
               edit.triggered.connect(
                   partial(
                       self.edit_frontend,
                       data,
                       col_indx,
                       row_indx,
                       header_name,
                       model,
                   )
               )
    
    
       def edit_frontend(self, data, col_indx, row_indx, header_name, model):
           # Open editor
           self.comm_edit =CommentEditor()
           self.comm_edit.show()
           
           self.comm_edit.te_comment_editor.setHtml(data)
    
           # Code for bold
           self.comm_edit.pb_bold.clicked.connect(self.bold_text)
           self.comm_edit.pb_bold.setShortcut(Qt.CTRL | Qt.Key_B)
           # Code for italic
           self.comm_edit.pb_italic.clicked.connect(self.italic_text)
           self.comm_edit.pb_italic.setShortcut(Qt.CTRL | Qt.Key_I)
           # Code for underline
           self.comm_edit.pb_underline.clicked.connect(self.underline_text)
           self.comm_edit.pb_underline.setShortcut(Qt.CTRL | Qt.Key_U)
           # Code for strikeout
           self.comm_edit.pb_strikeout.clicked.connect(self.strikeout_text)
           self.comm_edit.pb_strikeout.setShortcut(Qt.CTRL | Qt.Key_S)
           # Code for colour
           self.comm_edit.pb_colour.clicked.connect(self.colour_text)
    
           # Close event handler
           self.comm_edit.closed.connect(
                   partial(
                       self.win_closed,
                       col_indx,
                       row_indx,
                       model,
                   )
               )
    
    
       def win_closed(self,  col_indx, row_indx,  model  ):
           txt = self.comm_edit.te_comment_editor.toHtml()
           model.setData(model.index(row_indx, col_indx), txt)
    
       def bold_text(self): 
    
           fmt = QTextCharFormat()
           if self.comm_edit.pb_bold.isChecked(): 
               weight = QFont.Bold 
           else: 
               weight = QFont.Normal
               
           fmt.setFontWeight(weight)
           self.merge_format_on_word_or_selection(fmt)
       
       def italic_text(self):
           fmt = QTextCharFormat()
           fmt.setFontItalic(self.comm_edit.pb_italic.isChecked())
           self.merge_format_on_word_or_selection(fmt)
    
       def underline_text(self):
           fmt = QTextCharFormat()
           fmt.setFontUnderline(self.comm_edit.pb_underline.isChecked())
           self.merge_format_on_word_or_selection(fmt)
    
       def strikeout_text(self):
           fmt = QTextCharFormat()
           fmt.setFontStrikeOut(self.comm_edit.pb_strikeout.isChecked())
           self.merge_format_on_word_or_selection(fmt)
    
       def colour_text(self):
           color = QColorDialog.getColor()
           if color.isValid():
               fmt = QTextCharFormat()
               fmt.setForeground(color)
               self.merge_format_on_word_or_selection(fmt)
    
       def merge_format_on_word_or_selection(self, format):
     
           cursor = self.comm_edit.te_comment_editor.textCursor()
           if not cursor.hasSelection(): 
               cursor.select(QTextCursor.WordUnderCursor)
           cursor.mergeCharFormat(format)
           self.comm_edit.te_comment_editor.mergeCurrentCharFormat(format)
    
    
       def closeEvent(self, event):
           
           try :
               self.comm_edit.close()
           except AttributeError:
               pass
    
           event.accept()
    
    if __name__ == '__main__':
       app = QApplication(sys.argv)
       tv = TableViewer()
       sys.exit(app.exec_())
    
       
    

    enter image description here

    1. As mentioned earlier the commented block contains the code to create a SQLLite temp.db.

    2. The entries in Last name column can be modified with opening an UI acessed via a custom context menu.

    3. The edited rich text data is stored as HTML but diplayed as text.

    Hope this helps someone!