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:
Le Tan 2016-11-07 21:53:33 +08:00
parent db0797a538
commit ab91f755c0
21 changed files with 488 additions and 18 deletions

View File

@ -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" <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"> width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g> <g>
<path d="M464,144v288H48V144H464 M480,128H32v320h448V128L480,128z"/> <path d="M480,128H32v320h448V128L480,128z"/>
<rect x="72" y="96" width="368" height="16"/> <rect x="72" y="96" width="368" height="16"/>
<rect x="104" y="64" width="304" height="16"/> <rect x="104" y="64" width="304" height="16"/>
</g> </g>

Before

Width:  |  Height:  |  Size: 669 B

After

Width:  |  Height:  |  Size: 645 B

View 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

View File

@ -19,9 +19,12 @@
var placeholder = document.getElementById('placeholder'); var placeholder = document.getElementById('placeholder');
var renderer = new marked.Renderer(); var renderer = new marked.Renderer();
var toc = []; // Table of contents as a list var toc = []; // Table of contents as a list
var content; // Channel variable with content
var nameCounter = 0;
renderer.heading = function (text, level) { 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({ toc.push({
level: level, level: level,
anchor: escapedText, anchor: escapedText,
@ -44,11 +47,12 @@
var markdownToHtml = function (markdown, needToc) { var markdownToHtml = function (markdown, needToc) {
toc = []; toc = [];
var html = marked(markdown, { renderer: renderer });
nameCounter = 0;
if (needToc) { if (needToc) {
var html = marked(markdown, { renderer: renderer });
return html.replace(/<p>\[TOC\]<\/p>/ig, '<div class="vnote-toc"></div>'); return html.replace(/<p>\[TOC\]<\/p>/ig, '<div class="vnote-toc"></div>');
} else { } else {
return marked(markdown); return html;
} }
}; };
@ -74,7 +78,7 @@
}; };
var itemToHtml = function (item) { 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> // Turn a perfect toc to a tree using <ul>
@ -118,11 +122,16 @@
return front; return front;
}; };
var addToc = function() { var handleToc = function(needToc) {
var tocTree = tocToTree(toPerfectToc(toc)); var tocTree = tocToTree(toPerfectToc(toc));
var eles = document.getElementsByClassName('vnote-toc'); content.setToc(tocTree);
for (var i = 0; i < eles.length; ++i) {
eles[i].innerHTML = 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;
}
} }
}; };
@ -136,16 +145,25 @@
var html = markdownToHtml(text, needToc); var html = markdownToHtml(text, needToc);
placeholder.innerHTML = html; placeholder.innerHTML = html;
if (needToc) { handleToc(needToc);
addToc(); };
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, new QWebChannel(qt.webChannelTransport,
function(channel) { function(channel) {
var content = channel.objects.content; content = channel.objects.content;
updateText(content.text); updateText(content.text);
content.textChanged.connect(updateText); content.textChanged.connect(updateText);
content.requestScrollToAnchor.connect(scrollToAnchor);
} }
); );
</script> </script>

View File

@ -41,7 +41,9 @@ SOURCES += main.cpp\
vdownloader.cpp \ vdownloader.cpp \
veditarea.cpp \ veditarea.cpp \
veditwindow.cpp \ veditwindow.cpp \
vedittab.cpp vedittab.cpp \
voutline.cpp \
vtoc.cpp
HEADERS += vmainwindow.h \ HEADERS += vmainwindow.h \
vdirectorytree.h \ vdirectorytree.h \
@ -73,7 +75,9 @@ HEADERS += vmainwindow.h \
vdownloader.h \ vdownloader.h \
veditarea.h \ veditarea.h \
veditwindow.h \ veditwindow.h \
vedittab.h vedittab.h \
voutline.h \
vtoc.h
RESOURCES += \ RESOURCES += \
vnote.qrc vnote.qrc

View File

