diff options
-rw-r--r-- | kodereviewer/models/label.py | 96 | ||||
-rw-r--r-- | kodereviewer/models/line_model.py | 81 | ||||
-rw-r--r-- | kodereviewer/qml/Editor.qml | 100 | ||||
-rw-r--r-- | kodereviewer/qml/FilesView.qml | 61 | ||||
-rw-r--r-- | kodereviewer/qml/MarkdownTextArea.qml | 20 |
5 files changed, 358 insertions, 0 deletions
diff --git a/kodereviewer/models/label.py b/kodereviewer/models/label.py new file mode 100644 index 0000000..8349aa8 --- /dev/null +++ b/kodereviewer/models/label.py @@ -0,0 +1,96 @@ +from copy import copy +from enum import IntEnum, auto +from typing import Any, Optional + +from PySide6.QtCore import QAbstractListModel, QByteArray, QModelIndex, QObject, QPersistentModelIndex, QStandardPaths, QUrl, Qt, Signal, Slot, Property +from PySide6.QtQml import QmlElement + +from kodereviewer.project import Project +from kodereviewer.data import Label, PullRequest + +QML_IMPORT_NAME = "org.deprecated.kodereviewer" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class LabelModel(QAbstractListModel): + """Model to show pull request labels.""" + + _pull_request: Optional[PullRequest] + + class Roles(IntEnum): + Name = Qt.ItemDataRole.UserRole + 1 + LabelColor = auto() + TextColor = auto() + Description = auto() + + def __init__(self): + super().__init__() + self._pull_request = None + + pullRequestChanged = Signal() + + def get_pull_request(self) -> Optional[PullRequest]: + return self._pull_request + + def set_pull_request(self, pull_request: Optional[PullRequest]) -> None: + if pull_request is None: + return + + if self._pull_request is not None and self._pull_request == pull_request: + return + + self.beginResetModel() + self._pull_request = pull_request + self.endResetModel() + self.pullRequestChanged.emit() + + pullRequest = Property(PullRequest, fget=get_pull_request, fset=set_pull_request, + notify=pullRequestChanged) + + def data(self, + index: QModelIndex | QPersistentModelIndex, + role: int = Qt.ItemDataRole.DisplayRole) -> object: + if self._pull_request is None: + return None + + label: Label = self._pull_request.labels[index.row()] + + if role == self.Roles.Name: + return label.name + if role == self.Roles.LabelColor: + return f'#{label.color}' + if role == self.Roles.TextColor: + return self.text_color(label.color) + if role == self.Roles.Description: + return label.description + if role == Qt.ItemDataRole.DisplayRole: + return label.name + return None + + def rowCount(self, parent: QModelIndex | QPersistentModelIndex = QModelIndex()) -> int: + if self._pull_request is None: + return 0 + return len(self._pull_request.labels) + + def roleNames(self) -> dict[int, QByteArray]: + return { + self.Roles.Name: QByteArray(b'name'), + self.Roles.LabelColor: QByteArray(b'labelColor'), + self.Roles.TextColor: QByteArray(b'textColor'), + self.Roles.Description: QByteArray(b'description'), + } + + def text_color(self, background_color: str) -> str: + r = int(background_color[0:2], 16) + g = int(background_color[2:4], 16) + b = int(background_color[4:6], 16) + def _t(value: float) -> float: + if value <= 0.04045: + return value / 12.92 + return ((value + 0.055) / 1.055) ** 2.4 + srgb = [_t(value/255) for value in [r, g, b]] + L = 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2]; + if L > 0.179: + return '#000' + return '#fff' diff --git a/kodereviewer/models/line_model.py b/kodereviewer/models/line_model.py new file mode 100644 index 0000000..1814829 --- /dev/null +++ b/kodereviewer/models/line_model.py @@ -0,0 +1,81 @@ +"""Model used for line numbers in Editor.qml""" +from enum import auto, IntEnum +from typing import Optional + +from PySide6.QtCore import QAbstractListModel, QByteArray, QModelIndex, QObject, QPersistentModelIndex, QStandardPaths, QUrl, Qt, Signal, Slot, Property +from PySide6.QtQuick import QQuickTextDocument +from PySide6.QtQml import QmlElement + +QML_IMPORT_NAME = "org.deprecated.kodereviewer" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class LineModel(QAbstractListModel): + + _document: QQuickTextDocument | None + + class Roles(IntEnum): + LineHeight = Qt.ItemDataRole.UserRole + 1 + + def __init__(self, *args, **kwargs): + self._document = None + super().__init__(*args, **kwargs) + + + def get_document(self): + return self._document + + def set_document(self, document): + print('setting document') + if document == self._document: + return + self._document = document + self.documentChanged.emit() + self.resetModel() + + documentChanged = Signal() + document = Property(QQuickTextDocument, fget=get_document, fset=set_document, + notify=documentChanged) + + def data(self, + index: QModelIndex | QPersistentModelIndex, + role: int = Qt.ItemDataRole.DisplayRole) -> object: + + if not index.isValid(): + print('index not valid') + return + + if self._document is None: + print('document none') + return + + row = index.row() + if row < 0 or row > self.rowCount(): + print(f'row: {row}') + return + + if role == self.Roles.LineHeight: + text_doc = self._document.textDocument() + return int(text_doc.documentLayout().blockBoundingRect(text_doc.findBlockByNumber(row)).height()) + print('found role ?') + + + def rowCount(self, parent: QModelIndex | QPersistentModelIndex = QModelIndex()) -> int: + if self._document is None: + print('document is none: row count 0') + return 0 + print(f'returning {self._document.textDocument().blockCount()}') + return self._document.textDocument().blockCount(); + + def roleNames(self) -> dict[int, QByteArray]: + return { + self.Roles.LineHeight: QByteArray(b'lineHeight'), + } + + + @Slot() + def resetModel(self) -> None: + print('reseting model?') + self.beginResetModel() + self.endResetModel() diff --git a/kodereviewer/qml/Editor.qml b/kodereviewer/qml/Editor.qml new file mode 100644 index 0000000..9cc6cbf --- /dev/null +++ b/kodereviewer/qml/Editor.qml @@ -0,0 +1,100 @@ +pragma ComponentBehavior: Bound +import QtQuick 6.7 +import QtQuick.Controls 6.7 as QQC2 +import QtQuick.Layouts 6.7 + +import org.kde.kirigami as Kirigami + +import org.kde.syntaxhighlighting + +import org.deprecated.kodereviewer + +TextEdit { + + id: root + + required property string file + + signal commentClicked(string path, int startLine, int endLine) + signal newComment(string path, int startLine, int endLine) + + property string filename: "A.patch" + + property int lineHeight: contentHeight / lineCount + + leftPadding: lineNumberColumn.width + lineNumberColumn.anchors.leftMargin + Kirigami.Units.smallSpacing * 2 + + readOnly: true + textFormat: TextEdit.PlainText + wrapMode: TextEdit.Wrap + color: Kirigami.Theme.textColor + selectionColor: Kirigami.Theme.highlightColor + font.family: "monospace" + Kirigami.SpellCheck.enabled: false + + LineModel { + id: lineModel + document: root.textDocument + } + + onWidthChanged: lineModel.resetModel() + onHeightChanged: lineModel.resetModel() + + SyntaxHighlighter { + id: highlighter + textEdit: root + definition: Repository.definitionForFileName(root.filename) + } + + Kirigami.Separator { + anchors { + top: root.top + bottom: root.bottom + left: lineNumberColumn.right + leftMargin: Kirigami.Units.smallSpacing + } + } + + ColumnLayout { + id: lineNumberColumn + anchors { + top: root.top + topMargin: root.topPadding + left: root.left + leftMargin: Kirigami.Units.smallSpacing + } + spacing: 0 + Repeater { + id: repeater + model: lineModel + delegate: QQC2.Label { + id: label + required property int index + required property int lineHeight + Layout.fillWidth: true + Layout.preferredHeight: lineHeight + horizontalAlignment: Text.AlignRight + text: index + 1 + color: Kirigami.Theme.disabledTextColor + + font.family: "monospace" + } + } + } + + onTextChanged: print(root.textDocument) + + onFileChanged: { + repeater.model.resetModel() + } + + function selectionStartLine() { + const beforeText = text.toString().substr(0, selectionStart) + return beforeText.split("\n").length + } + + function selectionEndLine() { + const beforeText = text.toString().substr(0, selectionEnd) + return beforeText.split("\n").length + } +} diff --git a/kodereviewer/qml/FilesView.qml b/kodereviewer/qml/FilesView.qml new file mode 100644 index 0000000..299c353 --- /dev/null +++ b/kodereviewer/qml/FilesView.qml @@ -0,0 +1,61 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtCore +import QtQuick.Controls as QQC2 +import QtQuick.Layouts + +import org.kde.kirigami as Kirigami +import org.kde.kirigamiaddons.delegates as Delegates + +import org.deprecated.kodereviewer 1.0 + +QQC2.SplitView { + id: root + anchors.fill: parent + padding: 0 + spacing: 0 + ListModel { + id: fileModel + ListElement { + filename: "file1" + status: "added" + additions: 100 + deletions: 40 + patch: "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test" + } + ListElement { + filename: "file2" + status: "added" + additions: 100 + deletions: 40 + patch: "@@ -1,9 +1,9 @@ def something @@ -1000,7 +1000,7 @@ module Test" + } + } + + + QQC2.ScrollView { + SplitView.preferredWidth: 200 + implicitWidth: 200 + SplitView.maximumWidth: 400 + ListView { + model: fileModel + delegate: Delegates.RoundedItemDelegate { + required property string filename + required property string patch + highlighted: ListView.isCurrentItem + text: filename + + onClicked: { + textArea.text = patch + textArea.file = filename + } + } + } + } + + Editor { + id: textArea + text: "" + file: "" + } +} diff --git a/kodereviewer/qml/MarkdownTextArea.qml b/kodereviewer/qml/MarkdownTextArea.qml new file mode 100644 index 0000000..966b209 --- /dev/null +++ b/kodereviewer/qml/MarkdownTextArea.qml @@ -0,0 +1,20 @@ +import QtQml +import QtQuick 6.7 +import QtQuick.Controls 6.7 as QQC2 +import org.kde.kirigami as Kirigami +import org.kde.syntaxhighlighting 1 + +QQC2.TextArea { + id: root + + placeholderText: "Leave a comment" + wrapMode: TextEdit.Wrap + + Kirigami.SpellCheck.enabled: true + SyntaxHighlighter { + id: hightlighter + textEdit: root + repository: Repository + definition: Repository.definitionForFileName("la.md") + } +} |