initial commit

This commit is contained in:
2026-09-21 13:41:40 +09:00
commit 855c7328df
411 changed files with 85352 additions and 0 deletions
+403
View File
@@ -0,0 +1,403 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import DocView 1.0
ApplicationWindow {
id: window
width: 1100; height: 800; minimumWidth: 480; minimumHeight: 360
visible: true
title: reader.title + (reader.title === "DocView" ? "" : " — DocView")
property bool dark: reader.settings.ui.theme === "system"
? Qt.styleHints.colorScheme !== Qt.ColorScheme.Light : reader.settings.ui.theme !== "light"
property color backgroundColor: dark ? "#181c20" : "#f1f3f4"
property color surfaceColor: dark ? "#22282e" : "#ffffff"
property color textColor: dark ? "#d8e0e8" : "#24303b"
property color mutedColor: dark ? "#97a4b1" : "#586775"
property color accentColor: dark ? "#abc8de" : "#215f8e"
property bool tocVisible: reader.settings.ui.toc_visible
property bool pagesVisible: reader.settings.ui.pages_visible
property bool statusVisible: reader.settings.ui.status_bar
property var collapsed: ({})
property var tocRows: []
readonly property int visibleCurrentOutline: {
var current = reader.currentOutline, visible = -1
for (var i = 0; i < tocRows.length; ++i) {
if (tocRows[i].originalIndex === current) return i
// A collapsed ancestor represents the current section below it.
if (tocRows[i].originalIndex < current && collapsed[tocRows[i].originalIndex]) {
var depth = tocRows[i].depth || 0, within = true
for (var j = tocRows[i].originalIndex + 1; j <= current; ++j)
if ((reader.outline[j].depth || 0) <= depth) { within = false; break }
if (within) visible = i
}
}
return visible
}
property bool searchInput: false
property string previousContext: ""
property var commandNames: ({
"file.open": qsTr("文書を開く"), "app.quit": qsTr("終了"),
"operation.cancel": qsTr("処理を取り消す"), "document.root.choose": qsTr("文書フォルダーを指定"),
"document.info": qsTr("文書情報"), "history.clear": qsTr("履歴を消去"),
"scroll.down": qsTr("下へスクロール"), "scroll.up": qsTr("上へスクロール"),
"scroll.left": qsTr("左へスクロール"), "scroll.right": qsTr("右へスクロール"),
"scroll.half_down": qsTr("半画面進む"), "scroll.half_up": qsTr("半画面戻る"),
"scroll.page_down": qsTr("一画面進む"), "scroll.page_up": qsTr("一画面戻る"),
"nav.document_start": qsTr("文書の先頭"), "nav.document_end": qsTr("文書の末尾"),
"nav.page": qsTr("指定ページへ移動"), "nav.page_next": qsTr("次のPDFページ"),
"nav.page_previous": qsTr("前のPDFページ"), "nav.heading_next": qsTr("次の見出し"),
"nav.heading_previous": qsTr("前の見出し"), "nav.chapter_next": qsTr("次の章"),
"nav.chapter_previous": qsTr("前の章"), "panel.toc.toggle": qsTr("目次の表示切替"),
"panel.pages.toggle": qsTr("ページ一覧の表示切替"), "panel.status.toggle": qsTr("ステータスの表示切替"),
"focus.next": qsTr("操作する領域を切替"), "focus.content": qsTr("本文に戻る"),
"zoom.in": qsTr("拡大"), "zoom.out": qsTr("縮小"), "zoom.fit_width": qsTr("幅に合わせる"),
"view.rotate": qsTr("PDFを回転"), "command.open": qsTr("コマンドを入力"),
"config.reload": qsTr("設定を再読込"), "help.toggle": qsTr("操作一覧"),
"nav.back": qsTr("移動履歴を戻る"), "nav.forward": qsTr("移動履歴を進む"),
"search.open": qsTr("本文を検索"), "search.next": qsTr("次の検索結果"),
"search.previous": qsTr("前の検索結果"), "panel.next": qsTr("次の項目を選択"),
"panel.previous": qsTr("前の項目を選択"), "panel.collapse": qsTr("階層を折りたたむ"),
"panel.expand": qsTr("階層を展開"), "panel.first": qsTr("先頭項目を選択"),
"panel.last": qsTr("末尾項目を選択"), "panel.half_down": qsTr("項目を半画面進む"),
"panel.half_up": qsTr("項目を半画面戻る"), "panel.activate": qsTr("選択項目へ移動"),
"link.next": qsTr("次のリンク・PDF注釈を選択"), "link.previous": qsTr("前のリンク・PDF注釈を選択"),
"link.activate": qsTr("選択リンク・PDF注釈を開く"), "unbound": qsTr("割り当てなし")
})
function contextName(value) {
return value === "content" ? qsTr("本文") : value === "toc" ? qsTr("目次")
: value === "pages" ? qsTr("ページ一覧") : qsTr("共通")
}
color: backgroundColor
font.family: "monospace"
font.pixelSize: reader.settings.ui.font_size
function focusContent() {
previousContext = "" // Explicit navigation takes precedence over temporary UI restoration.
reader.context = "content"
if (reader.format === "pdf") pdf.forceActiveFocus()
else if (reader.format !== "") web.focusContent()
else if (recentList.count > 0) recentList.forceActiveFocus()
else content.forceActiveFocus()
}
function beginTemporary() {
if (!previousContext) previousContext = reader.context
}
function endTemporary() {
reader.mode = "normal"
var target = previousContext
previousContext = ""
if (!target) return // A successful action already selected its own focus.
if (target === "toc" && tocVisible && tocList.count > 0) {
reader.context = "toc"; tocList.forceActiveFocus()
} else if (target === "pages" && pagesVisible && pageList.count > 0) {
reader.context = "pages"; pageList.forceActiveFocus()
} else focusContent()
}
function setInputMode(mode) { reader.mode = mode }
function updateToc(resetSelection) {
var previous = resetSelection ? null : tocRows[tocList.currentIndex]
let list = [], skipDepth = -1
for (let i = 0; i < reader.outline.length; ++i) {
let row = reader.outline[i], depth = row.depth || 0
if (skipDepth >= 0 && depth > skipDepth) continue
skipDepth = -1
let copy = Object.assign({}, row)
copy.originalIndex = i
copy.hasChildren = i + 1 < reader.outline.length && (reader.outline[i+1].depth || 0) > depth
list.push(copy)
if (collapsed[i]) skipDepth = depth
}
tocRows = list
var selected = 0
if (previous) {
for (var row = 0; row < list.length; ++row)
if (list[row].originalIndex === previous.originalIndex && list[row].title === previous.title &&
list[row].href === previous.href && list[row].page === previous.page) { selected = row; break }
}
tocList.currentIndex = list.length ? selected : -1
}
function panelCommand(id) {
let list = reader.context === "toc" ? tocList : pageList
if (list.count === 0) return
let next = list.currentIndex
if (id === "panel.next") next++
if (id === "panel.previous") next--
if (id === "panel.first") next = 0
if (id === "panel.last") next = list.count - 1
if (id === "panel.half_down") next += Math.max(1, Math.floor(list.height / 56))
if (id === "panel.half_up") next -= Math.max(1, Math.floor(list.height / 56))
if (id === "panel.activate") { reader.activateItem(list.model[list.currentIndex]); return }
if (reader.context === "toc" && (id === "panel.collapse" || id === "panel.expand")) {
let row = tocRows[list.currentIndex]
let c = Object.assign({}, collapsed)
if (id === "panel.collapse") {
if (row.hasChildren && !c[row.originalIndex]) c[row.originalIndex] = true
else for(let i = list.currentIndex-1; i >= 0; --i) if ((tocRows[i].depth || 0) < (row.depth || 0)) { next = i; break }
} else {
if (c[row.originalIndex]) delete c[row.originalIndex]
else if (row.hasChildren) next++
}
collapsed = c; updateToc()
}
list.currentIndex = Math.max(0, Math.min(list.count-1,next))
list.positionViewAtIndex(list.currentIndex,ListView.Contain)
}
function beginInput(search) {
beginTemporary(); searchInput = search
reader.mode = search ? "search" : "command"
commandField.text = ""; commandField.forceActiveFocus()
}
function recentCommand(id) {
if (!recentList.count || reader.state !== "Empty") return
if (id === "link.next" || id === "link.previous") {
if (clearRecent.activeFocus) recentList.forceActiveFocus()
else clearRecent.forceActiveFocus()
return
}
if (id === "link.activate") {
if (clearRecent.activeFocus) reader.execute("history.clear")
else if (recentList.currentIndex >= 0) reader.openRecent(recentList.model[recentList.currentIndex].documentId)
return
}
let next = recentList.currentIndex
if (id === "scroll.down") next++
if (id === "scroll.up") next--
if (id === "nav.document_start") next = 0
if (id === "nav.document_end") next = recentList.count - 1
if (id === "scroll.half_down" || id === "scroll.page_down") next += Math.max(1, Math.floor(recentList.height / 52))
if (id === "scroll.half_up" || id === "scroll.page_up") next -= Math.max(1, Math.floor(recentList.height / 52))
recentList.currentIndex = Math.max(0, Math.min(recentList.count - 1, next))
recentList.positionViewAtIndex(recentList.currentIndex, ListView.Contain)
recentList.forceActiveFocus()
}
function endInput() { endTemporary() }
Connections {
target: reader
function onNavigationChanged() { window.updateToc() }
function onDocumentChanged() { window.collapsed = ({}); window.updateToc(true) }
function onRecentDocumentsChanged() {
Qt.callLater(function() { if (reader.format === "" && reader.mode === "normal") window.focusContent() })
}
function onUiCommand(id,args) {
if(id === "file.open") { beginTemporary(); reader.mode = "dialog"; fileDialog.open() }
else if(id === "panel.toc.toggle") { tocVisible = !tocVisible; if(!tocVisible && reader.context === "toc") focusContent() }
else if(id === "panel.pages.toggle") { pagesVisible = !pagesVisible; if(!pagesVisible && reader.context === "pages") focusContent() }
else if(id === "panel.status.toggle") statusVisible = !statusVisible
else if(id.startsWith("panel.")) panelCommand(id)
else if(id === "focus.next") {
previousContext = ""; let areas=["content"]; if(tocVisible && reader.outline.length)areas.push("toc");if(pagesVisible && reader.pages.length)areas.push("pages")
reader.context=areas[(areas.indexOf(reader.context)+1)%areas.length]
if(reader.context === "toc")tocList.forceActiveFocus();else if(reader.context === "pages")pageList.forceActiveFocus();else focusContent()
}
else if(id === "focus.content") focusContent()
else if(id === "command.open") beginInput(false)
else if(id === "search.open") beginInput(true)
else if(id === "help.toggle") { beginTemporary(); reader.mode="dialog"; helpDialog.open() }
else if(id === "document.info") { beginTemporary(); infoText.text=args.text; infoDialog.title=args.title || qsTr("文書情報"); reader.mode="dialog"; infoDialog.open() }
else if(id === "document.root.choose") { beginTemporary(); reader.mode="dialog"; folderDialog.open() }
else if(id === "history.clear") { beginTemporary(); reader.mode="dialog"; historyDialog.open() }
else if(id === "recent.navigate") recentCommand(args.command)
}
function onPasswordNeeded(name) { beginTemporary(); reader.mode="dialog"; passwordDialog.title=name; passwordField.text="";passwordDialog.open();passwordField.forceActiveFocus() }
function onEntryNeeded(candidates) { beginTemporary(); candidateList.model=candidates; reader.mode="dialog"; entryDialog.open();candidateList.forceActiveFocus() }
function onExternalLinkRequested(url) { beginTemporary(); externalDialog.destination=url;reader.mode="dialog";externalDialog.open() }
function onSettingsChanged() {
tocVisible=reader.settings.ui.toc_visible;pagesVisible=reader.settings.ui.pages_visible;statusVisible=reader.settings.ui.status_bar
if ((!tocVisible && reader.context === "toc") || (!pagesVisible && reader.context === "pages")) focusContent()
}
}
ColumnLayout {
anchors.fill: parent; spacing: 0
RowLayout {
Layout.fillWidth: true; Layout.fillHeight: true; spacing: 1
ColumnLayout {
objectName: "navigationSidebar"
visible: (tocVisible && (reader.outline.length > 0 || reader.outlineLoading)) || (pagesVisible && reader.pages.length > 0)
readonly property real panelWidth: Math.min(reader.settings.ui.panel_width,window.width*0.4)
Layout.preferredWidth: panelWidth
Layout.minimumWidth: panelWidth
Layout.maximumWidth: panelWidth
Layout.fillWidth: false
Layout.fillHeight: true; spacing: 1
Rectangle {
visible: tocVisible && (reader.outline.length > 0 || reader.outlineLoading)
Layout.fillWidth:true;Layout.fillHeight:true;color:surfaceColor
border.color:reader.context === "toc" ? accentColor : surfaceColor
ColumnLayout {
anchors.fill:parent;anchors.margins:8;spacing:6
Label { objectName:"outlineTitle";text:reader.outlineTitle;color:mutedColor;font.pixelSize:12 }
Label { objectName:"outlineLoading";visible:reader.outlineLoading;text:qsTr("目次を読み込んでいます…");color:mutedColor;wrapMode:Text.Wrap;Layout.fillWidth:true;textFormat:Text.PlainText }
ListView {
id:tocList;objectName:"tocList";Layout.fillWidth:true;Layout.fillHeight:true;clip:true;model:window.tocRows;currentIndex:0
delegate: Rectangle {
required property var modelData;required property int index
objectName:"tocRow";property bool readingCurrent:window.visibleCurrentOutline === index
property bool keyboardSelected:tocList.currentIndex === index
width:ListView.view.width;height:32;color:tocList.currentIndex === index ? (dark ? "#35414b" : "#dce6ee") : "transparent"
border.width:keyboardSelected ? 1 : 0;border.color:mutedColor
Label { anchors.fill:parent;anchors.leftMargin:4+(modelData.depth||0)*14;verticalAlignment:Text.AlignVCenter;color:textColor;elide:Text.ElideRight;text:(readingCurrent ? "> " : " ")+(modelData.hasChildren ? (window.collapsed[modelData.originalIndex] ? "+ " : "− ") : " ")+(modelData.title||qsTr("見出し"));textFormat:Text.PlainText }
MouseArea { anchors.fill:parent;onClicked:{tocList.currentIndex=index;reader.context="toc";reader.activateItem(modelData)} }
}
ScrollBar.vertical:ScrollBar{}
Accessible.name:qsTr("目次一覧")
}
}
}
Rectangle {
visible:pagesVisible && reader.pages.length > 0
Layout.fillWidth:true;Layout.fillHeight:true;color:surfaceColor
border.color:reader.context === "pages" ? accentColor : surfaceColor
ColumnLayout {
anchors.fill:parent;anchors.margins:8;spacing:6
Label{text:reader.listTitle;color:mutedColor;font.pixelSize:12}
ListView {
id:pageList;Layout.fillWidth:true;Layout.fillHeight:true;clip:true;model:reader.pages;currentIndex:0
delegate:Rectangle {
required property var modelData;required property int index
width:ListView.view.width;height:32;color:pageList.currentIndex === index ? (dark ? "#35414b" : "#dce6ee") : "transparent"
Label{anchors.fill:parent;anchors.leftMargin:4;verticalAlignment:Text.AlignVCenter;color:textColor;elide:Text.ElideRight;text:(reader.format === "pdf" && pdf.currentPage === index ? "> " : " ")+(modelData.title||modelData.href||String(index+1));textFormat:Text.PlainText}
MouseArea{anchors.fill:parent;onClicked:{pageList.currentIndex=index;reader.context="pages";reader.activateItem(modelData)}}
}
ScrollBar.vertical:ScrollBar{}
Accessible.name:reader.listTitle
}
}
}
}
Item {
id:content;objectName:"documentContent";Layout.fillWidth:true;Layout.fillHeight:true;focus:true
PdfCanvas { id:pdf;anchors.fill:parent;visible:reader.format === "pdf";backgroundColor:window.backgroundColor;Component.onCompleted:reader.attachCanvas(pdf);Accessible.name:qsTr("PDF本文") }
WebPane { id:web;anchors.fill:parent;visible:reader.format !== "pdf" && reader.format !== "" }
ColumnLayout {
id:initialScreen;objectName:"initialScreen"
readonly property real availableHeight:content.height-(noticeBanner.visible ? noticeBanner.height : 0)
anchors.centerIn:parent;width:Math.min(580,parent.width-48);spacing:8;visible:reader.format === ""
anchors.verticalCenterOffset:noticeBanner.visible ? -noticeBanner.height/2 : 0
Label { text:qsTr("Ctrl+O: 文書を開く");color:textColor;Layout.fillWidth:true }
Label { text:qsTr("?: 操作一覧");color:mutedColor;Layout.fillWidth:true }
Label { visible:recentList.count>0;text:qsTr("最近使った文書");color:textColor;Layout.topMargin:12;Layout.fillWidth:true }
ListView {
id:recentList;objectName:"recentDocumentsList"
readonly property real rowHeight: Math.max(52, window.font.pixelSize+34)
Layout.fillWidth:true;Layout.preferredHeight:Math.min(260,Math.max(rowHeight,initialScreen.availableHeight-4*window.font.pixelSize-154),count*rowHeight)
visible:count>0;enabled:reader.state === "Empty";clip:true
model:reader.recentDocuments;currentIndex:0;keyNavigationEnabled:true
onCountChanged:currentIndex=count>0 ? Math.max(0,Math.min(currentIndex,count-1)) : -1
Keys.onPressed:event=>{
if (event.key === Qt.Key_Home) currentIndex=0
else if (event.key === Qt.Key_End) currentIndex=count-1
else return
positionViewAtIndex(currentIndex,ListView.Contain);event.accepted=true
}
delegate:ItemDelegate {
required property var modelData;required property int index
width:ListView.view.width;height:recentList.rowHeight;padding:6;focusPolicy:Qt.NoFocus
highlighted:recentList.currentIndex===index
background:Rectangle {
color:recentList.currentIndex===index ? (dark ? "#35414b" : "#dce6ee") : surfaceColor
border.color:recentList.activeFocus && recentList.currentIndex===index ? accentColor : "transparent"
}
contentItem:Column {
Label { width:parent.width;text:modelData.title;textFormat:Text.PlainText;color:textColor;elide:Text.ElideRight }
Label { width:parent.width;text:modelData.path;textFormat:Text.PlainText;color:mutedColor;font.pixelSize:12;elide:Text.ElideMiddle }
}
Accessible.name:modelData.title+" — "+modelData.path
onClicked: { recentList.currentIndex=index;recentList.forceActiveFocus();reader.openRecent(modelData.documentId) }
}
ScrollBar.vertical:ScrollBar{}
Accessible.name:qsTr("最近使った文書。上下キーで選択し、Enterで開きます")
}
RowLayout {
visible:recentList.count>0;Layout.fillWidth:true
Label { text:qsTr("↑↓: 選択 Enter: 開く");color:mutedColor;font.pixelSize:12;Layout.fillWidth:true }
Button { id:clearRecent;objectName:"clearRecentDocuments";text:qsTr("履歴を消去…");enabled:reader.state === "Empty";onClicked:reader.execute("history.clear") }
}
}
Rectangle {
id:noticeBanner;objectName:"noticeBanner"
anchors.left:parent.left;anchors.right:parent.right;anchors.bottom:parent.bottom
visible:reader.notice.length > 0;height:noticeLabel.implicitHeight+20;color:surfaceColor
Label { id:noticeLabel;anchors.left:parent.left;anchors.right:parent.right;anchors.margins:12;anchors.verticalCenter:parent.verticalCenter;text:reader.notice;textFormat:Text.PlainText;wrapMode:Text.Wrap;color:textColor;Accessible.role:Accessible.AlertMessage }
}
DropArea { anchors.fill:parent;onDropped:drop=>{if(drop.hasUrls){reader.open(drop.urls[0]);drop.acceptProposedAction()}} }
}
}
Rectangle {
visible:statusVisible;Layout.fillWidth:true;Layout.preferredHeight:28;color:surfaceColor
RowLayout {
anchors.fill:parent;anchors.leftMargin:10;anchors.rightMargin:10
Label { Layout.fillWidth:true;text:reader.title;elide:Text.ElideMiddle;color:mutedColor;textFormat:Text.PlainText }
Label { text:reader.state === "Opening" ? qsTr("読込中") : reader.position;color:textColor }
Label { text:reader.pending;color:accentColor }
Label { text:reader.context === "content" ? qsTr("内容") : reader.context === "toc" ? qsTr("目次") : reader.listTitle;color:mutedColor;leftPadding:18 }
}
}
RowLayout {
visible:reader.mode === "command" || reader.mode === "search"
Layout.fillWidth:true;spacing:0
Label { text:window.searchInput ? "/" : ":";color:textColor;leftPadding:10 }
TextField {
id:commandField;Layout.fillWidth:true;selectByMouse:true;color:textColor;background:Rectangle{color:surfaceColor}Accessible.name:window.searchInput ? qsTr("本文検索") : qsTr("コマンド")
placeholderText:window.searchInput ? (reader.format === "epub" ? qsTr("表示中の章を検索") : reader.format === "pdf" ? qsTr("本文を検索") : qsTr("表示中のHTMLを検索")) : ""
onAccepted:{if(inputMethodComposing)return;if(window.searchInput){reader.search(text);endInput()}else if(reader.command(text)){if(reader.mode === "command")endInput();else text=""}}
Keys.onEscapePressed:event=>{if(!inputMethodComposing){endInput();event.accepted=true}}
}
}
}
FileDialog { id:fileDialog;title:qsTr("文書を開く");nameFilters:[qsTr("文書 (*.pdf *.html *.htm *.xhtml *.zip *.epub)"),qsTr("すべて (*)")];onAccepted:{reader.mode="normal";reader.open(selectedFile);endTemporary()}onRejected:endTemporary() }
FolderDialog { id:folderDialog;title:qsTr("文書フォルダーを指定");onAccepted:{reader.mode="normal";reader.setHtmlRoot(selectedFolder);endTemporary()}onRejected:endTemporary() }
Dialog {
id:passwordDialog;anchors.centerIn:parent;modal:true;standardButtons:Dialog.Ok|Dialog.Cancel
TextField{id:passwordField;width:300;echoMode:TextInput.Password;Accessible.name:qsTr("PDFパスワード");onAccepted:if(!inputMethodComposing)passwordDialog.accept()}
onAccepted:{reader.mode="normal";let value=passwordField.text;passwordField.text="";reader.password(value);endTemporary()}
onRejected:{passwordField.text="";reader.mode="normal";reader.cancel();endTemporary()}
}
Dialog {
id:entryDialog;objectName:"entryDialog";anchors.centerIn:parent;modal:true;title:qsTr("入口HTMLを選択");width:Math.min(600,window.width-48);height:Math.min(440,window.height-60);standardButtons:Dialog.Ok|Dialog.Cancel
ListView{id:candidateList;objectName:"entryCandidates";anchors.fill:parent;clip:true;currentIndex:0;delegate:ItemDelegate{required property string modelData;required property int index;width:ListView.view.width;text:modelData;highlighted:candidateList.currentIndex===index;onClicked:{candidateList.currentIndex=index;entryDialog.accept()}}Keys.onReturnPressed:entryDialog.accept()}
onAccepted:{reader.mode="normal";if(candidateList.currentIndex>=0)reader.chooseEntry(candidateList.model[candidateList.currentIndex]);endTemporary()}
onRejected:{reader.cancel();endTemporary()}
}
Dialog {
id:externalDialog;property url destination;anchors.centerIn:parent;modal:true;title:qsTr("外部ブラウザーで開く");width:Math.min(640,window.width-48);standardButtons:Dialog.Open|Dialog.Cancel
Label{width:parent.width;text:externalDialog.destination.toString();textFormat:Text.PlainText;wrapMode:Text.WrapAnywhere}
onAccepted:{reader.openExternal(destination);endTemporary()}
onRejected:endTemporary()
}
Dialog {
id:infoDialog;objectName:"infoDialog";anchors.centerIn:parent;modal:true;title:qsTr("文書情報")
width:Math.min(640,window.width-48);height:Math.min(implicitHeight,window.height-60)
standardButtons:Dialog.Close
contentItem:ScrollView {
id:infoScroll;objectName:"infoScroll";clip:true
contentWidth:availableWidth;implicitHeight:infoText.implicitHeight
Label{id:infoText;objectName:"infoText";width:infoScroll.availableWidth;wrapMode:Text.WrapAnywhere;textFormat:Text.PlainText}
}
onClosed:endTemporary()
}
Dialog {
id:historyDialog;objectName:"historyDialog";anchors.centerIn:parent;modal:true
title:qsTr("履歴と保存位置を消去");width:Math.min(580,window.width-48);standardButtons:Dialog.Ok|Dialog.Cancel
Label { id:historyConfirmation;objectName:"historyConfirmation";width:parent.width;wrapMode:Text.Wrap }
onAboutToShow:historyConfirmation.text=qsTr("%1 件の履歴と読書位置を消去します。原本は変更されません。").arg(reader.historyCount())
onAccepted:{reader.clearHistory();endTemporary()}
onRejected:endTemporary()
}
Dialog {
id:helpDialog;anchors.centerIn:parent;modal:true;title:qsTr("操作一覧");width:Math.min(760,window.width-48);height:Math.min(620,window.height-60);standardButtons:Dialog.Close
ColumnLayout {
anchors.fill:parent
Label{text:qsTr("Vim風の閲覧操作です。q単押しでは終了しません。:q で終了します。");wrapMode:Text.Wrap;Layout.fillWidth:true}
ListView {
Layout.fillWidth:true;Layout.fillHeight:true;clip:true;model:reader.settings.keybindings
delegate:Label{required property var modelData;width:ListView.view.width;text:modelData.keys.join(" ")+" "+(window.commandNames[modelData.command] || modelData.command)+" ["+window.contextName(modelData.context)+"]";textFormat:Text.PlainText;font.family:"monospace";height:26;elide:Text.ElideRight}
ScrollBar.vertical:ScrollBar{}
}
}
onClosed:endTemporary()
}
Shortcut { sequence:"?";enabled:helpDialog.visible;onActivated:helpDialog.close() }
Component.onCompleted:{updateToc();focusContent()}
}