@ -1,7 +1,5 @@
#include "vdocument.h" #include "vdocument.h"
#include <QtDebug>
VDocument::VDocument(QObject *parent) : QObject(parent) VDocument::VDocument(QObject *parent) : QObject(parent)
{ {
@ -15,8 +13,9 @@ VDocument::VDocument(const QString &text, QObject *parent)
void VDocument::setText(const QString &text) void VDocument::setText(const QString &text)
{ {
if (text == m_text) if (text == m_text) {
return; return;
}
m_text = text; m_text = text;
emit textChanged(m_text); emit textChanged(m_text);
} }
@ -25,3 +24,22 @@ QString VDocument::getText()
{ {
return m_text; 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);
}

View File

@ -8,17 +8,28 @@ class VDocument : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged) Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged)
Q_PROPERTY(QString toc MEMBER m_toc NOTIFY tocChanged)
public: public:
explicit VDocument(QObject *parent = 0); explicit VDocument(QObject *parent = 0);
VDocument(const QString &text, QObject *parent = 0); VDocument(const QString &text, QObject *parent = 0);
void setText(const QString &text); void setText(const QString &text);
QString getText(); QString getText();
QString getToc();
void scrollToAnchor(const QString &anchor);
public slots:
// Will be called in the HTML side
void setToc(const QString &toc);
signals: signals:
void textChanged(const QString &text); void textChanged(const QString &text);
void tocChanged(const QString &toc);
void requestScrollToAnchor(const QString &anchor);
private: private:
QString m_text; QString m_text;
QString m_toc;
}; };
#endif // VDOCUMENT_H #endif // VDOCUMENT_H

View File

@ -37,6 +37,8 @@ void VEditArea::insertSplitWindow(int idx)
this, &VEditArea::handleRemoveSplitRequest); this, &VEditArea::handleRemoveSplitRequest);
connect(win, &VEditWindow::getFocused, connect(win, &VEditWindow::getFocused,
this, &VEditArea::handleWindowFocused); this, &VEditArea::handleWindowFocused);
connect(win, &VEditWindow::outlineChanged,
this, &VEditArea::handleOutlineChanged);
int nrWin = splitter->count(); int nrWin = splitter->count();
if (nrWin == 1) { if (nrWin == 1) {
@ -163,6 +165,7 @@ void VEditArea::setCurrentWindow(int windowIndex, bool setFocus)
out: out:
// Update tab status // Update tab status
noticeTabStatus(); noticeTabStatus();
emit outlineChanged(getWindow(windowIndex)->getTabOutline());
} }
void VEditArea::noticeTabStatus() 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);
}

View File

@ -12,6 +12,7 @@
#include <QSplitter> #include <QSplitter>
#include "vnotebook.h" #include "vnotebook.h"
#include "veditwindow.h" #include "veditwindow.h"
#include "vtoc.h"
class VNote; class VNote;
@ -24,6 +25,7 @@ public:
signals: signals:
void curTabStatusChanged(const QString &notebook, const QString &relativePath, void curTabStatusChanged(const QString &notebook, const QString &relativePath,
bool editMode, bool modifiable); bool editMode, bool modifiable);
void outlineChanged(const VToc &toc);
protected: protected:
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
@ -38,11 +40,13 @@ public slots:
void saveAndReadFile(); void saveAndReadFile();
void handleNotebookRenamed(const QVector<VNotebook> &notebooks, const QString &oldName, void handleNotebookRenamed(const QVector<VNotebook> &notebooks, const QString &oldName,
const QString &newName); const QString &newName);
void handleOutlineItemActivated(const VAnchor &anchor);
private slots: private slots:
void handleSplitWindowRequest(VEditWindow *curWindow); void handleSplitWindowRequest(VEditWindow *curWindow);
void handleRemoveSplitRequest(VEditWindow *curWindow); void handleRemoveSplitRequest(VEditWindow *curWindow);
void handleWindowFocused(); void handleWindowFocused();
void handleOutlineChanged(const VToc &toc);
private: private:
void setupUI(); void setupUI();

View File

