mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
refactor outline logics
This commit is contained in:
parent
598e8144bb
commit
183b24915a
@ -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 \
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
return;
|
||||
// Only propagate it if it is current tab.
|
||||
VEditTab *tab = getCurrentTab();
|
||||
if (tab) {
|
||||
if (tab->getFile() == p_outline.getFile()) {
|
||||
emit outlineChanged(p_outline);
|
||||
}
|
||||
const VFile *file = getTab(idx)->getFile();
|
||||
if (p_toc.m_file == file) {
|
||||
emit outlineChanged(p_toc);
|
||||
} else {
|
||||
emit outlineChanged(VTableOfContent());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
return;
|
||||
// Only propagate it if it is current tab.
|
||||
VEditTab *tab = getCurrentTab();
|
||||
if (tab) {
|
||||
if (tab->getFile() == p_header.m_file) {
|
||||
emit currentHeaderChanged(p_header);
|
||||
}
|
||||
const VFile *file = getTab(idx)->getFile();
|
||||
if (p_anchor.m_file == file) {
|
||||
emit curHeaderChanged(p_anchor);
|
||||
} else {
|
||||
emit currentHeaderChanged(VHeaderPointer());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
{
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
142
src/vmdedit.cpp
142
src/vmdedit.cpp
@ -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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_blockNumber < 0) {
|
||||
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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
339
src/vmdtab.cpp
339
src/vmdtab.cpp
@ -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) {
|
||||
return;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
if (m_outline.isMatched(p_header)) {
|
||||
// Scroll only when @p_header is valid.
|
||||
scrollToHeaderInternal(p_header);
|
||||
}
|
||||
}
|
||||
|
||||
void VMdTab::updateCurHeader(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)
|
||||
void VMdTab::updateCurrentHeader(const QString &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) {
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
@ -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();
|
||||
}
|
||||
|
32
src/vmdtab.h
32
src/vmdtab.h
@ -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,
|
||||
|
167
src/voutline.cpp
167
src/voutline.cpp
@ -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);
|
||||
}
|
||||
|
@ -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
176
src/vtableofcontent.cpp
Normal 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
286
src/vtableofcontent.h
Normal 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
|
@ -1,6 +0,0 @@
|
||||
#include "vtoc.h"
|
||||
|
||||
VToc::VToc()
|
||||
: type(VHeaderType::Anchor), valid(false)
|
||||
{
|
||||
}
|
78
src/vtoc.h
78
src/vtoc.h
@ -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
|
Loading…
x
Reference in New Issue
Block a user