Фаза 4: редактор — DocumentManager (вкладки, автосохранение 5с, сохранение при закрытии), EditorPane/EditorPanel (вкладки, режимы, «открыть рядом», Ctrl+S/W/E/Tab), открытие из дерева; тест docmgr (14/14)
This commit is contained in:
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user