@ -3,6 +3,7 @@
#include <QWebChannel> #include <QWebChannel>
#include <QWebEngineView> #include <QWebEngineView>
#include <QFileInfo> #include <QFileInfo>
#include <QXmlStreamReader>
#include "vedittab.h" #include "vedittab.h"
#include "vedit.h" #include "vedit.h"
#include "vdocument.h" #include "vdocument.h"
@ -13,6 +14,7 @@
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vmarkdownconverter.h" #include "vmarkdownconverter.h"
#include "vnotebook.h" #include "vnotebook.h"
#include "vtoc.h"
extern VConfigManager vconfig; extern VConfigManager vconfig;
@ -117,6 +119,8 @@ void VEditTab::previewByConverter()
html.replace(tocExp, toc); html.replace(tocExp, toc);
QString completeHtml = VNote::preTemplateHtml + html + VNote::postTemplateHtml; QString completeHtml = VNote::preTemplateHtml + html + VNote::postTemplateHtml;
webPreviewer->setHtml(completeHtml, QUrl::fromLocalFile(noteFile->basePath + QDir::separator())); webPreviewer->setHtml(completeHtml, QUrl::fromLocalFile(noteFile->basePath + QDir::separator()));
// Hoedown will add '\n' while Marked does not
updateTocFromHtml(toc.replace("\n", ""));
} }
void VEditTab::showFileEditMode() void VEditTab::showFileEditMode()
@ -216,6 +220,8 @@ void VEditTab::setupMarkdownPreview()
if (mdConverterType == MarkdownConverterType::Marked) { if (mdConverterType == MarkdownConverterType::Marked) {
QWebChannel *channel = new QWebChannel(this); QWebChannel *channel = new QWebChannel(this);
channel->registerObject(QStringLiteral("content"), &document); channel->registerObject(QStringLiteral("content"), &document);
connect(&document, &VDocument::tocChanged,
this, &VEditTab::updateTocFromHtml);
page->setWebChannel(channel); page->setWebChannel(channel);
webPreviewer->setHtml(VNote::templateHtml, webPreviewer->setHtml(VNote::templateHtml,
QUrl::fromLocalFile(noteFile->basePath + QDir::separator())); QUrl::fromLocalFile(noteFile->basePath + QDir::separator()));
@ -235,3 +241,109 @@ void VEditTab::handleFocusChanged(QWidget *old, QWidget *now)
emit getFocused(); 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));
}
}
}

View File

@ -9,10 +9,12 @@
#include "vmarkdownconverter.h" #include "vmarkdownconverter.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vedit.h" #include "vedit.h"
#include "vtoc.h"
class QTextBrowser; class QTextBrowser;
class QWebEngineView; class QWebEngineView;
class VNote; class VNote;
class QXmlStreamReader;
class VEditTab : public QStackedWidget class VEditTab : public QStackedWidget
{ {
@ -31,12 +33,16 @@ public:
inline bool getIsEditMode() const; inline bool getIsEditMode() const;
inline bool isModified() const; inline bool isModified() const;
void focusTab(); void focusTab();
const VToc& getOutline() const;
void scrollToAnchor(const VAnchor& anchor);
signals: signals:
void getFocused(); void getFocused();
void outlineChanged(const VToc &toc);
private slots: private slots:
void handleFocusChanged(QWidget *old, QWidget *now); void handleFocusChanged(QWidget *old, QWidget *now);
void updateTocFromHtml(const QString &tocHtml);
private: private:
bool isMarkdown(const QString &name); bool isMarkdown(const QString &name);
@ -46,6 +52,8 @@ private:
void setupMarkdownPreview(); void setupMarkdownPreview();
void previewByConverter(); void previewByConverter();
inline bool isChild(QObject *obj); 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; VNoteFile *noteFile;
bool isEditMode; bool isEditMode;
@ -54,6 +62,7 @@ private:
QWebEngineView *webPreviewer; QWebEngineView *webPreviewer;
VDocument document; VDocument document;
MarkdownConverterType mdConverterType; MarkdownConverterType mdConverterType;
VToc tableOfContent;
}; };
inline bool VEditTab::getIsEditMode() const inline bool VEditTab::getIsEditMode() const

View File

