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

View File

@ -16,9 +16,15 @@ class VDocument : public QObject
public:
// @p_file could be NULL.
VDocument(const VFile *p_file, QObject *p_parent = 0);
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 setHtml(const QString &html);
// Request to highlight a segment text.
// Use p_id to identify the result.
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.
// Empty @anchor to indicate an invalid header.
// The header does not begins with '#'.
void setHeader(const QString &anchor);
void setLog(const QString &p_log);
@ -49,8 +56,12 @@ public slots:
signals:
void textChanged(const QString &text);
void tocChanged(const QString &toc);
void requestScrollToAnchor(const QString &anchor);
// @anchor is the id of that anchor, without '#'.
void headerChanged(const QString &anchor);
void htmlChanged(const QString &html);
void logChanged(const QString &p_log);

View File

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

View File

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

View File

@ -77,9 +77,9 @@ void VEditArea::insertSplitWindow(int idx)
connect(win, &VEditWindow::getFocused,
this, &VEditArea::handleWindowFocused);
connect(win, &VEditWindow::outlineChanged,
this, &VEditArea::handleOutlineChanged);
connect(win, &VEditWindow::curHeaderChanged,
this, &VEditArea::handleCurHeaderChanged);
this, &VEditArea::handleWindowOutlineChanged);
connect(win, &VEditWindow::currentHeaderChanged,
this, &VEditArea::handleWindowCurrentHeaderChanged);
connect(win, &VEditWindow::statusMessage,
this, &VEditArea::handleWindowStatusMessage);
connect(win, &VEditWindow::vimStatusUpdated,
@ -237,15 +237,13 @@ void VEditArea::updateWindowStatus()
Q_ASSERT(splitter->count() == 0);
emit tabStatusUpdated(VEditTabInfo());
emit outlineChanged(VToc());
emit curHeaderChanged(VAnchor());
emit outlineChanged(VTableOfContent());
emit currentHeaderChanged(VHeaderPointer());
return;
}
VEditWindow *win = getWindow(curWindowIndex);
win->updateTabStatus();
win->requestUpdateOutline();
win->requestUpdateCurHeader();
}
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();
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();
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
getWindow(curWindowIndex)->scrollCurTab(anchor);
VEditWindow *win = getCurrentWindow();
if (win) {
win->scrollToHeader(p_header);
}
}
bool VEditArea::isFileOpened(const VFile *p_file)
@ -648,6 +648,7 @@ VEditWindow *VEditArea::getCurrentWindow() const
if (curWindowIndex < 0) {
return NULL;
}
return getWindow(curWindowIndex);
}

View File

@ -12,7 +12,6 @@
#include <QSplitter>
#include "vnotebook.h"
#include "veditwindow.h"
#include "vtoc.h"
#include "vnavigationmode.h"
class VNote;
@ -82,8 +81,11 @@ signals:
// Emit when current window's tab status updated.
void tabStatusUpdated(const VEditTabInfo &p_info);
void outlineChanged(const VToc &toc);
void curHeaderChanged(const VAnchor &anchor);
// Emit when current window's tab's outline changed.
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.
void statusMessage(const QString &p_msg);
@ -106,7 +108,10 @@ public slots:
void saveFile();
void readFile();
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 handleDirectoryUpdated(const VDirectory *p_dir);
void handleNotebookUpdated(const VNotebook *p_notebook);
@ -118,8 +123,11 @@ private slots:
void handleRemoveSplitRequest(VEditWindow *curWindow);
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 handleFindOptionChanged(uint p_options);
void handleFindNext(const QString &p_text, uint p_options, bool p_forward);

View File

