fereria
10/20/2019 - 6:31 AM

TableView Delegate

#!python3
# -*- coding: utf-8 -*-

import sys
import os.path

from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtUiTools import QUiLoader

CURRENT_PATH = os.path.dirname(os.path.abspath(sys.argv[0]))


class TextEditDialog(QtWidgets.QDialog):

    def __init__(self, uiPos, parent=None):
        super(TextEditDialog, self).__init__(parent)
        layout = QtWidgets.QVBoxLayout()
        self.textEdit = QtWidgets.QTextEdit(self)
        layout.addWidget(self.textEdit)
        self.setLayout(layout)

        btn = QtWidgets.QPushButton("Close")
        btn.clicked.connect(self.close)
        layout.addWidget(btn)

        self.uiPos = uiPos

        self.setWindowFlags(QtCore.Qt.Popup)

    def showEvent(self, event):
        self.setGeometry(self.uiPos.x(), self.uiPos.y(), 400, 300)
        super(TextEditDialog, self).showEvent(event)

# TableViewの1行ごとのItemを管理するクラス


class DataItem(object):

    def __init__(self, name="", check=False, progress=0, text=""):

        self.name = name
        self.check = check
        self.progess = progress
        self.text = text

    def data(self, column):
        if column == 0:
            return self.name
        elif column == 1:
            return self.check
        elif column == 2:
            return self.progess
        elif column == 3:
            return self.text

    def setData(self, column, value):

        if column == 0:
            self.name = value
        elif column == 1:
            self.check = value
        elif column == 2:
            self.progess = value
        elif column == 3:
            self.text = value


class TableDelegate(QtWidgets.QItemDelegate):

    def __init__(self, parent=None):
        super(TableDelegate, self).__init__(parent)

    def createEditor(self, parent, option, index):
        """
        編集したいCellに対して、編集用のWidgetsを作成する
        """
        if index.column() == 0:
            return QtWidgets.QLineEdit(parent)

        if index.column() == 2:
            spin = QtWidgets.QSpinBox(parent)
            spin.setMinimum(0)
            spin.setMaximum(100)
            return spin
        if index.column() == 3:
            edit = TextEditDialog(parent.mapToGlobal(option.rect.topLeft()), parent)
            return edit

    def setEditorData(self, editor, index):
        """
        createEditorで作成したWidgetsを受け取って、
        Editorにデフォルト値を設定する。
        今回の場合、元々のCellに表示されていた内容を受け取り、
        QLineEditに対してデフォルト値をセットしている
        """

        if index.column() == 0:
            value = index.model().data(index, QtCore.Qt.DisplayRole)
            editor.setText(value)

        if index.column() == 2:
            value = index.model().data(index, QtCore.Qt.DisplayRole)
            editor.setValue(value)
        if index.column() == 3:
            value = index.model().data(index, QtCore.Qt.DisplayRole)
            editor.textEdit.setText(value)

    def editorEvent(self, event, model, option, index):
        """
        TableのCellに対してなにかしらのイベント(クリックした等)が発生したときに呼ばれる。
        """

        if index.column() == 1:
            if self.checkBoxRect(option).contains(event.pos().x(), event.pos().y()):
                if event.type() == QtCore.QEvent.MouseButtonPress:
                    currentValue = model.items[index.row()].data(index.column())
                    model.items[index.row()].setData(index.column(), not currentValue)
                    model.layoutChanged.emit()
                    return True
        return False

    def setModelData(self, editor, model, index):
        """
        編集した値を、Modelを経由して実体のオブジェクトに対してセットする
        """

        value = None

        if index.column() == 0:
            value = editor.text()

        if index.column() == 2:
            value = editor.value()

        if index.column() == 3:
            value = editor.textEdit.toPlainText()

        if value is not None:
            model.setData(index, value)

    def checkBoxRect(self, option):

        rect = option.rect
        rect.setX(rect.x() + 10)
        rect.setWidth(rect.width() - 20)
        return rect

    def paint(self, painter, option, index):
        """
        Cellの中の描画を行う
        """
        if index.column() in [0, 3]:
            painter.drawText(option.rect, QtCore.Qt.AlignCenter | QtCore.Qt.TextWordWrap, index.data())

        if index.column() == 1:
            btn = QtWidgets.QStyleOptionButton()
            btn.state |= QtWidgets.QStyle.State_Enabled
            if index.data() == True:
                btn.state |= QtWidgets.QStyle.State_On
            else:
                btn.state |= QtWidgets.QStyle.State_Off

            btn.rect = self.checkBoxRect(option)
            btn.text = "hogehoge"
            QtWidgets.QApplication.style().drawControl(QtWidgets.QStyle.CE_CheckBox, btn, painter)

        if index.column() == 2:

            bar = QtWidgets.QStyleOptionProgressBar()
            bar.rect = option.rect
            bar.rect.setHeight(option.rect.height() - 1)
            bar.rect.setTop(option.rect.top() + 1)
            bar.minimum = 0
            bar.maximum = 100
            bar.progress = int(index.data())
            bar.textVisible = True
            bar.text = str(index.data()) + '%'
            bar.textAlignment = QtCore.Qt.AlignCenter
            QtWidgets.QApplication.style().drawControl(QtWidgets.QStyle.CE_ProgressBar, bar, painter)

    def sizeHint(self, option, index):

        if index.column() == 0:
            return QtCore.QSize(100, 30)
        
        if index.column() == 2:
            return QtCore.QSize(200, 30)

        if index.column() == 3:
            document = QtGui.QTextDocument(index.data())
            document.setDefaultFont(option.font)
            return QtCore.QSize(document.idealWidth() + 50, 15 * (document.lineCount() + 1))

        return super(TableDelegate, self).sizeHint(option, index)