@ -165,6 +165,8 @@ int VEditWindow::openFileInTab(const QString &notebook, const QString &relativeP
modifiable); modifiable);
connect(editor, &VEditTab::getFocused, connect(editor, &VEditTab::getFocused,
this, &VEditWindow::getFocused); this, &VEditWindow::getFocused);
connect(editor, &VEditTab::outlineChanged,
this, &VEditWindow::handleOutlineChanged);
QJsonObject tabJson; QJsonObject tabJson;
tabJson["notebook"] = notebook; tabJson["notebook"] = notebook;
@ -290,6 +292,16 @@ void VEditWindow::getTabStatus(QString &notebook, QString &relativePath,
modifiable = tabJson["modifiable"].toBool(); modifiable = tabJson["modifiable"].toBool();
} }
VToc VEditWindow::getTabOutline() const
{
int idx = currentIndex();
if (idx == -1) {
return VToc();
}
return getTab(idx)->getOutline();
}
void VEditWindow::focusWindow() void VEditWindow::focusWindow()
{ {
int idx = currentIndex(); int idx = currentIndex();
@ -305,6 +317,7 @@ void VEditWindow::handleTabbarClicked(int index)
// The child will emit getFocused here // The child will emit getFocused here
focusWindow(); focusWindow();
noticeTabStatus(index); noticeTabStatus(index);
emit outlineChanged(getTab(index)->getOutline());
} }
void VEditWindow::mousePressEvent(QMouseEvent *event) void VEditWindow::mousePressEvent(QMouseEvent *event)
@ -356,3 +369,30 @@ void VEditWindow::updateTabListMenu()
menu->addAction(action); 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);
}
}

View File

@ -8,6 +8,7 @@
#include <QDir> #include <QDir>
#include "vnotebook.h" #include "vnotebook.h"
#include "vedittab.h" #include "vedittab.h"
#include "vtoc.h"
class VNote; class VNote;
class QPushButton; class QPushButton;
@ -33,8 +34,10 @@ public:
void setRemoveSplitEnable(bool enabled); void setRemoveSplitEnable(bool enabled);
void getTabStatus(QString &notebook, QString &relativePath, void getTabStatus(QString &notebook, QString &relativePath,
bool &editMode, bool &modifiable) const; bool &editMode, bool &modifiable) const;
VToc getTabOutline() const;
// Focus to current tab's editor // Focus to current tab's editor
void focusWindow(); void focusWindow();
void scrollCurTab(const VAnchor &anchor);
protected: protected:
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
@ -46,6 +49,7 @@ signals:
void requestRemoveSplit(VEditWindow *curWindow); void requestRemoveSplit(VEditWindow *curWindow);
// This widget or its children get the focus // This widget or its children get the focus
void getFocused(); void getFocused();
void outlineChanged(const VToc &toc);
private slots: private slots:
bool handleTabCloseRequest(int index); bool handleTabCloseRequest(int index);
@ -54,6 +58,7 @@ private slots:
void handleTabbarClicked(int index); void handleTabbarClicked(int index);
void contextMenuRequested(QPoint pos); void contextMenuRequested(QPoint pos);
void tabListJump(QAction *action); void tabListJump(QAction *action);
void handleOutlineChanged(const VToc &toc);
private: private:
void setupCornerWidget(); void setupCornerWidget();

View File