@ -3,12 +3,13 @@
#include <QWheelEvent>
VEditTab::VEditTab(VFile *p_file, VEditArea *p_editArea, QWidget *p_parent)
: QWidget(p_parent), m_file(p_file), m_isEditMode(false),
m_modified(false), m_editArea(p_editArea)
: QWidget(p_parent),
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,
this, &VEditTab::handleFocusChanged);
}
@ -33,7 +34,7 @@ bool VEditTab::isEditMode() const
bool VEditTab::isModified() const
{
return m_modified;
return m_file->isModified();
}
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)
{
QPoint angle = p_event->angleDelta();
@ -78,41 +69,41 @@ void VEditTab::wheelEvent(QWheelEvent *p_event)
p_event->ignore();
}
void VEditTab::updateStatus()
{
m_modified = m_file->isModified();
emit statusUpdated(fetchTabInfo());
}
VEditTabInfo VEditTab::fetchTabInfo()
VEditTabInfo VEditTab::fetchTabInfo() const
{
VEditTabInfo info;
info.m_editTab = this;
info.m_editTab = const_cast<VEditTab *>(this);
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)
{
if (p_info.m_editTab != this) {
// Clear and return.
m_infoToRestore.m_editTab = NULL;
m_infoToRestore.clear();
return;
}
if (restoreFromTabInfo(p_info)) {
// Clear and return.
m_infoToRestore.m_editTab = NULL;
m_infoToRestore.clear();
return;
}
// Save it and restore later.
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 <QString>
#include <QPointer>
#include "vtoc.h"
#include "vtableofcontent.h"
#include "vfile.h"
#include "utils/vvim.h"
#include "vedittabinfo.h"
@ -37,12 +37,9 @@ public:
void focusTab();
virtual void requestUpdateOutline();
virtual void requestUpdateCurHeader();
// Scroll to anchor @p_anchor.
virtual void scrollToAnchor(const VAnchor& p_anchor) = 0;
// Scroll to @p_header.
// Will emit currentHeaderChanged() if @p_header is valid.
virtual void scrollToHeader(const VHeaderPointer &p_header) { Q_UNUSED(p_header) }
VFile *getFile() const;
@ -72,21 +69,23 @@ public:
virtual void decorateText(TextDecoration p_decoration) {Q_UNUSED(p_decoration);}
// 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.
// If this tab is not ready yet, it will restore once it is ready.
void tryRestoreFromTabInfo(const VEditTabInfo &p_info);
// Emit signal to update current status.
virtual void updateStatus();
public slots:
// Enter edit mode
virtual void editFile() = 0;
// Update status of current tab. Emit statusUpdated().
virtual void updateStatus();
protected:
void wheelEvent(QWheelEvent *p_event) Q_DECL_OVERRIDE;
@ -102,10 +101,15 @@ protected:
// File related to this tab.
QPointer<VFile> m_file;
bool m_isEditMode;
bool m_modified;
VToc m_toc;
VAnchor m_curHeader;
// Table of content of this tab.
VTableOfContent m_outline;
// Current header in m_outline of this tab.
VHeaderPointer m_currentHeader;
VEditArea *m_editArea;
// Tab info to restore from once ready.
@ -114,9 +118,9 @@ protected:
signals:
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.
void statusUpdated(const VEditTabInfo &p_info);

View File

@ -10,10 +10,19 @@ struct VEditTabInfo
m_cursorBlockNumber(-1),
m_cursorPositionInBlock(-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;
// Cursor information. -1 for invalid info.
@ -21,8 +30,8 @@ struct VEditTabInfo
int m_cursorPositionInBlock;
int m_blockCount;
// Anchor index in outline.
int m_anchorIndex;
// Header index in outline.
int m_headerIndex;
};
#endif // VEDITTABINFO_H

View File

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

View File

@ -8,7 +8,6 @@
#include <QDir>
#include "vnotebook.h"
#include "vedittab.h"
#include "vtoc.h"
#include "vconstants.h"
#include "vnotefile.h"
@ -32,11 +31,19 @@ public:
void readFile();
void saveAndReadFile();
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
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 updateDirectoryInfo(const VDirectory *p_dir);
void updateNotebookInfo(const VNotebook *p_notebook);
@ -84,8 +91,10 @@ signals:
void requestRemoveSplit(VEditWindow *curWindow);
// This widget or its children get the focus
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.
void statusMessage(const QString &p_msg);
@ -105,8 +114,11 @@ private slots:
void handleCurrentIndexChanged(int p_index);
void contextMenuRequested(QPoint pos);
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 tabbarContextMenuRequested(QPoint p_pos);
void handleLocateAct();

View File

@ -3,13 +3,13 @@
#include <QSettings>
#include "vedittabinfo.h"
#include "vtoc.h"
#include "vtableofcontent.h"
#include "vedittab.h"
VFileSessionInfo::VFileSessionInfo()
: m_mode(OpenFileMode::Read),
m_anchorIndex(-1),
m_headerIndex(-1),
m_cursorBlockNumber(-1),
m_cursorPositionInBlock(-1)
{
@ -19,7 +19,7 @@ VFileSessionInfo::VFileSessionInfo(const QString &p_file,
OpenFileMode p_mode)
: m_file(p_file),
m_mode(p_mode),
m_anchorIndex(-1),
m_headerIndex(-1),
m_cursorBlockNumber(-1),
m_cursorPositionInBlock(-1)
{
@ -32,7 +32,7 @@ VFileSessionInfo VFileSessionInfo::fromEditTabInfo(const VEditTabInfo *p_tabInfo
VEditTab *tab = p_tabInfo->m_editTab;
VFileSessionInfo info(tab->getFile()->fetchPath(),
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_cursorPositionInBlock = p_tabInfo->m_cursorPositionInBlock;
@ -41,7 +41,7 @@ VFileSessionInfo VFileSessionInfo::fromEditTabInfo(const VEditTabInfo *p_tabInfo
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_cursorPositionInBlock = m_cursorPositionInBlock;
}
@ -57,7 +57,7 @@ VFileSessionInfo VFileSessionInfo::fromSettings(const QSettings *p_settings)
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_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_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_cursorPositionInBlock, m_cursorPositionInBlock);
}

View File

@ -12,7 +12,7 @@ namespace FileSessionConfig
static const QString c_mode = "mode";
// 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_cursorPositionInBlock = "cursor_position_in_block";
@ -44,8 +44,8 @@ public:
// Mode of this file in this session.
OpenFileMode m_mode;
// Index in outline of the anchor.
int m_anchorIndex;
// Index in outline of the header.
int m_headerIndex;
// Block number of cursor block.
int m_cursorBlockNumber;

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
#include "vmdeditoperations.h"
#include "vnote.h"
#include "vconfigmanager.h"
#include "vtoc.h"
#include "vtableofcontent.h"
#include "utils/vutils.h"
#include "utils/veditutils.h"
#include "utils/vpreviewutils.h"
@ -35,7 +35,7 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
document());
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
// in this case.
@ -74,7 +74,7 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
this, &VEdit::vimStatusUpdated);
connect(this, &VMdEdit::cursorPositionChanged,
this, &VMdEdit::updateCurHeader);
this, &VMdEdit::updateCurrentHeader);
connect(QApplication::clipboard(), &QClipboard::changed,
this, &VMdEdit::handleClipboardChanged);
@ -111,7 +111,7 @@ void VMdEdit::beginEdit()
setReadOnly(false);
}
updateOutline(m_mdHighlighter->getHeaderRegions());
updateHeaders(m_mdHighlighter->getHeaderRegions());
}
void VMdEdit::endEdit()
@ -345,43 +345,9 @@ void VMdEdit::clearUnusedImages()
m_initImages.clear();
}
int VMdEdit::currentCursorHeader() const
void VMdEdit::updateCurrentHeader()
{
if (m_headers.isEmpty()) {
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));
emit currentHeaderChanged(textCursor().block().blockNumber());
}
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();
QVector<VHeader> headers;
QVector<VTableOfContentItem> headers;
QVector<int> headerBlockNumbers;
QVector<QString> headerSequences;
if (!p_headerRegions.isEmpty()) {
@ -480,8 +446,10 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
if ((block.userState() == HighlightBlockState::Normal) &&
headerReg.exactMatch(block.text())) {
int level = headerReg.cap(1).length();
VHeader header(level, headerReg.cap(2).trimmed(),
"", block.firstLineNumber(), headers.size());
VTableOfContentItem header(headerReg.cap(2).trimmed(),
level,
block.blockNumber(),
headers.size());
headers.append(header);
headerBlockNumbers.append(block.blockNumber());
headerSequences.append(headerReg.cap(3));
@ -506,22 +474,25 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
QRegExp preReg(VUtils::c_headerPrefixRegExp);
int curLevel = baseLevel - 1;
for (int i = 0; i < headers.size(); ++i) {
VHeader &item = headers[i];
while (item.level > curLevel + 1) {
VTableOfContentItem &item = headers[i];
while (item.m_level > curLevel + 1) {
curLevel += 1;
// 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) {
addHeaderSequence(seqs, curLevel, headingSequenceBaseLevel);
}
}
item.index = m_headers.size();
item.m_index = m_headers.size();
m_headers.append(item);
curLevel = item.level;
curLevel = item.m_level;
if (autoSequence) {
addHeaderSequence(seqs, item.level, headingSequenceBaseLevel);
addHeaderSequence(seqs, item.m_level, headingSequenceBaseLevel);
QString seqStr = headerSequenceStr(seqs);
if (headerSequences[i] != seqStr) {
@ -536,39 +507,16 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
emit headersChanged(m_headers);
updateCurHeader();
updateCurrentHeader();
}
void VMdEdit::scrollToAnchor(const VAnchor &p_anchor)
bool VMdEdit::scrollToHeader(int p_blockNumber)
{
if (p_anchor.lineNumber == -1
|| 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;
if (p_blockNumber < 0) {
return false;
}
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 scrollToBlock(p_blockNumber);
}
QString VMdEdit::toPlainTextWithoutImg()
@ -742,9 +690,21 @@ void VMdEdit::resizeEvent(QResizeEvent *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)
@ -754,11 +714,11 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
}
QTextCursor cursor = textCursor();
int cursorLine = cursor.block().firstLineNumber();
int cursorLine = cursor.block().blockNumber();
int targetIdx = -1;
// -1: skip level check.
int targetLevel = 0;
int idx = currentCursorHeader();
int idx = indexOfCurrentHeader();
if (idx == -1) {
// Cursor locates at the beginning, before any headers.
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;
targetIdx >= 0 && targetIdx < m_headers.size();
targetIdx += delta) {
const VHeader &header = m_headers[targetIdx];
const VTableOfContentItem &header = m_headers[targetIdx];
if (header.isEmpty()) {
continue;
}
@ -783,7 +743,7 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
if (targetLevel == 0) {
// The target level has not been init yet.
Q_ASSERT(firstHeader);
targetLevel = header.level;
targetLevel = header.m_level;
if (p_relativeLevel < 0) {
targetLevel += p_relativeLevel;
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
&& (cursorLine == header.lineNumber
&& (cursorLine == header.m_blockNumber
|| p_forward)
&& idx != -1) {
// 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.
break;
}
} else if (header.level < targetLevel) {
} else if (header.m_level < targetLevel) {
// Stop by higher level.
return false;
}
@ -822,9 +782,9 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
}
// Jump to target header.
int line = m_headers[targetIdx].lineNumber;
int line = m_headers[targetIdx].m_blockNumber;
if (line > -1) {
QTextBlock block = document()->findBlockByLineNumber(line);
QTextBlock block = document()->findBlockByNumber(line);
if (block.isValid()) {
cursor.setPosition(block.position());
setTextCursor(cursor);
@ -851,7 +811,7 @@ void VMdEdit::finishOneAsyncJob(int p_idx)
m_freshEdit = false;
emit statusChanged();
updateOutline(m_mdHighlighter->getHeaderRegions());
updateHeaders(m_mdHighlighter->getHeaderRegions());
emit ready();
}

View File

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

View File

@ -11,7 +11,7 @@
#include "vconfigmanager.h"
#include "vmarkdownconverter.h"
#include "vnotebook.h"
#include "vtoc.h"
#include "vtableofcontent.h"
#include "vmdedit.h"
#include "dialog/vfindreplacedialog.h"
#include "veditarea.h"
@ -50,72 +50,109 @@ void VMdTab::setupUI()
setLayout(m_stacks);
}
void VMdTab::handleTextChanged()
{
V_ASSERT(m_file->isModifiable());
if (m_modified) {
return;
}
updateStatus();
}
void VMdTab::showFileReadMode()
{
m_isEditMode = false;
int outlineIndex = m_curHeader.m_outlineIndex;
VHeaderPointer header(m_currentHeader);
if (m_mdConType == MarkdownConverterType::Hoedown) {
viewWebByConverter();
} else {
m_document->updateText();
updateTocFromHtml(m_document->getToc());
updateOutlineFromHtml(m_document->getToc());
}
m_stacks->setCurrentWidget(m_webViewer);
clearSearchedWordHighlight();
scrollWebViewToAnchor(outlineIndex);
scrollWebViewToHeader(header);
updateStatus();
}
bool VMdTab::scrollWebViewToAnchor(int p_anchorIndex, bool p_strict)
bool VMdTab::scrollWebViewToHeader(const VHeaderPointer &p_header)
{
QString anchor;
VAnchor anch(m_file, anchor, -1, p_anchorIndex);
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 (!m_outline.isMatched(p_header)
|| m_outline.getType() != VTableOfContentType::Anchor) {
return false;
}
if (validIndex || !p_strict) {
m_curHeader = anch;
if (p_header.isValid()) {
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;
} else {
return false;
}
}
bool VMdTab::scrollToAnchor(int p_anchorIndex, bool p_strict)
bool VMdTab::scrollToHeaderInternal(const VHeaderPointer &p_header)
{
if (m_isEditMode) {
return dynamic_cast<VMdEdit *>(getEditor())->scrollToAnchor(p_anchorIndex);
return scrollEditorToHeader(p_header);
} else {
return scrollWebViewToAnchor(p_anchorIndex, p_strict);
return scrollWebViewToHeader(p_header);
}
}
@ -127,7 +164,7 @@ void VMdTab::viewWebByConverter()
g_config->getMarkdownExtensions(),
toc);
m_document->setHtml(html);
updateTocFromHtml(toc);
updateOutlineFromHtml(toc);
}
void VMdTab::showFileEditMode()
@ -136,42 +173,28 @@ void VMdTab::showFileEditMode()
return;
}
VHeaderPointer header(m_currentHeader);
m_isEditMode = true;
VMdEdit *mdEdit = dynamic_cast<VMdEdit *>(getEditor());
V_ASSERT(mdEdit);
// beginEdit() may change m_curHeader.
int outlineIndex = m_curHeader.m_outlineIndex;
mdEdit->beginEdit();
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.
// Generally, beginEdit() will generate the headers. Wait is needed when
// highlight completion is going to re-generate the headers.
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";
VUtils::sleepWait(100);
}
if (outlineIndex < 0 || outlineIndex >= headers.size()) {
lineNumber = -1;
outlineIndex = -1;
} else {
lineNumber = headers[outlineIndex].lineNumber;
}
VAnchor anchor(m_file, "", lineNumber, outlineIndex);
mdEdit->scrollToAnchor(anchor);
scrollEditorToHeader(header);
mdEdit->setFocus();
updateStatus();
}
bool VMdTab::closeFile(bool p_forced)
@ -273,8 +296,6 @@ bool VMdTab::saveFile()
m_editor->setModified(true);
}
updateStatus();
return ret;
}
@ -304,9 +325,9 @@ void VMdTab::setupMarkdownViewer()
QWebChannel *channel = new QWebChannel(m_webViewer);
channel->registerObject(QStringLiteral("content"), m_document);
connect(m_document, &VDocument::tocChanged,
this, &VMdTab::updateTocFromHtml);
connect(m_document, SIGNAL(headerChanged(const QString&)),
this, SLOT(updateCurHeader(const QString &)));
this, &VMdTab::updateOutlineFromHtml);
connect(m_document, SIGNAL(headerChanged(const QString &)),
this, SLOT(updateCurrentHeader(const QString &)));
connect(m_document, &VDocument::keyPressed,
this, &VMdTab::handleWebKeyPressed);
connect(m_document, SIGNAL(logicsFinished(void)),
@ -325,13 +346,13 @@ void VMdTab::setupMarkdownEditor()
qDebug() << "create Markdown editor";
m_editor = new VMdEdit(m_file, m_document, m_mdConType, this);
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,
this, &VMdTab::updateStatus);
connect(m_editor, SIGNAL(curHeaderChanged(VAnchor)),
this, SLOT(updateCurHeader(VAnchor)));
connect(m_editor, &VEdit::textChanged,
this, &VMdTab::handleTextChanged);
this, &VMdTab::updateStatus);
connect(m_editor, &VEdit::cursorPositionChanged,
this, &VMdTab::updateStatus);
connect(m_editor, &VEdit::saveAndRead,
@ -355,185 +376,71 @@ void VMdTab::setupMarkdownEditor()
m_stacks->addWidget(m_editor);
}
static void parseTocUl(QXmlStreamReader &p_xml, QVector<VHeader> &p_headers,
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)
void VMdTab::updateOutlineFromHtml(const QString &p_tocHtml)
{
if (m_isEditMode) {
return;
}
m_toc.type = VHeaderType::Anchor;
m_toc.headers.clear();
m_outline.clear();
if (!parseTocHtml(p_tocHtml, m_toc.headers)) {
return;
if (m_outline.parseTableFromHtml(p_tocHtml)) {
m_outline.setFile(m_file);
m_outline.setType(VTableOfContentType::Anchor);
}
m_toc.m_file = m_file;
m_toc.valid = true;
m_currentHeader.reset();
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) {
return;
}
m_toc.type = VHeaderType::LineNumber;
m_toc.headers = p_headers;
m_toc.m_file = m_file;
m_toc.valid = true;
m_outline.update(m_file,
p_headers,
VTableOfContentType::BlockNumber);
// Clear current header.
m_curHeader = VAnchor(m_file, "", -1, -1);
emit curHeaderChanged(m_curHeader);
m_currentHeader.reset();
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)) {
// Scroll only when @p_header is valid.
scrollToHeaderInternal(p_header);
}
}
void VMdTab::updateCurrentHeader(const QString &p_anchor)
{
if (m_isEditMode) {
return;
}
m_curHeader = p_anchor;
// Find the index of the anchor in outline.
int idx = m_outline.indexOfItemByAnchor(p_anchor);
m_currentHeader.update(m_file, idx);
if (m_isEditMode) {
dynamic_cast<VMdEdit *>(getEditor())->scrollToAnchor(p_anchor);
} else {
if (!p_anchor.anchor.isEmpty()) {
m_document->scrollToAnchor(p_anchor.anchor.mid(1));
}
}
emit currentHeaderChanged(m_currentHeader);
}
void VMdTab::updateCurHeader(const QString &p_anchor)
void VMdTab::updateCurrentHeader(int p_blockNumber)
{
if (m_isEditMode || m_curHeader.anchor.mid(1) == p_anchor) {
if (!m_isEditMode) {
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;
}
}
}
// 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);
}
void VMdTab::updateCurHeader(VAnchor p_anchor)
{
if (m_isEditMode) {
if (!p_anchor.anchor.isEmpty() || p_anchor.lineNumber == m_curHeader.lineNumber) {
return;
}
} else {
if (p_anchor.lineNumber != -1 || p_anchor.anchor == m_curHeader.anchor) {
return;
}
}
m_curHeader = p_anchor;
emit curHeaderChanged(m_curHeader);
emit currentHeaderChanged(m_currentHeader);
}
void VMdTab::insertImage()
@ -705,7 +612,7 @@ void VMdTab::requestUpdateVimStatus()
}
}
VEditTabInfo VMdTab::fetchTabInfo()
VEditTabInfo VMdTab::fetchTabInfo() const
{
VEditTabInfo info = VEditTab::fetchTabInfo();
@ -716,7 +623,7 @@ VEditTabInfo VMdTab::fetchTabInfo()
info.m_blockCount = m_editor->document()->blockCount();
}
info.m_anchorIndex = m_curHeader.m_outlineIndex;
info.m_headerIndex = m_currentHeader.m_index;
return info;
}
@ -730,15 +637,13 @@ void VMdTab::decorateText(TextDecoration p_decoration)
bool VMdTab::restoreFromTabInfo(const VEditTabInfo &p_info)
{
qDebug() << "restoreFromTabInfo" << p_info.m_anchorIndex;
if (p_info.m_editTab != this) {
return false;
}
// Restore anchor.
int anchorIdx = p_info.m_anchorIndex;
bool ret = scrollToAnchor(anchorIdx, true);
// Restore header.
VHeaderPointer header(m_file, p_info.m_headerIndex);
bool ret = scrollToHeaderInternal(header);
return ret;
}
@ -747,5 +652,5 @@ void VMdTab::restoreFromTabInfo()
restoreFromTabInfo(m_infoToRestore);
// Clear it anyway.
m_infoToRestore.m_editTab = NULL;
m_infoToRestore.clear();
}

