refactor outline logics

This commit is contained in:
Le Tan 2017-10-13 07:10:04 +08:00
parent 598e8144bb
commit 183b24915a
27 changed files with 939 additions and 726 deletions

View File

@ -44,7 +44,6 @@ SOURCES += main.cpp\
veditwindow.cpp \ veditwindow.cpp \
vedittab.cpp \ vedittab.cpp \
voutline.cpp \ voutline.cpp \
vtoc.cpp \
vsingleinstanceguard.cpp \ vsingleinstanceguard.cpp \
vdirectory.cpp \ vdirectory.cpp \
vfile.cpp \ vfile.cpp \
@ -78,7 +77,8 @@ SOURCES += main.cpp\
vnotefile.cpp \ vnotefile.cpp \
vattachmentlist.cpp \ vattachmentlist.cpp \
dialog/vsortdialog.cpp \ dialog/vsortdialog.cpp \
vfilesessioninfo.cpp vfilesessioninfo.cpp \
vtableofcontent.cpp
HEADERS += vmainwindow.h \ HEADERS += vmainwindow.h \
vdirectorytree.h \ vdirectorytree.h \
@ -108,7 +108,6 @@ HEADERS += vmainwindow.h \
veditwindow.h \ veditwindow.h \
vedittab.h \ vedittab.h \
voutline.h \ voutline.h \
vtoc.h \
vsingleinstanceguard.h \ vsingleinstanceguard.h \
vdirectory.h \ vdirectory.h \
vfile.h \ vfile.h \
@ -144,7 +143,8 @@ HEADERS += vmainwindow.h \
vnotefile.h \ vnotefile.h \
vattachmentlist.h \ vattachmentlist.h \
dialog/vsortdialog.h \ dialog/vsortdialog.h \
vfilesessioninfo.h vfilesessioninfo.h \
vtableofcontent.h
RESOURCES += \ RESOURCES += \
vnote.qrc \ vnote.qrc \

View File

@ -16,9 +16,15 @@ class VDocument : public QObject
public: public:
// @p_file could be NULL. // @p_file could be NULL.
VDocument(const VFile *p_file, QObject *p_parent = 0); VDocument(const VFile *p_file, QObject *p_parent = 0);
QString getToc(); QString getToc();
// Scroll to @anchor in the web.
// @anchor is the id without '#', like "toc_1". If empty, will scroll to top.
void scrollToAnchor(const QString &anchor); void scrollToAnchor(const QString &anchor);
void setHtml(const QString &html); void setHtml(const QString &html);
// Request to highlight a segment text. // Request to highlight a segment text.
// Use p_id to identify the result. // Use p_id to identify the result.
void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp); void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp);
@ -35,6 +41,7 @@ public slots:
// When the Web view has been scrolled, it will signal current header anchor. // When the Web view has been scrolled, it will signal current header anchor.
// Empty @anchor to indicate an invalid header. // Empty @anchor to indicate an invalid header.
// The header does not begins with '#'.
void setHeader(const QString &anchor); void setHeader(const QString &anchor);
void setLog(const QString &p_log); void setLog(const QString &p_log);
@ -49,8 +56,12 @@ public slots:
signals: signals:
void textChanged(const QString &text); void textChanged(const QString &text);
void tocChanged(const QString &toc); void tocChanged(const QString &toc);
void requestScrollToAnchor(const QString &anchor); void requestScrollToAnchor(const QString &anchor);
// @anchor is the id of that anchor, without '#'.
void headerChanged(const QString &anchor); void headerChanged(const QString &anchor);
void htmlChanged(const QString &html); void htmlChanged(const QString &html);
void logChanged(const QString &p_log); void logChanged(const QString &p_log);

View File

@ -4,7 +4,7 @@
#include "vedit.h" #include "vedit.h"
#include "vnote.h" #include "vnote.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vtoc.h" #include "vtableofcontent.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "utils/veditutils.h" #include "utils/veditutils.h"
#include "veditoperations.h" #include "veditoperations.h"
@ -157,15 +157,16 @@ void VEdit::reloadFile()
setModified(false); setModified(false);
} }
void VEdit::scrollToLine(int p_lineNumber) bool VEdit::scrollToBlock(int p_blockNumber)
{ {
Q_ASSERT(p_lineNumber >= 0); QTextBlock block = document()->findBlockByNumber(p_blockNumber);
QTextBlock block = document()->findBlockByLineNumber(p_lineNumber);
if (block.isValid()) { if (block.isValid()) {
VEditUtils::scrollBlockInPage(this, block.blockNumber(), 0); VEditUtils::scrollBlockInPage(this, block.blockNumber(), 0);
moveCursor(QTextCursor::EndOfBlock); moveCursor(QTextCursor::EndOfBlock);
return true;
} }
return false;
} }
bool VEdit::isModified() const bool VEdit::isModified() const

View File

@ -10,7 +10,6 @@
#include <QRect> #include <QRect>
#include <QFontMetrics> #include <QFontMetrics>
#include "vconstants.h" #include "vconstants.h"
#include "vtoc.h"
#include "vnotefile.h" #include "vnotefile.h"
class VEditOperations; class VEditOperations;
@ -81,7 +80,9 @@ public:
virtual void setModified(bool p_modified); virtual void setModified(bool p_modified);
bool isModified() const; bool isModified() const;
virtual void reloadFile(); virtual void reloadFile();
virtual void scrollToLine(int p_lineNumber);
virtual bool scrollToBlock(int p_blockNumber);
// User requests to insert an image. // User requests to insert an image.
virtual void insertImage(); virtual void insertImage();

View File

@ -77,9 +77,9 @@ void VEditArea::insertSplitWindow(int idx)
connect(win, &VEditWindow::getFocused, connect(win, &VEditWindow::getFocused,
this, &VEditArea::handleWindowFocused); this, &VEditArea::handleWindowFocused);
connect(win, &VEditWindow::outlineChanged, connect(win, &VEditWindow::outlineChanged,
this, &VEditArea::handleOutlineChanged); this, &VEditArea::handleWindowOutlineChanged);
connect(win, &VEditWindow::curHeaderChanged, connect(win, &VEditWindow::currentHeaderChanged,
this, &VEditArea::handleCurHeaderChanged); this, &VEditArea::handleWindowCurrentHeaderChanged);
connect(win, &VEditWindow::statusMessage, connect(win, &VEditWindow::statusMessage,
this, &VEditArea::handleWindowStatusMessage); this, &VEditArea::handleWindowStatusMessage);
connect(win, &VEditWindow::vimStatusUpdated, connect(win, &VEditWindow::vimStatusUpdated,
@ -237,15 +237,13 @@ void VEditArea::updateWindowStatus()
Q_ASSERT(splitter->count() == 0); Q_ASSERT(splitter->count() == 0);
emit tabStatusUpdated(VEditTabInfo()); emit tabStatusUpdated(VEditTabInfo());
emit outlineChanged(VToc()); emit outlineChanged(VTableOfContent());
emit curHeaderChanged(VAnchor()); emit currentHeaderChanged(VHeaderPointer());
return; return;
} }
VEditWindow *win = getWindow(curWindowIndex); VEditWindow *win = getWindow(curWindowIndex);
win->updateTabStatus(); win->updateTabStatus();
win->requestUpdateOutline();
win->requestUpdateCurHeader();
} }
bool VEditArea::closeFile(const VFile *p_file, bool p_forced) bool VEditArea::closeFile(const VFile *p_file, bool p_forced)
@ -436,26 +434,28 @@ void VEditArea::handleWindowFocused()
} }
} }
void VEditArea::handleOutlineChanged(const VToc &toc) void VEditArea::handleWindowOutlineChanged(const VTableOfContent &p_outline)
{ {
QObject *winObject = sender(); QObject *winObject = sender();
if (splitter->widget(curWindowIndex) == winObject) { if (splitter->widget(curWindowIndex) == winObject) {
emit outlineChanged(toc); emit outlineChanged(p_outline);
} }
} }
void VEditArea::handleCurHeaderChanged(const VAnchor &anchor) void VEditArea::handleWindowCurrentHeaderChanged(const VHeaderPointer &p_header)
{ {
QObject *winObject = sender(); QObject *winObject = sender();
if (splitter->widget(curWindowIndex) == winObject) { if (splitter->widget(curWindowIndex) == winObject) {
emit curHeaderChanged(anchor); emit currentHeaderChanged(p_header);
} }
} }
void VEditArea::handleOutlineItemActivated(const VAnchor &anchor) void VEditArea::scrollToHeader(const VHeaderPointer &p_header)
{ {
// Notice current window VEditWindow *win = getCurrentWindow();
getWindow(curWindowIndex)->scrollCurTab(anchor); if (win) {
win->scrollToHeader(p_header);
}
} }
bool VEditArea::isFileOpened(const VFile *p_file) bool VEditArea::isFileOpened(const VFile *p_file)
@ -648,6 +648,7 @@ VEditWindow *VEditArea::getCurrentWindow() const
if (curWindowIndex < 0) { if (curWindowIndex < 0) {
return NULL; return NULL;
} }
return getWindow(curWindowIndex); return getWindow(curWindowIndex);
} }

View File

@ -12,7 +12,6 @@
#include <QSplitter> #include <QSplitter>
#include "vnotebook.h" #include "vnotebook.h"
#include "veditwindow.h" #include "veditwindow.h"
#include "vtoc.h"
#include "vnavigationmode.h" #include "vnavigationmode.h"
class VNote; class VNote;
@ -82,8 +81,11 @@ signals:
// Emit when current window's tab status updated. // Emit when current window's tab status updated.
void tabStatusUpdated(const VEditTabInfo &p_info); void tabStatusUpdated(const VEditTabInfo &p_info);
void outlineChanged(const VToc &toc); // Emit when current window's tab's outline changed.
void curHeaderChanged(const VAnchor &anchor); void outlineChanged(const VTableOfContent &p_outline);
// Emit when current window's tab's current header changed.
void currentHeaderChanged(const VHeaderPointer &p_header);
// Emit when want to show message in status bar. // Emit when want to show message in status bar.
void statusMessage(const QString &p_msg); void statusMessage(const QString &p_msg);
@ -106,7 +108,10 @@ public slots:
void saveFile(); void saveFile();
void readFile(); void readFile();
void saveAndReadFile(); void saveAndReadFile();
void handleOutlineItemActivated(const VAnchor &anchor);
// Scroll current tab to @p_header.
void scrollToHeader(const VHeaderPointer &p_header);
void handleFileUpdated(const VFile *p_file); void handleFileUpdated(const VFile *p_file);
void handleDirectoryUpdated(const VDirectory *p_dir); void handleDirectoryUpdated(const VDirectory *p_dir);
void handleNotebookUpdated(const VNotebook *p_notebook); void handleNotebookUpdated(const VNotebook *p_notebook);
@ -118,8 +123,11 @@ private slots:
void handleRemoveSplitRequest(VEditWindow *curWindow); void handleRemoveSplitRequest(VEditWindow *curWindow);
void handleWindowFocused(); void handleWindowFocused();
void handleOutlineChanged(const VToc &toc);
void handleCurHeaderChanged(const VAnchor &anchor); void handleWindowOutlineChanged(const VTableOfContent &p_outline);
void handleWindowCurrentHeaderChanged(const VHeaderPointer &p_header);
void handleFindTextChanged(const QString &p_text, uint p_options); void handleFindTextChanged(const QString &p_text, uint p_options);
void handleFindOptionChanged(uint p_options); void handleFindOptionChanged(uint p_options);
void handleFindNext(const QString &p_text, uint p_options, bool p_forward); void handleFindNext(const QString &p_text, uint p_options, bool p_forward);

View File