@ -9,6 +9,7 @@
#include "dialog/vnotebookinfodialog.h" #include "dialog/vnotebookinfodialog.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "veditarea.h" #include "veditarea.h"
#include "voutline.h"
extern VConfigManager vconfig; extern VConfigManager vconfig;
@ -22,6 +23,7 @@ VMainWindow::VMainWindow(QWidget *parent)
initActions(); initActions();
initToolBar(); initToolBar();
initMenuBar(); initMenuBar();
initDockWindows();
updateNotebookComboBox(vnote->getNotebooks()); updateNotebookComboBox(vnote->getNotebooks());
} }
@ -283,7 +285,7 @@ void VMainWindow::initMenuBar()
{ {
QMenu *fileMenu = menuBar()->addMenu(tr("&File")); QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
QMenu *editMenu = menuBar()->addMenu(tr("&Edit")); QMenu *editMenu = menuBar()->addMenu(tr("&Edit"));
QMenu *viewMenu = menuBar()->addMenu(tr("&View")); viewMenu = menuBar()->addMenu(tr("&View"));
QMenu *markdownMenu = menuBar()->addMenu(tr("&Markdown")); QMenu *markdownMenu = menuBar()->addMenu(tr("&Markdown"));
QMenu *helpMenu = menuBar()->addMenu(tr("&Help")); QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
@ -335,6 +337,22 @@ void VMainWindow::initMenuBar()
helpMenu->addAction(aboutAct); 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> &notebooks) void VMainWindow::updateNotebookComboBox(const QVector<VNotebook> &notebooks)
{ {
notebookComboMuted = true; notebookComboMuted = true;

View File

@ -17,6 +17,8 @@ class VNotebook;
class QActionGroup; class QActionGroup;
class VFileList; class VFileList;
class VEditArea; class VEditArea;
class QToolBox;
class VOutline;
class VMainWindow : public QMainWindow class VMainWindow : public QMainWindow
{ {
@ -57,6 +59,7 @@ private:
void initActions(); void initActions();
void initToolBar(); void initToolBar();
void initMenuBar(); void initMenuBar();
void initDockWindows();
bool isConflictWithExistingNotebooks(const QString &name); bool isConflictWithExistingNotebooks(const QString &name);
void initPredefinedColorPixmaps(); void initPredefinedColorPixmaps();
void initRenderBackgroundMenu(QMenu *menu); void initRenderBackgroundMenu(QMenu *menu);
@ -80,6 +83,8 @@ private:
VDirectoryTree *directoryTree; VDirectoryTree *directoryTree;
QSplitter *mainSplitter; QSplitter *mainSplitter;
VEditArea *editArea; VEditArea *editArea;
QToolBox *toolBox;
VOutline *outline;
// Actions // Actions
QAction *newNoteAct; QAction *newNoteAct;
@ -105,6 +110,9 @@ private:
QActionGroup *backgroundColorAct; QActionGroup *backgroundColorAct;
QActionGroup *renderBackgroundAct; QActionGroup *renderBackgroundAct;
// Menus
QMenu *viewMenu;
QVector<QPixmap> predefinedColorPixmaps; QVector<QPixmap> predefinedColorPixmaps;
}; };

View File

@ -120,6 +120,8 @@ void VNote::removeNotebook(const QString &name)
} else { } else {
qDebug() << "delete" << path << "recursively"; qDebug() << "delete" << path << "recursively";
} }
notebookPathHash.remove(name);
emit notebooksDeleted(notebooks, name); emit notebooksDeleted(notebooks, name);
} }
@ -140,5 +142,23 @@ void VNote::renameNotebook(const QString &name, const QString &newName)
notebooks[index].setName(newName); notebooks[index].setName(newName);
vconfig.setNotebooks(notebooks); vconfig.setNotebooks(notebooks);
notebookPathHash.remove(name);
emit notebooksRenamed(notebooks, name, newName); 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;
}

View File

@ -6,6 +6,7 @@
#include <QSettings> #include <QSettings>
#include <QFont> #include <QFont>
#include <QObject> #include <QObject>
#include <QHash>
#include "vnotebook.h" #include "vnotebook.h"
enum OpenFileMode {Read = 0, Edit}; enum OpenFileMode {Read = 0, Edit};
@ -31,6 +32,8 @@ public:
void removeNotebook(const QString &name); void removeNotebook(const QString &name);
void renameNotebook(const QString &name, const QString &newName); void renameNotebook(const QString &name, const QString &newName);
QString getNotebookPath(const QString &name);
public slots: public slots:
void updateTemplate(); void updateTemplate();
@ -44,6 +47,7 @@ signals:
private: private:
QVector<VNotebook> notebooks; QVector<VNotebook> notebooks;
QHash<QString, QString> notebookPathHash;
}; };
#endif // VNOTE_H #endif // VNOTE_H

View File

@ -57,5 +57,6 @@
<file>resources/icons/corner_menu.svg</file> <file>resources/icons/corner_menu.svg</file>
<file>resources/icons/remove_split.svg</file> <file>resources/icons/remove_split.svg</file>
<file>resources/icons/corner_tablist.svg</file> <file>resources/icons/corner_tablist.svg</file>
<file>resources/icons/outline.svg</file>
</qresource> </qresource>
</RCC> </RCC>

79
src/voutline.cpp Normal file
View 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
View 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
View File

@ -0,0 +1,6 @@
#include "vtoc.h"
VToc::VToc()
: curHeaderIndex(0), type(VHeaderType::Anchor), valid(false)
{
}

42
src/vtoc.h Normal file
View 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