View File

@ -31,8 +31,8 @@ public:
// Save file.
bool saveFile() Q_DECL_OVERRIDE;
// Scroll to anchor @p_anchor.
void scrollToAnchor(const VAnchor& p_anchor) Q_DECL_OVERRIDE;
// Scroll to @p_header.
void scrollToHeader(const VHeaderPointer &p_header) Q_DECL_OVERRIDE;
void insertImage() Q_DECL_OVERRIDE;
@ -63,27 +63,25 @@ public:
void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE;
// Create a filled VEditTabInfo.
VEditTabInfo fetchTabInfo() Q_DECL_OVERRIDE;
VEditTabInfo fetchTabInfo() const Q_DECL_OVERRIDE;
public slots:
// Enter edit mode.
void editFile() Q_DECL_OVERRIDE;
private slots:
// Handle text changed in m_editor.
void handleTextChanged();
// Update m_outline according to @p_tocHtml for read mode.
void updateOutlineFromHtml(const QString &p_tocHtml);
// Update m_toc according to @p_tocHtml for read mode.
void updateTocFromHtml(const QString &p_tocHtml);
// Update m_toc accroding to @p_headers for edit mode.
void updateTocFromHeaders(const QVector<VHeader> &p_headers);
// Update m_outline accroding to @p_headers for edit mode.
void updateOutlineFromHeaders(const QVector<VTableOfContentItem> &p_headers);
// 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.
void updateCurHeader(VAnchor p_anchor);
void updateCurrentHeader(int p_blockNumber);
// Handle key press event in Web view.
void handleWebKeyPressed(int p_key, bool p_ctrl, bool p_shift);
@ -117,16 +115,14 @@ private:
void viewWebByConverter();
// 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.
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.
// @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.
bool scrollToAnchor(int p_anchorIndex, bool p_strict = false);
bool scrollToHeaderInternal(const VHeaderPointer &p_header);
// Search text in Web view.
void findTextInWebView(const QString &p_text, uint p_options, bool p_peek,

View File

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

View File

@ -5,11 +5,13 @@
#include <QVector>
#include <QMap>
#include <QChar>
#include "vtoc.h"
#include "vtableofcontent.h"
#include "vnavigationmode.h"
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
{
Q_OBJECT
@ -23,45 +25,60 @@ public:
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
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:
void updateOutline(const VToc &toc);
void updateCurHeader(const VAnchor &anchor);
// Called to update outline and the tree.
// 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:
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
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:
// Update tree according to outline.
void updateTreeFromOutline();
// @index: the index in @headers.
void updateTreeByLevel(const QVector<VHeader> &headers, int &index, QTreeWidgetItem *parent,
QTreeWidgetItem *last, int level);
void updateTreeByLevel(const QVector<VTableOfContentItem> &headers,
int &index,
QTreeWidgetItem *parent,
QTreeWidgetItem *last,
int level);
void expandTree();
void selectAnchor(const QString &anchor);
bool selectAnchorOne(QTreeWidgetItem *item, const QString &anchor);
void selectLineNumber(int lineNumber);
bool selectLineNumberOne(QTreeWidgetItem *item, int lineNumber);
// Set the item corresponding to @p_header as current item.
void selectHeader(const VHeaderPointer &p_header);
bool selectHeaderOne(QTreeWidgetItem *p_item, const VHeaderPointer &p_header);
QList<QTreeWidgetItem *> getVisibleItems() const;
QList<QTreeWidgetItem *> getVisibleChildItems(const QTreeWidgetItem *p_item) const;
// Fill the info of @p_item.
void fillItem(QTreeWidgetItem *p_item, const VHeader &p_header);
// Check if @p_toc is valid.
void checkOutline(const VToc &p_toc) const;
void fillItem(QTreeWidgetItem *p_item, const VTableOfContentItem &p_header);
// Return NULL if no corresponding header in outline.
const VHeader *getHeaderFromItem(QTreeWidgetItem *p_item) const;
const VTableOfContentItem *getHeaderFromItem(QTreeWidgetItem *p_item) const;
VToc outline;
VAnchor curHeader;
VTableOfContent m_outline;
VHeaderPointer m_currentHeader;
// When true, won't emit outlineItemActivated().
bool m_muted;
// Navigation Mode.
// 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