class TableModel(QtCore.QAbstractTableModel):

    def __init__(self, parent=None):
        super(TableModel, self).__init__(parent)
        self.items = []

    def headerData(self, col, orientation, role):

        HEADER = ['名前', 'チェック', '進行状況', 'せつめい']

        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return HEADER[col]

        if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
            return str(col + 1).zfill(3)

    def addItem(self, item):

        self.items.append(item)
        self.layoutChanged.emit()

    def rowCount(self, parent=QtCore.QModelIndex()):
        u"""行数を返す"""
        return len(self.items)

    def columnCount(self, parent):
        u"""カラム数を返す"""
        return 4

    def data(self, index, role=QtCore.Qt.DisplayRole):

        if not index.isValid():
            return None

        if role == QtCore.Qt.DisplayRole:
            return self.items[index.row()].data(index.column())

        if role == QtCore.Qt.UserRole:
            return self.items[index.row()]

    def setData(self, index, value, role=QtCore.Qt.EditRole):

        if role == QtCore.Qt.EditRole:
            index.data(QtCore.Qt.UserRole).setData(index.column(), value)
            self.headerDataChanged.emit(QtCore.Qt.Vertical, index.row(), index.row())
            self.dataChanged.emit(index, index)

    def flags(self, index):

        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled

    def upData(self, index):
        """
        引数のIndexを1つ上に移動する
        """
        row = index.row()
        if row > 0:
            buff = self.items.pop(row)
            self.items.insert(row - 1, buff)
            self.layoutChanged.emit()

    def downData(self, index):
        """
        引数のIndexを一つ下に移動する
        """
        row = index.row()
        if row < len(self.items):
            buff = self.items.pop(row)
            self.items.insert(row + 1, buff)
            self.layoutChanged.emit()


class UISample(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(UISample, self).__init__(parent)

        self.ui = QUiLoader().load(os.path.join(CURRENT_PATH, 'tableView.ui'))

        self.resize(600, 400)

        self.model = TableModel()

        dataA = DataItem('A', False, 0, "")
        dataB = DataItem('B', False, 0, "")
        dataC = DataItem('C', False, 0, "")
        self.model.addItem(dataA)
        self.model.addItem(dataB)
        self.model.addItem(dataC)

        self.ui.tableView.setModel(self.model)

        self.ui.tableView.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
        self.ui.tableView.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)

        self.delegate = TableDelegate()
        self.ui.tableView.setItemDelegate(self.delegate)

        self.setCentralWidget(self.ui)

        self.ui.upBtn.clicked.connect(self.upBtnClicked)
        self.ui.downBtn.clicked.connect(self.downBtnClicked)

    def upBtnClicked(self):

        index = self.ui.tableView.currentIndex()

        self.model.upData(index)
        chIndex = self.model.index(index.row() - 1, index.column())
        if chIndex.isValid():
            self.ui.tableView.setCurrentIndex(chIndex)

    def downBtnClicked(self):

        index = self.ui.tableView.currentIndex()

        self.model.downData(index)
        chIndex = self.model.index(index.row() + 1, index.column())
        if chIndex.isValid():
            self.ui.tableView.setCurrentIndex(chIndex)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    a = UISample()
    a.show()
    sys.exit(app.exec_())