├── screenshot.png ├── source ├── qtquick │ ├── delegate │ │ ├── CellBackground.qml │ │ ├── CellBool.qml │ │ ├── CellText.qml │ │ ├── CellNumber.qml │ │ ├── CellColor.qml │ │ ├── CellTextEditor.qml │ │ ├── CellList.qml │ │ ├── CellColorEditor.qml │ │ ├── CellBaseEditor.qml │ │ ├── CellBase.js │ │ ├── CellBase.qml │ │ ├── CellNumberEditor.qml │ │ ├── CellPathEditor.qml │ │ └── CellDelegate.qml │ ├── Utils.js │ ├── HeaderDelegate.qml │ ├── qml.qrc │ ├── RoundRect.qml │ ├── __main.qml │ ├── ResizableColumnHeader.qml │ └── CellView.qml └── cpp │ ├── __main.cpp │ ├── MyType.h │ ├── TableModel.h │ ├── Property.h │ ├── Property.cpp │ └── TableModel.cpp ├── README.md └── edi-tableview.pro /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unau-thorized/edi-tableview/HEAD/screenshot.png -------------------------------------------------------------------------------- /source/qtquick/delegate/CellBackground.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Rectangle { 4 | anchors.fill: parent 5 | color:"#eee" 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # edi-tableview 2 | A very simple Property Editor with QtQuick control 2 TableView 3 | ![Alt text](screenshot.png?raw=true "Title") 4 | -------------------------------------------------------------------------------- /source/qtquick/delegate/CellBool.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | import QtQuick.Controls 2.14 3 | 4 | CellBase { 5 | objectName: "!" 6 | contentItem: Switch { 7 | enabled: !readOnly 8 | checked: value 9 | checkable: true 10 | onClicked: onAccepted(checked) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source/qtquick/delegate/CellText.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | CellBase { 3 | contentItem: Text { 4 | text: value 5 | clip: true 6 | horizontalAlignment: Text.AlignHCenter 7 | verticalAlignment: Text.AlignVCenter 8 | elide: Text.ElideRight 9 | } 10 | editor: "CellTextEditor.qml" 11 | } 12 | -------------------------------------------------------------------------------- /source/qtquick/Utils.js: -------------------------------------------------------------------------------- 1 | .pragma library 2 | 3 | function pathToUrl(path) { 4 | return encodeURIComponent("file://"+path) 5 | } 6 | 7 | function urlToPath(urldata) { 8 | var path = urldata.toString() 9 | path = path.replace(/^(file:\/{2})/,"") // remove prefixed "file://" 10 | return decodeURIComponent(path) // unescape html codes like '%23' for '#' 11 | } 12 | -------------------------------------------------------------------------------- /source/qtquick/delegate/CellNumber.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | CellBase { 3 | contentItem: Text { 4 | text : value 5 | clip: true 6 | horizontalAlignment: Text.AlignRight 7 | verticalAlignment: Text.AlignVCenter 8 | color: "#2E7D32" 9 | elide: Text.ElideLeft 10 | rightPadding: 2 11 | } 12 | editor: "CellNumberEditor.qml" 13 | } 14 | 15 | -------------------------------------------------------------------------------- /source/qtquick/delegate/CellColor.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | CellBase { 3 | id: root 4 | contentItem: Rectangle { 5 | color: value 6 | anchors { 7 | fill: parent 8 | topMargin: 2 9 | bottomMargin: 2 10 | leftMargin: 10 11 | rightMargin: 10 12 | } 13 | border.color: root.readOnly ? "#bbb" : "black" 14 | border.width: 2 15 | } 16 | editor: "CellColorEditor.qml" 17 | } 18 | -------------------------------------------------------------------------------- /source/qtquick/HeaderDelegate.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.5 3 | 4 | Rectangle { 5 | property alias text : label.text 6 | property alias textColor: label.color 7 | property alias horizontalAlignment: label.horizontalAlignment 8 | // implicitWidth: 100 9 | // implicitHeight: 30 10 | color:"#eec" 11 | Text { 12 | id: label 13 | anchors.fill: parent 14 | verticalAlignment: Text.AlignVCenter 15 | horizontalAlignment: Text.AlignHCenter 16 | rightPadding: 5 17 | leftPadding: 5 18 | font.bold: true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/qtquick/delegate/CellTextEditor.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | import QtQuick.Controls 2.14 3 | 4 | CellBaseEditor { 5 | id: editor 6 | contentItem : TextField { 7 | text: cell.value 8 | onAccepted: { 9 | if (typeof cell.onAccepted !== "function") { 10 | console.log("[ERROR] CellTextEditor : cell.onAccepted is not a Function!!") 11 | return 12 | } 13 | cell.onAccepted(text) 14 | editor.hide() 15 | } 16 | Keys.onEscapePressed: editor.cancelEdit() 17 | onFocusChanged: if (!focus) editor.cancelEdit() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/qtquick/delegate/CellList.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | import QtQuick.Controls 2.14 3 | 4 | //Item { 5 | // id: root 6 | // property alias model : cbox.model 7 | // property var value 8 | // property bool readOnly 9 | // property var onAccepted : undefined 10 | CellBase { 11 | id: root 12 | objectName: "!" 13 | contentItem: ComboBox { 14 | enabled: !readOnly 15 | model: option.values 16 | editable: false 17 | currentIndex: model.indexOf(value) 18 | //onCurrentIndexChanged: root.onAccepted(model[currentIndex]) 19 | onActivated: root.onAccepted(model[index]) 20 | } 21 | } 22 | //} 23 | -------------------------------------------------------------------------------- /source/qtquick/delegate/CellColorEditor.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | import QtQuick.Controls 2.14 3 | import QtQuick.Dialogs 1.2 4 | 5 | ColorDialog { 6 | id: colorEdit 7 | property var cell : undefined 8 | title: "Please choose a color" 9 | visible: true 10 | currentColor: cell.value 11 | modality: Qt.ApplicationModal 12 | onAccepted: { 13 | if (typeof cell.onAccepted !== "function") { 14 | console.log("[ERROR] cell.onAccepted is not a function!!") 15 | return 16 | } 17 | cell.onAccepted(color) 18 | destroy() 19 | } 20 | onRejected: destroy() 21 | function activate() { 22 | show() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /source/qtquick/delegate/CellBaseEditor.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | import QtQuick.Controls 2.14 3 | 4 | Control { 5 | id: root 6 | property var cell : undefined 7 | anchors.fill:parent 8 | visible: false 9 | 10 | function activate() { 11 | if (typeof cell.onAccepted !== "function") console.log("[ERROR] cell.onAccepted is not a function!!") 12 | if (!enabled ) return 13 | visible = true 14 | contentItem.forceActiveFocus() 15 | } 16 | 17 | function cloneCellGeometry() { 18 | x = cell.x 19 | y = cell.y 20 | width = cell.width 21 | height = cell.height 22 | } 23 | function cancelEdit() { 24 | root.destroy() 25 | } 26 | function hide() { 27 | root.destroy() 28 | } 29 | onCellChanged: parent = cell; 30 | } 31 | -------------------------------------------------------------------------------- /source/qtquick/delegate/CellBase.js: -------------------------------------------------------------------------------- 1 | var component 2 | var editor 3 | var parent 4 | 5 | function createEditor(owner) { 6 | parent= owner 7 | if (!parent.editor) return; 8 | component = Qt.createComponent(parent.editor); 9 | if (component.status === Component.Ready) finishCreation(); 10 | else component.statusChanged.connect(finishCreation) 11 | } 12 | 13 | function finishCreation() { 14 | if (component.status === Component.Ready) { 15 | editor = component.createObject(parent, { cell: parent }) 16 | if (editor === null) console.log("Error creating object") 17 | else editor.activate() 18 | } else if (component.status === Component.Error) { 19 | console.log("Error loading component:", component.errorString()) // Error Handling 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/cpp/__main.cpp: -------------------------------------------------------------------------------- 1 | #include "TableModel.h" 2 | 3 | #include "MyType.h" 4 | #include "Property.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | 12 | int main(int argc, char *argv[]) 13 | { 14 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 15 | 16 | QGuiApplication app(argc, argv); 17 | app.setWindowIcon(QIcon(QT_ICON_PATH)); // iconless on taskbar? not anymore. 18 | 19 | qmlRegisterType ("App.Class", 0, 1, "TableModel"); 20 | qmlRegisterType ("App.Class", 0, 1, "Property"); 21 | qmlRegisterUncreatableMetaObject(MetaTypeNamespace::staticMetaObject, "App.Class", 0, 1, "MetaType", "Access to enums & flags only"); 22 | 23 | 24 | QQmlApplicationEngine engine; 25 | engine.load(QUrl(QStringLiteral("qrc:/__main.qml"))); 26 | if (engine.rootObjects().isEmpty()) return -1; 27 | 28 | return app.exec(); 29 | } 30 | -------------------------------------------------------------------------------- /source/qtquick/delegate/CellBase.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | import QtQuick.Controls 2.14 3 | import App.Class 0.1 as Class 4 | import "CellBase.js" as Js 5 | Control { 6 | id: root 7 | // from model 8 | property var value 9 | property int type : Class.MetaType.UnknownType 10 | property var flags 11 | property int editorType 12 | property var option 13 | // local 14 | property bool readOnly : false 15 | property bool hasDomain : flags & Class.Property.DomainList 16 | property string editor 17 | implicitWidth: 100 18 | implicitHeight: 30 19 | 20 | background : CellBackground { } 21 | MouseArea { 22 | id: clickarea 23 | anchors.fill: parent 24 | onClicked: if (!readOnly) edit() 25 | visible: root.objectName !== "!" 26 | } 27 | function onAccepted(value) { 28 | model.cellData = value 29 | } 30 | function edit() { 31 | Js.createEditor(this); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /source/qtquick/delegate/CellNumberEditor.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | import QtQuick.Controls 2.14 3 | import App.Class 0.1 as Class 4 | 5 | CellTextEditor { 6 | IntValidator { id: intValidator } 7 | DoubleValidator { id: doubleValidator } 8 | 9 | Component.onCompleted: { 10 | if (cell.option.hasOwnProperty("min")) { 11 | console.log("["+cell.option.min+" .. "+cell.option.max+"]") 12 | if (cell.type === Class.MetaType.Int) setInt(cell.option.min, cell.option.max) 13 | if (cell.type === Class.MetaType.Double) setDouble(cell.option.min, cell.option.max) 14 | } 15 | } 16 | function setInt(min,max) { 17 | intValidator.bottom = min 18 | intValidator.top = max 19 | contentItem.validator=intValidator 20 | } 21 | function setDouble(min,max) { 22 | doubleValidator.bottom = min 23 | doubleValidator.top = max 24 | doubleValidator.decimals=3 25 | contentItem.validator = doubleValidator 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /source/qtquick/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | __main.qml 4 | CellView.qml 5 | ResizableColumnHeader.qml 6 | HeaderDelegate.qml 7 | RoundRect.qml 8 | delegate/CellColor.qml 9 | delegate/CellNumber.qml 10 | delegate/CellBool.qml 11 | delegate/CellText.qml 12 | delegate/CellList.qml 13 | delegate/CellBase.qml 14 | delegate/CellBaseEditor.qml 15 | delegate/CellPathEditor.qml 16 | delegate/CellBase.js 17 | delegate/CellNumberEditor.qml 18 | delegate/CellColorEditor.qml 19 | delegate/CellBackground.qml 20 | delegate/CellTextEditor.qml 21 | Utils.js 22 | delegate/CellDelegate.qml 23 | 24 | 25 | -------------------------------------------------------------------------------- /source/qtquick/RoundRect.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | Rectangle { 4 | id: root 5 | radius: 5 6 | property bool topLeftCorner: false 7 | property bool topRightCorner: false 8 | property bool bottomLeftCorner: false 9 | property bool bottomRightCorner: false 10 | Rectangle { 11 | color: parent.color 12 | visible:!parent.topLeftCorner 13 | width: parent.radius 14 | height: parent.radius 15 | anchors.top : parent.top 16 | anchors.left: parent.left 17 | } 18 | Rectangle { 19 | color: parent.color 20 | visible:!parent.topRightCorner 21 | width: parent.radius 22 | height: parent.radius 23 | anchors.top : parent.top 24 | anchors.right: parent.right 25 | } 26 | Rectangle { 27 | color: parent.color 28 | visible:!parent.bottomLeftCorner 29 | width: parent.radius 30 | height: parent.radius 31 | anchors.bottom : parent.bottom 32 | anchors.left: parent.left 33 | } 34 | Rectangle { 35 | color: parent.color 36 | visible:!parent.bottomRightCorner 37 | width: parent.radius 38 | height: parent.radius 39 | anchors.bottom : parent.bottom 40 | anchors.right: parent.right 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /source/qtquick/__main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | import QtQuick.Window 2.14 3 | import QtQuick.Layouts 1.14 4 | import QtQuick.Controls 2.14 5 | import App.Class 0.1 as Class 6 | import "delegate" 7 | Window { 8 | id: root 9 | visible: true 10 | width: 800 11 | height: 500 12 | minimumWidth: 300 13 | minimumHeight: 200 14 | title: "EDI-TABLEVIEW" 15 | color: "#aaa" 16 | RowLayout { 17 | anchors.fill: parent 18 | spacing: 5 19 | CellView { 20 | Layout.fillHeight: true 21 | Layout.fillWidth: true 22 | model: tableModel 23 | } 24 | CellView { 25 | Layout.fillHeight: true 26 | model: tableModel 27 | delegate: CellText { 28 | value: model.cellData 29 | readOnly: true 30 | } 31 | } 32 | ColumnLayout { 33 | Layout.fillHeight: true 34 | Button { 35 | text: "Clear" 36 | onClicked: tableModel.clear() 37 | } 38 | Button { 39 | text: "Test data" 40 | onClicked: tableModel.testData() 41 | } 42 | Item { Layout.fillHeight: true } // VerticalStretch 43 | } 44 | } 45 | 46 | Class.TableModel { 47 | id: tableModel 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /source/qtquick/delegate/CellPathEditor.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | import QtQuick.Controls 2.14 3 | import QtQuick.Layouts 1.14 4 | import Qt.labs.platform 1.1 5 | import "../Utils.js" as Utils 6 | CellBaseEditor { 7 | id: editor 8 | contentItem : TextField { 9 | id: field 10 | text: cell.value 11 | //rightPadding: height 12 | onAccepted: { 13 | if (typeof cell.onAccepted !== "function") { 14 | console.log("[ERROR] CellTextEditor : cell.onAccepted is not a Function!!") 15 | return 16 | } 17 | cell.onAccepted(text) 18 | editor.hide() 19 | } 20 | Keys.onEscapePressed: editor.cancelEdit() 21 | onFocusChanged: if (!focus && !button.focus) editor.cancelEdit() 22 | ToolButton { 23 | id: button 24 | width: parent.height 25 | height: parent.height 26 | anchors.right: parent.right 27 | text:"..." 28 | onClicked: folderDialog.openPath(field.text.toString()) 29 | } 30 | Component.onCompleted: rightPadding = height // avoid binding 31 | } 32 | 33 | FolderDialog { 34 | id: folderDialog 35 | onAccepted: { 36 | field.text = Utils.urlToPath(folder.toString()) 37 | field.onAccepted() 38 | } 39 | function openPath(path) { 40 | options = FolderDialog.ShowDirsOnly 41 | currentFolder = Utils.pathToUrl(field.text) 42 | open() 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /edi-tableview.pro: -------------------------------------------------------------------------------- 1 | QT += quick 2 | CONFIG += c++17 3 | 4 | CONFIG(debug, debug|release) { 5 | message(DEBUG MODE) 6 | CONFIG+=qml_debug 7 | } else { 8 | message(RELEASE MODE) 9 | } 10 | 11 | # app.setWindowIcon(QIcon(QT_ICON_PATH)); // iconless on taskbar? not anymore. 12 | QID=$$[QT_INSTALL_DATA] 13 | QT_ICON= $$section(QID, /, 0, -3)/QtIcon.png 14 | QT_ICON_STR = '\\"$${QT_ICON}\\"' 15 | DEFINES += QT_ICON_PATH=\"$${QT_ICON_STR}\" 16 | 17 | # The following define makes your compiler emit warnings if you use 18 | # any Qt feature that has been marked deprecated (the exact warnings 19 | # depend on your compiler). Refer to the documentation for the 20 | # deprecated API to know how to port your code away from it. 21 | DEFINES += QT_DEPRECATED_WARNINGS 22 | 23 | # You can also make your code fail to compile if it uses deprecated APIs. 24 | # In order to do so, uncomment the following line. 25 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 26 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 27 | 28 | SOURCES += \ 29 | source/cpp/Property.cpp \ 30 | source/cpp/TableModel.cpp \ 31 | source/cpp/__main.cpp 32 | 33 | RESOURCES += source/qtquick/qml.qrc 34 | 35 | # Additional import path used to resolve QML modules in Qt Creator's code model 36 | QML_IMPORT_PATH = 37 | 38 | # Additional import path used to resolve QML modules just for Qt Quick Designer 39 | QML_DESIGNER_IMPORT_PATH = 40 | 41 | # Default rules for deployment. 42 | qnx: target.path = /tmp/$${TARGET}/bin 43 | else: unix:!android: target.path = /opt/$${TARGET}/bin 44 | !isEmpty(target.path): INSTALLS += target 45 | 46 | HEADERS += \ 47 | source/cpp/Property.h \ 48 | source/cpp/TableModel.h \ 49 | source/cpp/MyType.h 50 | 51 | DISTFILES += \ 52 | README.md 53 | -------------------------------------------------------------------------------- /source/cpp/MyType.h: -------------------------------------------------------------------------------- 1 | #ifndef MYTYPE_H 2 | #define MYTYPE_H 3 | 4 | #include 5 | #include 6 | 7 | namespace MetaTypeNamespace 8 | { 9 | Q_NAMESPACE 10 | enum Type : int { 11 | UnknownType = 0, Bool = 1, Int = 2, UInt = 3, LongLong = 4, ULongLong = 5, 12 | Double = 6, Long = 32, Short = 33, Char = 34, ULong = 35, UShort = 36, 13 | UChar = 37, Float = 38, 14 | VoidStar = 31, 15 | QChar = 7, QString = 10, QStringList = 11, QByteArray = 12, 16 | QBitArray = 13, QDate = 14, QTime = 15, QDateTime = 16, QUrl = 17, 17 | QLocale = 18, QRect = 19, QRectF = 20, QSize = 21, QSizeF = 22, 18 | QLine = 23, QLineF = 24, QPoint = 25, QPointF = 26, QRegExp = 27, 19 | QEasingCurve = 29, QUuid = 30, QVariant = 41, QModelIndex = 42, 20 | QPersistentModelIndex = 50, QRegularExpression = 44, 21 | QJsonValue = 45, QJsonObject = 46, QJsonArray = 47, QJsonDocument = 48, 22 | QByteArrayList = 49, QObjectStar = 39, SChar = 40, 23 | Void = 43, 24 | Nullptr = 51, 25 | QVariantMap = 8, QVariantList = 9, QVariantHash = 28, 26 | QCborSimpleType = 52, QCborValue = 53, QCborArray = 54, QCborMap = 55, 27 | 28 | // Gui types 29 | QFont = 64, QPixmap = 65, QBrush = 66, QColor = 67, QPalette = 68, 30 | QIcon = 69, QImage = 70, QPolygon = 71, QRegion = 72, QBitmap = 73, 31 | QCursor = 74, QKeySequence = 75, QPen = 76, QTextLength = 77, QTextFormat = 78, 32 | QMatrix = 79, QTransform = 80, QMatrix4x4 = 81, QVector2D = 82, 33 | QVector3D = 83, QVector4D = 84, QQuaternion = 85, QPolygonF = 86, 34 | 35 | // Widget types 36 | QSizePolicy = 121, 37 | LastCoreType = QCborMap, 38 | LastGuiType = QPolygonF, 39 | User = 1024 40 | }; 41 | Q_ENUM_NS(Type) 42 | }; 43 | 44 | #endif // MYTYPE_H 45 | -------------------------------------------------------------------------------- /source/qtquick/ResizableColumnHeader.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Window 2.12 3 | import QtQuick.Controls 2.5 4 | import QtQuick.Layouts 1.12 5 | ListView { 6 | id:root 7 | property var len : [200,200] 8 | property var count : root.model.length 9 | property real defaultWidth : 150 10 | property real minimalWidth : 50 11 | signal columnWidthChanged 12 | 13 | orientation: ListView.Horizontal 14 | clip: true 15 | delegate: HeaderDelegate { 16 | id: header 17 | // width: root.len[index] ?? defaultWidth // only Qt>= 5.15 18 | width: root.len[index] ? root.len[index] : defaultWidth 19 | height: root.height 20 | color:"#eec" 21 | text: ""+modelData+"" 22 | Rectangle { 23 | id: resizeHandle 24 | color: Qt.darker(parent.color, 1.05) 25 | height: parent.height 26 | width: 10 27 | anchors.right: parent.right 28 | anchors.verticalCenter: parent.verticalCenter 29 | MouseArea { 30 | id: mouseHandle 31 | anchors.fill: parent 32 | drag{ target: parent; axis: Drag.XAxis } 33 | hoverEnabled: true 34 | cursorShape: Qt.SizeHorCursor 35 | onMouseXChanged: { 36 | if (drag.active) { 37 | var newWidth = header.width + mouseX 38 | if (newWidth >= minimalWidth) { 39 | header.width = newWidth 40 | root.len[index] = newWidth 41 | root.columnWidthChanged() 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | onCountChanged: modelCountChanged() 49 | // Component.onCompleted: resetColumns() 50 | 51 | function columnWidthProvider(column) { 52 | return len[column] 53 | } 54 | function resetColumns() { 55 | len=[] 56 | for (var i=0; i 5 | class Property; 6 | 7 | class TableModel : public QAbstractTableModel 8 | { 9 | Q_OBJECT 10 | 11 | Q_PROPERTY(QStringList columnNames READ columnNames WRITE setColumnNames NOTIFY columnNamesChanged) 12 | Q_PROPERTY(QStringList rowNames READ rowNames WRITE setRowNames NOTIFY rowNamesChanged) 13 | Q_PROPERTY(int count READ count NOTIFY countChanged) 14 | 15 | Q_ENUMS(Roles) 16 | public: 17 | explicit TableModel(QObject *parent=nullptr); 18 | ~TableModel() override = default; 19 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 20 | int columnCount(const QModelIndex &parent = QModelIndex()) const override; 21 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 22 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; 23 | enum Roles {CellDataRole = Qt::UserRole+1, CellTypeRole,CellFlagsRole, CellEditorRole, CellOptionRole}; 24 | QHash roleNames() const override; 25 | // EDITING 26 | bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; 27 | Qt::ItemFlags flags(const QModelIndex &index) const override; 28 | // PROPERTIES 29 | QStringList columnNames() const; 30 | QStringList rowNames() const; 31 | int count() const; 32 | public slots: 33 | void testData(); 34 | void clear(); 35 | signals: 36 | void columnNamesChanged(QStringList columnNames); 37 | void rowNamesChanged(QStringList rowNames); 38 | void countChanged(int count); 39 | private: 40 | bool indexInRange(const QModelIndex &index) const; 41 | void setColumnNames(QStringList columnNames); 42 | void setRowNames(QStringList rowNames); 43 | 44 | QList properties_; 45 | QStringList columnNames_; 46 | QStringList rowNames_; 47 | 48 | void clearData(); 49 | void createIncrementalRowLabel(); 50 | }; 51 | 52 | #endif // TABLEMODEL_H 53 | -------------------------------------------------------------------------------- /source/cpp/Property.h: -------------------------------------------------------------------------------- 1 | #ifndef VARIABLE_H 2 | #define VARIABLE_H 3 | 4 | #include 5 | #include 6 | 7 | class Property : public QObject { 8 | Q_OBJECT 9 | Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) 10 | Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) 11 | Q_PROPERTY(QString path READ path WRITE setPath) 12 | Q_PROPERTY(int flags READ flags WRITE setFlags NOTIFY flagsChanged) 13 | Q_PROPERTY(QVariantMap option READ option WRITE setOption NOTIFY optionChanged) 14 | Q_PROPERTY(int editorType READ editorType WRITE setEditorType NOTIFY editorTypeChanged) 15 | Q_ENUMS(FlagType) 16 | Q_ENUMS(EditorType) 17 | public: 18 | enum FlagType {ReadOnly=1}; 19 | enum EditorType {TextEditor,NumberEditor,BoolEditor,ColorEditor,ListEditor,PathEditor}; 20 | 21 | explicit Property(); 22 | explicit Property(const QString &name, const QVariant &value, int flags=0); 23 | explicit Property(const QString &name, const QVariant &value, const QVariantMap& option, int flags=0); 24 | explicit Property(const QString& name, const QVariant& value, const QVariantList& domain, int flags=0); 25 | explicit Property(const QString& name, int value, int min, int max, int flags=0); 26 | explicit Property(const QString& name, double value, double min, double max, int flags=0); 27 | 28 | QString name() const; 29 | QVariant value() const; 30 | QVariant::Type type() const; 31 | QString path() const; 32 | int flags() const; 33 | int editorType() const; 34 | QVariantMap option() const; 35 | 36 | signals: 37 | void nameChanged(QString name); 38 | void valueChanged(QVariant value); 39 | void flagsChanged(int flags); 40 | void editorTypeChanged(int editorType); 41 | void optionChanged(QVariantMap option); 42 | 43 | public slots: 44 | void setName(QString name); 45 | void setValue(QVariant value); 46 | void setPath(QString path); 47 | void setFlags(int flags); 48 | void setEditorType(int editorType); 49 | void setOption(QVariantMap option); 50 | 51 | protected: 52 | int suitableEditor() const; 53 | private: 54 | QString name_; 55 | QVariant value_; 56 | QString path_; // deprecated 57 | int flags_; 58 | int editorType_; 59 | QVariantMap option_; 60 | }; 61 | 62 | #endif // VARIABLE_H 63 | -------------------------------------------------------------------------------- /source/cpp/Property.cpp: -------------------------------------------------------------------------------- 1 | #include "Property.h" 2 | #include "MyType.h" 3 | 4 | #include 5 | 6 | 7 | 8 | Property::Property() : QObject(), name_(""), value_(""), path_(""), flags_(0) { 9 | } 10 | 11 | Property::Property(const QString &name, const QVariant &value, int flags) : QObject(), name_(name), value_(value), path_(""), flags_(flags) { 12 | editorType_=suitableEditor(); 13 | } 14 | 15 | Property::Property(const QString &name, const QVariant &value, const QVariantMap &option, int flags) 16 | : Property (name, value,flags) { 17 | option_ = option; 18 | } 19 | 20 | Property::Property(const QString &name, const QVariant &value, const QVariantList &domain, int flags) 21 | : Property(name,value,QVariantMap{{"values",domain}},flags) { 22 | editorType_ = ListEditor; 23 | } 24 | 25 | Property::Property(const QString &name, int value, int min, int max, int flags) 26 | : Property(name, value, {{"min", min},{"max", max}},flags) { 27 | editorType_ = NumberEditor; 28 | } 29 | 30 | Property::Property(const QString &name, double value, double min, double max, int flags) 31 | : Property(name, value, {{"min",min},{"max",max}}, flags) { 32 | editorType_ = NumberEditor; 33 | } 34 | 35 | QString Property::name() const { 36 | return name_; 37 | } 38 | QVariant Property::value() const { 39 | return value_; 40 | } 41 | QVariant::Type Property::type() const { 42 | return value_.type(); 43 | } 44 | QString Property::path() const { 45 | return path_; 46 | } 47 | int Property::flags() const { 48 | return flags_; 49 | } 50 | int Property::editorType() const { 51 | return editorType_; 52 | } 53 | 54 | QVariantMap Property::option() const { 55 | return option_; 56 | } 57 | 58 | void Property::setName(QString name) { 59 | if (name_ != name) emit nameChanged(name_ = name); 60 | } 61 | void Property::setValue(QVariant value) { 62 | if (value_ != value) emit valueChanged(value_ = value); 63 | } 64 | void Property::setPath(QString path) { 65 | path_ = path; 66 | } 67 | void Property::setFlags(int flags) { 68 | if (flags_ != flags) emit flagsChanged(flags_ = flags); 69 | } 70 | void Property::setEditorType(int EditorType) { 71 | if (editorType_ != EditorType) emit editorTypeChanged(editorType_ = EditorType); 72 | } 73 | 74 | void Property::setOption(QVariantMap option) { 75 | if (option_ != option) emit optionChanged(option_ = option); 76 | } 77 | 78 | using namespace MetaTypeNamespace; 79 | 80 | 81 | int Property::suitableEditor() const { 82 | unsigned int type = value_.type(); 83 | static QVector numerical { Type::Int,Type::UInt,Type::LongLong ,Type::ULongLong, Type::Double,Type::Long,Type::Short,Type::ULong,Type::UShort,Type::Float,}; 84 | if (type == Type::Bool) return BoolEditor; 85 | if (type == Type::QColor) return ColorEditor; 86 | if (numerical.contains(type)) return NumberEditor; 87 | return TextEditor; 88 | } 89 | 90 | -------------------------------------------------------------------------------- /source/qtquick/CellView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.14 2 | import QtQuick.Window 2.14 3 | import QtQuick.Layouts 1.14 4 | import QtQuick.Controls 2.14 5 | import App.Class 0.1 as Class 6 | import Qt.labs.qmlmodels 1.0 7 | import "./delegate" 8 | 9 | GridLayout { 10 | id: root 11 | property Class.TableModel model 12 | property int defaultRowHeight : 30 13 | property int defaultColumnWidth : 130 14 | property bool readOnly : false 15 | property bool interactive : true 16 | property alias delegate : table.delegate 17 | 18 | rows: 2 19 | columns:2 20 | rowSpacing: 2 21 | columnSpacing: 2 22 | // 1 23 | RoundRect { 24 | id: corner 25 | width: rowHeader.width 26 | height:columnHeader.height 27 | color: "#EEEDA1" // "#eec" 28 | visible: model.count 29 | topLeftCorner: true 30 | radius: height / 2 31 | } 32 | ResizableColumnHeader { // COLUMN HEADER 33 | id: columnHeader 34 | Layout.fillWidth: true 35 | height: root.defaultRowHeight 36 | defaultWidth: root.defaultColumnWidth 37 | spacing: 1 38 | model: root.model.columnNames 39 | contentX: table.contentX 40 | interactive: false 41 | onColumnWidthChanged: table.forceLayout() 42 | } 43 | // 2 44 | ListView { // ROW HEADER 45 | id: rowHeader 46 | Layout.fillHeight: true 47 | width: 50 48 | spacing: 1 49 | clip:true 50 | model: root.model.rowNames 51 | delegate: HeaderDelegate { 52 | height: root.defaultRowHeight 53 | width: rowHeader.width; 54 | text: modelData; 55 | horizontalAlignment: Text.AlignRight 56 | } 57 | contentY: table.contentY 58 | interactive: false 59 | } 60 | TableView { 61 | id: table 62 | Layout.fillWidth: true 63 | Layout.fillHeight: true 64 | columnSpacing: 1 65 | rowSpacing: 1 66 | clip: true 67 | columnWidthProvider: columnHeader.columnWidthProvider 68 | rowHeightProvider: function (column) { return root.defaultRowHeight } 69 | model:root.model 70 | delegate: CellDelegate { role:"cellEditor" } 71 | 72 | ScrollBar.horizontal: ScrollBar { orientation: Qt.Horizontal } 73 | ScrollBar.vertical: ScrollBar { } 74 | 75 | // DelegateChooser { 76 | // id: chooser 77 | // role: "cellEditor" 78 | // DelegateChoice { 79 | // roleValue: Class.Property.PathEditor 80 | // CellText { 81 | // value: model.cellData 82 | // type: model.cellType 83 | // option: model.cellOption 84 | // flags: model.cellFlags 85 | // readOnly: root.interactive ? cellFlags & Class.Property.ReadOnly : true 86 | // editor: 'CellPathEditor.qml' 87 | // } 88 | // } 89 | // DelegateChoice { 90 | // roleValue: Class.Property.ListEditor 91 | // CellList { 92 | // value: model.cellData 93 | // type: model.cellType 94 | // option: model.cellOption 95 | // flags: model.cellFlags 96 | // readOnly: root.interactive ? cellFlags & Class.Property.ReadOnly : true 97 | // } 98 | // } 99 | // DelegateChoice { 100 | // roleValue: Class.Property.ColorEditor 101 | // CellColor { 102 | // value: model.cellData 103 | // type: model.cellType 104 | // option: model.cellOption 105 | // flags: model.cellFlags 106 | // readOnly: root.interactive ? cellFlags & Class.Property.ReadOnly : true 107 | // } 108 | // } 109 | // DelegateChoice { 110 | // roleValue: Class.Property.TextEditor 111 | // CellText { 112 | // value: model.cellData 113 | // type: model.cellType 114 | // option: model.cellOption 115 | // flags: model.cellFlags 116 | // readOnly: root.interactive ? cellFlags & Class.Property.ReadOnly : true 117 | // } 118 | // } 119 | // DelegateChoice { 120 | // roleValue: Class.Property.BoolEditor 121 | // CellBool { 122 | // value: model.cellData 123 | // type: model.cellType 124 | // option: model.cellOption 125 | // flags: model.cellFlags 126 | // readOnly: root.interactive ? cellFlags & Class.Property.ReadOnly : true 127 | // } 128 | // } 129 | // DelegateChoice { 130 | // roleValue: Class.Property.NumberEditor 131 | // CellNumber { 132 | // value: model.cellData 133 | // type: model.cellType 134 | // option: model.cellOption 135 | // flags: model.cellFlags 136 | // readOnly: root.interactive ? cellFlags & Class.Property.ReadOnly : true 137 | // } 138 | // } 139 | // } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /source/cpp/TableModel.cpp: -------------------------------------------------------------------------------- 1 | #include "TableModel.h" 2 | #include "Property.h" 3 | #include 4 | #include 5 | #include 6 | 7 | void TableModel::createIncrementalRowLabel() { 8 | for (int i=0; i< rowCount(); ++i) rowNames_ << QString::number(i); 9 | } 10 | 11 | void TableModel::testData() { 12 | beginResetModel(); 13 | clearData(); 14 | 15 | properties_ = { new Property{"Name","mogrify"}, 16 | new Property{"Description","a R/O value", Property::ReadOnly}, 17 | new Property{"Value",1.0}, 18 | new Property{"Enabled",true}, 19 | new Property{"Visible",false,Property::ReadOnly} , 20 | new Property{"Width",500}, 21 | new Property{"thickness",10, 1, 20}, 22 | new Property{"Delta",10.2, -20.0, 20.0}, 23 | new Property{"FG color",QColor("red")}, 24 | new Property{"BG color",QColor("yellow")}, 25 | new Property{"Float [ ]",2.22, {1.11, 2.22, 3.33} }, 26 | new Property{"Search Path ", QDir::homePath() }, }; 27 | properties_.last()->setEditorType(Property::PathEditor); 28 | 29 | columnNames_ << "Name" << "Value"; 30 | createIncrementalRowLabel(); 31 | endResetModel(); 32 | emit columnNamesChanged(columnNames_); 33 | emit rowNamesChanged(rowNames_); 34 | emit countChanged(count()); 35 | } 36 | 37 | void TableModel::clearData() { 38 | properties_.clear(); 39 | columnNames_.clear(); 40 | rowNames_.clear(); 41 | qDeleteAll(properties_); 42 | } 43 | 44 | void TableModel::clear() { 45 | beginResetModel(); 46 | clearData(); 47 | endResetModel(); 48 | emit columnNamesChanged(columnNames_); 49 | emit rowNamesChanged(rowNames_); 50 | emit countChanged(count()); 51 | } 52 | 53 | TableModel::TableModel(QObject *parent) : QAbstractTableModel (parent) { 54 | } 55 | 56 | int TableModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) 57 | if (parent.isValid()) return 0; 58 | return properties_.count(); 59 | } 60 | 61 | int TableModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) 62 | if (parent.isValid()) return 0; 63 | return columnNames().count(); 64 | } 65 | 66 | QVariant TableModel::data(const QModelIndex &index, int role) const { 67 | QVariant result; 68 | if (!index.isValid() || !indexInRange(index)) return result; 69 | auto property = properties_.at(index.row()); 70 | bool isNameColumn (index.column() == 0); 71 | switch (role) { 72 | case CellDataRole: result = isNameColumn ? property->name() : property->value(); 73 | break; 74 | case CellTypeRole: result = isNameColumn ? QMetaType::QString : property->type(); 75 | break; 76 | case CellFlagsRole: result = isNameColumn ? Property::ReadOnly : property->flags(); 77 | break; 78 | case CellEditorRole: result = isNameColumn ? Property::TextEditor : property->editorType(); 79 | break; 80 | case CellOptionRole: result = isNameColumn ? QVariant() : property->option(); 81 | break; 82 | default: 83 | break; 84 | } 85 | return result; 86 | } 87 | 88 | QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const { 89 | QVariant result; 90 | if (role == Qt::DisplayRole) { 91 | if (orientation == Qt::Horizontal) result=columnNames_.at(section); 92 | if (orientation == Qt::Vertical) result=rowNames_.at(section); 93 | } 94 | return result; 95 | } 96 | 97 | QHash TableModel::roleNames() const { 98 | return { 99 | {CellDataRole, "cellData"}, 100 | {CellTypeRole, "cellType"}, 101 | {CellFlagsRole, "cellFlags"}, 102 | {CellEditorRole,"cellEditor"}, 103 | {CellOptionRole,"cellOption"}, 104 | }; 105 | } 106 | 107 | bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role) { 108 | if (!index.isValid() || !indexInRange(index)) return false; 109 | switch (role) { 110 | case CellDataRole: 111 | if (index.column()== 0) properties_[index.row()]->setName(value.toString()); 112 | else properties_[index.row()]->setValue(value); 113 | break; 114 | default: 115 | return false; 116 | } 117 | 118 | emit dataChanged(index, index, {role}); 119 | return true; 120 | } 121 | 122 | Qt::ItemFlags TableModel::flags(const QModelIndex &index) const { Q_UNUSED(index) 123 | return Qt::ItemIsEditable; 124 | } 125 | 126 | QStringList TableModel::columnNames() const { 127 | return columnNames_; 128 | } 129 | 130 | QStringList TableModel::rowNames() const { 131 | return rowNames_; 132 | } 133 | 134 | int TableModel::count() const { 135 | return rowCount(); 136 | } 137 | 138 | bool TableModel::indexInRange(const QModelIndex &index) const { 139 | return index.row()>=0 && index.row()=0 && index.column()