@ -3,12 +3,13 @@
#include <QWheelEvent> #include <QWheelEvent>
VEditTab::VEditTab(VFile *p_file, VEditArea *p_editArea, QWidget *p_parent) VEditTab::VEditTab(VFile *p_file, VEditArea *p_editArea, QWidget *p_parent)
: QWidget(p_parent), m_file(p_file), m_isEditMode(false), : QWidget(p_parent),
m_modified(false), m_editArea(p_editArea) m_file(p_file),
m_isEditMode(false),
m_outline(p_file),
m_currentHeader(p_file, -1),
m_editArea(p_editArea)
{ {
m_toc.m_file = m_file;
m_curHeader.m_file = m_file;
connect(qApp, &QApplication::focusChanged, connect(qApp, &QApplication::focusChanged,
this, &VEditTab::handleFocusChanged); this, &VEditTab::handleFocusChanged);
} }
@ -33,7 +34,7 @@ bool VEditTab::isEditMode() const
bool VEditTab::isModified() const bool VEditTab::isModified() const
{ {
return m_modified; return m_file->isModified();
} }
VFile *VEditTab::getFile() const VFile *VEditTab::getFile() const
@ -53,16 +54,6 @@ void VEditTab::handleFocusChanged(QWidget * /* p_old */, QWidget *p_now)
} }
} }
void VEditTab::requestUpdateCurHeader()
{
emit curHeaderChanged(m_curHeader);
}
void VEditTab::requestUpdateOutline()
{
emit outlineChanged(m_toc);
}
void VEditTab::wheelEvent(QWheelEvent *p_event) void VEditTab::wheelEvent(QWheelEvent *p_event)
{ {
QPoint angle = p_event->angleDelta(); QPoint angle = p_event->angleDelta();
@ -78,41 +69,41 @@ void VEditTab::wheelEvent(QWheelEvent *p_event)
p_event->ignore(); p_event->ignore();
} }
void VEditTab::updateStatus() VEditTabInfo VEditTab::fetchTabInfo() const
{
m_modified = m_file->isModified();
emit statusUpdated(fetchTabInfo());
}
VEditTabInfo VEditTab::fetchTabInfo()
{ {
VEditTabInfo info; VEditTabInfo info;
info.m_editTab = this; info.m_editTab = const_cast<VEditTab *>(this);
return info; return info;
} }
VAnchor VEditTab::getCurrentHeader() const const VHeaderPointer &VEditTab::getCurrentHeader() const
{ {
return m_curHeader; return m_currentHeader;
}
const VTableOfContent &VEditTab::getOutline() const
{
return m_outline;
} }
void VEditTab::tryRestoreFromTabInfo(const VEditTabInfo &p_info) void VEditTab::tryRestoreFromTabInfo(const VEditTabInfo &p_info)
{ {
if (p_info.m_editTab != this) { if (p_info.m_editTab != this) {
// Clear and return. m_infoToRestore.clear();
m_infoToRestore.m_editTab = NULL;
return; return;
} }
if (restoreFromTabInfo(p_info)) { if (restoreFromTabInfo(p_info)) {
// Clear and return. m_infoToRestore.clear();
m_infoToRestore.m_editTab = NULL;
return; return;
} }
// Save it and restore later. // Save it and restore later.
m_infoToRestore = p_info; m_infoToRestore = p_info;
qDebug() << "save info for restore later" << p_info.m_anchorIndex; }
void VEditTab::updateStatus()
{
emit statusUpdated(fetchTabInfo());
} }

View File

@ -4,7 +4,7 @@
#include <QWidget> #include <QWidget>
#include <QString> #include <QString>
#include <QPointer> #include <QPointer>
#include "vtoc.h" #include "vtableofcontent.h"
#include "vfile.h" #include "vfile.h"
#include "utils/vvim.h" #include "utils/vvim.h"
#include "vedittabinfo.h" #include "vedittabinfo.h"
@ -37,12 +37,9 @@ public:
void focusTab(); void focusTab();
virtual void requestUpdateOutline(); // Scroll to @p_header.
// Will emit currentHeaderChanged() if @p_header is valid.
virtual void requestUpdateCurHeader(); virtual void scrollToHeader(const VHeaderPointer &p_header) { Q_UNUSED(p_header) }
// Scroll to anchor @p_anchor.
virtual void scrollToAnchor(const VAnchor& p_anchor) = 0;
VFile *getFile() const; VFile *getFile() const;
@ -72,21 +69,23 @@ public:
virtual void decorateText(TextDecoration p_decoration) {Q_UNUSED(p_decoration);} virtual void decorateText(TextDecoration p_decoration) {Q_UNUSED(p_decoration);}
// Create a filled VEditTabInfo. // Create a filled VEditTabInfo.
virtual VEditTabInfo fetchTabInfo(); virtual VEditTabInfo fetchTabInfo() const;
VAnchor getCurrentHeader() const; const VTableOfContent &getOutline() const;
const VHeaderPointer &getCurrentHeader() const;
// Restore status from @p_info. // Restore status from @p_info.
// If this tab is not ready yet, it will restore once it is ready. // If this tab is not ready yet, it will restore once it is ready.
void tryRestoreFromTabInfo(const VEditTabInfo &p_info); void tryRestoreFromTabInfo(const VEditTabInfo &p_info);
// Emit signal to update current status.
virtual void updateStatus();
public slots: public slots:
// Enter edit mode // Enter edit mode
virtual void editFile() = 0; virtual void editFile() = 0;
// Update status of current tab. Emit statusUpdated().
virtual void updateStatus();
protected: protected:
void wheelEvent(QWheelEvent *p_event) Q_DECL_OVERRIDE; void wheelEvent(QWheelEvent *p_event) Q_DECL_OVERRIDE;
@ -102,10 +101,15 @@ protected:
// File related to this tab. // File related to this tab.
QPointer<VFile> m_file; QPointer<VFile> m_file;
bool m_isEditMode; bool m_isEditMode;
bool m_modified;
VToc m_toc; // Table of content of this tab.
VAnchor m_curHeader; VTableOfContent m_outline;
// Current header in m_outline of this tab.
VHeaderPointer m_currentHeader;
VEditArea *m_editArea; VEditArea *m_editArea;
// Tab info to restore from once ready. // Tab info to restore from once ready.
@ -114,9 +118,9 @@ protected:
signals: signals:
void getFocused(); void getFocused();
void outlineChanged(const VToc &p_toc); void outlineChanged(const VTableOfContent &p_outline);
void curHeaderChanged(const VAnchor &p_anchor); void currentHeaderChanged(const VHeaderPointer &p_header);
// The status of current tab has updates. // The status of current tab has updates.
void statusUpdated(const VEditTabInfo &p_info); void statusUpdated(const VEditTabInfo &p_info);

View File

@ -10,10 +10,19 @@ struct VEditTabInfo
m_cursorBlockNumber(-1), m_cursorBlockNumber(-1),
m_cursorPositionInBlock(-1), m_cursorPositionInBlock(-1),
m_blockCount(-1), m_blockCount(-1),
m_anchorIndex(-1) m_headerIndex(-1)
{ {
} }
void clear()
{
m_editTab = NULL;
m_cursorBlockNumber = -1;
m_cursorPositionInBlock = -1;
m_blockCount = -1;
m_headerIndex = -1;
}
VEditTab *m_editTab; VEditTab *m_editTab;
// Cursor information. -1 for invalid info. // Cursor information. -1 for invalid info.
@ -21,8 +30,8 @@ struct VEditTabInfo
int m_cursorPositionInBlock; int m_cursorPositionInBlock;
int m_blockCount; int m_blockCount;
// Anchor index in outline. // Header index in outline.
int m_anchorIndex; int m_headerIndex;
}; };
#endif // VEDITTABINFO_H #endif // VEDITTABINFO_H

View File

