Фаза 5: панель форматирования — FormatToolbar/TbButton, JS-форматирование в EditorPane (инлайн/списки/задачи/заголовки/цвета/индексы/цитата/выноска/обтекание), TableEditor (2×1, полосы «+» 20px)
This commit is contained in:
@@ -100,11 +100,14 @@ qt_add_qml_module(flora
|
|||||||
qml/panels/FileTreePanel.qml
|
qml/panels/FileTreePanel.qml
|
||||||
qml/panels/EditorPanel.qml
|
qml/panels/EditorPanel.qml
|
||||||
qml/panels/EditorPane.qml
|
qml/panels/EditorPane.qml
|
||||||
|
qml/panels/FormatToolbar.qml
|
||||||
|
qml/panels/TbButton.qml
|
||||||
qml/panels/RightPanel.qml
|
qml/panels/RightPanel.qml
|
||||||
qml/wizard/WelcomeWizard.qml
|
qml/wizard/WelcomeWizard.qml
|
||||||
qml/dialogs/ProviderSetupGuide.qml
|
qml/dialogs/ProviderSetupGuide.qml
|
||||||
qml/dialogs/NameDialog.qml
|
qml/dialogs/NameDialog.qml
|
||||||
qml/dialogs/ConfirmDialog.qml
|
qml/dialogs/ConfirmDialog.qml
|
||||||
|
qml/dialogs/TableEditor.qml
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Тесты ---
|
# --- Тесты ---
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
// Редактор markdown-таблицы. По умолчанию 2×1.
|
||||||
|
// Справа — столбец «+» (добавить строку под строкой), снизу — ряд «+»
|
||||||
|
// (добавить столбец правее столбца). Полосы «+» растут вместе с таблицей.
|
||||||
|
Dialog {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
signal markdownReady(string md)
|
||||||
|
|
||||||
|
title: qsTr("Таблица")
|
||||||
|
modal: true
|
||||||
|
width: 560
|
||||||
|
anchors.centerIn: parent
|
||||||
|
closePolicy: Dialog.NoAutoClose
|
||||||
|
standardButtons: Dialog.NoButton
|
||||||
|
|
||||||
|
property int rows: 1
|
||||||
|
property int cols: 2
|
||||||
|
property var cells: [] // плоский список строк (rows*cols)
|
||||||
|
|
||||||
|
readonly property int cellW: 120
|
||||||
|
readonly property int cellH: 34
|
||||||
|
|
||||||
|
function ensureCells() {
|
||||||
|
while (cells.length < rows * cols)
|
||||||
|
cells.push("")
|
||||||
|
while (cells.length > rows * cols)
|
||||||
|
cells.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
function cell(r, c) {
|
||||||
|
const i = r * cols + c
|
||||||
|
return i < cells.length ? cells[i] : ""
|
||||||
|
}
|
||||||
|
function setCell(r, c, v) {
|
||||||
|
const i = r * cols + c
|
||||||
|
if (i < cells.length) cells[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRowAfter(r) {
|
||||||
|
const src = []
|
||||||
|
for (let c = 0; c < cols; ++c) src.push(cell(r, c))
|
||||||
|
const ins = r + 1
|
||||||
|
const flat = []
|
||||||
|
for (let rr = 0; rr <= rows; ++rr) {
|
||||||
|
if (rr === ins) for (const v of src) flat.push(v)
|
||||||
|
for (let c = 0; c < cols; ++c) {
|
||||||
|
if (rr < rows) flat.push(cell(rr, c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cells = flat
|
||||||
|
rows += 1
|
||||||
|
rebuildGrid()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addColAfter(c) {
|
||||||
|
const src = []
|
||||||
|
for (let r = 0; r < rows; ++r) src.push(cell(r, c))
|
||||||
|
const ins = c + 1
|
||||||
|
const flat = []
|
||||||
|
for (let rr = 0; rr < rows; ++rr) {
|
||||||
|
for (let cc = 0; cc <= cols; ++cc) {
|
||||||
|
if (cc === ins) flat.push("")
|
||||||
|
if (cc < cols) flat.push(cell(rr, cc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cells = flat
|
||||||
|
cols += 1
|
||||||
|
rebuildGrid()
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMarkdown() {
|
||||||
|
let out = ""
|
||||||
|
for (let c = 0; c < cols; ++c)
|
||||||
|
out += (c === 0 ? "| " : " | ") + cell(0, c)
|
||||||
|
out += " |\n"
|
||||||
|
for (let c = 0; c < cols; ++c)
|
||||||
|
out += (c === 0 ? "| " : " | ") + "---"
|
||||||
|
out += " |\n"
|
||||||
|
for (let r = 1; r < rows; ++r) {
|
||||||
|
for (let c = 0; c < cols; ++c)
|
||||||
|
out += (c === 0 ? "| " : " | ") + cell(r, c)
|
||||||
|
out += " |\n"
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuildGrid() {
|
||||||
|
// Repeater-ы привязаны к root.rows/root.cols напрямую — ничего не делаем.
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetDialog() {
|
||||||
|
rows = 1
|
||||||
|
cols = 2
|
||||||
|
cells = []
|
||||||
|
for (let i = 0; i < rows * cols; ++i) cells.push("")
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
resetDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
contentWidth: cellGrid.width + 26
|
||||||
|
contentHeight: cellGrid.height + 30
|
||||||
|
clip: true
|
||||||
|
ScrollBar.horizontal: ScrollBar {}
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: tableRow
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Grid {
|
||||||
|
id: cellGrid
|
||||||
|
columns: root.cols
|
||||||
|
columnSpacing: 2
|
||||||
|
rowSpacing: 2
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.rows * root.cols
|
||||||
|
delegate: TextField {
|
||||||
|
width: root.cellW
|
||||||
|
height: root.cellH
|
||||||
|
text: root.cell(Math.floor(model / root.cols), model % root.cols)
|
||||||
|
onTextEdited: root.setCell(Math.floor(model / root.cols), model % root.cols, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Полоса «+» справа: добавить строку под строкой (20px шириной)
|
||||||
|
Grid {
|
||||||
|
columns: 1
|
||||||
|
columnSpacing: 2
|
||||||
|
rowSpacing: 2
|
||||||
|
anchors.leftMargin: 4
|
||||||
|
Repeater {
|
||||||
|
id: rowAddStrip
|
||||||
|
model: root.rows
|
||||||
|
delegate: Button {
|
||||||
|
width: 20
|
||||||
|
height: root.cellH
|
||||||
|
text: "+"
|
||||||
|
font.pixelSize: 12
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Добавить строку под этой")
|
||||||
|
onClicked: root.addRowAfter(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.top: tableRow.bottom
|
||||||
|
anchors.topMargin: 4
|
||||||
|
Grid {
|
||||||
|
columns: root.cols
|
||||||
|
columnSpacing: 2
|
||||||
|
rowSpacing: 2
|
||||||
|
Repeater {
|
||||||
|
id: colAddStrip
|
||||||
|
model: root.cols
|
||||||
|
delegate: Button {
|
||||||
|
width: root.cellW
|
||||||
|
height: 20
|
||||||
|
text: "+"
|
||||||
|
font.pixelSize: 12
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Добавить столбец правее этого")
|
||||||
|
onClicked: root.addColAfter(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
Label {
|
||||||
|
text: qsTr("Строк: %1 Столбцов: %2").arg(root.rows).arg(root.cols)
|
||||||
|
color: "#888"
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: qsTr("Отмена")
|
||||||
|
onClicked: root.reject()
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: qsTr("Вставить")
|
||||||
|
highlighted: true
|
||||||
|
onClicked: {
|
||||||
|
root.markdownReady(buildMarkdown())
|
||||||
|
root.accept()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,132 @@ Item {
|
|||||||
headerTitle.text = title + (documentManager.isDirty(pane.docIndex) ? " •" : "")
|
headerTitle.text = title + (documentManager.isDirty(pane.docIndex) ? " •" : "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly property bool hasFocus: editor.activeFocus || viewer.activeFocus
|
||||||
|
|
||||||
|
// ---------- Функции форматирования (используются FormatToolbar) ----------
|
||||||
|
function syncText() {
|
||||||
|
documentManager.updateContent(pane.docIndex, editor.text)
|
||||||
|
}
|
||||||
|
function lineStart(pos) {
|
||||||
|
const t = editor.text
|
||||||
|
let i = Math.min(Math.max(pos, 0), t.length)
|
||||||
|
while (i > 0 && t[i - 1] !== "\n") --i
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
function lineEnd(pos) {
|
||||||
|
const t = editor.text
|
||||||
|
let i = Math.min(Math.max(pos, 0), t.length)
|
||||||
|
while (i < t.length && t[i] !== "\n") ++i
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
function replaceRange(start, end, newText) {
|
||||||
|
editor.remove(start, end)
|
||||||
|
editor.insert(start, newText)
|
||||||
|
editor.cursorPosition = start + newText.length
|
||||||
|
syncText()
|
||||||
|
editor.forceActiveFocus()
|
||||||
|
}
|
||||||
|
// Обёртка выделения pre…post (жирный, курсив и т.п.)
|
||||||
|
function applyInline(pre, post) {
|
||||||
|
const s = editor.selectionStart, e = editor.selectionEnd
|
||||||
|
const sel = editor.selectedText
|
||||||
|
if (s === e) {
|
||||||
|
const cp = editor.cursorPosition
|
||||||
|
editor.insert(cp, pre + post)
|
||||||
|
editor.cursorPosition = cp + pre.length
|
||||||
|
} else {
|
||||||
|
const t = pre + sel + post
|
||||||
|
editor.remove(s, e)
|
||||||
|
editor.insert(s, t)
|
||||||
|
editor.cursorPosition = s + pre.length + sel.length
|
||||||
|
}
|
||||||
|
syncText()
|
||||||
|
editor.forceActiveFocus()
|
||||||
|
}
|
||||||
|
// Префикс к каждой строке выделения (списки, цитата)
|
||||||
|
function applyLinePrefix(prefix) {
|
||||||
|
let s = editor.selectionStart, e = editor.selectionEnd
|
||||||
|
if (s === e) { s = lineStart(editor.cursorPosition); e = lineEnd(editor.cursorPosition) }
|
||||||
|
else { s = lineStart(s); e = lineEnd(e) }
|
||||||
|
const lines = editor.text.substring(s, e).split("\n")
|
||||||
|
for (let i = 0; i < lines.length; ++i)
|
||||||
|
lines[i] = prefix + lines[i]
|
||||||
|
replaceRange(s, e, lines.join("\n"))
|
||||||
|
}
|
||||||
|
// Переключение задач: "- [ ]"/"- [x]" ⇄ "- "
|
||||||
|
function applyTaskToggle() {
|
||||||
|
let s = editor.selectionStart, e = editor.selectionEnd
|
||||||
|
if (s === e) { s = lineStart(editor.cursorPosition); e = lineEnd(editor.cursorPosition) }
|
||||||
|
else { s = lineStart(s); e = lineEnd(e) }
|
||||||
|
const lines = editor.text.substring(s, e).split("\n")
|
||||||
|
for (let i = 0; i < lines.length; ++i) {
|
||||||
|
const ln = lines[i]
|
||||||
|
if (/^[-*]\s+\[[ x]\]\s+/.test(ln))
|
||||||
|
lines[i] = ln.replace(/^([-*])\s+\[[ x]\]\s+/, "$1 ")
|
||||||
|
else
|
||||||
|
lines[i] = "- [ ] " + ln
|
||||||
|
}
|
||||||
|
replaceRange(s, e, lines.join("\n"))
|
||||||
|
}
|
||||||
|
// Заголовок уровня 1..6
|
||||||
|
function applyHeading(level) {
|
||||||
|
let s = editor.selectionStart, e = editor.selectionEnd
|
||||||
|
if (s === e) { s = lineStart(editor.cursorPosition); e = lineEnd(editor.cursorPosition) }
|
||||||
|
else { s = lineStart(s); e = lineEnd(e) }
|
||||||
|
const lines = editor.text.substring(s, e).split("\n")
|
||||||
|
for (let i = 0; i < lines.length; ++i)
|
||||||
|
lines[i] = "#".repeat(level) + " " + lines[i].replace(/^#+\s*/, "")
|
||||||
|
replaceRange(s, e, lines.join("\n"))
|
||||||
|
}
|
||||||
|
function applyColor(hex, isHighlight) {
|
||||||
|
if (isHighlight)
|
||||||
|
applyInline("<mark style=\"background:" + hex + "\">", "</mark>")
|
||||||
|
else
|
||||||
|
applyInline("<span style=\"color:" + hex + "\">", "</span>")
|
||||||
|
}
|
||||||
|
function applySubSup(kind) {
|
||||||
|
applyInline(kind === "sup" ? "<sup>" : "<sub>", kind === "sup" ? "</sup>" : "</sub>")
|
||||||
|
}
|
||||||
|
function insertText(t) {
|
||||||
|
editor.insert(editor.cursorPosition, t)
|
||||||
|
syncText()
|
||||||
|
editor.forceActiveFocus()
|
||||||
|
}
|
||||||
|
// Обтекание картинок/таблиц директивой ![[...|float:left|width:40%]]
|
||||||
|
function applyFloatWrap() {
|
||||||
|
const s = editor.selectionStart, e = editor.selectionEnd
|
||||||
|
const sel = editor.selectedText.trim()
|
||||||
|
if (sel.length > 0) {
|
||||||
|
let inner = sel
|
||||||
|
if (/^!\[\[.+\]\]$/.test(sel))
|
||||||
|
inner = sel.slice(3, -2)
|
||||||
|
const text = "![[" + inner
|
||||||
|
+ (inner.includes("|") ? "" : "|float:left|width:40%") + "]]"
|
||||||
|
editor.remove(s, e)
|
||||||
|
editor.insert(s, text)
|
||||||
|
editor.cursorPosition = s + text.length
|
||||||
|
} else {
|
||||||
|
const t = "![[image.png|float:left|width:40%]]"
|
||||||
|
editor.insert(editor.cursorPosition, t)
|
||||||
|
editor.cursorPosition += t.length
|
||||||
|
}
|
||||||
|
syncText()
|
||||||
|
editor.forceActiveFocus()
|
||||||
|
}
|
||||||
|
// Выноска: > [!TYPE] + строки выделения
|
||||||
|
function applyCallout(type) {
|
||||||
|
let s = editor.selectionStart, e = editor.selectionEnd
|
||||||
|
if (s === e) { s = lineStart(editor.cursorPosition); e = lineEnd(editor.cursorPosition) }
|
||||||
|
else { s = lineStart(s); e = lineEnd(e) }
|
||||||
|
const lines = editor.text.substring(s, e).split("\n").filter(l => l.trim().length > 0)
|
||||||
|
const out = ["> [!NOTE]".replace("NOTE", type)]
|
||||||
|
if (lines.length > 0)
|
||||||
|
for (const l of lines) out.push("> " + l)
|
||||||
|
else
|
||||||
|
out.push("> Текст выноски")
|
||||||
|
replaceRange(s, e, out.join("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|||||||
@@ -103,6 +103,18 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----- Панель форматирования (Фаза 5) -----
|
||||||
|
FormatToolbar {
|
||||||
|
id: formatToolbar
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: documentManager.activeIndex >= 0 && toolbarShouldShow()
|
||||||
|
targetPane: activePane()
|
||||||
|
onTableRequested: {
|
||||||
|
tableEditor.resetDialog()
|
||||||
|
tableEditor.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----- Область редактора (одна или две панели) -----
|
// ----- Область редактора (одна или две панели) -----
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -143,6 +155,35 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----- Функции активной панели и таблица -----
|
||||||
|
function activePane() {
|
||||||
|
if (secondaryPane.visible && secondaryPane.hasFocus)
|
||||||
|
return secondaryPane
|
||||||
|
return primaryPane
|
||||||
|
}
|
||||||
|
function toolbarShouldShow() {
|
||||||
|
const p = primaryPane
|
||||||
|
const s = secondaryPane
|
||||||
|
return (p.hasFocus && p.editMode)
|
||||||
|
|| (s.visible && s.hasFocus && s.editMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: primaryPane
|
||||||
|
function onActiveFocusChanged() { formatToolbar.targetPane = root.activePane() }
|
||||||
|
}
|
||||||
|
Connections {
|
||||||
|
target: secondaryPane
|
||||||
|
function onActiveFocusChanged() { formatToolbar.targetPane = root.activePane() }
|
||||||
|
}
|
||||||
|
|
||||||
|
TableEditor {
|
||||||
|
id: tableEditor
|
||||||
|
onMarkdownReady: (md) => {
|
||||||
|
root.activePane().insertText(md)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----- Горячие клавиши -----
|
// ----- Горячие клавиши -----
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: "Ctrl+S"
|
sequence: "Ctrl+S"
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
// Панель быстрого форматирования текста (видна в режиме редактирования).
|
||||||
|
// Работает с выделенным текстом в активной панели (targetPane).
|
||||||
|
Item {
|
||||||
|
id: toolbar
|
||||||
|
|
||||||
|
property var targetPane: null // EditorPane
|
||||||
|
readonly property bool usable: targetPane && targetPane.docIndex >= 0
|
||||||
|
|
||||||
|
signal tableRequested()
|
||||||
|
|
||||||
|
implicitHeight: 34
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "#f3f3f3"
|
||||||
|
border.color: "#ddd"
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 4
|
||||||
|
anchors.rightMargin: 4
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
TbButton { text: "B"; tip: qsTr("Жирный"); onTapped: targetPane.applyInline("**", "**") }
|
||||||
|
TbButton { text: "I"; tip: qsTr("Курсив"); onTapped: targetPane.applyInline("*", "*") }
|
||||||
|
TbButton { text: "U"; tip: qsTr("Подчёркнутый"); onTapped: targetPane.applyInline("<u>", "</u>") }
|
||||||
|
TbButton { text: "S"; tip: qsTr("Зачёркнутый"); onTapped: targetPane.applyInline("~~", "~~") }
|
||||||
|
TbButton { text: "=="; tip: qsTr("Выделение текста"); onTapped: targetPane.applyInline("==", "==") }
|
||||||
|
|
||||||
|
TbButton {
|
||||||
|
text: "A"
|
||||||
|
tip: qsTr("Цвет текста")
|
||||||
|
bold: true
|
||||||
|
onTapped: { colorPopup.highlightMode = false; colorPopup.openAt(this) }
|
||||||
|
}
|
||||||
|
TbButton {
|
||||||
|
text: "A"
|
||||||
|
tip: qsTr("Цвет выделения")
|
||||||
|
accent: true
|
||||||
|
onTapped: { colorPopup.highlightMode = true; colorPopup.openAt(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
TbButton { text: "x²"; tip: qsTr("Верхний индекс"); onTapped: targetPane.applySubSup("sup") }
|
||||||
|
TbButton { text: "x₂"; tip: qsTr("Нижний индекс"); onTapped: targetPane.applySubSup("sub") }
|
||||||
|
|
||||||
|
Item { width: 6 }
|
||||||
|
Rectangle { width: 1; height: 20; color: "#ccc" }
|
||||||
|
Item { width: 6 }
|
||||||
|
|
||||||
|
TbButton { text: "H#"; tip: qsTr("Заголовок 1–6"); onTapped: headingMenu.popup() }
|
||||||
|
TbButton { text: "☰"; tip: qsTr("Списки"); onTapped: listMenu.popup() }
|
||||||
|
TbButton { text: "❝"; tip: qsTr("Цитата"); onTapped: targetPane.applyLinePrefix("> ") }
|
||||||
|
TbButton { text: "⚠"; tip: qsTr("Выноска"); onTapped: calloutMenu.popup() }
|
||||||
|
TbButton { text: "▦"; tip: qsTr("Таблица"); onTapped: toolbar.tableRequested() }
|
||||||
|
TbButton { text: "⊡"; tip: qsTr("Обтекание картинки/таблицы"); onTapped: targetPane.applyFloatWrap() }
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Меню: заголовки ----------
|
||||||
|
Menu {
|
||||||
|
id: headingMenu
|
||||||
|
title: qsTr("Заголовок")
|
||||||
|
Repeater {
|
||||||
|
model: [1, 2, 3, 4, 5, 6]
|
||||||
|
MenuItem {
|
||||||
|
text: "H" + modelData + " — " + "######".slice(0, modelData)
|
||||||
|
onClicked: targetPane.applyHeading(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Меню: списки ----------
|
||||||
|
Menu {
|
||||||
|
id: listMenu
|
||||||
|
title: qsTr("Список")
|
||||||
|
MenuItem {
|
||||||
|
text: qsTr("Маркированный")
|
||||||
|
onClicked: targetPane.applyLinePrefix("- ")
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: qsTr("Нумерованный")
|
||||||
|
onClicked: targetPane.applyLinePrefix("1. ")
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: qsTr("Задачи (- [ ])")
|
||||||
|
onClicked: targetPane.applyTaskToggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Меню: выноски ----------
|
||||||
|
Menu {
|
||||||
|
id: calloutMenu
|
||||||
|
title: qsTr("Выноска")
|
||||||
|
Repeater {
|
||||||
|
model: ["NOTE", "INFO", "WARN", "ERROR"]
|
||||||
|
MenuItem {
|
||||||
|
text: modelData
|
||||||
|
onClicked: targetPane.applyCallout(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Палитра цветов ----------
|
||||||
|
property var colorList: [
|
||||||
|
"#000000", "#ffffff", "#9e9e9e", "#795548", "#607d8b",
|
||||||
|
"#f44336", "#ff9800", "#ffeb3b", "#4caf50", "#00bcd4",
|
||||||
|
"#2196f3", "#3f51b5", "#9c27b0", "#e91e63"
|
||||||
|
]
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
id: colorPopup
|
||||||
|
property bool highlightMode: false
|
||||||
|
function openAt(btn) {
|
||||||
|
x = btn.mapToItem(toolbar, 0, 0).x
|
||||||
|
y = btn.mapToItem(toolbar, 0, 0).y + btn.height
|
||||||
|
open()
|
||||||
|
}
|
||||||
|
width: grid.width + 12
|
||||||
|
height: grid.height + 12
|
||||||
|
Grid {
|
||||||
|
id: grid
|
||||||
|
anchors.centerIn: parent
|
||||||
|
columns: 7
|
||||||
|
spacing: 3
|
||||||
|
Repeater {
|
||||||
|
model: toolbar.colorList
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: 22
|
||||||
|
height: 22
|
||||||
|
radius: 3
|
||||||
|
color: modelData
|
||||||
|
border.color: modelData === "#ffffff" ? "#bbb" : "transparent"
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
toolbar.targetPane.applyColor(modelData, colorPopup.highlightMode)
|
||||||
|
colorPopup.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
|
||||||
|
// Компактная кнопка тулбара.
|
||||||
|
Button {
|
||||||
|
id: b
|
||||||
|
|
||||||
|
property string tip: ""
|
||||||
|
property bool bold: false
|
||||||
|
property bool accent: false
|
||||||
|
signal tapped()
|
||||||
|
|
||||||
|
implicitWidth: b.text.length > 1 ? 34 : 28
|
||||||
|
implicitHeight: 26
|
||||||
|
font.bold: bold
|
||||||
|
highlighted: accent
|
||||||
|
font.pixelSize: 13
|
||||||
|
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: tip
|
||||||
|
|
||||||
|
onClicked: b.tapped()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user