diff --git a/src/resources/markdown-it.js b/src/resources/markdown-it.js
index 31282867..cee6ac5b 100644
--- a/src/resources/markdown-it.js
+++ b/src/resources/markdown-it.js
@@ -83,85 +83,6 @@ var markdownToHtml = function(markdown, needToc) {
}
};
-// Handle wrong title levels, such as '#' followed by '###'
-var toPerfectToc = function(toc) {
- var i;
- var curLevel = 1;
- var perfToc = [];
- for (i in toc) {
- var item = toc[i];
- while (item.level > curLevel + 1) {
- curLevel += 1;
- var tmp = { level: curLevel,
- anchor: item.anchor,
- title: '[EMPTY]'
- };
- perfToc.push(tmp);
- }
- perfToc.push(item);
- curLevel = item.level;
- }
- return perfToc;
-};
-
-var itemToHtml = function(item) {
- return '' + item.title + '';
-};
-
-// Turn a perfect toc to a tree using
-var tocToTree = function(toc) {
- var i;
- var front = '- ';
- var ending = ['
'];
- var curLevel = 1;
- for (i in toc) {
- var item = toc[i];
- if (item.level == curLevel) {
- front += '';
- front += '- ';
- front += itemToHtml(item);
- } else if (item.level > curLevel) {
- // assert(item.level - curLevel == 1)
- front += '');
- front += '
- ';
- front += itemToHtml(item);
- ending.push('
');
- curLevel = item.level;
- } else {
- while (item.level < curLevel) {
- var ele = ending.pop();
- front += ele;
- if (ele == '
') {
- curLevel--;
- }
- }
- front += '';
- front += '';
- front += itemToHtml(item);
- }
- }
- while (ending.length > 0) {
- front += ending.pop();
- }
- front = front.replace("", "");
- front = '';
- return front;
-};
-
-var handleToc = function(needToc) {
- var tocTree = tocToTree(toPerfectToc(toc));
- content.setToc(tocTree);
-
- // Add it to html
- if (needToc) {
- var eles = document.getElementsByClassName('vnote-toc');
- for (var i = 0; i < eles.length; ++i) {
- eles[i].innerHTML = tocTree;
- }
- }
-};
-
var updateText = function(text) {
var needToc = mdHasTocSection(text);
var html = markdownToHtml(text, needToc);
diff --git a/src/resources/markdown_template.js b/src/resources/markdown_template.js
index 1ea3b30b..c6c1dbdf 100644
--- a/src/resources/markdown_template.js
+++ b/src/resources/markdown_template.js
@@ -41,11 +41,23 @@ new QWebChannel(qt.webChannelTransport,
}
});
+var g_muteScroll = false;
+
var scrollToAnchor = function(anchor) {
+ g_muteScroll = true;
+ if (!anchor) {
+ window.scrollTo(0, 0);
+ g_muteScroll = false;
+ return;
+ }
+
var anc = document.getElementById(anchor);
if (anc != null) {
anc.scrollIntoView();
}
+
+ // Disable scroll temporarily.
+ setTimeout("g_muteScroll = false", 100);
};
window.onwheel = function(e) {
@@ -57,13 +69,19 @@ window.onwheel = function(e) {
}
window.onscroll = function() {
+ if (g_muteScroll) {
+ return;
+ }
+
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset;
var eles = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
if (eles.length == 0) {
+ content.setHeader("");
return;
}
- var curIdx = 0;
+
+ var curIdx = -1;
var biaScrollTop = scrollTop + 50;
for (var i = 0; i < eles.length; ++i) {
if (biaScrollTop >= eles[i].offsetTop) {
@@ -73,10 +91,12 @@ window.onscroll = function() {
}
}
- var curHeader = eles[curIdx].getAttribute("id");
- if (curHeader != null) {
- content.setHeader(curHeader);
+ var curHeader = null;
+ if (curIdx != -1) {
+ curHeader = eles[curIdx].getAttribute("id");
}
+
+ content.setHeader(curHeader ? curHeader : "");
};
document.onkeydown = function(e) {
@@ -326,3 +346,106 @@ var escapeHtml = function(text) {
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
};
+
+// Return the topest level of @toc, starting from 1.
+var baseLevelOfToc = function(p_toc) {
+ var level = -1;
+ for (i in p_toc) {
+ if (level == -1) {
+ level = p_toc[i].level;
+ } else if (level > p_toc[i].level) {
+ level = p_toc[i].level;
+ }
+ }
+
+ if (level == -1) {
+ level = 1;
+ }
+
+ return level;
+};
+
+// Handle wrong title levels, such as '#' followed by '###'
+var toPerfectToc = function(p_toc, p_baseLevel) {
+ var i;
+ var curLevel = p_baseLevel - 1;
+ var perfToc = [];
+ for (i in p_toc) {
+ var item = p_toc[i];
+
+ // Insert empty header.
+ while (item.level > curLevel + 1) {
+ curLevel += 1;
+ var tmp = { level: curLevel,
+ anchor: '',
+ title: '[EMPTY]'
+ };
+ perfToc.push(tmp);
+ }
+
+ perfToc.push(item);
+ curLevel = item.level;
+ }
+
+ return perfToc;
+};
+
+var itemToHtml = function(item) {
+ return '' + item.title + '';
+};
+
+// Turn a perfect toc to a tree using
+var tocToTree = function(p_toc, p_baseLevel) {
+ var i;
+ var front = '- ';
+ var ending = ['
'];
+ var curLevel = p_baseLevel;
+ for (i in p_toc) {
+ var item = p_toc[i];
+ if (item.level == curLevel) {
+ front += '';
+ front += '- ';
+ front += itemToHtml(item);
+ } else if (item.level > curLevel) {
+ // assert(item.level - curLevel == 1)
+ front += '');
+ front += '
- ';
+ front += itemToHtml(item);
+ ending.push('
');
+ curLevel = item.level;
+ } else {
+ while (item.level < curLevel) {
+ var ele = ending.pop();
+ front += ele;
+ if (ele == '
') {
+ curLevel--;
+ }
+ }
+ front += '';
+ front += '';
+ front += itemToHtml(item);
+ }
+ }
+ while (ending.length > 0) {
+ front += ending.pop();
+ }
+ front = front.replace("", "");
+ front = '';
+ return front;
+};
+
+var handleToc = function(needToc) {
+ var baseLevel = baseLevelOfToc(toc);
+ var tocTree = tocToTree(toPerfectToc(toc, baseLevel), baseLevel);
+ content.setToc(tocTree, baseLevel);
+
+ // Add it to html
+ if (needToc) {
+ var eles = document.getElementsByClassName('vnote-toc');
+ for (var i = 0; i < eles.length; ++i) {
+ eles[i].innerHTML = tocTree;
+ }
+ }
+};
+
diff --git a/src/resources/marked.js b/src/resources/marked.js
index 5bdd4e8a..55300c9f 100644
--- a/src/resources/marked.js
+++ b/src/resources/marked.js
@@ -36,85 +36,6 @@ var markdownToHtml = function(markdown, needToc) {
}
};
-// Handle wrong title levels, such as '#' followed by '###'
-var toPerfectToc = function(toc) {
- var i;
- var curLevel = 1;
- var perfToc = [];
- for (i in toc) {
- var item = toc[i];
- while (item.level > curLevel + 1) {
- curLevel += 1;
- var tmp = { level: curLevel,
- anchor: item.anchor,
- title: '[EMPTY]'
- };
- perfToc.push(tmp);
- }
- perfToc.push(item);
- curLevel = item.level;
- }
- return perfToc;
-};
-
-var itemToHtml = function(item) {
- return '' + item.title + '';
-};
-
-// Turn a perfect toc to a tree using
-var tocToTree = function(toc) {
- var i;
- var front = '- ';
- var ending = ['
'];
- var curLevel = 1;
- for (i in toc) {
- var item = toc[i];
- if (item.level == curLevel) {
- front += '';
- front += '- ';
- front += itemToHtml(item);
- } else if (item.level > curLevel) {
- // assert(item.level - curLevel == 1)
- front += '');
- front += '
- ';
- front += itemToHtml(item);
- ending.push('
');
- curLevel = item.level;
- } else {
- while (item.level < curLevel) {
- var ele = ending.pop();
- front += ele;
- if (ele == '
') {
- curLevel--;
- }
- }
- front += '';
- front += '';
- front += itemToHtml(item);
- }
- }
- while (ending.length > 0) {
- front += ending.pop();
- }
- front = front.replace("", "");
- front = '';
- return front;
-};
-
-var handleToc = function(needToc) {
- var tocTree = tocToTree(toPerfectToc(toc));
- content.setToc(tocTree);
-
- // Add it to html
- if (needToc) {
- var eles = document.getElementsByClassName('vnote-toc');
- for (var i = 0; i < eles.length; ++i) {
- eles[i].innerHTML = tocTree;
- }
- }
-};
-
var mdHasTocSection = function(markdown) {
var n = markdown.search(/(\n|^)\[toc\]/i);
return n != -1;
diff --git a/src/resources/showdown.js b/src/resources/showdown.js
index cec52b3e..e05f14ec 100644
--- a/src/resources/showdown.js
+++ b/src/resources/showdown.js
@@ -44,85 +44,6 @@ var markdownToHtml = function(markdown, needToc) {
}
};
-// Handle wrong title levels, such as '#' followed by '###'
-var toPerfectToc = function(toc) {
- var i;
- var curLevel = 1;
- var perfToc = [];
- for (i in toc) {
- var item = toc[i];
- while (item.level > curLevel + 1) {
- curLevel += 1;
- var tmp = { level: curLevel,
- anchor: item.anchor,
- title: '[EMPTY]'
- };
- perfToc.push(tmp);
- }
- perfToc.push(item);
- curLevel = item.level;
- }
- return perfToc;
-};
-
-var itemToHtml = function(item) {
- return '' + item.title + '';
-};
-
-// Turn a perfect toc to a tree using
-var tocToTree = function(toc) {
- var i;
- var front = '- ';
- var ending = ['
'];
- var curLevel = 1;
- for (i in toc) {
- var item = toc[i];
- if (item.level == curLevel) {
- front += '';
- front += '- ';
- front += itemToHtml(item);
- } else if (item.level > curLevel) {
- // assert(item.level - curLevel == 1)
- front += '');
- front += '
- ';
- front += itemToHtml(item);
- ending.push('
');
- curLevel = item.level;
- } else {
- while (item.level < curLevel) {
- var ele = ending.pop();
- front += ele;
- if (ele == '
') {
- curLevel--;
- }
- }
- front += '';
- front += '';
- front += itemToHtml(item);
- }
- }
- while (ending.length > 0) {
- front += ending.pop();
- }
- front = front.replace("", "");
- front = '';
- return front;
-};
-
-var handleToc = function(needToc) {
- var tocTree = tocToTree(toPerfectToc(toc));
- content.setToc(tocTree);
-
- // Add it to html
- if (needToc) {
- var eles = document.getElementsByClassName('vnote-toc');
- for (var i = 0; i < eles.length; ++i) {
- eles[i].innerHTML = tocTree;
- }
- }
-};
-
var mdHasTocSection = function(markdown) {
var n = markdown.search(/(\n|^)\[toc\]/i);
return n != -1;
diff --git a/src/vconstants.h b/src/vconstants.h
index f8ad17b9..6317dceb 100644
--- a/src/vconstants.h
+++ b/src/vconstants.h
@@ -32,4 +32,6 @@ namespace DirConfig
static const QString c_imageFolder = "image_folder";
static const QString c_name = "name";
}
+
+static const QString c_emptyHeaderName = "[EMPTY]";
#endif
diff --git a/src/vdocument.cpp b/src/vdocument.cpp
index 98d4cc23..92ea9d19 100644
--- a/src/vdocument.cpp
+++ b/src/vdocument.cpp
@@ -14,7 +14,7 @@ void VDocument::updateText()
}
}
-void VDocument::setToc(const QString &toc)
+void VDocument::setToc(const QString &toc, int /* baseLevel */)
{
if (toc == m_toc) {
return;
@@ -30,6 +30,8 @@ QString VDocument::getToc()
void VDocument::scrollToAnchor(const QString &anchor)
{
+ m_header = anchor;
+
emit requestScrollToAnchor(anchor);
}
@@ -38,6 +40,7 @@ void VDocument::setHeader(const QString &anchor)
if (anchor == m_header) {
return;
}
+
m_header = anchor;
emit headerChanged(m_header);
}
diff --git a/src/vdocument.h b/src/vdocument.h
index abae4062..40b00bbb 100644
--- a/src/vdocument.h
+++ b/src/vdocument.h
@@ -27,8 +27,16 @@ public:
public slots:
// Will be called in the HTML side
- void setToc(const QString &toc);
+
+ // @toc: the HTML of the TOC.
+ // @baseLevel: the base level of @toc, starting from 1. It is the top level
+ // in the @toc.
+ void setToc(const QString &toc, int baseLevel);
+
+ // When the Web view has been scrolled, it will signal current header anchor.
+ // Empty @anchor to indicate an invalid header.
void setHeader(const QString &anchor);
+
void setLog(const QString &p_log);
void keyPressEvent(int p_key, bool p_ctrl, bool p_shift);
void updateText();
diff --git a/src/vhtmltab.cpp b/src/vhtmltab.cpp
index 85e47f7d..8576684b 100644
--- a/src/vhtmltab.cpp
+++ b/src/vhtmltab.cpp
@@ -195,7 +195,7 @@ void VHtmlTab::discardAndRead()
readFile();
}
-void VHtmlTab::scrollToAnchor(const VAnchor &p_anchor)
+void VHtmlTab::scrollToAnchor(const VAnchor & /* p_anchor */)
{
}
@@ -240,7 +240,7 @@ void VHtmlTab::clearSearchedWordHighlight()
m_editor->clearSearchedWordHighlight();
}
-void VHtmlTab::zoom(bool p_zoomIn, qreal p_step)
+void VHtmlTab::zoom(bool /* p_zoomIn */, qreal /* p_step */)
{
}
diff --git a/src/vmdedit.cpp b/src/vmdedit.cpp
index 6eb7c495..f52e9a77 100644
--- a/src/vmdedit.cpp
+++ b/src/vmdedit.cpp
@@ -244,56 +244,87 @@ void VMdEdit::clearUnusedImages()
void VMdEdit::updateCurHeader()
{
- int curHeader = 0;
- QTextCursor cursor(this->textCursor());
- int curLine = cursor.block().firstLineNumber();
+ if (m_headers.isEmpty()) {
+ return;
+ }
+
+ int curLine = textCursor().block().firstLineNumber();
int i = 0;
for (i = m_headers.size() - 1; i >= 0; --i) {
- if (m_headers[i].lineNumber <= curLine) {
- curHeader = m_headers[i].lineNumber;
- break;
+ if (!m_headers[i].isEmpty()) {
+ if (m_headers[i].lineNumber <= curLine) {
+ break;
+ }
}
}
- emit curHeaderChanged(curHeader, i == -1 ? 0 : i);
+
+ if (i == -1) {
+ emit curHeaderChanged(VAnchor(m_file, "", -1, -1));
+ return;
+ }
+
+ V_ASSERT(m_headers[i].index == i);
+
+ emit curHeaderChanged(VAnchor(m_file, "", m_headers[i].lineNumber, m_headers[i].index));
}
void VMdEdit::generateEditOutline()
{
QTextDocument *doc = document();
+
m_headers.clear();
+
+ QVector headers;
+
// Assume that each block contains only one line
// Only support # syntax for now
QRegExp headerReg("(#{1,6})\\s*(\\S.*)"); // Need to trim the spaces
- int lastLevel = 0;
+ int baseLevel = -1;
for (QTextBlock block = doc->begin(); block != doc->end(); block = block.next()) {
- Q_ASSERT(block.lineCount() == 1);
+ V_ASSERT(block.lineCount() == 1);
if ((block.userState() == HighlightBlockState::Normal) &&
headerReg.exactMatch(block.text())) {
int level = headerReg.cap(1).length();
VHeader header(level, headerReg.cap(2).trimmed(),
- "", block.firstLineNumber());
- while (level > lastLevel + 1) {
- // Insert empty level.
- m_headers.append(VHeader(++lastLevel, "[EMPTY]",
- "", block.firstLineNumber()));
+ "", block.firstLineNumber(), headers.size());
+ headers.append(header);
+
+ if (baseLevel == -1) {
+ baseLevel = level;
+ } else if (baseLevel > level) {
+ baseLevel = level;
}
- m_headers.append(header);
- lastLevel = level;
}
}
+ int curLevel = baseLevel - 1;
+ for (auto & item : headers) {
+ while (item.level > curLevel + 1) {
+ curLevel += 1;
+
+ // Insert empty level which is an invalid header.
+ m_headers.append(VHeader(curLevel, c_emptyHeaderName, "", -1, m_headers.size()));
+ }
+
+ item.index = m_headers.size();
+ m_headers.append(item);
+ curLevel = item.level;
+ }
+
emit headersChanged(m_headers);
+
updateCurHeader();
}
-void VMdEdit::scrollToHeader(int p_headerIndex)
+void VMdEdit::scrollToHeader(const VAnchor &p_anchor)
{
- Q_ASSERT(p_headerIndex >= 0);
- if (p_headerIndex < m_headers.size()) {
- int line = m_headers[p_headerIndex].lineNumber;
- qDebug() << "scroll editor to" << p_headerIndex << "line" << line;
- scrollToLine(line);
+ if (p_anchor.lineNumber == -1
+ || p_anchor.m_outlineIndex < 0
+ || p_anchor.m_outlineIndex >= m_headers.size()) {
+ return;
}
+
+ scrollToLine(p_anchor.lineNumber);
}
QString VMdEdit::toPlainTextWithoutImg() const
@@ -407,3 +438,8 @@ void VMdEdit::resizeEvent(QResizeEvent *p_event)
VEdit::resizeEvent(p_event);
}
+
+const QVector &VMdEdit::getHeaders() const
+{
+ return m_headers;
+}
diff --git a/src/vmdedit.h b/src/vmdedit.h
index f5c61f1b..03a2dc16 100644
--- a/src/vmdedit.h
+++ b/src/vmdedit.h
@@ -32,19 +32,27 @@ public:
// @p_path is the absolute path of the inserted image.
void imageInserted(const QString &p_path);
- // Scroll to m_headers[p_headerIndex].
- void scrollToHeader(int p_headerIndex);
+ void scrollToHeader(const VAnchor &p_anchor);
+
// Like toPlainText(), but remove special blocks containing images.
QString toPlainTextWithoutImg() const;
+ const QVector &getHeaders() const;
+
signals:
void headersChanged(const QVector &headers);
- void curHeaderChanged(int p_lineNumber, int p_outlineIndex);
+
+ // Signal when current header change.
+ void curHeaderChanged(VAnchor p_anchor);
+
void statusChanged();
private slots:
void generateEditOutline();
+
+ // When there is no header in current cursor, will signal an invalid header.
void updateCurHeader();
+
void handleEditStateChanged(KeyState p_state);
void handleSelectionChanged();
void handleClipboardChanged(QClipboard::Mode p_mode);
diff --git a/src/vmdtab.cpp b/src/vmdtab.cpp
index 0385e639..760325a8 100644
--- a/src/vmdtab.cpp
+++ b/src/vmdtab.cpp
@@ -50,8 +50,8 @@ void VMdTab::setupUI()
this, &VMdTab::updateTocFromHeaders);
connect(dynamic_cast(m_editor), &VMdEdit::statusChanged,
this, &VMdTab::noticeStatusChanged);
- connect(m_editor, SIGNAL(curHeaderChanged(int, int)),
- this, SLOT(updateCurHeader(int, int)));
+ connect(m_editor, SIGNAL(curHeaderChanged(VAnchor)),
+ this, SLOT(updateCurHeader(VAnchor)));
connect(m_editor, &VEdit::textChanged,
this, &VMdTab::handleTextChanged);
connect(m_editor, &VEdit::saveAndRead,
@@ -91,6 +91,7 @@ void VMdTab::showFileReadMode()
m_isEditMode = false;
int outlineIndex = m_curHeader.m_outlineIndex;
+
if (m_mdConType == MarkdownConverterType::Hoedown) {
viewWebByConverter();
} else {
@@ -100,6 +101,7 @@ void VMdTab::showFileReadMode()
m_stacks->setCurrentWidget(m_webViewer);
clearSearchedWordHighlight();
+
scrollWebViewToHeader(outlineIndex);
noticeStatusChanged();
@@ -107,14 +109,20 @@ void VMdTab::showFileReadMode()
void VMdTab::scrollWebViewToHeader(int p_outlineIndex)
{
- V_ASSERT(p_outlineIndex >= 0);
+ QString anchor;
- if (p_outlineIndex < m_toc.headers.size()) {
- QString anchor = m_toc.headers[p_outlineIndex].anchor;
- if (!anchor.isEmpty()) {
- m_document->scrollToAnchor(anchor.mid(1));
- }
+ m_curHeader = VAnchor(m_file, anchor, -1, p_outlineIndex);
+
+ if (p_outlineIndex < m_toc.headers.size() && p_outlineIndex >= 0) {
+ QString tmp = m_toc.headers[p_outlineIndex].anchor;
+ V_ASSERT(!tmp.isEmpty());
+ m_curHeader.anchor = tmp;
+ anchor = tmp.mid(1);
}
+
+ m_document->scrollToAnchor(anchor);
+
+ emit curHeaderChanged(m_curHeader);
}
void VMdTab::viewWebByConverter()
@@ -136,14 +144,28 @@ void VMdTab::showFileEditMode()
m_isEditMode = true;
+ VMdEdit *mdEdit = dynamic_cast(m_editor);
+ V_ASSERT(mdEdit);
+
// beginEdit() may change m_curHeader.
int outlineIndex = m_curHeader.m_outlineIndex;
+ int lineNumber = -1;
+ auto headers = mdEdit->getHeaders();
+ if (outlineIndex < 0 || outlineIndex >= headers.size()) {
+ lineNumber = -1;
+ outlineIndex = -1;
+ } else {
+ lineNumber = headers[outlineIndex].lineNumber;
+ }
- m_editor->beginEdit();
- m_stacks->setCurrentWidget(m_editor);
+ VAnchor anchor(m_file, "", lineNumber, outlineIndex);
- dynamic_cast(m_editor)->scrollToHeader(outlineIndex);
- m_editor->setFocus();
+ mdEdit->beginEdit();
+ m_stacks->setCurrentWidget(mdEdit);
+
+ mdEdit->scrollToHeader(anchor);
+
+ mdEdit->setFocus();
noticeStatusChanged();
}
@@ -368,7 +390,7 @@ static void parseTocLi(QXmlStreamReader &p_xml, QVector &p_headers, int
return;
}
- VHeader header(p_level, name, anchor, -1);
+ VHeader header(p_level, name, anchor, -1, p_headers.size());
p_headers.append(header);
} else {
// Error
@@ -376,7 +398,7 @@ static void parseTocLi(QXmlStreamReader &p_xml, QVector &p_headers, int
}
} else if (p_xml.name() == "ul") {
// Such as header 3 under header 1 directly
- VHeader header(p_level, "[EMPTY]", "#", -1);
+ VHeader header(p_level, c_emptyHeaderName, "#", -1, p_headers.size());
p_headers.append(header);
parseTocUl(p_xml, p_headers, p_level + 1);
} else {
@@ -478,10 +500,9 @@ void VMdTab::scrollToAnchor(const VAnchor &p_anchor)
}
m_curHeader = p_anchor;
+
if (m_isEditMode) {
- if (p_anchor.lineNumber > -1) {
- m_editor->scrollToLine(p_anchor.lineNumber);
- }
+ dynamic_cast(m_editor)->scrollToHeader(p_anchor);
} else {
if (!p_anchor.anchor.isEmpty()) {
m_document->scrollToAnchor(p_anchor.anchor.mid(1));
@@ -500,26 +521,31 @@ void VMdTab::updateCurHeader(const QString &p_anchor)
const QVector &headers = m_toc.headers;
for (int i = 0; i < headers.size(); ++i) {
if (headers[i].anchor == m_curHeader.anchor) {
- m_curHeader.m_outlineIndex = i;
+ V_ASSERT(headers[i].index == i);
+ m_curHeader.m_outlineIndex = headers[i].index;
break;
}
}
-
- emit curHeaderChanged(m_curHeader);
}
+
+ emit curHeaderChanged(m_curHeader);
}
-void VMdTab::updateCurHeader(int p_lineNumber, int p_outlineIndex)
+void VMdTab::updateCurHeader(VAnchor p_anchor)
{
- if (!m_isEditMode || m_curHeader.lineNumber == p_lineNumber) {
- return;
+ 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 = VAnchor(m_file, "", p_lineNumber);
- m_curHeader.m_outlineIndex = p_outlineIndex;
- if (p_lineNumber > -1) {
- emit curHeaderChanged(m_curHeader);
- }
+ m_curHeader = p_anchor;
+
+ emit curHeaderChanged(m_curHeader);
}
void VMdTab::insertImage()
diff --git a/src/vmdtab.h b/src/vmdtab.h
index 82d04220..b3050232 100644
--- a/src/vmdtab.h
+++ b/src/vmdtab.h
@@ -76,7 +76,7 @@ private slots:
void updateCurHeader(const QString &p_anchor);
// Editor requests to update current header.
- void updateCurHeader(int p_lineNumber, int p_outlineIndex);
+ void updateCurHeader(VAnchor p_anchor);
// Handle key press event in Web view.
void handleWebKeyPressed(int p_key, bool p_ctrl, bool p_shift);
diff --git a/src/voutline.cpp b/src/voutline.cpp
index 7158424e..212a3d87 100644
--- a/src/voutline.cpp
+++ b/src/voutline.cpp
@@ -1,7 +1,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -9,8 +8,10 @@
#include "vtoc.h"
#include "utils/vutils.h"
#include "vnote.h"
+#include "vconfigmanager.h"
extern VNote *g_vnote;
+extern VConfigManager vconfig;
VOutline::VOutline(QWidget *parent)
: QTreeWidget(parent), VNavigationMode()
@@ -23,23 +24,38 @@ VOutline::VOutline(QWidget *parent)
this, &VOutline::handleCurItemChanged);
}
+void VOutline::checkOutline(const VToc &p_toc) const
+{
+ const QVector &headers = p_toc.headers;
+
+ for (int i = 0; i < headers.size(); ++i) {
+ V_ASSERT(headers[i].index == i);
+ }
+}
+
void VOutline::updateOutline(const VToc &toc)
{
// Clear current header
curHeader = VAnchor();
+
+ checkOutline(toc);
+
outline = toc;
- updateTreeFromOutline(outline);
+
+ updateTreeFromOutline();
+
expandTree();
}
-void VOutline::updateTreeFromOutline(const VToc &toc)
+void VOutline::updateTreeFromOutline()
{
clear();
- if (!toc.valid) {
+ if (!outline.valid) {
return;
}
- const QVector &headers = toc.headers;
+
+ const QVector &headers = outline.headers;
int idx = 0;
updateTreeByLevel(headers, idx, NULL, NULL, 1);
}
@@ -56,13 +72,8 @@ void VOutline::updateTreeByLevel(const QVector &headers, int &index,
} else {
item = new QTreeWidgetItem(this);
}
- QJsonObject itemJson;
- itemJson["anchor"] = header.anchor;
- itemJson["line_number"] = header.lineNumber;
- itemJson["outline_index"] = index;
- item->setData(0, Qt::UserRole, itemJson);
- item->setText(0, header.name);
- item->setToolTip(0, header.name);
+
+ fillItem(item, header);
last = item;
++index;
@@ -74,6 +85,17 @@ void VOutline::updateTreeByLevel(const QVector &headers, int &index,
}
}
+void VOutline::fillItem(QTreeWidgetItem *p_item, const VHeader &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);
+
+ if (p_header.isEmpty()) {
+ p_item->setForeground(0, QColor("grey"));
+ }
+}
+
void VOutline::expandTree()
{
if (topLevelItemCount() == 0) {
@@ -87,20 +109,22 @@ void VOutline::handleCurItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem
if (!p_curItem) {
return;
}
- QJsonObject itemJson = p_curItem->data(0, Qt::UserRole).toJsonObject();
- QString anchor = itemJson["anchor"].toString();
- int lineNumber = itemJson["line_number"].toInt();
- int outlineIndex = itemJson["outline_index"].toInt();
- VAnchor tmp;
- tmp.m_file = outline.m_file;
- tmp.anchor = anchor;
- tmp.lineNumber = lineNumber;
- tmp.m_outlineIndex = outlineIndex;
+
+ const VHeader *header = getHeaderFromItem(p_curItem);
+ if (!header) {
+ return;
+ }
+
+ VAnchor tmp(outline.m_file, header->anchor, header->lineNumber, header->index);
if (tmp == curHeader) {
return;
}
+
curHeader = tmp;
- emit outlineItemActivated(curHeader);
+
+ if (!header->isEmpty()) {
+ emit outlineItemActivated(curHeader);
+ }
}
void VOutline::updateCurHeader(const VAnchor &anchor)
@@ -108,17 +132,24 @@ void VOutline::updateCurHeader(const VAnchor &anchor)
if (anchor == curHeader) {
return;
}
+
curHeader = anchor;
if (outline.type == VHeaderType::Anchor) {
selectAnchor(anchor.anchor);
} else {
- // Select by lineNumber
+ // Select by lineNumber.
selectLineNumber(anchor.lineNumber);
}
}
void VOutline::selectAnchor(const QString &anchor)
{
+ setCurrentItem(NULL);
+
+ if (anchor.isEmpty()) {
+ return;
+ }
+
int nrTop = topLevelItemCount();
for (int i = 0; i < nrTop; ++i) {
if (selectAnchorOne(topLevelItem(i), anchor)) {
@@ -132,9 +163,13 @@ bool VOutline::selectAnchorOne(QTreeWidgetItem *item, const QString &anchor)
if (!item) {
return false;
}
- QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject();
- QString itemAnchor = itemJson["anchor"].toString();
- if (itemAnchor == anchor) {
+
+ const VHeader *header = getHeaderFromItem(item);
+ if (!header) {
+ return false;
+ }
+
+ if (header->anchor == anchor) {
setCurrentItem(item);
return true;
}
@@ -150,6 +185,12 @@ bool VOutline::selectAnchorOne(QTreeWidgetItem *item, const QString &anchor)
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)) {
@@ -163,9 +204,13 @@ bool VOutline::selectLineNumberOne(QTreeWidgetItem *item, int lineNumber)
if (!item) {
return false;
}
- QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject();
- int itemLineNum = itemJson["line_number"].toInt();
- if (itemLineNum == lineNumber) {
+
+ const VHeader *header = getHeaderFromItem(item);
+ if (!header) {
+ return false;
+ }
+
+ if (header->lineNumber == lineNumber) {
// Select this item
setCurrentItem(item);
return true;
@@ -329,3 +374,18 @@ QList VOutline::getVisibleChildItems(const QTreeWidgetItem *p
}
return items;
}
+
+const VHeader *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;
+}
diff --git a/src/voutline.h b/src/voutline.h
index 04c4f5dc..6867e1c0 100644
--- a/src/voutline.h
+++ b/src/voutline.h
@@ -36,9 +36,13 @@ private slots:
void handleCurItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem *p_preItem);
private:
- void updateTreeFromOutline(const VToc &toc);
+ // Update tree according to outline.
+ void updateTreeFromOutline();
+
+ // @index: the index in @headers.
void updateTreeByLevel(const QVector &headers, int &index, QTreeWidgetItem *parent,
QTreeWidgetItem *last, int level);
+
void expandTree();
void selectAnchor(const QString &anchor);
bool selectAnchorOne(QTreeWidgetItem *item, const QString &anchor);
@@ -47,6 +51,15 @@ private:
QList getVisibleItems() const;
QList 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;
+
+ // Return NULL if no corresponding header in outline.
+ const VHeader *getHeaderFromItem(QTreeWidgetItem *p_item) const;
+
VToc outline;
VAnchor curHeader;
diff --git a/src/vtoc.h b/src/vtoc.h
index 8b0227c3..36f66eef 100644
--- a/src/vtoc.h
+++ b/src/vtoc.h
@@ -14,30 +14,53 @@ enum VHeaderType
struct VHeader
{
- VHeader() : level(1), lineNumber(-1) {}
- VHeader(int level, const QString &name, const QString &anchor, int lineNumber)
- : level(level), name(name), anchor(anchor), lineNumber(lineNumber) {}
+ 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() : lineNumber(-1), m_outlineIndex(0) {}
- VAnchor(const VFile *file, const QString &anchor, int lineNumber)
- : m_file(file), anchor(anchor), lineNumber(lineNumber), m_outlineIndex(0) {}
+ 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 this anchor in VToc outline.
+
+ // 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.lineNumber == lineNumber
+ && p_anchor.m_outlineIndex == m_outlineIndex);
}
};