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()} }