@ -466,15 +466,15 @@ void VEditWindow::updateTabStatus(int p_index)
if (p_index == -1) { if (p_index == -1) {
emit tabStatusUpdated(VEditTabInfo()); emit tabStatusUpdated(VEditTabInfo());
emit outlineChanged(VToc()); emit outlineChanged(VTableOfContent());
emit curHeaderChanged(VAnchor()); emit currentHeaderChanged(VHeaderPointer());
return; return;
} }
VEditTab *tab = getTab(p_index); VEditTab *tab = getTab(p_index);
tab->updateStatus(); emit tabStatusUpdated(tab->fetchTabInfo());
tab->requestUpdateOutline(); emit outlineChanged(tab->getOutline());
tab->requestUpdateCurHeader(); emit currentHeaderChanged(tab->getCurrentHeader());
} }
void VEditWindow::updateTabInfo(int p_index) void VEditWindow::updateTabInfo(int p_index)
@ -506,26 +506,24 @@ void VEditWindow::updateAllTabsSequence()
} }
} }
// Be requested to report current outline VTableOfContent VEditWindow::getOutline() const
void VEditWindow::requestUpdateOutline()
{ {
int idx = currentIndex(); int idx = currentIndex();
if (idx == -1) { if (idx == -1) {
emit outlineChanged(VToc()); return VTableOfContent();
return;
} }
getTab(idx)->requestUpdateOutline();
return getTab(idx)->getOutline();
} }
// Be requested to report current header VHeaderPointer VEditWindow::getCurrentHeader() const
void VEditWindow::requestUpdateCurHeader()
{ {
int idx = currentIndex(); int idx = currentIndex();
if (idx == -1) { if (idx == -1) {
emit curHeaderChanged(VAnchor()); return VHeaderPointer();
return;
} }
getTab(idx)->requestUpdateCurHeader();
return getTab(idx)->getCurrentHeader();
} }
// Focus this windows. Try to focus current tab. // Focus this windows. Try to focus current tab.
@ -681,44 +679,39 @@ bool VEditWindow::canRemoveSplit()
return splitter->count() > 1; return splitter->count() > 1;
} }
void VEditWindow::handleOutlineChanged(const VToc &p_toc) void VEditWindow::handleTabOutlineChanged(const VTableOfContent &p_outline)
{ {
// Only propagate it if it is current tab // Only propagate it if it is current tab.
int idx = currentIndex(); VEditTab *tab = getCurrentTab();
if (idx == -1) { if (tab) {
emit outlineChanged(VToc()); if (tab->getFile() == p_outline.getFile()) {
return; emit outlineChanged(p_outline);
} }
const VFile *file = getTab(idx)->getFile(); } else {
if (p_toc.m_file == file) { emit outlineChanged(VTableOfContent());
emit outlineChanged(p_toc); return;
} }
} }
void VEditWindow::handleCurHeaderChanged(const VAnchor &p_anchor) void VEditWindow::handleTabCurrentHeaderChanged(const VHeaderPointer &p_header)
{ {
// Only propagate it if it is current tab // Only propagate it if it is current tab.
int idx = currentIndex(); VEditTab *tab = getCurrentTab();
if (idx == -1) { if (tab) {
emit curHeaderChanged(VAnchor()); if (tab->getFile() == p_header.m_file) {
return; emit currentHeaderChanged(p_header);
} }
const VFile *file = getTab(idx)->getFile(); } else {
if (p_anchor.m_file == file) { emit currentHeaderChanged(VHeaderPointer());
emit curHeaderChanged(p_anchor); return;
} }
} }
void VEditWindow::scrollCurTab(const VAnchor &p_anchor) void VEditWindow::scrollToHeader(const VHeaderPointer &p_header)
{ {
int idx = currentIndex(); VEditTab *tab = getCurrentTab();
if (idx == -1) { if (tab) {
emit curHeaderChanged(VAnchor()); tab->scrollToHeader(p_header);
return;
}
const VFile *file = getTab(idx)->getFile();
if (file == p_anchor.m_file) {
getTab(idx)->scrollToAnchor(p_anchor);
} }
} }
@ -916,9 +909,9 @@ void VEditWindow::connectEditTab(const VEditTab *p_tab)
connect(p_tab, &VEditTab::getFocused, connect(p_tab, &VEditTab::getFocused,
this, &VEditWindow::getFocused); this, &VEditWindow::getFocused);
connect(p_tab, &VEditTab::outlineChanged, connect(p_tab, &VEditTab::outlineChanged,
this, &VEditWindow::handleOutlineChanged); this, &VEditWindow::handleTabOutlineChanged);
connect(p_tab, &VEditTab::curHeaderChanged, connect(p_tab, &VEditTab::currentHeaderChanged,
this, &VEditWindow::handleCurHeaderChanged); this, &VEditWindow::handleTabCurrentHeaderChanged);
connect(p_tab, &VEditTab::statusUpdated, connect(p_tab, &VEditTab::statusUpdated,
this, &VEditWindow::handleTabStatusUpdated); this, &VEditWindow::handleTabStatusUpdated);
connect(p_tab, &VEditTab::statusMessage, connect(p_tab, &VEditTab::statusMessage,

View File

@ -8,7 +8,6 @@
#include <QDir> #include <QDir>
#include "vnotebook.h" #include "vnotebook.h"
#include "vedittab.h" #include "vedittab.h"
#include "vtoc.h"
#include "vconstants.h" #include "vconstants.h"
#include "vnotefile.h" #include "vnotefile.h"
@ -32,11 +31,19 @@ public:
void readFile(); void readFile();
void saveAndReadFile(); void saveAndReadFile();
bool closeAllFiles(bool p_forced); bool closeAllFiles(bool p_forced);
void requestUpdateOutline();
void requestUpdateCurHeader(); // Return outline of current tab.
VTableOfContent getOutline() const;
// Return current header of current tab.
VHeaderPointer getCurrentHeader() const;
// Focus to current tab's editor // Focus to current tab's editor
void focusWindow(); void focusWindow();
void scrollCurTab(const VAnchor &p_anchor);
// Scroll current tab to header @p_header.
void scrollToHeader(const VHeaderPointer &p_header);
void updateFileInfo(const VFile *p_file); void updateFileInfo(const VFile *p_file);
void updateDirectoryInfo(const VDirectory *p_dir); void updateDirectoryInfo(const VDirectory *p_dir);
void updateNotebookInfo(const VNotebook *p_notebook); void updateNotebookInfo(const VNotebook *p_notebook);
@ -84,8 +91,10 @@ 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);
void curHeaderChanged(const VAnchor &anchor); void outlineChanged(const VTableOfContent &p_outline);
void currentHeaderChanged(const VHeaderPointer &p_header);
// Emit when want to show message in status bar. // Emit when want to show message in status bar.
void statusMessage(const QString &p_msg); void statusMessage(const QString &p_msg);
@ -105,8 +114,11 @@ private slots:
void handleCurrentIndexChanged(int p_index); void handleCurrentIndexChanged(int p_index);
void contextMenuRequested(QPoint pos); void contextMenuRequested(QPoint pos);
void tabListJump(VFile *p_file); void tabListJump(VFile *p_file);
void handleOutlineChanged(const VToc &p_toc);
void handleCurHeaderChanged(const VAnchor &p_anchor); void handleTabOutlineChanged(const VTableOfContent &p_outline);
void handleTabCurrentHeaderChanged(const VHeaderPointer &p_header);
void updateSplitMenu(); void updateSplitMenu();
void tabbarContextMenuRequested(QPoint p_pos); void tabbarContextMenuRequested(QPoint p_pos);
void handleLocateAct(); void handleLocateAct();

View File

@ -3,13 +3,13 @@
#include <QSettings> #include <QSettings>
#include "vedittabinfo.h" #include "vedittabinfo.h"
#include "vtoc.h" #include "vtableofcontent.h"
#include "vedittab.h" #include "vedittab.h"
VFileSessionInfo::VFileSessionInfo() VFileSessionInfo::VFileSessionInfo()
: m_mode(OpenFileMode::Read), : m_mode(OpenFileMode::Read),
m_anchorIndex(-1), m_headerIndex(-1),
m_cursorBlockNumber(-1), m_cursorBlockNumber(-1),
m_cursorPositionInBlock(-1) m_cursorPositionInBlock(-1)
{ {
@ -19,7 +19,7 @@ VFileSessionInfo::VFileSessionInfo(const QString &p_file,
OpenFileMode p_mode) OpenFileMode p_mode)
: m_file(p_file), : m_file(p_file),
m_mode(p_mode), m_mode(p_mode),
m_anchorIndex(-1), m_headerIndex(-1),
m_cursorBlockNumber(-1), m_cursorBlockNumber(-1),
m_cursorPositionInBlock(-1) m_cursorPositionInBlock(-1)
{ {
@ -32,7 +32,7 @@ VFileSessionInfo VFileSessionInfo::fromEditTabInfo(const VEditTabInfo *p_tabInfo
VEditTab *tab = p_tabInfo->m_editTab; VEditTab *tab = p_tabInfo->m_editTab;
VFileSessionInfo info(tab->getFile()->fetchPath(), VFileSessionInfo info(tab->getFile()->fetchPath(),
tab->isEditMode() ? OpenFileMode::Edit : OpenFileMode::Read); tab->isEditMode() ? OpenFileMode::Edit : OpenFileMode::Read);
info.m_anchorIndex = p_tabInfo->m_anchorIndex; info.m_headerIndex = p_tabInfo->m_headerIndex;
info.m_cursorBlockNumber = p_tabInfo->m_cursorBlockNumber; info.m_cursorBlockNumber = p_tabInfo->m_cursorBlockNumber;
info.m_cursorPositionInBlock = p_tabInfo->m_cursorPositionInBlock; info.m_cursorPositionInBlock = p_tabInfo->m_cursorPositionInBlock;
@ -41,7 +41,7 @@ VFileSessionInfo VFileSessionInfo::fromEditTabInfo(const VEditTabInfo *p_tabInfo
void VFileSessionInfo::toEditTabInfo(VEditTabInfo *p_tabInfo) const void VFileSessionInfo::toEditTabInfo(VEditTabInfo *p_tabInfo) const
{ {
p_tabInfo->m_anchorIndex = m_anchorIndex; p_tabInfo->m_headerIndex = m_headerIndex;
p_tabInfo->m_cursorBlockNumber = m_cursorBlockNumber; p_tabInfo->m_cursorBlockNumber = m_cursorBlockNumber;
p_tabInfo->m_cursorPositionInBlock = m_cursorPositionInBlock; p_tabInfo->m_cursorPositionInBlock = m_cursorPositionInBlock;
} }
@ -57,7 +57,7 @@ VFileSessionInfo VFileSessionInfo::fromSettings(const QSettings *p_settings)
info.m_mode = OpenFileMode::Read; info.m_mode = OpenFileMode::Read;
} }
info.m_anchorIndex = p_settings->value(FileSessionConfig::c_anchorIndex).toInt(); info.m_headerIndex = p_settings->value(FileSessionConfig::c_headerIndex).toInt();
info.m_cursorBlockNumber = p_settings->value(FileSessionConfig::c_cursorBlockNumber).toInt(); info.m_cursorBlockNumber = p_settings->value(FileSessionConfig::c_cursorBlockNumber).toInt();
info.m_cursorPositionInBlock = p_settings->value(FileSessionConfig::c_cursorPositionInBlock).toInt(); info.m_cursorPositionInBlock = p_settings->value(FileSessionConfig::c_cursorPositionInBlock).toInt();
@ -68,7 +68,7 @@ void VFileSessionInfo::toSettings(QSettings *p_settings) const
{ {
p_settings->setValue(FileSessionConfig::c_file, m_file); p_settings->setValue(FileSessionConfig::c_file, m_file);
p_settings->setValue(FileSessionConfig::c_mode, (int)m_mode); p_settings->setValue(FileSessionConfig::c_mode, (int)m_mode);
p_settings->setValue(FileSessionConfig::c_anchorIndex, m_anchorIndex); p_settings->setValue(FileSessionConfig::c_headerIndex, m_headerIndex);
p_settings->setValue(FileSessionConfig::c_cursorBlockNumber, m_cursorBlockNumber); p_settings->setValue(FileSessionConfig::c_cursorBlockNumber, m_cursorBlockNumber);
p_settings->setValue(FileSessionConfig::c_cursorPositionInBlock, m_cursorPositionInBlock); p_settings->setValue(FileSessionConfig::c_cursorPositionInBlock, m_cursorPositionInBlock);
} }

View File

@ -12,7 +12,7 @@ namespace FileSessionConfig
static const QString c_mode = "mode"; static const QString c_mode = "mode";
// Index in outline of the anchor. // Index in outline of the anchor.
static const QString c_anchorIndex = "anchor_index"; static const QString c_headerIndex = "header_index";
static const QString c_cursorBlockNumber = "cursor_block_number"; static const QString c_cursorBlockNumber = "cursor_block_number";
static const QString c_cursorPositionInBlock = "cursor_position_in_block"; static const QString c_cursorPositionInBlock = "cursor_position_in_block";
@ -44,8 +44,8 @@ public:
// Mode of this file in this session. // Mode of this file in this session.
OpenFileMode m_mode; OpenFileMode m_mode;
// Index in outline of the anchor. // Index in outline of the header.
int m_anchorIndex; int m_headerIndex;
// Block number of cursor block. // Block number of cursor block.
int m_cursorBlockNumber; int m_cursorBlockNumber;

View File

@ -32,7 +32,7 @@ void VHtmlTab::setupUI()
{ {
m_editor = new VEdit(m_file, this); m_editor = new VEdit(m_file, this);
connect(m_editor, &VEdit::textChanged, connect(m_editor, &VEdit::textChanged,
this, &VHtmlTab::handleTextChanged); this, &VHtmlTab::updateStatus);
connect(m_editor, &VEdit::saveAndRead, connect(m_editor, &VEdit::saveAndRead,
this, &VHtmlTab::saveAndRead); this, &VHtmlTab::saveAndRead);
connect(m_editor, &VEdit::discardAndRead, connect(m_editor, &VEdit::discardAndRead,
@ -52,17 +52,6 @@ void VHtmlTab::setupUI()
setLayout(mainLayout); setLayout(mainLayout);
} }
void VHtmlTab::handleTextChanged()
{
V_ASSERT(m_file->isModifiable());
if (m_modified) {
return;
}
updateStatus();
}
void VHtmlTab::showFileReadMode() void VHtmlTab::showFileReadMode()
{ {
m_isEditMode = false; m_isEditMode = false;
@ -194,10 +183,6 @@ void VHtmlTab::discardAndRead()
readFile(); readFile();
} }
void VHtmlTab::scrollToAnchor(const VAnchor & /* p_anchor */)
{
}
void VHtmlTab::insertImage() void VHtmlTab::insertImage()
{ {
} }

View File

@ -26,9 +26,6 @@ public:
// Save file. // Save file.
bool saveFile() Q_DECL_OVERRIDE; bool saveFile() Q_DECL_OVERRIDE;
// Scroll to anchor @p_anchor.
void scrollToAnchor(const VAnchor& p_anchor) Q_DECL_OVERRIDE;
void insertImage() Q_DECL_OVERRIDE; void insertImage() Q_DECL_OVERRIDE;
// Search @p_text in current note. // Search @p_text in current note.
@ -53,9 +50,6 @@ public slots:
void editFile() Q_DECL_OVERRIDE; void editFile() Q_DECL_OVERRIDE;
private slots: private slots:
// Handle text changed in m_editor.
void handleTextChanged();
// m_editor requests to save changes and enter read mode. // m_editor requests to save changes and enter read mode.
void saveAndRead(); void saveAndRead();

View File

@ -1100,13 +1100,16 @@ void VMainWindow::initDockWindows()
toolDock->setObjectName("tools_dock"); toolDock->setObjectName("tools_dock");
toolDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); toolDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
toolBox = new QToolBox(this); toolBox = new QToolBox(this);
// Outline tree.
outline = new VOutline(this); outline = new VOutline(this);
connect(editArea, &VEditArea::outlineChanged, connect(editArea, &VEditArea::outlineChanged,
outline, &VOutline::updateOutline); outline, &VOutline::updateOutline);
connect(editArea, &VEditArea::currentHeaderChanged,
outline, &VOutline::updateCurrentHeader);
connect(outline, &VOutline::outlineItemActivated, connect(outline, &VOutline::outlineItemActivated,
editArea, &VEditArea::handleOutlineItemActivated); editArea, &VEditArea::scrollToHeader);
connect(editArea, &VEditArea::curHeaderChanged,
outline, &VOutline::updateCurHeader);
toolBox->addItem(outline, QIcon(":/resources/icons/outline.svg"), tr("Outline")); toolBox->addItem(outline, QIcon(":/resources/icons/outline.svg"), tr("Outline"));
toolDock->setWidget(toolBox); toolDock->setWidget(toolBox);
addDockWidget(Qt::RightDockWidgetArea, toolDock); addDockWidget(Qt::RightDockWidgetArea, toolDock);

View File

@ -145,6 +145,7 @@ private slots:
void handleVimStatusUpdated(const VVim *p_vim); void handleVimStatusUpdated(const VVim *p_vim);
// Handle the status update of the current tab of VEditArea. // Handle the status update of the current tab of VEditArea.
// Will be called frequently.
void handleAreaTabStatusUpdated(const VEditTabInfo &p_info); void handleAreaTabStatusUpdated(const VEditTabInfo &p_info);
// Check the shared memory between different instances to see if we have // Check the shared memory between different instances to see if we have

View File

@ -5,7 +5,7 @@
#include "vmdeditoperations.h" #include "vmdeditoperations.h"
#include "vnote.h" #include "vnote.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vtoc.h" #include "vtableofcontent.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "utils/veditutils.h" #include "utils/veditutils.h"
#include "utils/vpreviewutils.h" #include "utils/vpreviewutils.h"
@ -35,7 +35,7 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
document()); document());
connect(m_mdHighlighter, &HGMarkdownHighlighter::headersUpdated, connect(m_mdHighlighter, &HGMarkdownHighlighter::headersUpdated,
this, &VMdEdit::updateOutline); this, &VMdEdit::updateHeaders);
// After highlight, the cursor may trun into non-visible. We should make it visible // After highlight, the cursor may trun into non-visible. We should make it visible
// in this case. // in this case.
@ -74,7 +74,7 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
this, &VEdit::vimStatusUpdated); this, &VEdit::vimStatusUpdated);
connect(this, &VMdEdit::cursorPositionChanged, connect(this, &VMdEdit::cursorPositionChanged,
this, &VMdEdit::updateCurHeader); this, &VMdEdit::updateCurrentHeader);
connect(QApplication::clipboard(), &QClipboard::changed, connect(QApplication::clipboard(), &QClipboard::changed,
this, &VMdEdit::handleClipboardChanged); this, &VMdEdit::handleClipboardChanged);
@ -111,7 +111,7 @@ void VMdEdit::beginEdit()
setReadOnly(false); setReadOnly(false);
} }
updateOutline(m_mdHighlighter->getHeaderRegions()); updateHeaders(m_mdHighlighter->getHeaderRegions());
} }
void VMdEdit::endEdit() void VMdEdit::endEdit()
@ -345,43 +345,9 @@ void VMdEdit::clearUnusedImages()
m_initImages.clear(); m_initImages.clear();
} }
int VMdEdit::currentCursorHeader() const void VMdEdit::updateCurrentHeader()
{ {
if (m_headers.isEmpty()) { emit currentHeaderChanged(textCursor().block().blockNumber());
return -1;
}
int curLine = textCursor().block().firstLineNumber();
int i = 0;
for (i = m_headers.size() - 1; i >= 0; --i) {
if (!m_headers[i].isEmpty()) {
if (m_headers[i].lineNumber <= curLine) {
break;
}
}
}
if (i == -1) {
return -1;
} else {
Q_ASSERT(m_headers[i].index == i);
return i;
}
}
void VMdEdit::updateCurHeader()
{
if (m_headers.isEmpty()) {
return;
}
int idx = currentCursorHeader();
if (idx == -1) {
emit curHeaderChanged(VAnchor(m_file, "", -1, -1));
return;
}
emit curHeaderChanged(VAnchor(m_file, "", m_headers[idx].lineNumber, m_headers[idx].index));
} }
static void addHeaderSequence(QVector<int> &p_sequence, int p_level, int p_baseLevel) static void addHeaderSequence(QVector<int> &p_sequence, int p_level, int p_baseLevel)
@ -448,11 +414,11 @@ static void insertSequenceToHeader(QTextBlock p_block,
} }
} }
void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions) void VMdEdit::updateHeaders(const QVector<VElementRegion> &p_headerRegions)
{ {
QTextDocument *doc = document(); QTextDocument *doc = document();
QVector<VHeader> headers; QVector<VTableOfContentItem> headers;
QVector<int> headerBlockNumbers; QVector<int> headerBlockNumbers;
QVector<QString> headerSequences; QVector<QString> headerSequences;
if (!p_headerRegions.isEmpty()) { if (!p_headerRegions.isEmpty()) {
@ -480,8 +446,10 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
if ((block.userState() == HighlightBlockState::Normal) && if ((block.userState() == HighlightBlockState::Normal) &&
headerReg.exactMatch(block.text())) { headerReg.exactMatch(block.text())) {
int level = headerReg.cap(1).length(); int level = headerReg.cap(1).length();
VHeader header(level, headerReg.cap(2).trimmed(), VTableOfContentItem header(headerReg.cap(2).trimmed(),
"", block.firstLineNumber(), headers.size()); level,
block.blockNumber(),
headers.size());
headers.append(header); headers.append(header);
headerBlockNumbers.append(block.blockNumber()); headerBlockNumbers.append(block.blockNumber());
headerSequences.append(headerReg.cap(3)); headerSequences.append(headerReg.cap(3));
@ -506,22 +474,25 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
QRegExp preReg(VUtils::c_headerPrefixRegExp); QRegExp preReg(VUtils::c_headerPrefixRegExp);
int curLevel = baseLevel - 1; int curLevel = baseLevel - 1;
for (int i = 0; i < headers.size(); ++i) { for (int i = 0; i < headers.size(); ++i) {
VHeader &item = headers[i]; VTableOfContentItem &item = headers[i];
while (item.level > curLevel + 1) { while (item.m_level > curLevel + 1) {
curLevel += 1; curLevel += 1;
// Insert empty level which is an invalid header. // Insert empty level which is an invalid header.
m_headers.append(VHeader(curLevel, c_emptyHeaderName, "", -1, m_headers.size())); m_headers.append(VTableOfContentItem(c_emptyHeaderName,
curLevel,
-1,
m_headers.size()));
if (autoSequence) { if (autoSequence) {
addHeaderSequence(seqs, curLevel, headingSequenceBaseLevel); addHeaderSequence(seqs, curLevel, headingSequenceBaseLevel);
} }
} }
item.index = m_headers.size(); item.m_index = m_headers.size();
m_headers.append(item); m_headers.append(item);
curLevel = item.level; curLevel = item.m_level;
if (autoSequence) { if (autoSequence) {
addHeaderSequence(seqs, item.level, headingSequenceBaseLevel); addHeaderSequence(seqs, item.m_level, headingSequenceBaseLevel);
QString seqStr = headerSequenceStr(seqs); QString seqStr = headerSequenceStr(seqs);
if (headerSequences[i] != seqStr) { if (headerSequences[i] != seqStr) {
@ -536,39 +507,16 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
emit headersChanged(m_headers); emit headersChanged(m_headers);
updateCurHeader(); updateCurrentHeader();
} }
void VMdEdit::scrollToAnchor(const VAnchor &p_anchor) bool VMdEdit::scrollToHeader(int p_blockNumber)
{ {
if (p_anchor.lineNumber == -1 if (p_blockNumber < 0) {
|| p_anchor.m_outlineIndex < 0) {
// Move to the start of document if m_headers is not empty.
// Otherwise, there is no outline, so just let it be.
if (!m_headers.isEmpty()) {
moveCursor(QTextCursor::Start);
}
return;
} else if (p_anchor.m_outlineIndex >= m_headers.size()) {
return;
}
scrollToLine(p_anchor.lineNumber);
}
bool VMdEdit::scrollToAnchor(int p_anchorIndex)
{
if (p_anchorIndex >= 0 && p_anchorIndex < m_headers.size()) {
int lineNumber = m_headers[p_anchorIndex].lineNumber;
if (lineNumber >= 0) {
scrollToLine(lineNumber);
return true;
}
}
return false; return false;
}
return scrollToBlock(p_blockNumber);
} }
QString VMdEdit::toPlainTextWithoutImg() QString VMdEdit::toPlainTextWithoutImg()
@ -742,9 +690,21 @@ void VMdEdit::resizeEvent(QResizeEvent *p_event)
VEdit::resizeEvent(p_event); VEdit::resizeEvent(p_event);
} }
const QVector<VHeader> &VMdEdit::getHeaders() const int VMdEdit::indexOfCurrentHeader() const
{ {
return m_headers; if (m_headers.isEmpty()) {
return -1;
}
int blockNumber = textCursor().block().blockNumber();
for (int i = m_headers.size() - 1; i >= 0; --i) {
if (!m_headers[i].isEmpty()
&& m_headers[i].m_blockNumber <= blockNumber) {
return i;
}
}
return -1;
} }
bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
@ -754,11 +714,11 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
} }
QTextCursor cursor = textCursor(); QTextCursor cursor = textCursor();
int cursorLine = cursor.block().firstLineNumber(); int cursorLine = cursor.block().blockNumber();
int targetIdx = -1; int targetIdx = -1;
// -1: skip level check. // -1: skip level check.
int targetLevel = 0; int targetLevel = 0;
int idx = currentCursorHeader(); int idx = indexOfCurrentHeader();
if (idx == -1) { if (idx == -1) {
// Cursor locates at the beginning, before any headers. // Cursor locates at the beginning, before any headers.
if (p_relativeLevel < 0 || !p_forward) { if (p_relativeLevel < 0 || !p_forward) {
@ -775,7 +735,7 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
for (targetIdx = idx == -1 ? 0 : idx; for (targetIdx = idx == -1 ? 0 : idx;
targetIdx >= 0 && targetIdx < m_headers.size(); targetIdx >= 0 && targetIdx < m_headers.size();
targetIdx += delta) { targetIdx += delta) {
const VHeader &header = m_headers[targetIdx]; const VTableOfContentItem &header = m_headers[targetIdx];
if (header.isEmpty()) { if (header.isEmpty()) {
continue; continue;
} }
@ -783,7 +743,7 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
if (targetLevel == 0) { if (targetLevel == 0) {
// The target level has not been init yet. // The target level has not been init yet.
Q_ASSERT(firstHeader); Q_ASSERT(firstHeader);
targetLevel = header.level; targetLevel = header.m_level;
if (p_relativeLevel < 0) { if (p_relativeLevel < 0) {
targetLevel += p_relativeLevel; targetLevel += p_relativeLevel;
if (targetLevel < 1) { if (targetLevel < 1) {
@ -795,9 +755,9 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
} }
} }
if (targetLevel == -1 || header.level == targetLevel) { if (targetLevel == -1 || header.m_level == targetLevel) {
if (firstHeader if (firstHeader
&& (cursorLine == header.lineNumber && (cursorLine == header.m_blockNumber
|| p_forward) || p_forward)
&& idx != -1) { && idx != -1) {
// This header is not counted for the repeat. // This header is not counted for the repeat.
@ -809,7 +769,7 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
// Found. // Found.
break; break;
} }
} else if (header.level < targetLevel) { } else if (header.m_level < targetLevel) {
// Stop by higher level. // Stop by higher level.
return false; return false;
} }
@ -822,9 +782,9 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
} }
// Jump to target header. // Jump to target header.
int line = m_headers[targetIdx].lineNumber; int line = m_headers[targetIdx].m_blockNumber;
if (line > -1) { if (line > -1) {
QTextBlock block = document()->findBlockByLineNumber(line); QTextBlock block = document()->findBlockByNumber(line);
if (block.isValid()) { if (block.isValid()) {
cursor.setPosition(block.position()); cursor.setPosition(block.position());
setTextCursor(cursor); setTextCursor(cursor);
@ -851,7 +811,7 @@ void VMdEdit::finishOneAsyncJob(int p_idx)
m_freshEdit = false; m_freshEdit = false;
emit statusChanged(); emit statusChanged();
updateOutline(m_mdHighlighter->getHeaderRegions()); updateHeaders(m_mdHighlighter->getHeaderRegions());
emit ready(); emit ready();
} }

View File

@ -7,7 +7,7 @@
#include <QColor> #include <QColor>
#include <QClipboard> #include <QClipboard>
#include <QImage> #include <QImage>
#include "vtoc.h" #include "vtableofcontent.h"
#include "veditoperations.h" #include "veditoperations.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "utils/vutils.h" #include "utils/vutils.h"
@ -32,35 +32,34 @@ public:
// @p_path is the absolute path of the inserted image. // @p_path is the absolute path of the inserted image.
void imageInserted(const QString &p_path); void imageInserted(const QString &p_path);
void scrollToAnchor(const VAnchor &p_anchor); // Scroll to header @p_blockNumber.
// Return true if @p_blockNumber is valid to scroll to.
// Scroll to anchor given the the index in outline. bool scrollToHeader(int p_blockNumber);
// Return true if @p_anchorIndex is valid.
bool scrollToAnchor(int p_anchorIndex);
// Like toPlainText(), but remove image preview characters. // Like toPlainText(), but remove image preview characters.
QString toPlainTextWithoutImg(); QString toPlainTextWithoutImg();
const QVector<VHeader> &getHeaders() const;
public slots: public slots:
bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE; bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE;
signals: signals:
void headersChanged(const QVector<VHeader> &headers); // Signal when headers change.
void headersChanged(const QVector<VTableOfContentItem> &p_headers);
// Signal when current header change. // Signal when current header change.
void curHeaderChanged(VAnchor p_anchor); void currentHeaderChanged(int p_blockNumber);
// Signal when the status of VMdEdit changed. // Signal when the status of VMdEdit changed.
// Will be emitted by VImagePreviewer for now. // Will be emitted by VImagePreviewer for now.
void statusChanged(); void statusChanged();
private slots: private slots:
void updateOutline(const QVector<VElementRegion> &p_headerRegions); // Update m_headers according to elements.
void updateHeaders(const QVector<VElementRegion> &p_headerRegions);
// Update current header according to cursor position.
// When there is no header in current cursor, will signal an invalid header. // When there is no header in current cursor, will signal an invalid header.
void updateCurHeader(); void updateCurrentHeader();
void handleClipboardChanged(QClipboard::Mode p_mode); void handleClipboardChanged(QClipboard::Mode p_mode);
@ -99,9 +98,6 @@ private:
// in the selection. Get the QImage. // in the selection. Get the QImage.
QImage tryGetSelectedImage(); QImage tryGetSelectedImage();
// Return the header index in m_headers where current cursor locates.
int currentCursorHeader() const;
QString getPlainTextWithoutPreviewImage() const; QString getPlainTextWithoutPreviewImage() const;
// Try to get all the regions of preview image within @p_block. // Try to get all the regions of preview image within @p_block.
@ -111,6 +107,9 @@ private:
void finishOneAsyncJob(int p_idx); void finishOneAsyncJob(int p_idx);
// Index in m_headers of current header which contains the cursor.
int indexOfCurrentHeader() const;
HGMarkdownHighlighter *m_mdHighlighter; HGMarkdownHighlighter *m_mdHighlighter;
VCodeBlockHighlightHelper *m_cbHighlighter; VCodeBlockHighlightHelper *m_cbHighlighter;
VImagePreviewer *m_imagePreviewer; VImagePreviewer *m_imagePreviewer;
@ -121,7 +120,8 @@ private:
// Image links right at the beginning of the edit. // Image links right at the beginning of the edit.
QVector<ImageLink> m_initImages; QVector<ImageLink> m_initImages;
QVector<VHeader> m_headers; // Mainly used for title jump.
QVector<VTableOfContentItem> m_headers;
bool m_freshEdit; bool m_freshEdit;

View File

@ -11,7 +11,7 @@
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vmarkdownconverter.h" #include "vmarkdownconverter.h"
#include "vnotebook.h" #include "vnotebook.h"
#include "vtoc.h" #include "vtableofcontent.h"
#include "vmdedit.h" #include "vmdedit.h"
#include "dialog/vfindreplacedialog.h" #include "dialog/vfindreplacedialog.h"
#include "veditarea.h" #include "veditarea.h"
@ -50,72 +50,109 @@ void VMdTab::setupUI()
setLayout(m_stacks); setLayout(m_stacks);
} }
void VMdTab::handleTextChanged()
{
V_ASSERT(m_file->isModifiable());
if (m_modified) {
return;
}
updateStatus();
}
void VMdTab::showFileReadMode() void VMdTab::showFileReadMode()
{ {
m_isEditMode = false; m_isEditMode = false;
int outlineIndex = m_curHeader.m_outlineIndex; VHeaderPointer header(m_currentHeader);
if (m_mdConType == MarkdownConverterType::Hoedown) { if (m_mdConType == MarkdownConverterType::Hoedown) {
viewWebByConverter(); viewWebByConverter();
} else { } else {
m_document->updateText(); m_document->updateText();
updateTocFromHtml(m_document->getToc()); updateOutlineFromHtml(m_document->getToc());
} }
m_stacks->setCurrentWidget(m_webViewer); m_stacks->setCurrentWidget(m_webViewer);
clearSearchedWordHighlight(); clearSearchedWordHighlight();
scrollWebViewToAnchor(outlineIndex); scrollWebViewToHeader(header);
updateStatus(); updateStatus();
} }
bool VMdTab::scrollWebViewToAnchor(int p_anchorIndex, bool p_strict) bool VMdTab::scrollWebViewToHeader(const VHeaderPointer &p_header)
{ {
QString anchor; if (!m_outline.isMatched(p_header)
|| m_outline.getType() != VTableOfContentType::Anchor) {
VAnchor anch(m_file, anchor, -1, p_anchorIndex); return false;
bool validIndex = false;
if (p_anchorIndex < m_toc.headers.size() && p_anchorIndex >= 0) {
QString tmp = m_toc.headers[p_anchorIndex].anchor;
Q_ASSERT(!tmp.isEmpty());
anch.anchor = tmp;
anchor = tmp.mid(1);
validIndex = true;
} }
if (validIndex || !p_strict) { if (p_header.isValid()) {
m_curHeader = anch; const VTableOfContentItem *item = m_outline.getItem(p_header);
if (item) {
if (item->m_anchor.isEmpty()) {
return false;
}
m_document->scrollToAnchor(anchor); m_currentHeader = p_header;
m_document->scrollToAnchor(item->m_anchor);
} else {
return false;
}
} else {
if (m_outline.isEmpty()) {
// Let it be.
m_currentHeader = p_header;
} else {
// Scroll to top.
m_currentHeader = p_header;
m_document->scrollToAnchor("");
}
}
emit curHeaderChanged(m_curHeader); emit currentHeaderChanged(m_currentHeader);
return true;
}
bool VMdTab::scrollEditorToHeader(const VHeaderPointer &p_header)
{
if (!m_outline.isMatched(p_header)
|| m_outline.getType() != VTableOfContentType::BlockNumber) {
return false;
}
VMdEdit *mdEdit = dynamic_cast<VMdEdit *>(getEditor());
int blockNumber = -1;
if (p_header.isValid()) {
const VTableOfContentItem *item = m_outline.getItem(p_header);
if (item) {
blockNumber = item->m_blockNumber;
if (blockNumber == -1) {
// Empty item.
return false;
}
} else {
return false;
}
} else {
if (m_outline.isEmpty()) {
// No outline and scroll to -1 index.
// Just let it be.
m_currentHeader = p_header;
return true;
} else {
// Has outline and scroll to -1 index.
// Scroll to top.
blockNumber = 0;
}
}
if (mdEdit->scrollToHeader(blockNumber)) {
m_currentHeader = p_header;
return true; return true;
} else { } else {
return false; return false;
} }
} }
bool VMdTab::scrollToAnchor(int p_anchorIndex, bool p_strict) bool VMdTab::scrollToHeaderInternal(const VHeaderPointer &p_header)
{ {
if (m_isEditMode) { if (m_isEditMode) {
return dynamic_cast<VMdEdit *>(getEditor())->scrollToAnchor(p_anchorIndex); return scrollEditorToHeader(p_header);
} else { } else {
return scrollWebViewToAnchor(p_anchorIndex, p_strict); return scrollWebViewToHeader(p_header);
} }
} }
@ -127,7 +164,7 @@ void VMdTab::viewWebByConverter()
g_config->getMarkdownExtensions(), g_config->getMarkdownExtensions(),
toc); toc);
m_document->setHtml(html); m_document->setHtml(html);
updateTocFromHtml(toc); updateOutlineFromHtml(toc);
} }
void VMdTab::showFileEditMode() void VMdTab::showFileEditMode()
@ -136,42 +173,28 @@ void VMdTab::showFileEditMode()
return; return;
} }
VHeaderPointer header(m_currentHeader);
m_isEditMode = true; m_isEditMode = true;
VMdEdit *mdEdit = dynamic_cast<VMdEdit *>(getEditor()); VMdEdit *mdEdit = dynamic_cast<VMdEdit *>(getEditor());
V_ASSERT(mdEdit); V_ASSERT(mdEdit);
// beginEdit() may change m_curHeader.
int outlineIndex = m_curHeader.m_outlineIndex;
mdEdit->beginEdit(); mdEdit->beginEdit();
m_stacks->setCurrentWidget(mdEdit); m_stacks->setCurrentWidget(mdEdit);
int lineNumber = -1;
const QVector<VHeader> &headers = mdEdit->getHeaders();
// If editor is not init, we need to wait for it to init headers. // If editor is not init, we need to wait for it to init headers.
// Generally, beginEdit() will generate the headers. Wait is needed when // Generally, beginEdit() will generate the headers. Wait is needed when
// highlight completion is going to re-generate the headers. // highlight completion is going to re-generate the headers.
int nrRetry = 5; int nrRetry = 5;
while (outlineIndex > -1 && headers.isEmpty() && nrRetry-- > 0) { while (header.m_index > -1 && m_outline.isEmpty() && nrRetry-- > 0) {
qDebug() << "wait another 100 ms for editor's headers ready"; qDebug() << "wait another 100 ms for editor's headers ready";
VUtils::sleepWait(100); VUtils::sleepWait(100);
} }
if (outlineIndex < 0 || outlineIndex >= headers.size()) { scrollEditorToHeader(header);
lineNumber = -1;
outlineIndex = -1;
} else {
lineNumber = headers[outlineIndex].lineNumber;
}
VAnchor anchor(m_file, "", lineNumber, outlineIndex);
mdEdit->scrollToAnchor(anchor);
mdEdit->setFocus(); mdEdit->setFocus();
updateStatus();
} }
bool VMdTab::closeFile(bool p_forced) bool VMdTab::closeFile(bool p_forced)
@ -273,8 +296,6 @@ bool VMdTab::saveFile()
m_editor->setModified(true); m_editor->setModified(true);
} }
updateStatus();
return ret; return ret;
} }
@ -304,9 +325,9 @@ void VMdTab::setupMarkdownViewer()
QWebChannel *channel = new QWebChannel(m_webViewer); QWebChannel *channel = new QWebChannel(m_webViewer);
channel->registerObject(QStringLiteral("content"), m_document); channel->registerObject(QStringLiteral("content"), m_document);
connect(m_document, &VDocument::tocChanged, connect(m_document, &VDocument::tocChanged,
this, &VMdTab::updateTocFromHtml); this, &VMdTab::updateOutlineFromHtml);
connect(m_document, SIGNAL(headerChanged(const QString&)), connect(m_document, SIGNAL(headerChanged(const QString &)),
this, SLOT(updateCurHeader(const QString &))); this, SLOT(updateCurrentHeader(const QString &)));
connect(m_document, &VDocument::keyPressed, connect(m_document, &VDocument::keyPressed,
this, &VMdTab::handleWebKeyPressed); this, &VMdTab::handleWebKeyPressed);
connect(m_document, SIGNAL(logicsFinished(void)), connect(m_document, SIGNAL(logicsFinished(void)),
@ -325,13 +346,13 @@ void VMdTab::setupMarkdownEditor()
qDebug() << "create Markdown editor"; qDebug() << "create Markdown editor";
m_editor = new VMdEdit(m_file, m_document, m_mdConType, this); m_editor = new VMdEdit(m_file, m_document, m_mdConType, this);
connect(dynamic_cast<VMdEdit *>(m_editor), &VMdEdit::headersChanged, connect(dynamic_cast<VMdEdit *>(m_editor), &VMdEdit::headersChanged,
this, &VMdTab::updateTocFromHeaders); this, &VMdTab::updateOutlineFromHeaders);
connect(dynamic_cast<VMdEdit *>(m_editor), SIGNAL(currentHeaderChanged(int)),
this, SLOT(updateCurrentHeader(int)));
connect(dynamic_cast<VMdEdit *>(m_editor), &VMdEdit::statusChanged, connect(dynamic_cast<VMdEdit *>(m_editor), &VMdEdit::statusChanged,
this, &VMdTab::updateStatus); this, &VMdTab::updateStatus);
connect(m_editor, SIGNAL(curHeaderChanged(VAnchor)),
this, SLOT(updateCurHeader(VAnchor)));
connect(m_editor, &VEdit::textChanged, connect(m_editor, &VEdit::textChanged,
this, &VMdTab::handleTextChanged); this, &VMdTab::updateStatus);
connect(m_editor, &VEdit::cursorPositionChanged, connect(m_editor, &VEdit::cursorPositionChanged,
this, &VMdTab::updateStatus); this, &VMdTab::updateStatus);
connect(m_editor, &VEdit::saveAndRead, connect(m_editor, &VEdit::saveAndRead,
@ -355,185 +376,71 @@ void VMdTab::setupMarkdownEditor()
m_stacks->addWidget(m_editor); m_stacks->addWidget(m_editor);
} }
static void parseTocUl(QXmlStreamReader &p_xml, QVector<VHeader> &p_headers, void VMdTab::updateOutlineFromHtml(const QString &p_tocHtml)
int p_level);
static void parseTocLi(QXmlStreamReader &p_xml, QVector<VHeader> &p_headers, int p_level)
{
Q_ASSERT(p_xml.isStartElement() && p_xml.name() == "li");
if (p_xml.readNextStartElement()) {
if (p_xml.name() == "a") {
QString anchor = p_xml.attributes().value("href").toString();
QString name;
if (p_xml.readNext()) {
if (p_xml.tokenString() == "Characters") {
name = p_xml.text().toString();
} else if (!p_xml.isEndElement()) {
qWarning() << "TOC HTML <a> should be ended by </a>" << p_xml.name();
return;
}
VHeader header(p_level, name, anchor, -1, p_headers.size());
p_headers.append(header);
} else {
// Error
return;
}
} else if (p_xml.name() == "ul") {
// Such as header 3 under header 1 directly
VHeader header(p_level, c_emptyHeaderName, "#", -1, p_headers.size());
p_headers.append(header);
parseTocUl(p_xml, p_headers, p_level + 1);
} else {
qWarning() << "TOC HTML <li> should contain <a> or <ul>" << p_xml.name();
return;
}
}
while (p_xml.readNext()) {
if (p_xml.isEndElement()) {
if (p_xml.name() == "li") {
return;
}
continue;
}
if (p_xml.name() == "ul") {
// Nested unordered list
parseTocUl(p_xml, p_headers, p_level + 1);
} else {
return;
}
}
}
static void parseTocUl(QXmlStreamReader &p_xml, QVector<VHeader> &p_headers,
int p_level)
{
Q_ASSERT(p_xml.isStartElement() && p_xml.name() == "ul");
while (p_xml.readNextStartElement()) {
if (p_xml.name() == "li") {
parseTocLi(p_xml, p_headers, p_level);
} else {
qWarning() << "TOC HTML <ul> should contain <li>" << p_xml.name();
break;
}
}
}
static bool parseTocHtml(const QString &p_tocHtml,
QVector<VHeader> &p_headers)
{
if (!p_tocHtml.isEmpty()) {
QXmlStreamReader xml(p_tocHtml);
if (xml.readNextStartElement()) {
if (xml.name() == "ul") {
parseTocUl(xml, p_headers, 1);
} else {
qWarning() << "TOC HTML does not start with <ul>";
}
}
if (xml.hasError()) {
qWarning() << "fail to parse TOC in HTML";
return false;
}
}
return true;
}
void VMdTab::updateTocFromHtml(const QString &p_tocHtml)
{ {
if (m_isEditMode) { if (m_isEditMode) {
return; return;
} }
m_toc.type = VHeaderType::Anchor; m_outline.clear();
m_toc.headers.clear();
if (!parseTocHtml(p_tocHtml, m_toc.headers)) { if (m_outline.parseTableFromHtml(p_tocHtml)) {
return; m_outline.setFile(m_file);
m_outline.setType(VTableOfContentType::Anchor);
} }
m_toc.m_file = m_file; m_currentHeader.reset();
m_toc.valid = true;
emit outlineChanged(m_toc); emit outlineChanged(m_outline);
} }
void VMdTab::updateTocFromHeaders(const QVector<VHeader> &p_headers) void VMdTab::updateOutlineFromHeaders(const QVector<VTableOfContentItem> &p_headers)
{ {
if (!m_isEditMode) { if (!m_isEditMode) {
return; return;
} }
m_toc.type = VHeaderType::LineNumber; m_outline.update(m_file,
m_toc.headers = p_headers; p_headers,
m_toc.m_file = m_file; VTableOfContentType::BlockNumber);
m_toc.valid = true;
// Clear current header. m_currentHeader.reset();
m_curHeader = VAnchor(m_file, "", -1, -1);
emit curHeaderChanged(m_curHeader);
emit outlineChanged(m_toc); emit outlineChanged(m_outline);
} }
void VMdTab::scrollToAnchor(const VAnchor &p_anchor) void VMdTab::scrollToHeader(const VHeaderPointer &p_header)
{ {
if (p_anchor == m_curHeader) { if (m_outline.isMatched(p_header)) {
return; // Scroll only when @p_header is valid.
} scrollToHeaderInternal(p_header);
m_curHeader = p_anchor;
if (m_isEditMode) {
dynamic_cast<VMdEdit *>(getEditor())->scrollToAnchor(p_anchor);
} else {
if (!p_anchor.anchor.isEmpty()) {
m_document->scrollToAnchor(p_anchor.anchor.mid(1));
}
} }
} }
void VMdTab::updateCurHeader(const QString &p_anchor) void VMdTab::updateCurrentHeader(const QString &p_anchor)
{
if (m_isEditMode || m_curHeader.anchor.mid(1) == p_anchor) {
return;
}
m_curHeader = VAnchor(m_file, "#" + p_anchor, -1);
if (!p_anchor.isEmpty()) {
const QVector<VHeader> &headers = m_toc.headers;
for (int i = 0; i < headers.size(); ++i) {
if (headers[i].anchor == m_curHeader.anchor) {
V_ASSERT(headers[i].index == i);
m_curHeader.m_outlineIndex = headers[i].index;
break;
}
}
}
emit curHeaderChanged(m_curHeader);
}
void VMdTab::updateCurHeader(VAnchor p_anchor)
{ {
if (m_isEditMode) { if (m_isEditMode) {
if (!p_anchor.anchor.isEmpty() || p_anchor.lineNumber == m_curHeader.lineNumber) {
return; return;
} }
} else {
if (p_anchor.lineNumber != -1 || p_anchor.anchor == m_curHeader.anchor) { // Find the index of the anchor in outline.
int idx = m_outline.indexOfItemByAnchor(p_anchor);
m_currentHeader.update(m_file, idx);
emit currentHeaderChanged(m_currentHeader);
}
void VMdTab::updateCurrentHeader(int p_blockNumber)
{
if (!m_isEditMode) {
return; return;
} }
}
m_curHeader = p_anchor; // Find the index of the block number in outline.
int idx = m_outline.indexOfItemByBlockNumber(p_blockNumber);
m_currentHeader.update(m_file, idx);
emit curHeaderChanged(m_curHeader); emit currentHeaderChanged(m_currentHeader);
} }
void VMdTab::insertImage() void VMdTab::insertImage()
@ -705,7 +612,7 @@ void VMdTab::requestUpdateVimStatus()
} }
} }
VEditTabInfo VMdTab::fetchTabInfo() VEditTabInfo VMdTab::fetchTabInfo() const
{ {
VEditTabInfo info = VEditTab::fetchTabInfo(); VEditTabInfo info = VEditTab::fetchTabInfo();
@ -716,7 +623,7 @@ VEditTabInfo VMdTab::fetchTabInfo()
info.m_blockCount = m_editor->document()->blockCount(); info.m_blockCount = m_editor->document()->blockCount();
} }
info.m_anchorIndex = m_curHeader.m_outlineIndex; info.m_headerIndex = m_currentHeader.m_index;
return info; return info;
} }
@ -730,15 +637,13 @@ void VMdTab::decorateText(TextDecoration p_decoration)
bool VMdTab::restoreFromTabInfo(const VEditTabInfo &p_info) bool VMdTab::restoreFromTabInfo(const VEditTabInfo &p_info)
{ {
qDebug() << "restoreFromTabInfo" << p_info.m_anchorIndex;
if (p_info.m_editTab != this) { if (p_info.m_editTab != this) {
return false; return false;
} }
// Restore anchor. // Restore header.
int anchorIdx = p_info.m_anchorIndex; VHeaderPointer header(m_file, p_info.m_headerIndex);
bool ret = scrollToAnchor(anchorIdx, true); bool ret = scrollToHeaderInternal(header);
return ret; return ret;
} }
@ -747,5 +652,5 @@ void VMdTab::restoreFromTabInfo()
restoreFromTabInfo(m_infoToRestore); restoreFromTabInfo(m_infoToRestore);
// Clear it anyway. // Clear it anyway.
m_infoToRestore.m_editTab = NULL; m_infoToRestore.clear();
} }

