mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
add dock widget to display outline
1. Support displaying outline of Markdown in read mode; 2. Support navigating by outline using Marked; Signed-off-by: Le Tan <tamlokveer@gmail.com>
This commit is contained in:
parent
db0797a538
commit
ab91f755c0
@ -4,7 +4,7 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M464,144v288H48V144H464 M480,128H32v320h448V128L480,128z"/>
|
||||
<path d="M480,128H32v320h448V128L480,128z"/>
|
||||
<rect x="72" y="96" width="368" height="16"/>
|
||||
<rect x="104" y="64" width="304" height="16"/>
|
||||
</g>
|
||||
|
Before Width: | Height: | Size: 669 B After Width: | Height: | Size: 645 B |
23
src/resources/icons/outline.svg
Normal file
23
src/resources/icons/outline.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M432,80v352H80V80H432 M448,64H64v384h384V64L448,64z"/>
|
||||
<g>
|
||||
<rect x="192" y="152" width="192" height="16"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="192" y="248" width="192" height="16"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="192" y="344" width="192" height="16"/>
|
||||
</g>
|
||||
</g>
|
||||
<circle cx="144" cy="160" r="16"/>
|
||||
<circle cx="144" cy="256" r="16"/>
|
||||
<circle cx="144" cy="352" r="16"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 886 B |
@ -19,9 +19,12 @@
|
||||
var placeholder = document.getElementById('placeholder');
|
||||
var renderer = new marked.Renderer();
|
||||
var toc = []; // Table of contents as a list
|
||||
var content; // Channel variable with content
|
||||
var nameCounter = 0;
|
||||
|
||||
renderer.heading = function (text, level) {
|
||||
var escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
|
||||
// Use number to avoid issues with Chinese
|
||||
var escapedText = 'toc_' + nameCounter++;
|
||||
toc.push({
|
||||
level: level,
|
||||
anchor: escapedText,
|
||||
@ -44,11 +47,12 @@
|
||||
|
||||
var markdownToHtml = function (markdown, needToc) {
|
||||
toc = [];
|
||||
if (needToc) {
|
||||
var html = marked(markdown, { renderer: renderer });
|
||||
nameCounter = 0;
|
||||
if (needToc) {
|
||||
return html.replace(/<p>\[TOC\]<\/p>/ig, '<div class="vnote-toc"></div>');
|
||||
} else {
|
||||
return marked(markdown);
|
||||
return html;
|
||||
}
|
||||
};
|
||||
|
||||
@ -74,7 +78,7 @@
|
||||
};
|
||||
|
||||
var itemToHtml = function (item) {
|
||||
return '<a href=#' + item.anchor + '>' + item.title + '</a>';
|
||||
return '<a href="#' + item.anchor + '">' + item.title + '</a>';
|
||||
};
|
||||
|
||||
// Turn a perfect toc to a tree using <ul>
|
||||
@ -118,12 +122,17 @@
|
||||
return front;
|
||||
};
|
||||
|
||||
var addToc = function() {
|
||||
var handleToc = function(needToc) {
|
||||
var tocTree = tocToTree(toPerfectToc(toc));
|
||||
content.setToc(tocTree);
|
||||
|
||||
// Add it to html
|
||||
if (needToc) {
|
||||
var eles = document.getElementsByClassName('vnote-toc');
|
||||
for (var i = 0; i < eles.length; ++i) {
|
||||
eles[i].innerHTML = tocTree;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var mdHasTocSection = function(markdown) {
|
||||
@ -136,16 +145,25 @@
|
||||
var html = markdownToHtml(text, needToc);
|
||||
placeholder.innerHTML = html;
|
||||
|
||||
if (needToc) {
|
||||
addToc();
|
||||
handleToc(needToc);
|
||||
};
|
||||
|
||||
var scrollToAnchor = function(anchor) {
|
||||
var eles = document.getElementsByTagName('a');
|
||||
for (var i = 0; i < eles.length; ++i) {
|
||||
if (eles[i].name == anchor) {
|
||||
eles[i].scrollIntoView();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new QWebChannel(qt.webChannelTransport,
|
||||
function(channel) {
|
||||
var content = channel.objects.content;
|
||||
content = channel.objects.content;
|
||||
updateText(content.text);
|
||||
content.textChanged.connect(updateText);
|
||||
content.requestScrollToAnchor.connect(scrollToAnchor);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
@ -41,7 +41,9 @@ SOURCES += main.cpp\
|
||||
vdownloader.cpp \
|
||||
veditarea.cpp \
|
||||
veditwindow.cpp \
|
||||
vedittab.cpp
|
||||
vedittab.cpp \
|
||||
voutline.cpp \
|
||||
vtoc.cpp
|
||||
|
||||
HEADERS += vmainwindow.h \
|
||||
vdirectorytree.h \
|
||||
@ -73,7 +75,9 @@ HEADERS += vmainwindow.h \
|
||||
vdownloader.h \
|
||||
veditarea.h \
|
||||
veditwindow.h \
|
||||
vedittab.h
|
||||
vedittab.h \
|
||||
voutline.h \
|
||||
vtoc.h
|
||||
|
||||
RESOURCES += \
|
||||
vnote.qrc
|
||||
|
@ -1,7 +1,5 @@
|
||||
#include "vdocument.h"
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
VDocument::VDocument(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
@ -15,8 +13,9 @@ VDocument::VDocument(const QString &text, QObject *parent)
|
||||
|
||||
void VDocument::setText(const QString &text)
|
||||
{
|
||||
if (text == m_text)
|
||||
if (text == m_text) {
|
||||
return;
|
||||
}
|
||||
m_text = text;
|
||||
emit textChanged(m_text);
|
||||
}
|
||||
@ -25,3 +24,22 @@ QString VDocument::getText()
|
||||
{
|
||||
return m_text;
|
||||
}
|
||||
|
||||
void VDocument::setToc(const QString &toc)
|
||||
{
|
||||
if (toc == m_toc) {
|
||||
return;
|
||||
}
|
||||
m_toc = toc;
|
||||
emit tocChanged(m_toc);
|
||||
}
|
||||
|
||||
QString VDocument::getToc()
|
||||
{
|
||||
return m_toc;
|
||||
}
|
||||
|
||||
void VDocument::scrollToAnchor(const QString &anchor)
|
||||
{
|
||||
emit requestScrollToAnchor(anchor);
|
||||
}
|
||||
|
@ -8,17 +8,28 @@ class VDocument : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged)
|
||||
Q_PROPERTY(QString toc MEMBER m_toc NOTIFY tocChanged)
|
||||
|
||||
public:
|
||||
explicit VDocument(QObject *parent = 0);
|
||||
VDocument(const QString &text, QObject *parent = 0);
|
||||
void setText(const QString &text);
|
||||
QString getText();
|
||||
QString getToc();
|
||||
void scrollToAnchor(const QString &anchor);
|
||||
|
||||
public slots:
|
||||
// Will be called in the HTML side
|
||||
void setToc(const QString &toc);
|
||||
|
||||
signals:
|
||||
void textChanged(const QString &text);
|
||||
void tocChanged(const QString &toc);
|
||||
void requestScrollToAnchor(const QString &anchor);
|
||||
|
||||
private:
|
||||
QString m_text;
|
||||
QString m_toc;
|
||||
};
|
||||
|
||||
#endif // VDOCUMENT_H
|
||||
|
@ -37,6 +37,8 @@ void VEditArea::insertSplitWindow(int idx)
|
||||
this, &VEditArea::handleRemoveSplitRequest);
|
||||
connect(win, &VEditWindow::getFocused,
|
||||
this, &VEditArea::handleWindowFocused);
|
||||
connect(win, &VEditWindow::outlineChanged,
|
||||
this, &VEditArea::handleOutlineChanged);
|
||||
|
||||
int nrWin = splitter->count();
|
||||
if (nrWin == 1) {
|
||||
@ -163,6 +165,7 @@ void VEditArea::setCurrentWindow(int windowIndex, bool setFocus)
|
||||
out:
|
||||
// Update tab status
|
||||
noticeTabStatus();
|
||||
emit outlineChanged(getWindow(windowIndex)->getTabOutline());
|
||||
}
|
||||
|
||||
void VEditArea::noticeTabStatus()
|
||||
@ -278,3 +281,17 @@ void VEditArea::handleWindowFocused()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VEditArea::handleOutlineChanged(const VToc &toc)
|
||||
{
|
||||
QObject *winObject = sender();
|
||||
if (splitter->widget(curWindowIndex) == winObject) {
|
||||
emit outlineChanged(toc);
|
||||
}
|
||||
}
|
||||
|
||||
void VEditArea::handleOutlineItemActivated(const VAnchor &anchor)
|
||||
{
|
||||
// Notice current window
|
||||
getWindow(curWindowIndex)->scrollCurTab(anchor);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <QSplitter>
|
||||
#include "vnotebook.h"
|
||||
#include "veditwindow.h"
|
||||
#include "vtoc.h"
|
||||
|
||||
class VNote;
|
||||
|
||||
@ -24,6 +25,7 @@ public:
|
||||
signals:
|
||||
void curTabStatusChanged(const QString ¬ebook, const QString &relativePath,
|
||||
bool editMode, bool modifiable);
|
||||
void outlineChanged(const VToc &toc);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
|
||||
@ -38,11 +40,13 @@ public slots:
|
||||
void saveAndReadFile();
|
||||
void handleNotebookRenamed(const QVector<VNotebook> ¬ebooks, const QString &oldName,
|
||||
const QString &newName);
|
||||
void handleOutlineItemActivated(const VAnchor &anchor);
|
||||
|
||||
private slots:
|
||||
void handleSplitWindowRequest(VEditWindow *curWindow);
|
||||
void handleRemoveSplitRequest(VEditWindow *curWindow);
|
||||
void handleWindowFocused();
|
||||
void handleOutlineChanged(const VToc &toc);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
112
src/vedittab.cpp
112
src/vedittab.cpp
@ -3,6 +3,7 @@
|
||||
#include <QWebChannel>
|
||||
#include <QWebEngineView>
|
||||
#include <QFileInfo>
|
||||
#include <QXmlStreamReader>
|
||||
#include "vedittab.h"
|
||||
#include "vedit.h"
|
||||
#include "vdocument.h"
|
||||
@ -13,6 +14,7 @@
|
||||
#include "vconfigmanager.h"
|
||||
#include "vmarkdownconverter.h"
|
||||
#include "vnotebook.h"
|
||||
#include "vtoc.h"
|
||||
|
||||
extern VConfigManager vconfig;
|
||||
|
||||
@ -117,6 +119,8 @@ void VEditTab::previewByConverter()
|
||||
html.replace(tocExp, toc);
|
||||
QString completeHtml = VNote::preTemplateHtml + html + VNote::postTemplateHtml;
|
||||
webPreviewer->setHtml(completeHtml, QUrl::fromLocalFile(noteFile->basePath + QDir::separator()));
|
||||
// Hoedown will add '\n' while Marked does not
|
||||
updateTocFromHtml(toc.replace("\n", ""));
|
||||
}
|
||||
|
||||
void VEditTab::showFileEditMode()
|
||||
@ -216,6 +220,8 @@ void VEditTab::setupMarkdownPreview()
|
||||
if (mdConverterType == MarkdownConverterType::Marked) {
|
||||
QWebChannel *channel = new QWebChannel(this);
|
||||
channel->registerObject(QStringLiteral("content"), &document);
|
||||
connect(&document, &VDocument::tocChanged,
|
||||
this, &VEditTab::updateTocFromHtml);
|
||||
page->setWebChannel(channel);
|
||||
webPreviewer->setHtml(VNote::templateHtml,
|
||||
QUrl::fromLocalFile(noteFile->basePath + QDir::separator()));
|
||||
@ -235,3 +241,109 @@ void VEditTab::handleFocusChanged(QWidget *old, QWidget *now)
|
||||
emit getFocused();
|
||||
}
|
||||
}
|
||||
|
||||
void VEditTab::updateTocFromHtml(const QString &tocHtml)
|
||||
{
|
||||
qDebug() << tocHtml;
|
||||
tableOfContent.type = VHeaderType::Anchor;
|
||||
QVector<VHeader> &headers = tableOfContent.headers;
|
||||
headers.clear();
|
||||
|
||||
QXmlStreamReader xml(tocHtml);
|
||||
if (xml.readNextStartElement()) {
|
||||
if (xml.name() == "ul") {
|
||||
parseTocUl(xml, headers, 1);
|
||||
} else {
|
||||
qWarning() << "error: TOC HTML does not start with <ul>";
|
||||
}
|
||||
}
|
||||
if (xml.hasError()) {
|
||||
qWarning() << "error: fail to parse TOC in HTML";
|
||||
return;
|
||||
}
|
||||
|
||||
tableOfContent.curHeaderIndex = 0;
|
||||
tableOfContent.filePath = QDir::cleanPath(QDir(noteFile->basePath).filePath(noteFile->fileName));
|
||||
tableOfContent.valid = true;
|
||||
|
||||
emit outlineChanged(tableOfContent);
|
||||
}
|
||||
|
||||
void VEditTab::parseTocUl(QXmlStreamReader &xml, QVector<VHeader> &headers, int level)
|
||||
{
|
||||
Q_ASSERT(xml.isStartElement() && xml.name() == "ul");
|
||||
|
||||
while (xml.readNextStartElement()) {
|
||||
if (xml.name() == "li") {
|
||||
parseTocLi(xml, headers, level);
|
||||
} else {
|
||||
qWarning() << "error: TOC HTML <ul> should contain <li>" << xml.name();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VEditTab::parseTocLi(QXmlStreamReader &xml, QVector<VHeader> &headers, int level)
|
||||
{
|
||||
Q_ASSERT(xml.isStartElement() && xml.name() == "li");
|
||||
|
||||
if (xml.readNextStartElement()) {
|
||||
if (xml.name() == "a") {
|
||||
QString anchor = xml.attributes().value("href").toString();
|
||||
QString name;
|
||||
if (xml.readNext()) {
|
||||
if (xml.tokenString() == "Characters") {
|
||||
name = xml.text().toString();
|
||||
} else if (!xml.isEndElement()) {
|
||||
qWarning() << "error: TOC HTML <a> should be ended by </a>" << xml.name();
|
||||
return;
|
||||
}
|
||||
VHeader header;
|
||||
header.level = level;
|
||||
header.name = name;
|
||||
header.anchor = anchor;
|
||||
header.lineNumber = -1;
|
||||
headers.append(header);
|
||||
} else {
|
||||
// Error
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
qWarning() << "error: TOC HTML <li> should contain <a> or <ul>" << xml.name();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (xml.readNext()) {
|
||||
if (xml.isEndElement()) {
|
||||
if (xml.name() == "li") {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (xml.name() == "ul") {
|
||||
// Nested unordered list
|
||||
parseTocUl(xml, headers, level + 1);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const VToc& VEditTab::getOutline() const
|
||||
{
|
||||
return tableOfContent;
|
||||
}
|
||||
|
||||
void VEditTab::scrollToAnchor(const VAnchor &anchor)
|
||||
{
|
||||
if (isEditMode) {
|
||||
if (anchor.lineNumber > -1) {
|
||||
|
||||
}
|
||||
} else {
|
||||
if (!anchor.anchor.isEmpty()) {
|
||||
document.scrollToAnchor(anchor.anchor.mid(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,10 +9,12 @@
|
||||
#include "vmarkdownconverter.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vedit.h"
|
||||
#include "vtoc.h"
|
||||
|
||||
class QTextBrowser;
|
||||
class QWebEngineView;
|
||||
class VNote;
|
||||
class QXmlStreamReader;
|
||||
|
||||
class VEditTab : public QStackedWidget
|
||||
{
|
||||
@ -31,12 +33,16 @@ public:
|
||||
inline bool getIsEditMode() const;
|
||||
inline bool isModified() const;
|
||||
void focusTab();
|
||||
const VToc& getOutline() const;
|
||||
void scrollToAnchor(const VAnchor& anchor);
|
||||
|
||||
signals:
|
||||
void getFocused();
|
||||
void outlineChanged(const VToc &toc);
|
||||
|
||||
private slots:
|
||||
void handleFocusChanged(QWidget *old, QWidget *now);
|
||||
void updateTocFromHtml(const QString &tocHtml);
|
||||
|
||||
private:
|
||||
bool isMarkdown(const QString &name);
|
||||
@ -46,6 +52,8 @@ private:
|
||||
void setupMarkdownPreview();
|
||||
void previewByConverter();
|
||||
inline bool isChild(QObject *obj);
|
||||
void parseTocUl(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
|
||||
void parseTocLi(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
|
||||
|
||||
VNoteFile *noteFile;
|
||||
bool isEditMode;
|
||||
@ -54,6 +62,7 @@ private:
|
||||
QWebEngineView *webPreviewer;
|
||||
VDocument document;
|
||||
MarkdownConverterType mdConverterType;
|
||||
VToc tableOfContent;
|
||||
};
|
||||
|
||||
inline bool VEditTab::getIsEditMode() const
|
||||
|
@ -165,6 +165,8 @@ int VEditWindow::openFileInTab(const QString ¬ebook, const QString &relativeP
|
||||
modifiable);
|
||||
connect(editor, &VEditTab::getFocused,
|
||||
this, &VEditWindow::getFocused);
|
||||
connect(editor, &VEditTab::outlineChanged,
|
||||
this, &VEditWindow::handleOutlineChanged);
|
||||
|
||||
QJsonObject tabJson;
|
||||
tabJson["notebook"] = notebook;
|
||||
@ -290,6 +292,16 @@ void VEditWindow::getTabStatus(QString ¬ebook, QString &relativePath,
|
||||
modifiable = tabJson["modifiable"].toBool();
|
||||
}
|
||||
|
||||
VToc VEditWindow::getTabOutline() const
|
||||
{
|
||||
int idx = currentIndex();
|
||||
if (idx == -1) {
|
||||
return VToc();
|
||||
}
|
||||
|
||||
return getTab(idx)->getOutline();
|
||||
}
|
||||
|
||||
void VEditWindow::focusWindow()
|
||||
{
|
||||
int idx = currentIndex();
|
||||
@ -305,6 +317,7 @@ void VEditWindow::handleTabbarClicked(int index)
|
||||
// The child will emit getFocused here
|
||||
focusWindow();
|
||||
noticeTabStatus(index);
|
||||
emit outlineChanged(getTab(index)->getOutline());
|
||||
}
|
||||
|
||||
void VEditWindow::mousePressEvent(QMouseEvent *event)
|
||||
@ -356,3 +369,30 @@ void VEditWindow::updateTabListMenu()
|
||||
menu->addAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
void VEditWindow::handleOutlineChanged(const VToc &toc)
|
||||
{
|
||||
// Only propagate it if it is current tab
|
||||
int idx = currentIndex();
|
||||
QJsonObject tabJson = tabBar()->tabData(idx).toJsonObject();
|
||||
Q_ASSERT(!tabJson.isEmpty());
|
||||
QString path = vnote->getNotebookPath(tabJson["notebook"].toString());
|
||||
path = QDir::cleanPath(QDir(path).filePath(tabJson["relative_path"].toString()));
|
||||
|
||||
if (toc.filePath == path) {
|
||||
emit outlineChanged(toc);
|
||||
}
|
||||
}
|
||||
|
||||
void VEditWindow::scrollCurTab(const VAnchor &anchor)
|
||||
{
|
||||
int idx = currentIndex();
|
||||
QJsonObject tabJson = tabBar()->tabData(idx).toJsonObject();
|
||||
Q_ASSERT(!tabJson.isEmpty());
|
||||
QString path = vnote->getNotebookPath(tabJson["notebook"].toString());
|
||||
path = QDir::cleanPath(QDir(path).filePath(tabJson["relative_path"].toString()));
|
||||
|
||||
if (path == anchor.filePath) {
|
||||
getTab(idx)->scrollToAnchor(anchor);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <QDir>
|
||||
#include "vnotebook.h"
|
||||
#include "vedittab.h"
|
||||
#include "vtoc.h"
|
||||
|
||||
class VNote;
|
||||
class QPushButton;
|
||||
@ -33,8 +34,10 @@ public:
|
||||
void setRemoveSplitEnable(bool enabled);
|
||||
void getTabStatus(QString ¬ebook, QString &relativePath,
|
||||
bool &editMode, bool &modifiable) const;
|
||||
VToc getTabOutline() const;
|
||||
// Focus to current tab's editor
|
||||
void focusWindow();
|
||||
void scrollCurTab(const VAnchor &anchor);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
|
||||
@ -46,6 +49,7 @@ signals:
|
||||
void requestRemoveSplit(VEditWindow *curWindow);
|
||||
// This widget or its children get the focus
|
||||
void getFocused();
|
||||
void outlineChanged(const VToc &toc);
|
||||
|
||||
private slots:
|
||||
bool handleTabCloseRequest(int index);
|
||||
@ -54,6 +58,7 @@ private slots:
|
||||
void handleTabbarClicked(int index);
|
||||
void contextMenuRequested(QPoint pos);
|
||||
void tabListJump(QAction *action);
|
||||
void handleOutlineChanged(const VToc &toc);
|
||||
|
||||
private:
|
||||
void setupCornerWidget();
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "dialog/vnotebookinfodialog.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "veditarea.h"
|
||||
#include "voutline.h"
|
||||
|
||||
extern VConfigManager vconfig;
|
||||
|
||||
@ -22,6 +23,7 @@ VMainWindow::VMainWindow(QWidget *parent)
|
||||
initActions();
|
||||
initToolBar();
|
||||
initMenuBar();
|
||||
initDockWindows();
|
||||
|
||||
updateNotebookComboBox(vnote->getNotebooks());
|
||||
}
|
||||
@ -283,7 +285,7 @@ void VMainWindow::initMenuBar()
|
||||
{
|
||||
QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
|
||||
QMenu *editMenu = menuBar()->addMenu(tr("&Edit"));
|
||||
QMenu *viewMenu = menuBar()->addMenu(tr("&View"));
|
||||
viewMenu = menuBar()->addMenu(tr("&View"));
|
||||
QMenu *markdownMenu = menuBar()->addMenu(tr("&Markdown"));
|
||||
QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
|
||||
|
||||
@ -335,6 +337,22 @@ void VMainWindow::initMenuBar()
|
||||
helpMenu->addAction(aboutAct);
|
||||
}
|
||||
|
||||
void VMainWindow::initDockWindows()
|
||||
{
|
||||
QDockWidget *dock = new QDockWidget(tr("Tools"), this);
|
||||
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||
toolBox = new QToolBox(this);
|
||||
outline = new VOutline(this);
|
||||
connect(editArea, &VEditArea::outlineChanged,
|
||||
outline, &VOutline::updateOutline);
|
||||
connect(outline, &VOutline::outlineItemActivated,
|
||||
editArea, &VEditArea::handleOutlineItemActivated);
|
||||
toolBox->addItem(outline, QIcon(":/resources/icons/outline.svg"), tr("Outline"));
|
||||
dock->setWidget(toolBox);
|
||||
addDockWidget(Qt::RightDockWidgetArea, dock);
|
||||
viewMenu->addAction(dock->toggleViewAction());
|
||||
}
|
||||
|
||||
void VMainWindow::updateNotebookComboBox(const QVector<VNotebook> ¬ebooks)
|
||||
{
|
||||
notebookComboMuted = true;
|
||||
|
@ -17,6 +17,8 @@ class VNotebook;
|
||||
class QActionGroup;
|
||||
class VFileList;
|
||||
class VEditArea;
|
||||
class QToolBox;
|
||||
class VOutline;
|
||||
|
||||
class VMainWindow : public QMainWindow
|
||||
{
|
||||
@ -57,6 +59,7 @@ private:
|
||||
void initActions();
|
||||
void initToolBar();
|
||||
void initMenuBar();
|
||||
void initDockWindows();
|
||||
bool isConflictWithExistingNotebooks(const QString &name);
|
||||
void initPredefinedColorPixmaps();
|
||||
void initRenderBackgroundMenu(QMenu *menu);
|
||||
@ -80,6 +83,8 @@ private:
|
||||
VDirectoryTree *directoryTree;
|
||||
QSplitter *mainSplitter;
|
||||
VEditArea *editArea;
|
||||
QToolBox *toolBox;
|
||||
VOutline *outline;
|
||||
|
||||
// Actions
|
||||
QAction *newNoteAct;
|
||||
@ -105,6 +110,9 @@ private:
|
||||
QActionGroup *backgroundColorAct;
|
||||
QActionGroup *renderBackgroundAct;
|
||||
|
||||
// Menus
|
||||
QMenu *viewMenu;
|
||||
|
||||
QVector<QPixmap> predefinedColorPixmaps;
|
||||
};
|
||||
|
||||
|
@ -120,6 +120,8 @@ void VNote::removeNotebook(const QString &name)
|
||||
} else {
|
||||
qDebug() << "delete" << path << "recursively";
|
||||
}
|
||||
|
||||
notebookPathHash.remove(name);
|
||||
emit notebooksDeleted(notebooks, name);
|
||||
}
|
||||
|
||||
@ -140,5 +142,23 @@ void VNote::renameNotebook(const QString &name, const QString &newName)
|
||||
notebooks[index].setName(newName);
|
||||
vconfig.setNotebooks(notebooks);
|
||||
|
||||
notebookPathHash.remove(name);
|
||||
emit notebooksRenamed(notebooks, name, newName);
|
||||
}
|
||||
|
||||
QString VNote::getNotebookPath(const QString &name)
|
||||
{
|
||||
QString path = notebookPathHash.value(name);
|
||||
if (path.isEmpty()) {
|
||||
for (int i = 0; i < notebooks.size(); ++i) {
|
||||
if (notebooks[i].getName() == name) {
|
||||
path = notebooks[i].getPath();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!path.isEmpty()) {
|
||||
notebookPathHash.insert(name, path);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <QSettings>
|
||||
#include <QFont>
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
#include "vnotebook.h"
|
||||
|
||||
enum OpenFileMode {Read = 0, Edit};
|
||||
@ -31,6 +32,8 @@ public:
|
||||
void removeNotebook(const QString &name);
|
||||
void renameNotebook(const QString &name, const QString &newName);
|
||||
|
||||
QString getNotebookPath(const QString &name);
|
||||
|
||||
public slots:
|
||||
void updateTemplate();
|
||||
|
||||
@ -44,6 +47,7 @@ signals:
|
||||
|
||||
private:
|
||||
QVector<VNotebook> notebooks;
|
||||
QHash<QString, QString> notebookPathHash;
|
||||
};
|
||||
|
||||
#endif // VNOTE_H
|
||||
|
@ -57,5 +57,6 @@
|
||||
<file>resources/icons/corner_menu.svg</file>
|
||||
<file>resources/icons/remove_split.svg</file>
|
||||
<file>resources/icons/corner_tablist.svg</file>
|
||||
<file>resources/icons/outline.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
79
src/voutline.cpp
Normal file
79
src/voutline.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
#include <QDebug>
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include "voutline.h"
|
||||
#include "vtoc.h"
|
||||
|
||||
VOutline::VOutline(QWidget *parent)
|
||||
: QTreeWidget(parent)
|
||||
{
|
||||
setColumnCount(1);
|
||||
setHeaderHidden(true);
|
||||
|
||||
connect(this, &VOutline::itemClicked,
|
||||
this, &VOutline::handleItemClicked);
|
||||
}
|
||||
|
||||
void VOutline::updateOutline(const VToc &toc)
|
||||
{
|
||||
outline = toc;
|
||||
updateTreeFromOutline(outline);
|
||||
expandTree();
|
||||
}
|
||||
|
||||
void VOutline::updateTreeFromOutline(const VToc &toc)
|
||||
{
|
||||
clear();
|
||||
|
||||
if (!toc.valid) {
|
||||
return;
|
||||
}
|
||||
const QVector<VHeader> &headers = toc.headers;
|
||||
int idx = 0;
|
||||
updateTreeByLevel(headers, idx, NULL, NULL, 1);
|
||||
}
|
||||
|
||||
void VOutline::updateTreeByLevel(const QVector<VHeader> &headers, int &index,
|
||||
QTreeWidgetItem *parent, QTreeWidgetItem *last, int level)
|
||||
{
|
||||
while (index < headers.size()) {
|
||||
const VHeader &header = headers[index];
|
||||
QTreeWidgetItem *item;
|
||||
if (header.level == level) {
|
||||
if (parent) {
|
||||
item = new QTreeWidgetItem(parent);
|
||||
} else {
|
||||
item = new QTreeWidgetItem(this);
|
||||
}
|
||||
QJsonObject itemJson;
|
||||
itemJson["anchor"] = header.anchor;
|
||||
itemJson["line_number"] = header.lineNumber;
|
||||
item->setData(0, Qt::UserRole, itemJson);
|
||||
item->setText(0, header.name);
|
||||
|
||||
last = item;
|
||||
++index;
|
||||
} else if (header.level < level) {
|
||||
return;
|
||||
} else {
|
||||
updateTreeByLevel(headers, index, last, NULL, level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VOutline::expandTree()
|
||||
{
|
||||
expandAll();
|
||||
}
|
||||
|
||||
|
||||
void VOutline::handleItemClicked(QTreeWidgetItem *item, int column)
|
||||
{
|
||||
Q_ASSERT(item && column == 0);
|
||||
QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject();
|
||||
QString anchor = itemJson["anchor"].toString();
|
||||
int lineNumber = itemJson["line_number"].toInt();
|
||||
qDebug() << "click anchor" << anchor << lineNumber;
|
||||
emit outlineItemActivated(VAnchor(outline.filePath, anchor, lineNumber));
|
||||
}
|
31
src/voutline.h
Normal file
31
src/voutline.h
Normal file
@ -0,0 +1,31 @@
|
||||
#ifndef VOUTLINE_H
|
||||
#define VOUTLINE_H
|
||||
|
||||
#include <QTreeWidget>
|
||||
#include "vtoc.h"
|
||||
|
||||
class VOutline : public QTreeWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VOutline(QWidget *parent = 0);
|
||||
|
||||
signals:
|
||||
void outlineItemActivated(const VAnchor &anchor);
|
||||
|
||||
public slots:
|
||||
void updateOutline(const VToc &toc);
|
||||
|
||||
private slots:
|
||||
void handleItemClicked(QTreeWidgetItem *item, int column);
|
||||
|
||||
private:
|
||||
void updateTreeFromOutline(const VToc &toc);
|
||||
void updateTreeByLevel(const QVector<VHeader> &headers, int &index, QTreeWidgetItem *parent,
|
||||
QTreeWidgetItem *last, int level);
|
||||
void expandTree();
|
||||
|
||||
VToc outline;
|
||||
};
|
||||
|
||||
#endif // VOUTLINE_H
|
6
src/vtoc.cpp
Normal file
6
src/vtoc.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include "vtoc.h"
|
||||
|
||||
VToc::VToc()
|
||||
: curHeaderIndex(0), type(VHeaderType::Anchor), valid(false)
|
||||
{
|
||||
}
|
42
src/vtoc.h
Normal file
42
src/vtoc.h
Normal file
@ -0,0 +1,42 @@
|
||||
#ifndef VTOC_H
|
||||
#define VTOC_H
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
enum VHeaderType
|
||||
{
|
||||
Anchor = 0,
|
||||
LineNumber
|
||||
};
|
||||
|
||||
struct VHeader
|
||||
{
|
||||
int level;
|
||||
QString name;
|
||||
QString anchor;
|
||||
int lineNumber;
|
||||
};
|
||||
|
||||
struct VAnchor
|
||||
{
|
||||
VAnchor(const QString filePath, const QString &anchor, int lineNumber)
|
||||
: filePath(filePath), anchor(anchor), lineNumber(lineNumber) {}
|
||||
QString filePath;
|
||||
QString anchor;
|
||||
int lineNumber;
|
||||
};
|
||||
|
||||
class VToc
|
||||
{
|
||||
public:
|
||||
VToc();
|
||||
|
||||
QVector<VHeader> headers;
|
||||
int curHeaderIndex;
|
||||
int type;
|
||||
QString filePath;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
#endif // VTOC_H
|
Loading…
x
Reference in New Issue
Block a user