Фаза 4: редактор — DocumentManager (вкладки, автосохранение 5с, сохранение при закрытии), EditorPane/EditorPanel (вкладки, режимы, «открыть рядом», Ctrl+S/W/E/Tab), открытие из дерева; тест docmgr (14/14)

This commit is contained in:
Flora (opencode)
2026-08-17 01:14:00 +03:00
parent c740c9a756
commit e7d873903b
9 changed files with 736 additions and 25 deletions
+5 -2
View File
@@ -178,8 +178,11 @@ ApplicationWindow {
onOpenVaultRequested: vaultSwitcher.open()
onOpenFileRequested: (path) => {
// Фаза 4: открытие вкладки в редакторе. Пока — лог.
console.log("Открыть файл: " + path)
if (documentManager.isTextForEditor(path)) {
documentManager.openDocument(path)
} else {
leftPanel.showStatus(qsTr("Предпросмотр HTML/PDF/изображений появится в Фазе 6-10"), false)
}
}
}
+145
View File
@@ -0,0 +1,145 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
// Панель одного документа: шапка (заголовок, режим, действия) + текст.
// Режимы: редактирование (TextArea) / просмотр (read-only TextArea).
Item {
id: pane
property int docIndex: -1
property bool editMode: true
property bool canOpenBeside: false // кнопка «открыть рядом» (заглавная панель)
property bool closable: false // кнопка «закрыть панель» (вторая панель)
signal openBeside()
signal closePane()
function refreshHeader() {
const title = documentManager.titleAt(pane.docIndex)
headerTitle.text = title + (documentManager.isDirty(pane.docIndex) ? " •" : "")
}
ColumnLayout {
anchors.fill: parent
spacing: 0
Rectangle {
Layout.fillWidth: true
implicitHeight: 30
color: "whitesmoke"
RowLayout {
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 4
spacing: 6
Label {
id: headerTitle
Layout.fillWidth: true
elide: Text.ElideMiddle
color: "#444"
text: ""
}
// Переключатель режимов
ToolButton {
text: "✎"
checkable: true
checked: pane.editMode
ToolTip.visible: hovered
ToolTip.text: qsTr("Режим редактирования (Ctrl+E)")
onClicked: { pane.editMode = true; editBtn.checked = true; viewBtn.checked = false }
}
ToolButton {
id: viewBtn
text: "👁"
checkable: true
checked: !pane.editMode
ToolTip.visible: hovered
ToolTip.text: qsTr("Режим просмотра (Ctrl+E)")
onClicked: { pane.editMode = false; editBtn.checked = false; viewBtn.checked = true }
}
ToolButton {
id: editBtn
visible: false
checkable: true
checked: true
onClicked: { pane.editMode = true; viewBtn.checked = false }
}
ToolButton {
text: "\u25B8\u25CB\u25C0" // «открыть рядом» (две панели)
visible: pane.canOpenBeside && !documentManager.sideBySide
ToolTip.visible: hovered
ToolTip.text: qsTr("Открыть рядом (две панели)")
onClicked: pane.openBeside()
}
ToolButton {
text: "\u2715"
visible: pane.closable
ToolTip.visible: hovered
ToolTip.text: qsTr("Закрыть вторую панель")
onClicked: pane.closePane()
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "white"
TextArea {
id: editor
anchors.fill: parent
visible: pane.editMode
text: documentManager.contentAt(pane.docIndex)
onTextEdited: documentManager.updateContent(pane.docIndex, text)
selectByMouse: true
font.family: "Menlo, Consolas, 'DejaVu Sans Mono', monospace"
font.pixelSize: 14
wrapMode: TextEdit.NoWrap
padding: 10
placeholderText: qsTr("Документ не выбран")
}
TextArea {
id: viewer
anchors.fill: parent
visible: !pane.editMode
text: documentManager.contentAt(pane.docIndex)
readOnly: true
selectByMouse: true
font.family: "Menlo, Consolas, 'DejaVu Sans Mono', monospace"
font.pixelSize: 14
wrapMode: TextEdit.Wrap
padding: 12
placeholderText: qsTr("Документ не выбран")
}
}
}
// Свежий заголовок при смене признака «изменено»
Connections {
target: documentManager
function onDirtyFlagChanged(i) {
if (i === pane.docIndex)
pane.refreshHeader()
}
}
// Просмотр обновляется при изменении контента (например, при правке в соседней панели)
Connections {
target: documentManager
function onContentChanged(i) {
if (i === pane.docIndex && !pane.editMode)
viewer.text = documentManager.contentAt(i)
}
}
onDocIndexChanged: pane.refreshHeader()
Component.onCompleted: pane.refreshHeader()
}
+137 -22
View File
@@ -3,60 +3,175 @@ import QtQuick.Controls
import QtQuick.Layouts
// Центральная панель: панель вкладок + редактор.
// Фаза 0 — каркас: вкладки, два столбца «открыть рядом» (задел),
// переключение режимов редактирование/просмотр (задел).
// Поддержка двух панелей рядом («открыть рядом»): правая = вторая вкладка.
Item {
id: root
readonly property bool editMode: true // Фаза 4: реальный переключатель
function baseName(p) {
const idx = p.lastIndexOf("/")
return idx >= 0 ? p.substring(idx + 1) : p
}
ColumnLayout {
anchors.fill: parent
spacing: 0
// Панель вкладок
// ----- Панель вкладок -----
Rectangle {
Layout.fillWidth: true
implicitHeight: 32
color: "whitesmoke"
color: "#ececec"
RowLayout {
anchors.fill: parent
anchors.leftMargin: 4
spacing: 4
anchors.rightMargin: 4
spacing: 2
Repeater {
model: 0 // открытые вкладки наполняются в Фазе 4
model: documentManager.openTabs
Rectangle {
id: tab
required property string modelData
readonly property bool active: index === documentManager.activeIndex
width: Math.max(96, Math.min(200, tabLabel.implicitWidth + 44))
height: 26
radius: 4
color: active ? "white" : "#e0e0e0"
RowLayout {
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 2
spacing: 4
Label {
id: tabLabel
Layout.fillWidth: true
elide: Text.ElideMiddle
text: root.baseName(modelData)
+ (documentManager.isDirty(index) ? " •" : "")
font.pixelSize: 12
color: active ? "#111" : "#555"
}
ToolButton {
text: "\u2715"
implicitWidth: 20
implicitHeight: 20
font.pixelSize: 9
opacity: tab.active || hovered ? 1 : 0
onClicked: documentManager.closeTab(index)
}
}
MouseArea {
anchors.fill: parent
onClicked: documentManager.setActiveIndex(index)
onDoubleClicked: documentManager.closeTab(index)
}
}
}
Item { Layout.fillWidth: true }
Label {
Layout.fillWidth: true
text: qsTr("— нет открытых файлов —")
color: "#bbb"
visible: documentManager.openTabs.length === 0
text: qsTr("Откройте файл из дерева")
color: "#999"
font.pixelSize: 12
}
ToolButton {
text: "▮▮" // «открыть рядом» (Фаза 4)
id: splitBtn
text: "\u25A1\u25A1"
checkable: true
checked: documentManager.sideBySide
enabled: documentManager.openTabs.length > 0
ToolTip.visible: hovered
ToolTip.text: qsTr("Открыть рядом (две панели)")
enabled: false
onToggled: {
if (checked) {
documentManager.setSecondaryIndex(documentManager.activeIndex)
documentManager.setSideBySide(true)
} else {
documentManager.setSideBySide(false)
}
}
}
}
}
// Область редактирования
Rectangle {
// ----- Область редактора (одна или две панели) -----
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
color: "white"
Label {
anchors.centerIn: parent
text: root.editMode
? qsTr("Редактор\nОткройте файл из дерева (Фаза 4)")
: qsTr("Просмотр\n(Фаза 4)")
horizontalAlignment: Text.AlignHCenter
color: "#bbb"
spacing: 0
EditorPane {
id: primaryPane
Layout.fillWidth: true
docIndex: documentManager.activeIndex
canOpenBeside: true
onOpenBeside: {
documentManager.setSecondaryIndex(documentManager.activeIndex)
documentManager.setSideBySide(true)
splitBtn.checked = true
}
}
Rectangle {
visible: documentManager.sideBySide
Layout.fillHeight: true
width: 1
color: "#ccc"
}
EditorPane {
id: secondaryPane
visible: documentManager.sideBySide
Layout.preferredWidth: 360
Layout.maximumWidth: 640
docIndex: documentManager.secondaryIndex
closable: true
onClosePane: {
documentManager.setSideBySide(false)
splitBtn.checked = false
}
}
}
}
// ----- Горячие клавиши -----
Shortcut {
sequence: "Ctrl+S"
context: Qt.WindowShortcut
onActivated: {
documentManager.saveAll()
}
}
Shortcut {
sequence: "Ctrl+W"
context: Qt.WindowShortcut
onActivated: {
documentManager.closeTab(documentManager.activeIndex)
}
}
Shortcut {
sequence: "Ctrl+E"
context: Qt.WindowShortcut
onActivated: {
primaryPane.editMode = !primaryPane.editMode
}
}
Shortcut {
sequence: "Ctrl+Tab"
context: Qt.WindowShortcut
onActivated: {
const n = documentManager.openTabs.length
if (n > 0)
documentManager.setActiveIndex((documentManager.activeIndex + 1) % n)
}
}
}