View File

@ -31,8 +31,8 @@ public:
// Save file. // Save file.
bool saveFile() Q_DECL_OVERRIDE; bool saveFile() Q_DECL_OVERRIDE;
// Scroll to anchor @p_anchor. // Scroll to @p_header.
void scrollToAnchor(const VAnchor& p_anchor) Q_DECL_OVERRIDE; void scrollToHeader(const VHeaderPointer &p_header) Q_DECL_OVERRIDE;
void insertImage() Q_DECL_OVERRIDE; void insertImage() Q_DECL_OVERRIDE;
@ -63,27 +63,25 @@ public:
void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE; void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE;
// Create a filled VEditTabInfo. // Create a filled VEditTabInfo.
VEditTabInfo fetchTabInfo() Q_DECL_OVERRIDE; VEditTabInfo fetchTabInfo() const Q_DECL_OVERRIDE;
public slots: public slots:
// Enter edit mode. // Enter edit mode.
void editFile() Q_DECL_OVERRIDE; void editFile() Q_DECL_OVERRIDE;
private slots: private slots:
// Handle text changed in m_editor. // Update m_outline according to @p_tocHtml for read mode.
void handleTextChanged(); void updateOutlineFromHtml(const QString &p_tocHtml);
// Update m_toc according to @p_tocHtml for read mode. // Update m_outline accroding to @p_headers for edit mode.
void updateTocFromHtml(const QString &p_tocHtml); void updateOutlineFromHeaders(const QVector<VTableOfContentItem> &p_headers);
// Update m_toc accroding to @p_headers for edit mode.
void updateTocFromHeaders(const QVector<VHeader> &p_headers);
// Web viewer requests to update current header. // Web viewer requests to update current header.
void updateCurHeader(const QString &p_anchor); // @p_anchor is the anchor of the header, like "toc_1".
void updateCurrentHeader(const QString &p_anchor);
// Editor requests to update current header. // Editor requests to update current header.
void updateCurHeader(VAnchor p_anchor); void updateCurrentHeader(int p_blockNumber);
// Handle key press event in Web view. // Handle key press event in Web view.
void handleWebKeyPressed(int p_key, bool p_ctrl, bool p_shift); void handleWebKeyPressed(int p_key, bool p_ctrl, bool p_shift);
@ -117,16 +115,14 @@ private:
void viewWebByConverter(); void viewWebByConverter();
// Scroll Web view to given header. // Scroll Web view to given header.
// @p_anchorIndex is the index in m_toc.headers.
// @p_strict: if true, scroll only when @p_anchorIndex is valid.
// Return true if scroll was made. // Return true if scroll was made.
bool scrollWebViewToAnchor(int p_anchorIndex, bool p_strict = false); bool scrollWebViewToHeader(const VHeaderPointer &p_header);
bool scrollEditorToHeader(const VHeaderPointer &p_header);
// Scroll web/editor to given header. // Scroll web/editor to given header.
// @p_anchorIndex is the index in m_toc.headers.
// @p_strict: if true, scroll only when @p_anchorIndex is valid.
// Return true if scroll was made. // Return true if scroll was made.
bool scrollToAnchor(int p_anchorIndex, bool p_strict = false); bool scrollToHeaderInternal(const VHeaderPointer &p_header);
// Search text in Web view. // Search text in Web view.
void findTextInWebView(const QString &p_text, uint p_options, bool p_peek, void findTextInWebView(const QString &p_text, uint p_options, bool p_peek,

View File

@ -1,44 +1,39 @@
#include <QDebug>
#include <QVector> #include <QVector>
#include <QString> #include <QString>
#include <QKeyEvent> #include <QKeyEvent>
#include <QLabel> #include <QLabel>
#include <QCoreApplication> #include <QCoreApplication>
#include "voutline.h" #include "voutline.h"
#include "vtoc.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vnote.h" #include "vnote.h"
#include "vfile.h"
extern VNote *g_vnote; extern VNote *g_vnote;
VOutline::VOutline(QWidget *parent) VOutline::VOutline(QWidget *parent)
: QTreeWidget(parent), VNavigationMode() : QTreeWidget(parent),
VNavigationMode(),
m_muted(false)
{ {
setColumnCount(1); setColumnCount(1);
setHeaderHidden(true); setHeaderHidden(true);
setSelectionMode(QAbstractItemView::SingleSelection); setSelectionMode(QAbstractItemView::SingleSelection);
// TODO: jump to the header when user click the same item twice.
connect(this, &VOutline::currentItemChanged, connect(this, &VOutline::currentItemChanged,
this, &VOutline::handleCurItemChanged); this, &VOutline::handleCurrentItemChanged);
} }
void VOutline::checkOutline(const VToc &p_toc) const void VOutline::updateOutline(const VTableOfContent &p_outline)
{ {
const QVector<VHeader> &headers = p_toc.headers; if (p_outline == m_outline) {
return;
for (int i = 0; i < headers.size(); ++i) {
V_ASSERT(headers[i].index == i);
} }
}
void VOutline::updateOutline(const VToc &toc)
{
// Clear current header // Clear current header
curHeader = VAnchor(); m_currentHeader.clear();
checkOutline(toc); m_outline = p_outline;
outline = toc;
updateTreeFromOutline(); updateTreeFromOutline();
@ -49,22 +44,25 @@ void VOutline::updateTreeFromOutline()
{ {
clear(); clear();
if (!outline.valid) { if (m_outline.isEmpty()) {
return; return;
} }
const QVector<VHeader> &headers = outline.headers; const QVector<VTableOfContentItem> &headers = m_outline.getTable();
int idx = 0; int idx = 0;
updateTreeByLevel(headers, idx, NULL, NULL, 1); updateTreeByLevel(headers, idx, NULL, NULL, 1);
} }
void VOutline::updateTreeByLevel(const QVector<VHeader> &headers, int &index, void VOutline::updateTreeByLevel(const QVector<VTableOfContentItem> &headers,
QTreeWidgetItem *parent, QTreeWidgetItem *last, int level) int &index,
QTreeWidgetItem *parent,
QTreeWidgetItem *last,
int level)
{ {
while (index < headers.size()) { while (index < headers.size()) {
const VHeader &header = headers[index]; const VTableOfContentItem &header = headers[index];
QTreeWidgetItem *item; QTreeWidgetItem *item;
if (header.level == level) { if (header.m_level == level) {
if (parent) { if (parent) {
item = new QTreeWidgetItem(parent); item = new QTreeWidgetItem(parent);
} else { } else {
@ -75,7 +73,7 @@ void VOutline::updateTreeByLevel(const QVector<VHeader> &headers, int &index,
last = item; last = item;
++index; ++index;
} else if (header.level < level) { } else if (header.m_level < level) {
return; return;
} else { } else {
updateTreeByLevel(headers, index, last, NULL, level + 1); updateTreeByLevel(headers, index, last, NULL, level + 1);
@ -83,11 +81,11 @@ void VOutline::updateTreeByLevel(const QVector<VHeader> &headers, int &index,
} }
} }
void VOutline::fillItem(QTreeWidgetItem *p_item, const VHeader &p_header) void VOutline::fillItem(QTreeWidgetItem *p_item, const VTableOfContentItem &p_header)
{ {
p_item->setData(0, Qt::UserRole, p_header.index); p_item->setData(0, Qt::UserRole, p_header.m_index);
p_item->setText(0, p_header.name); p_item->setText(0, p_header.m_name);
p_item->setToolTip(0, p_header.name); p_item->setToolTip(0, p_header.m_name);
if (p_header.isEmpty()) { if (p_header.isEmpty()) {
p_item->setForeground(0, QColor("grey")); p_item->setForeground(0, QColor("grey"));
@ -99,127 +97,81 @@ void VOutline::expandTree()
if (topLevelItemCount() == 0) { if (topLevelItemCount() == 0) {
return; return;
} }
expandAll(); expandAll();
} }
void VOutline::handleCurItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem * /*p_preItem*/) void VOutline::handleCurrentItemChanged(QTreeWidgetItem *p_curItem,
QTreeWidgetItem * p_preItem)
{ {
Q_UNUSED(p_preItem);
if (!p_curItem) { if (!p_curItem) {
return; return;
} }
const VHeader *header = getHeaderFromItem(p_curItem); const VTableOfContentItem *header = getHeaderFromItem(p_curItem);
if (!header) { Q_ASSERT(header);
return; m_currentHeader.update(m_outline.getFile(), header->m_index);
}
VAnchor tmp(outline.m_file, header->anchor, header->lineNumber, header->index); if (!header->isEmpty() && !m_muted) {
if (tmp == curHeader) { emit outlineItemActivated(m_currentHeader);
return;
}
curHeader = tmp;
if (!header->isEmpty()) {
emit outlineItemActivated(curHeader);
} }
} }
void VOutline::updateCurHeader(const VAnchor &anchor) void VOutline::updateCurrentHeader(const VHeaderPointer &p_header)
{ {
if (anchor == curHeader) { if (p_header == m_currentHeader
|| !m_outline.isMatched(p_header)) {
return; return;
} }
curHeader = anchor; // Item change should not emit the signal.
if (outline.type == VHeaderType::Anchor) { m_muted = true;
selectAnchor(anchor.anchor); m_currentHeader = p_header;
} else { selectHeader(m_currentHeader);
// Select by lineNumber. m_muted = false;
selectLineNumber(anchor.lineNumber);
}
} }
void VOutline::selectAnchor(const QString &anchor) void VOutline::selectHeader(const VHeaderPointer &p_header)
{ {
setCurrentItem(NULL); setCurrentItem(NULL);
if (anchor.isEmpty()) { if (!m_outline.getItem(p_header)) {
return; return;
} }
int nrTop = topLevelItemCount(); int nrTop = topLevelItemCount();
for (int i = 0; i < nrTop; ++i) { for (int i = 0; i < nrTop; ++i) {
if (selectAnchorOne(topLevelItem(i), anchor)) { if (selectHeaderOne(topLevelItem(i), p_header)) {
return; return;
} }
} }
} }
bool VOutline::selectAnchorOne(QTreeWidgetItem *item, const QString &anchor) bool VOutline::selectHeaderOne(QTreeWidgetItem *p_item, const VHeaderPointer &p_header)
{ {
if (!item) { if (!p_item) {
return false; return false;
} }
const VHeader *header = getHeaderFromItem(item); const VTableOfContentItem *header = getHeaderFromItem(p_item);
if (!header) { if (!header) {
return false; return false;
} }
if (header->anchor == anchor) { if (header->isMatched(p_header)) {
setCurrentItem(item); setCurrentItem(p_item);
return true; return true;
} }
int nrChild = item->childCount(); int nrChild = p_item->childCount();
for (int i = 0; i < nrChild; ++i) { for (int i = 0; i < nrChild; ++i) {
if (selectAnchorOne(item->child(i), anchor)) { if (selectHeaderOne(p_item->child(i), p_header)) {
return true; return true;
} }
} }
return false;
}
void VOutline::selectLineNumber(int lineNumber)
{
setCurrentItem(NULL);
if (lineNumber == -1) {
return;
}
int nrTop = topLevelItemCount();
for (int i = 0; i < nrTop; ++i) {
if (selectLineNumberOne(topLevelItem(i), lineNumber)) {
return;
}
}
}
bool VOutline::selectLineNumberOne(QTreeWidgetItem *item, int lineNumber)
{
if (!item) {
return false;
}
const VHeader *header = getHeaderFromItem(item);
if (!header) {
return false;
}
if (header->lineNumber == lineNumber) {
// Select this item
setCurrentItem(item);
return true;
}
int nrChild = item->childCount();
for (int i = 0; i < nrChild; ++i) {
if (selectLineNumberOne(item->child(i), lineNumber)) {
return true;
}
}
return false; return false;
} }
@ -373,17 +325,8 @@ QList<QTreeWidgetItem *> VOutline::getVisibleChildItems(const QTreeWidgetItem *p
return items; return items;
} }
const VHeader *VOutline::getHeaderFromItem(QTreeWidgetItem *p_item) const const VTableOfContentItem *VOutline::getHeaderFromItem(QTreeWidgetItem *p_item) const
{ {
const VHeader *header = NULL;
int index = p_item->data(0, Qt::UserRole).toInt(); int index = p_item->data(0, Qt::UserRole).toInt();
if (index < 0 || index >= outline.headers.size()) { return m_outline.getItem(index);
return header;
}
header = &(outline.headers[index]);
Q_ASSERT(header->index == index);
return header;
} }

View File

@ -5,11 +5,13 @@
#include <QVector> #include <QVector>
#include <QMap> #include <QMap>
#include <QChar> #include <QChar>
#include "vtoc.h" #include "vtableofcontent.h"
#include "vnavigationmode.h" #include "vnavigationmode.h"
class QLabel; class QLabel;
// Display table of content as a tree and enable user to click an item to
// jump to that header.
class VOutline : public QTreeWidget, public VNavigationMode class VOutline : public QTreeWidget, public VNavigationMode
{ {
Q_OBJECT Q_OBJECT
@ -23,45 +25,60 @@ public:
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE; bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
signals: signals:
void outlineItemActivated(const VAnchor &anchor); // Emit when current item changed by user and header of that item is not empty.
// Do not worry about infinite recursion.
void outlineItemActivated(const VHeaderPointer &p_header);
public slots: public slots:
void updateOutline(const VToc &toc); // Called to update outline and the tree.
void updateCurHeader(const VAnchor &anchor); // Just clear the tree if @p_outline is empty.
void updateOutline(const VTableOfContent &p_outline);
// Called to update current header in the tree.
// Will not emit outlineItemActivated().
void updateCurrentHeader(const VHeaderPointer &p_header);
protected: protected:
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
private slots: private slots:
void handleCurItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem *p_preItem); // Handle current item change even of the tree.
// Do not response if m_muted is true.
void handleCurrentItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem *p_preItem);
private: private:
// Update tree according to outline. // Update tree according to outline.
void updateTreeFromOutline(); void updateTreeFromOutline();
// @index: the index in @headers. // @index: the index in @headers.
void updateTreeByLevel(const QVector<VHeader> &headers, int &index, QTreeWidgetItem *parent, void updateTreeByLevel(const QVector<VTableOfContentItem> &headers,
QTreeWidgetItem *last, int level); int &index,
QTreeWidgetItem *parent,
QTreeWidgetItem *last,
int level);
void expandTree(); void expandTree();
void selectAnchor(const QString &anchor);
bool selectAnchorOne(QTreeWidgetItem *item, const QString &anchor); // Set the item corresponding to @p_header as current item.
void selectLineNumber(int lineNumber); void selectHeader(const VHeaderPointer &p_header);
bool selectLineNumberOne(QTreeWidgetItem *item, int lineNumber);
bool selectHeaderOne(QTreeWidgetItem *p_item, const VHeaderPointer &p_header);
QList<QTreeWidgetItem *> getVisibleItems() const; QList<QTreeWidgetItem *> getVisibleItems() const;
QList<QTreeWidgetItem *> getVisibleChildItems(const QTreeWidgetItem *p_item) const; QList<QTreeWidgetItem *> getVisibleChildItems(const QTreeWidgetItem *p_item) const;
// Fill the info of @p_item. // Fill the info of @p_item.
void fillItem(QTreeWidgetItem *p_item, const VHeader &p_header); void fillItem(QTreeWidgetItem *p_item, const VTableOfContentItem &p_header);
// Check if @p_toc is valid.
void checkOutline(const VToc &p_toc) const;
// Return NULL if no corresponding header in outline. // Return NULL if no corresponding header in outline.
const VHeader *getHeaderFromItem(QTreeWidgetItem *p_item) const; const VTableOfContentItem *getHeaderFromItem(QTreeWidgetItem *p_item) const;
VToc outline; VTableOfContent m_outline;
VAnchor curHeader;
VHeaderPointer m_currentHeader;
// When true, won't emit outlineItemActivated().
bool m_muted;
// Navigation Mode. // Navigation Mode.
// Map second key to QTreeWidgetItem. // Map second key to QTreeWidgetItem.

176
src/vtableofcontent.cpp Normal file
View File

@ -0,0 +1,176 @@
#include "vtableofcontent.h"
#include "vconstants.h"
#include <QXmlStreamReader>
#include <QDebug>
VTableOfContent::VTableOfContent()
: m_file(NULL), m_type(VTableOfContentType::Anchor)
{
}
VTableOfContent::VTableOfContent(const VFile *p_file)
: m_file(p_file), m_type(VTableOfContentType::Anchor)
{
}
void VTableOfContent::update(const VFile *p_file,
const QVector<VTableOfContentItem> &p_table,
VTableOfContentType p_type)
{
m_file = p_file;
m_table = p_table;
m_type = p_type;
}
static bool parseTocUl(QXmlStreamReader &p_xml,
QVector<VTableOfContentItem> &p_table,
int p_level);
static bool parseTocLi(QXmlStreamReader &p_xml,
QVector<VTableOfContentItem> &p_table,
int p_level)
{
Q_ASSERT(p_xml.isStartElement() && p_xml.name() == "li");
if (p_xml.readNextStartElement()) {
if (p_xml.name() == "a") {
QString anchor = p_xml.attributes().value("href").toString().mid(1);
QString name;
if (p_xml.readNext()) {
if (p_xml.tokenString() == "Characters") {
name = p_xml.text().toString();
} else if (!p_xml.isEndElement()) {
qWarning() << "TOC HTML <a> should be ended by </a>" << p_xml.name();
return false;
}
VTableOfContentItem header(name, p_level, anchor, p_table.size());
p_table.append(header);
} else {
// Error
return false;
}
} else if (p_xml.name() == "ul") {
// Such as header 3 under header 1 directly
VTableOfContentItem header(c_emptyHeaderName, p_level, "", p_table.size());
p_table.append(header);
parseTocUl(p_xml, p_table, p_level + 1);
} else {
qWarning() << "TOC HTML <li> should contain <a> or <ul>" << p_xml.name();
return false;
}
}
while (p_xml.readNext()) {
if (p_xml.isEndElement()) {
if (p_xml.name() == "li") {
return true;
}
continue;
}
if (p_xml.name() == "ul") {
// Nested unordered list
if (!parseTocUl(p_xml, p_table, p_level + 1)) {
return false;
}
} else {
return false;
}
}
return true;
}
static bool parseTocUl(QXmlStreamReader &p_xml,
QVector<VTableOfContentItem> &p_table,
int p_level)
{
bool ret = true;
Q_ASSERT(p_xml.isStartElement() && p_xml.name() == "ul");
while (p_xml.readNextStartElement()) {
if (p_xml.name() == "li") {
if (!parseTocLi(p_xml, p_table, p_level)) {
ret = false;
break;
}
} else {
qWarning() << "TOC HTML <ul> should contain <li>" << p_xml.name();
ret = false;
break;
}
}
return ret;
}
bool VTableOfContent::parseTableFromHtml(const QString &p_html)
{
bool ret = true;
m_table.clear();
if (!p_html.isEmpty()) {
QXmlStreamReader xml(p_html);
if (xml.readNextStartElement()) {
if (xml.name() == "ul") {
ret = parseTocUl(xml, m_table, 1);
} else {
qWarning() << "TOC HTML does not start with <ul>" << p_html;
ret = false;
}
}
if (xml.hasError()) {
qWarning() << "fail to parse TOC in HTML" << p_html;
ret = false;
}
}
return ret;
}
int VTableOfContent::indexOfItemByAnchor(const QString &p_anchor) const
{
if (p_anchor.isEmpty()
|| isEmpty()
|| m_type != VTableOfContentType::Anchor) {
return -1;
}
for (int i = 0; i < m_table.size(); ++i) {
if (m_table[i].m_anchor == p_anchor) {
return i;
}
}
return -1;
}
int VTableOfContent::indexOfItemByBlockNumber(int p_blockNumber) const
{
if (p_blockNumber == -1
|| isEmpty()
|| m_type != VTableOfContentType::BlockNumber) {
return -1;
}
for (int i = m_table.size() - 1; i >= 0; --i) {
if (!m_table[i].isEmpty()
&& m_table[i].m_blockNumber <= p_blockNumber) {
return i;
}
}
return -1;
}
bool VTableOfContent::operator==(const VTableOfContent &p_outline) const
{
return m_file == p_outline.getFile()
&& m_type == p_outline.getType()
&& m_table == p_outline.getTable();
}

286
src/vtableofcontent.h Normal file
View File

@ -0,0 +1,286 @@
#ifndef VTABLEOFCONTENT_H
#define VTABLEOFCONTENT_H
#include <QString>
#include <QVector>
class VFile;
struct VHeaderPointer
{
VHeaderPointer()
: m_file(NULL), m_index(-1)
{
}
VHeaderPointer(const VFile *p_file, int p_index)
: m_file(p_file), m_index(p_index)
{
}
bool operator==(const VHeaderPointer &p_header) const
{
return m_file == p_header.m_file
&& m_index == p_header.m_index;
}
void reset()
{
m_index = -1;
}
void clear()
{
m_file = NULL;
reset();
}
void update(const VFile *p_file, int p_index)
{
m_file = p_file;
m_index = p_index;
}
bool isValid() const
{
return m_index > -1;
}
QString toString() const
{
return QString("VHeaderPointer file: %1 index: %2")
.arg((long long)m_file)
.arg(m_index);
}
// The corresponding file.
const VFile *m_file;
// Index of the header item in VTableOfContent which this instance points to.
int m_index;
};
struct VTableOfContentItem
{
VTableOfContentItem()
: m_level(1), m_blockNumber(-1), m_index(-1)
{
}
VTableOfContentItem(const QString &p_name,
int p_level,
const QString &p_anchor,
int p_index)
: m_name(p_name),
m_level(p_level),
m_anchor(p_anchor),
m_blockNumber(-1),
m_index(p_index)
{
}
VTableOfContentItem(const QString &p_name,
int p_level,
int p_blockNumber,
int p_index)
: m_name(p_name),
m_level(p_level),
m_blockNumber(p_blockNumber),
m_index(p_index)
{
}
// Whether it is an empty item.
// An empty item points to nothing.
bool isEmpty() const
{
if (m_anchor.isEmpty()) {
return m_blockNumber == -1;
}
return false;
}
bool isMatched(const VHeaderPointer &p_header) const
{
return m_index == p_header.m_index;
}
bool operator==(const VTableOfContentItem &p_item) const
{
return m_name == p_item.m_name
&& m_level == p_item.m_level
&& m_anchor == p_item.m_anchor
&& m_blockNumber == p_item.m_blockNumber
&& m_index == p_item.m_index;
}
// Name of the item to display.
QString m_name;
// Level of this item, based on 1.
int m_level;
// Use an anchor to identify the position of this item.
QString m_anchor;
// Use block number to identify the position of this item.
// -1 to indicate invalid.
int m_blockNumber;
// Index in VTableOfContent, based on 0.
// -1 for invalid value.
int m_index;
};
enum class VTableOfContentType
{
Anchor = 0,
BlockNumber
};
class VTableOfContent
{
public:
VTableOfContent();
VTableOfContent(const VFile *p_file);
void update(const VFile *p_file,
const QVector<VTableOfContentItem> &p_table,
VTableOfContentType p_type);
// Parse m_table from html.
bool parseTableFromHtml(const QString &p_html);
const VFile *getFile() const;
void setFile(const VFile *p_file);
VTableOfContentType getType() const;
void setType(VTableOfContentType p_type);
void clearTable();
const QVector<VTableOfContentItem> &getTable() const;
void setTable(const QVector<VTableOfContentItem> &p_table);
void clear();
// Return the index in @m_table of @p_anchor.
int indexOfItemByAnchor(const QString &p_anchor) const;
// Return the last index in @m_table which has smaller block number than @p_blockNumber.
int indexOfItemByBlockNumber(int p_blockNumber) const;
const VTableOfContentItem *getItem(int p_idx) const;
const VTableOfContentItem *getItem(const VHeaderPointer &p_header) const;
bool isEmpty() const;
// Whether @p_header is pointing to this outline.
bool isMatched(const VHeaderPointer &p_header) const;
bool operator==(const VTableOfContent &p_outline) const;
QString toString() const;
private:
// Corresponding file.
const VFile *m_file;
// Table of content.
QVector<VTableOfContentItem> m_table;
// Type of the table of content: by anchor or by block number.
VTableOfContentType m_type;
};
inline VTableOfContentType VTableOfContent::getType() const
{
return m_type;
}
inline void VTableOfContent::setType(VTableOfContentType p_type)
{
m_type = p_type;
}
inline void VTableOfContent::clearTable()
{
m_table.clear();
}
inline const QVector<VTableOfContentItem> &VTableOfContent::getTable() const
{
return m_table;
}
inline void VTableOfContent::setTable(const QVector<VTableOfContentItem> &p_table)
{
m_table = p_table;
}
inline void VTableOfContent::clear()
{
m_file = NULL;
m_table.clear();
m_type = VTableOfContentType::Anchor;
}
inline void VTableOfContent::setFile(const VFile *p_file)
{
m_file = p_file;
}
inline const VFile *VTableOfContent::getFile() const
{
return m_file;
}
inline const VTableOfContentItem *VTableOfContent::getItem(int p_idx) const
{
if (!m_file
|| p_idx < 0
|| p_idx >= m_table.size()) {
return NULL;
}
return &m_table[p_idx];
}
inline const VTableOfContentItem *VTableOfContent::getItem(const VHeaderPointer &p_header) const
{
if (p_header.m_file != m_file) {
return NULL;
}
return getItem(p_header.m_index);
}
inline bool VTableOfContent::isEmpty() const
{
return !m_file || m_table.isEmpty();
}
inline bool VTableOfContent::isMatched(const VHeaderPointer &p_header) const
{
return m_file && m_file == p_header.m_file;
}
inline QString VTableOfContent::toString() const
{
return QString("VTableOfContent file: %1 isAnchor: %2 tableSize: %3")
.arg((long long)m_file)
.arg(m_type == VTableOfContentType::Anchor)
.arg(m_table.size());
}
#endif // VTABLEOFCONTENT_H

View File

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

View File

@ -1,78 +0,0 @@
#ifndef VTOC_H
#define VTOC_H
#include <QString>
#include <QVector>
class VFile;
enum VHeaderType
{
Anchor = 0,
LineNumber
};
struct VHeader
{
VHeader() : level(1), lineNumber(-1), index(-1) {}
VHeader(int level, const QString &name, const QString &anchor, int lineNumber, int index)
: level(level), name(name), anchor(anchor), lineNumber(lineNumber), index(index) {}
int level;
QString name;
QString anchor;
int lineNumber;
// Index in the outline, based on 0.
int index;
// Whether it is an empty (fake) header.
bool isEmpty() const
{
if (anchor.isEmpty()) {
return lineNumber == -1;
} else {
return anchor == "#";
}
}
};
struct VAnchor
{
VAnchor() : m_file(NULL), lineNumber(-1), m_outlineIndex(-1) {}
VAnchor(const VFile *file, const QString &anchor, int lineNumber, int outlineIndex = -1)
: m_file(file), anchor(anchor), lineNumber(lineNumber), m_outlineIndex(outlineIndex) {}
// The file this anchor points to.
const VFile *m_file;
// The string anchor. For Web view.
QString anchor;
// The line number anchor. For edit view.
int lineNumber;
// Index of the header for this anchor in VToc outline.
// Used to translate current header between read and edit mode.
int m_outlineIndex;
bool operator==(const VAnchor &p_anchor) const {
return (p_anchor.m_file == m_file
&& p_anchor.anchor == anchor
&& p_anchor.lineNumber == lineNumber
&& p_anchor.m_outlineIndex == m_outlineIndex);
}
};
class VToc
{
public:
VToc();
QVector<VHeader> headers;
int type;
const VFile *m_file;
bool valid;
};
#endif // VTOC_H