mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-04 21:39:52 +08:00
editor: auto format table
This commit is contained in:
parent
bcb6adef30
commit
70caa4d932
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
||||
/VNote.pro.user
|
||||
VNote.pro.user
|
||||
|
@ -1,5 +1,5 @@
|
||||
# VNote
|
||||
- [英文 English](./README.md)
|
||||
[英文 English](./README.md)
|
||||
|
||||
**VNote是一个更懂程序员和Markdown的笔记!**
|
||||
|
||||
|
@ -121,6 +121,40 @@ struct VMathjaxBlock
|
||||
};
|
||||
|
||||
|
||||
struct VTableBlock
|
||||
{
|
||||
VTableBlock()
|
||||
: m_startPos(-1),
|
||||
m_endPos(-1)
|
||||
{
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return m_startPos > -1 && m_endPos >= m_startPos;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_startPos = m_endPos = -1;
|
||||
m_borders.clear();
|
||||
}
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return QString("table [%1,%2) borders %3").arg(m_startPos)
|
||||
.arg(m_endPos)
|
||||
.arg(m_borders.size());
|
||||
}
|
||||
|
||||
int m_startPos;
|
||||
int m_endPos;
|
||||
|
||||
// Global position of the table borders in ascending order.
|
||||
QVector<int> m_borders;
|
||||
};
|
||||
|
||||
|
||||
// Highlight unit with global position and string style name.
|
||||
struct HLUnitPos
|
||||
{
|
||||
@ -157,6 +191,11 @@ struct VElementRegion
|
||||
return m_startPos <= p_pos && m_endPos > p_pos;
|
||||
}
|
||||
|
||||
bool contains(const VElementRegion &p_reg) const
|
||||
{
|
||||
return m_startPos <= p_reg.m_startPos && m_endPos >= p_reg.m_endPos;
|
||||
}
|
||||
|
||||
bool intersect(int p_start, int p_end) const
|
||||
{
|
||||
return !(p_end <= m_startPos || p_start >= m_endPos);
|
||||
|
@ -51,6 +51,8 @@ PegHighlighterResult::PegHighlighterResult(const PegMarkdownHighlighter *p_peg,
|
||||
parseMathjaxBlocks(p_peg, p_result);
|
||||
|
||||
parseHRuleBlocks(p_peg, p_result);
|
||||
|
||||
parseTableBlocks(p_peg, p_result);
|
||||
}
|
||||
|
||||
static bool compHLUnit(const HLUnit &p_a, const HLUnit &p_b)
|
||||
@ -270,6 +272,66 @@ void PegHighlighterResult::parseFencedCodeBlocks(const PegMarkdownHighlighter *p
|
||||
}
|
||||
}
|
||||
|
||||
void PegHighlighterResult::parseTableBlocks(const PegMarkdownHighlighter *p_peg,
|
||||
const QSharedPointer<PegParseResult> &p_result)
|
||||
{
|
||||
const QVector<VElementRegion> &tableRegs = p_result->m_tableRegions;
|
||||
const QVector<VElementRegion> &headerRegs = p_result->m_tableHeaderRegions;
|
||||
const QVector<VElementRegion> &borderRegs = p_result->m_tableBorderRegions;
|
||||
|
||||
VTableBlock item;
|
||||
int headerIdx = 0, borderIdx = 0;
|
||||
for (int tableIdx = 0; tableIdx < tableRegs.size(); ++tableIdx) {
|
||||
const auto ® = tableRegs[tableIdx];
|
||||
if (headerIdx < headerRegs.size()) {
|
||||
if (reg.contains(headerRegs[headerIdx])) {
|
||||
// A new table.
|
||||
if (item.isValid()) {
|
||||
// Save previous table.
|
||||
m_tableBlocks.append(item);
|
||||
|
||||
auto &table = m_tableBlocks.back();
|
||||
// Fill borders.
|
||||
for (; borderIdx < borderRegs.size(); ++borderIdx) {
|
||||
if (borderRegs[borderIdx].m_startPos >= table.m_startPos
|
||||
&& borderRegs[borderIdx].m_endPos <= table.m_endPos) {
|
||||
table.m_borders.append(borderRegs[borderIdx].m_startPos);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item.clear();
|
||||
item.m_startPos = reg.m_startPos;
|
||||
item.m_endPos = reg.m_endPos;
|
||||
|
||||
++headerIdx;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue previous table.
|
||||
item.m_endPos = reg.m_endPos;
|
||||
}
|
||||
|
||||
if (item.isValid()) {
|
||||
// Another table.
|
||||
m_tableBlocks.append(item);
|
||||
|
||||
// Fill borders.
|
||||
auto &table = m_tableBlocks.back();
|
||||
for (; borderIdx < borderRegs.size(); ++borderIdx) {
|
||||
if (borderRegs[borderIdx].m_startPos >= table.m_startPos
|
||||
&& borderRegs[borderIdx].m_endPos <= table.m_endPos) {
|
||||
table.m_borders.append(borderRegs[borderIdx].m_startPos);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool isDisplayFormulaRawEnd(const QString &p_text)
|
||||
{
|
||||
QRegExp regex("\\\\end\\{[^{}\\s\\r\\n]+\\}$");
|
||||
@ -330,8 +392,8 @@ void PegHighlighterResult::parseMathjaxBlocks(const PegMarkdownHighlighter *p_pe
|
||||
break;
|
||||
}
|
||||
|
||||
int pib = r.m_startPos - block.position();
|
||||
int length = r.m_endPos - r.m_startPos;
|
||||
int pib = qMax(r.m_startPos - block.position(), 0);
|
||||
int length = qMin(r.m_endPos - block.position() - pib, block.length() - 1);
|
||||
QString text = block.text().mid(pib, length);
|
||||
if (inBlock) {
|
||||
item.m_text = item.m_text + "\n" + text;
|
||||
|
@ -88,6 +88,10 @@ public:
|
||||
|
||||
QSet<int> m_hruleBlocks;
|
||||
|
||||
// All table blocks.
|
||||
// Sorted by start position ascendingly.
|
||||
QVector<VTableBlock> m_tableBlocks;
|
||||
|
||||
private:
|
||||
// Parse highlight elements for blocks from one parse result.
|
||||
static void parseBlocksHighlightOne(QVector<QVector<HLUnit>> &p_blocksHighlights,
|
||||
@ -108,6 +112,10 @@ private:
|
||||
void parseHRuleBlocks(const PegMarkdownHighlighter *p_peg,
|
||||
const QSharedPointer<PegParseResult> &p_result);
|
||||
|
||||
// Parse table blocks from parse results.
|
||||
void parseTableBlocks(const PegMarkdownHighlighter *p_peg,
|
||||
const QSharedPointer<PegParseResult> &p_result);
|
||||
|
||||
#if 0
|
||||
void parseBlocksElementRegionOne(QHash<int, QVector<VElementRegion>> &p_regs,
|
||||
const QTextDocument *p_doc,
|
||||
|
@ -203,7 +203,7 @@ static bool containSpecialChar(const QString &p_str)
|
||||
QChar la = p_str[p_str.size() - 1];
|
||||
|
||||
return fi == '#'
|
||||
|| la == '`' || la == '$' || la == '*' || la == '_';
|
||||
|| la == '`' || la == '$' || la == '~' || la == '*' || la == '_';
|
||||
}
|
||||
|
||||
bool PegMarkdownHighlighter::preHighlightSingleFormatBlock(const QVector<QVector<HLUnit>> &p_highlights,
|
||||
@ -757,6 +757,8 @@ void PegMarkdownHighlighter::completeHighlight(QSharedPointer<PegHighlighterResu
|
||||
emit mathjaxBlocksUpdated(p_result->m_mathjaxBlocks);
|
||||
}
|
||||
|
||||
emit tableBlocksUpdated(p_result->m_tableBlocks);
|
||||
|
||||
emit imageLinksUpdated(p_result->m_imageRegions);
|
||||
emit headersUpdated(p_result->m_headerRegions);
|
||||
}
|
||||
|
@ -70,6 +70,9 @@ signals:
|
||||
// Emitted when Mathjax blocks updated.
|
||||
void mathjaxBlocksUpdated(const QVector<VMathjaxBlock> &p_mathjaxBlocks);
|
||||
|
||||
// Emitted when table blocks updated.
|
||||
void tableBlocksUpdated(const QVector<VTableBlock> &p_tableBlocks);
|
||||
|
||||
protected:
|
||||
void highlightBlock(const QString &p_text) Q_DECL_OVERRIDE;
|
||||
|
||||
|
@ -25,30 +25,20 @@ void PegParseResult::parse(QAtomicInt &p_stop, bool p_fast)
|
||||
parseDisplayFormulaRegions(p_stop);
|
||||
|
||||
parseHRuleRegions(p_stop);
|
||||
|
||||
parseTableRegions(p_stop);
|
||||
|
||||
parseTableHeaderRegions(p_stop);
|
||||
|
||||
parseTableBorderRegions(p_stop);
|
||||
}
|
||||
|
||||
void PegParseResult::parseImageRegions(QAtomicInt &p_stop)
|
||||
{
|
||||
// From Qt5.7, the capacity is preserved.
|
||||
m_imageRegions.clear();
|
||||
if (isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pmh_element *elem = m_pmhElements[pmh_IMAGE];
|
||||
while (elem != NULL) {
|
||||
if (elem->end <= elem->pos) {
|
||||
elem = elem->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p_stop.load() == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_imageRegions.push_back(VElementRegion(m_offset + elem->pos, m_offset + elem->end));
|
||||
elem = elem->next;
|
||||
}
|
||||
parseRegions(p_stop,
|
||||
pmh_IMAGE,
|
||||
m_imageRegions,
|
||||
false);
|
||||
}
|
||||
|
||||
void PegParseResult::parseHeaderRegions(QAtomicInt &p_stop)
|
||||
@ -113,64 +103,63 @@ void PegParseResult::parseFencedCodeBlockRegions(QAtomicInt &p_stop)
|
||||
|
||||
void PegParseResult::parseInlineEquationRegions(QAtomicInt &p_stop)
|
||||
{
|
||||
m_inlineEquationRegions.clear();
|
||||
if (isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pmh_element *elem = m_pmhElements[pmh_INLINEEQUATION];
|
||||
while (elem != NULL) {
|
||||
if (elem->end <= elem->pos) {
|
||||
elem = elem->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p_stop.load() == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_inlineEquationRegions.push_back(VElementRegion(m_offset + elem->pos, m_offset + elem->end));
|
||||
elem = elem->next;
|
||||
}
|
||||
parseRegions(p_stop,
|
||||
pmh_INLINEEQUATION,
|
||||
m_inlineEquationRegions,
|
||||
false);
|
||||
}
|
||||
|
||||
void PegParseResult::parseDisplayFormulaRegions(QAtomicInt &p_stop)
|
||||
{
|
||||
m_displayFormulaRegions.clear();
|
||||
if (isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pmh_element *elem = m_pmhElements[pmh_DISPLAYFORMULA];
|
||||
while (elem != NULL) {
|
||||
if (elem->end <= elem->pos) {
|
||||
elem = elem->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p_stop.load() == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_displayFormulaRegions.push_back(VElementRegion(m_offset + elem->pos, m_offset + elem->end));
|
||||
elem = elem->next;
|
||||
}
|
||||
|
||||
if (p_stop.load() == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(m_displayFormulaRegions.begin(), m_displayFormulaRegions.end());
|
||||
parseRegions(p_stop,
|
||||
pmh_DISPLAYFORMULA,
|
||||
m_displayFormulaRegions,
|
||||
true);
|
||||
}
|
||||
|
||||
void PegParseResult::parseHRuleRegions(QAtomicInt &p_stop)
|
||||
{
|
||||
m_hruleRegions.clear();
|
||||
parseRegions(p_stop,
|
||||
pmh_HRULE,
|
||||
m_hruleRegions,
|
||||
false);
|
||||
}
|
||||
|
||||
void PegParseResult::parseTableRegions(QAtomicInt &p_stop)
|
||||
{
|
||||
parseRegions(p_stop,
|
||||
pmh_TABLE,
|
||||
m_tableRegions,
|
||||
true);
|
||||
}
|
||||
|
||||
void PegParseResult::parseTableHeaderRegions(QAtomicInt &p_stop)
|
||||
{
|
||||
parseRegions(p_stop,
|
||||
pmh_TABLEHEADER,
|
||||
m_tableHeaderRegions,
|
||||
true);
|
||||
}
|
||||
|
||||
void PegParseResult::parseTableBorderRegions(QAtomicInt &p_stop)
|
||||
{
|
||||
parseRegions(p_stop,
|
||||
pmh_TABLEBORDER,
|
||||
m_tableBorderRegions,
|
||||
true);
|
||||
}
|
||||
|
||||
void PegParseResult::parseRegions(QAtomicInt &p_stop,
|
||||
pmh_element_type p_type,
|
||||
QVector<VElementRegion> &p_result,
|
||||
bool p_sort)
|
||||
{
|
||||
p_result.clear();
|
||||
if (isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pmh_element *elem = m_pmhElements[pmh_HRULE];
|
||||
pmh_element *elem = m_pmhElements[p_type];
|
||||
while (elem != NULL) {
|
||||
if (elem->end <= elem->pos) {
|
||||
elem = elem->next;
|
||||
@ -181,9 +170,13 @@ void PegParseResult::parseHRuleRegions(QAtomicInt &p_stop)
|
||||
return;
|
||||
}
|
||||
|
||||
m_hruleRegions.push_back(VElementRegion(m_offset + elem->pos, m_offset + elem->end));
|
||||
p_result.push_back(VElementRegion(m_offset + elem->pos, m_offset + elem->end));
|
||||
elem = elem->next;
|
||||
}
|
||||
|
||||
if (p_sort && p_stop.load() != 1) {
|
||||
std::sort(p_result.begin(), p_result.end());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -114,6 +114,16 @@ struct PegParseResult
|
||||
// HRule regions.
|
||||
QVector<VElementRegion> m_hruleRegions;
|
||||
|
||||
// All table regions.
|
||||
// Sorted by start position.
|
||||
QVector<VElementRegion> m_tableRegions;
|
||||
|
||||
// All table header regions.
|
||||
QVector<VElementRegion> m_tableHeaderRegions;
|
||||
|
||||
// All table border regions.
|
||||
QVector<VElementRegion> m_tableBorderRegions;
|
||||
|
||||
private:
|
||||
void parseImageRegions(QAtomicInt &p_stop);
|
||||
|
||||
@ -126,6 +136,17 @@ private:
|
||||
void parseDisplayFormulaRegions(QAtomicInt &p_stop);
|
||||
|
||||
void parseHRuleRegions(QAtomicInt &p_stop);
|
||||
|
||||
void parseTableRegions(QAtomicInt &p_stop);
|
||||
|
||||
void parseTableHeaderRegions(QAtomicInt &p_stop);
|
||||
|
||||
void parseTableBorderRegions(QAtomicInt &p_stop);
|
||||
|
||||
void parseRegions(QAtomicInt &p_stop,
|
||||
pmh_element_type p_type,
|
||||
QVector<VElementRegion> &p_result,
|
||||
bool p_sort = false);
|
||||
};
|
||||
|
||||
class PegParserWorker : public QThread
|
||||
|
@ -143,6 +143,7 @@ hr {
|
||||
|
||||
table {
|
||||
padding: 0;
|
||||
margin: 1rem 0.5rem;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
|
@ -141,6 +141,7 @@ hr {
|
||||
|
||||
table {
|
||||
padding: 0;
|
||||
margin: 1rem 0.5rem;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
|
@ -136,6 +136,7 @@ hr {
|
||||
|
||||
table {
|
||||
padding: 0;
|
||||
margin: 1rem 0.5rem;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,7 @@ hr {
|
||||
|
||||
table {
|
||||
padding: 0;
|
||||
margin: 1rem 0.5rem;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
|
15
src/src.pro
15
src/src.pro
@ -19,6 +19,13 @@ ICON = resources/icons/vnote.icns
|
||||
|
||||
TRANSLATIONS += translations/vnote_zh_CN.ts
|
||||
|
||||
*-g++ {
|
||||
QMAKE_CFLAGS_WARN_ON += -Wno-class-memaccess
|
||||
QMAKE_CXXFLAGS_WARN_ON += -Wno-class-memaccess
|
||||
QMAKE_CFLAGS += -Wno-class-memaccess
|
||||
QMAKE_CXXFLAGS += -Wno-class-memaccess
|
||||
}
|
||||
|
||||
SOURCES += main.cpp\
|
||||
vmainwindow.cpp \
|
||||
vdirectorytree.cpp \
|
||||
@ -150,7 +157,9 @@ SOURCES += main.cpp\
|
||||
utils/vkeyboardlayoutmanager.cpp \
|
||||
dialog/vkeyboardlayoutmappingdialog.cpp \
|
||||
vfilelistwidget.cpp \
|
||||
widgets/vcombobox.cpp
|
||||
widgets/vcombobox.cpp \
|
||||
vtablehelper.cpp \
|
||||
vtable.cpp
|
||||
|
||||
HEADERS += vmainwindow.h \
|
||||
vdirectorytree.h \
|
||||
@ -293,7 +302,9 @@ HEADERS += vmainwindow.h \
|
||||
utils/vkeyboardlayoutmanager.h \
|
||||
dialog/vkeyboardlayoutmappingdialog.h \
|
||||
vfilelistwidget.h \
|
||||
widgets/vcombobox.h
|
||||
widgets/vcombobox.h \
|
||||
vtablehelper.h \
|
||||
vtable.h
|
||||
|
||||
RESOURCES += \
|
||||
vnote.qrc \
|
||||
|
@ -213,6 +213,7 @@ public:
|
||||
virtual bool findW(const QRegExp &p_exp,
|
||||
QTextDocument::FindFlags p_options = QTextDocument::FindFlags()) = 0;
|
||||
|
||||
virtual bool isReadOnlyW() const = 0;
|
||||
virtual void setReadOnlyW(bool p_ro) = 0;
|
||||
|
||||
virtual QWidget *viewportW() const = 0;
|
||||
|
@ -820,7 +820,7 @@ void VMainWindow::initHelpMenu()
|
||||
docAct->setToolTip(tr("View VNote's documentation"));
|
||||
connect(docAct, &QAction::triggered,
|
||||
this, []() {
|
||||
QString url("http://vnote.readthedocs.io");
|
||||
QString url("https://tamlok.github.io/vnote");
|
||||
QDesktopServices::openUrl(url);
|
||||
});
|
||||
|
||||
@ -828,7 +828,7 @@ void VMainWindow::initHelpMenu()
|
||||
donateAct->setToolTip(tr("Donate to VNote or view the donate list"));
|
||||
connect(donateAct, &QAction::triggered,
|
||||
this, []() {
|
||||
QString url("https://github.com/tamlok/vnote#donate");
|
||||
QString url("https://tamlok.github.io/vnote/en_us/#!donate.md");
|
||||
QDesktopServices::openUrl(url);
|
||||
});
|
||||
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "vgraphvizhelper.h"
|
||||
#include "vmdtab.h"
|
||||
#include "vdownloader.h"
|
||||
#include "vtablehelper.h"
|
||||
|
||||
extern VWebUtils *g_webUtils;
|
||||
|
||||
@ -109,6 +110,10 @@ VMdEditor::VMdEditor(VFile *p_file,
|
||||
connect(m_previewMgr, &VPreviewManager::requestUpdateImageLinks,
|
||||
m_pegHighlighter, &PegMarkdownHighlighter::updateHighlight);
|
||||
|
||||
m_tableHelper = new VTableHelper(this);
|
||||
connect(m_pegHighlighter, &PegMarkdownHighlighter::tableBlocksUpdated,
|
||||
m_tableHelper, &VTableHelper::updateTableBlocks);
|
||||
|
||||
m_editOps = new VMdEditOperations(this, m_file);
|
||||
connect(m_editOps, &VEditOperations::statusMessage,
|
||||
m_object, &VEditorObject::statusMessage);
|
||||
@ -1446,7 +1451,8 @@ void VMdEditor::initLinkAndPreviewMenu(QAction *p_before, QMenu *p_menu, const Q
|
||||
if (regExp.indexIn(text) > -1) {
|
||||
const QVector<VElementRegion> &imgRegs = m_pegHighlighter->getImageRegions();
|
||||
for (auto const & reg : imgRegs) {
|
||||
if (!reg.contains(pos)) {
|
||||
if (!reg.contains(pos)
|
||||
&& (!reg.contains(pos - 1) || pos != (block.position() + text.size()))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1608,7 +1614,8 @@ bool VMdEditor::initInPlacePreviewMenu(QAction *p_before,
|
||||
int pib = p_pos - p_block.position();
|
||||
for (auto info : previews) {
|
||||
const VPreviewedImageInfo &pii = info->m_imageInfo;
|
||||
if (pii.contains(pib)) {
|
||||
if (pii.contains(pib)
|
||||
|| (pii.contains(pib - 1) && pib == p_block.length() - 1)) {
|
||||
const QPixmap *img = findImage(pii.m_imageName);
|
||||
if (img) {
|
||||
image = *img;
|
||||
|
@ -22,6 +22,7 @@ class VDocument;
|
||||
class VPreviewManager;
|
||||
class VCopyTextAsHtmlDialog;
|
||||
class VEditTab;
|
||||
class VTableHelper;
|
||||
|
||||
class VMdEditor : public VTextEdit, public VEditor
|
||||
{
|
||||
@ -152,6 +153,11 @@ public:
|
||||
return find(p_exp, p_options);
|
||||
}
|
||||
|
||||
bool isReadOnlyW() const Q_DECL_OVERRIDE
|
||||
{
|
||||
return isReadOnly();
|
||||
}
|
||||
|
||||
void setReadOnlyW(bool p_ro) Q_DECL_OVERRIDE
|
||||
{
|
||||
setReadOnly(p_ro);
|
||||
@ -325,6 +331,8 @@ private:
|
||||
|
||||
VPreviewManager *m_previewMgr;
|
||||
|
||||
VTableHelper *m_tableHelper;
|
||||
|
||||
// Image links inserted while editing.
|
||||
QVector<ImageLink> m_insertedImages;
|
||||
|
||||
|
585
src/vtable.cpp
Normal file
585
src/vtable.cpp
Normal file
@ -0,0 +1,585 @@
|
||||
#include "vtable.h"
|
||||
|
||||
#include <QTextDocument>
|
||||
#include <QTextLayout>
|
||||
|
||||
#include "veditor.h"
|
||||
|
||||
const QString VTable::c_defaultDelimiter = "---";
|
||||
|
||||
enum { HeaderRowIndex = 0, DelimiterRowIndex = 1 };
|
||||
|
||||
VTable::VTable(VEditor *p_editor, const VTableBlock &p_block)
|
||||
: m_editor(p_editor)
|
||||
{
|
||||
parseFromTableBlock(p_block);
|
||||
}
|
||||
|
||||
bool VTable::isValid() const
|
||||
{
|
||||
return header() && header()->isValid()
|
||||
&& delimiter() && delimiter()->isValid();
|
||||
}
|
||||
|
||||
void VTable::parseFromTableBlock(const VTableBlock &p_block)
|
||||
{
|
||||
clear();
|
||||
|
||||
QTextDocument *doc = m_editor->documentW();
|
||||
|
||||
QTextBlock block = doc->findBlock(p_block.m_startPos);
|
||||
if (!block.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int lastBlockNumber = doc->findBlock(p_block.m_endPos - 1).blockNumber();
|
||||
if (lastBlockNumber == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QVector<int> &borders = p_block.m_borders;
|
||||
if (borders.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int numRows = lastBlockNumber - block.blockNumber() + 1;
|
||||
if (numRows <= DelimiterRowIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
calculateBasicWidths(block, borders[0]);
|
||||
|
||||
int borderIdx = 0;
|
||||
m_rows.reserve(numRows);
|
||||
for (int i = 0; i < numRows; ++i) {
|
||||
m_rows.append(Row());
|
||||
if (!parseOneRow(block, borders, borderIdx, m_rows.last())) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
block = block.next();
|
||||
}
|
||||
}
|
||||
|
||||
bool VTable::parseOneRow(const QTextBlock &p_block,
|
||||
const QVector<int> &p_borders,
|
||||
int &p_borderIdx,
|
||||
Row &p_row) const
|
||||
{
|
||||
if (!p_block.isValid() || p_borderIdx >= p_borders.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
p_row.m_block = p_block;
|
||||
|
||||
QString text = p_block.text();
|
||||
int startPos = p_block.position();
|
||||
int endPos = startPos + text.length();
|
||||
|
||||
if (p_borders[p_borderIdx] < startPos
|
||||
|| p_borders[p_borderIdx] >= endPos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (; p_borderIdx < p_borders.size(); ++p_borderIdx) {
|
||||
int border = p_borders[p_borderIdx];
|
||||
if (border >= endPos) {
|
||||
break;
|
||||
}
|
||||
|
||||
int offset = border - startPos;
|
||||
if (text[offset] != '|') {
|
||||
return false;
|
||||
}
|
||||
|
||||
int nextIdx = p_borderIdx + 1;
|
||||
if (nextIdx >= p_borders.size() || p_borders[nextIdx] >= endPos) {
|
||||
// The last border of this row.
|
||||
++p_borderIdx;
|
||||
break;
|
||||
}
|
||||
|
||||
int nextOffset = p_borders[nextIdx] - startPos;
|
||||
if (text[nextOffset] != '|') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Got one cell.
|
||||
Cell cell;
|
||||
cell.m_offset = offset;
|
||||
cell.m_length = nextOffset - offset;
|
||||
cell.m_text = text.mid(cell.m_offset, cell.m_length);
|
||||
|
||||
p_row.m_cells.append(cell);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VTable::clear()
|
||||
{
|
||||
m_rows.clear();
|
||||
m_spaceWidth = 0;
|
||||
m_minusWidth = 0;
|
||||
m_colonWidth = 0;
|
||||
m_defaultDelimiterWidth = 0;
|
||||
}
|
||||
|
||||
void VTable::format()
|
||||
{
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
int curRowIdx = cursor.blockNumber() - m_rows[0].m_block.blockNumber();
|
||||
int curPib = -1;
|
||||
if (curRowIdx < 0 || curRowIdx >= m_rows.size()) {
|
||||
curRowIdx = -1;
|
||||
} else {
|
||||
curPib = cursor.positionInBlock();
|
||||
}
|
||||
|
||||
int nrCols = calculateColumnCount();
|
||||
for (int i = 0; i < nrCols; ++i) {
|
||||
formatOneColumn(i, curRowIdx, curPib);
|
||||
}
|
||||
}
|
||||
|
||||
int VTable::calculateColumnCount() const
|
||||
{
|
||||
int nr = 0;
|
||||
|
||||
// Find the longest row.
|
||||
for (const auto & row : m_rows) {
|
||||
if (row.m_cells.size() > nr) {
|
||||
nr = row.m_cells.size();
|
||||
}
|
||||
}
|
||||
|
||||
return nr;
|
||||
}
|
||||
|
||||
VTable::Row *VTable::header() const
|
||||
{
|
||||
if (m_rows.size() <= HeaderRowIndex) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return const_cast<VTable::Row *>(&m_rows[HeaderRowIndex]);
|
||||
}
|
||||
|
||||
VTable::Row *VTable::delimiter() const
|
||||
{
|
||||
if (m_rows.size() <= DelimiterRowIndex) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return const_cast<VTable::Row *>(&m_rows[DelimiterRowIndex]);
|
||||
}
|
||||
|
||||
void VTable::formatOneColumn(int p_idx, int p_cursorRowIdx, int p_cursorPib)
|
||||
{
|
||||
QVector<CellInfo> cells;
|
||||
int targetWidth = 0;
|
||||
fetchCellInfoOfColumn(p_idx, cells, targetWidth);
|
||||
|
||||
// Get the alignment of this column.
|
||||
const VTable::Alignment align = getColumnAlignment(p_idx);
|
||||
|
||||
// Calculate the formatted text of each cell.
|
||||
for (int rowIdx = 0; rowIdx < cells.size(); ++rowIdx) {
|
||||
auto & info = cells[rowIdx];
|
||||
auto & row = m_rows[rowIdx];
|
||||
if (row.m_cells.size() <= p_idx) {
|
||||
row.m_cells.resize(p_idx + 1);
|
||||
}
|
||||
auto & cell = row.m_cells[p_idx];
|
||||
Q_ASSERT(cell.m_formattedText.isEmpty());
|
||||
Q_ASSERT(cell.m_cursorCoreOffset == -1);
|
||||
|
||||
// Record the cursor position.
|
||||
if (rowIdx == p_cursorRowIdx) {
|
||||
if (cell.m_offset <= p_cursorPib && cell.m_offset + cell.m_length > p_cursorPib) {
|
||||
// Cursor in this cell.
|
||||
int offset = p_cursorPib - cell.m_offset;
|
||||
offset = offset - info.m_coreOffset;
|
||||
if (offset > info.m_coreLength) {
|
||||
offset = info.m_coreLength;
|
||||
} else if (offset < 0) {
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
cell.m_cursorCoreOffset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDelimiterRow(rowIdx)) {
|
||||
if (!isDelimiterCellWellFormatted(cell, info, targetWidth)) {
|
||||
QString core;
|
||||
// Round to 1 when above 0.5 approximately.
|
||||
int delta = m_minusWidth / 2;
|
||||
switch (align) {
|
||||
case Alignment::None:
|
||||
core = QString((targetWidth + delta) / m_minusWidth, '-');
|
||||
break;
|
||||
|
||||
case Alignment::Left:
|
||||
core = ":";
|
||||
core += QString((targetWidth - m_colonWidth + delta) / m_minusWidth, '-');
|
||||
break;
|
||||
|
||||
case Alignment::Center:
|
||||
core = ":";
|
||||
core += QString((targetWidth - 2 * m_colonWidth + delta) / m_minusWidth, '-');
|
||||
core += ":";
|
||||
break;
|
||||
|
||||
case Alignment::Right:
|
||||
core = QString((targetWidth - m_colonWidth + delta) / m_minusWidth, '-');
|
||||
core += ":";
|
||||
break;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
Alignment fakeAlign = align == Alignment::None ? Alignment::Left : align;
|
||||
cell.m_formattedText = generateFormattedText(core,
|
||||
0,
|
||||
fakeAlign);
|
||||
}
|
||||
} else {
|
||||
Alignment fakeAlign = align;
|
||||
if (fakeAlign == Alignment::None) {
|
||||
// For Alignment::None, we make the header align center while
|
||||
// content cells align left.
|
||||
if (isHeaderRow(rowIdx)) {
|
||||
fakeAlign = Alignment::Center;
|
||||
} else {
|
||||
fakeAlign = Alignment::Left;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isCellWellFormatted(row, cell, info, targetWidth, fakeAlign)) {
|
||||
QString core = cell.m_text.mid(info.m_coreOffset, info.m_coreLength);
|
||||
int nr = (targetWidth - info.m_coreWidth + m_spaceWidth / 2) / m_spaceWidth;
|
||||
cell.m_formattedText = generateFormattedText(core, nr, fakeAlign);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VTable::fetchCellInfoOfColumn(int p_idx,
|
||||
QVector<CellInfo> &p_cellsInfo,
|
||||
int &p_targetWidth) const
|
||||
{
|
||||
p_targetWidth = m_defaultDelimiterWidth;
|
||||
p_cellsInfo.resize(m_rows.size());
|
||||
|
||||
// Fetch the trimmed core content and its width.
|
||||
for (int i = 0; i < m_rows.size(); ++i) {
|
||||
auto & row = m_rows[i];
|
||||
auto & info = p_cellsInfo[i];
|
||||
|
||||
if (row.m_cells.size() <= p_idx) {
|
||||
// Need to add a new cell later.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the info of this cell.
|
||||
const auto & cell = row.m_cells[p_idx];
|
||||
int first = 1, last = cell.m_length - 2;
|
||||
for (; first <= last; ++first) {
|
||||
if (cell.m_text[first] != ' ') {
|
||||
// Found the core content.
|
||||
info.m_coreOffset = first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (first > last) {
|
||||
// Empty cell.
|
||||
continue;
|
||||
}
|
||||
|
||||
for (; last >= first; --last) {
|
||||
if (cell.m_text[last] != ' ') {
|
||||
// Found the last of core content.
|
||||
info.m_coreLength = last - first + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the core width.
|
||||
info.m_coreWidth = calculateTextWidth(row.m_block,
|
||||
cell.m_offset + info.m_coreOffset,
|
||||
info.m_coreLength);
|
||||
// Delimiter row's width should not be considered.
|
||||
if (info.m_coreWidth > p_targetWidth && !isDelimiterRow(i)) {
|
||||
p_targetWidth = info.m_coreWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VTable::calculateBasicWidths(const QTextBlock &p_block, int p_borderPos)
|
||||
{
|
||||
QFont font;
|
||||
|
||||
int pib = p_borderPos - p_block.position();
|
||||
QVector<QTextLayout::FormatRange> fmts = p_block.layout()->formats();
|
||||
for (const auto & fmt : fmts) {
|
||||
if (fmt.start <= pib && fmt.start + fmt.length > pib) {
|
||||
// Hit.
|
||||
if (!fmt.format.fontFamily().isEmpty()) {
|
||||
font = fmt.format.font();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QFontMetrics fm(font);
|
||||
m_spaceWidth = fm.width(' ');
|
||||
m_minusWidth = fm.width('-');
|
||||
m_colonWidth = fm.width(':');
|
||||
m_defaultDelimiterWidth = fm.width(c_defaultDelimiter);
|
||||
}
|
||||
|
||||
int VTable::calculateTextWidth(const QTextBlock &p_block, int p_pib, int p_length) const
|
||||
{
|
||||
QTextLine line = p_block.layout()->lineForTextPosition(p_pib);
|
||||
if (line.isValid()) {
|
||||
return line.cursorToX(p_pib + p_length) - line.cursorToX(p_pib);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool VTable::isHeaderRow(int p_idx) const
|
||||
{
|
||||
return p_idx == HeaderRowIndex;
|
||||
}
|
||||
|
||||
bool VTable::isDelimiterRow(int p_idx) const
|
||||
{
|
||||
return p_idx == DelimiterRowIndex;
|
||||
}
|
||||
|
||||
QString VTable::generateFormattedText(const QString &p_core,
|
||||
int p_nrSpaces,
|
||||
Alignment p_align) const
|
||||
{
|
||||
Q_ASSERT(p_align != Alignment::None);
|
||||
|
||||
// Align left.
|
||||
int leftSpaces = 0;
|
||||
int rightSpaces = p_nrSpaces;
|
||||
|
||||
if (p_align == Alignment::Center) {
|
||||
leftSpaces = p_nrSpaces / 2;
|
||||
rightSpaces = p_nrSpaces - leftSpaces;
|
||||
} else if (p_align == Alignment::Right) {
|
||||
leftSpaces = p_nrSpaces;
|
||||
rightSpaces = 0;
|
||||
}
|
||||
|
||||
return QString("| %1%2%3 ").arg(QString(leftSpaces, ' '))
|
||||
.arg(p_core)
|
||||
.arg(QString(rightSpaces, ' '));
|
||||
}
|
||||
|
||||
VTable::Alignment VTable::getColumnAlignment(int p_idx) const
|
||||
{
|
||||
Row *row = delimiter();
|
||||
if (row->m_cells.size() <= p_idx) {
|
||||
return Alignment::None;
|
||||
}
|
||||
|
||||
QString core = row->m_cells[p_idx].m_text.mid(1).trimmed();
|
||||
Q_ASSERT(!core.isEmpty());
|
||||
bool leftColon = core[0] == ':';
|
||||
bool rightColon = core[core.size() - 1] == ':';
|
||||
if (leftColon) {
|
||||
if (rightColon) {
|
||||
return Alignment::Center;
|
||||
} else {
|
||||
return Alignment::Left;
|
||||
}
|
||||
} else {
|
||||
if (rightColon) {
|
||||
return Alignment::Right;
|
||||
} else {
|
||||
return Alignment::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool equalWidth(int p_a, int p_b, int p_margin = 5)
|
||||
{
|
||||
return qAbs(p_a - p_b) < p_margin;
|
||||
}
|
||||
|
||||
bool VTable::isDelimiterCellWellFormatted(const Cell &p_cell,
|
||||
const CellInfo &p_info,
|
||||
int p_targetWidth) const
|
||||
{
|
||||
// We could use core width here for delimiter cell.
|
||||
if (!equalWidth(p_info.m_coreWidth, p_targetWidth, m_minusWidth)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString &text = p_cell.m_text;
|
||||
if (text.size() < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text[1] != ' ' || text[text.size() - 1] != ' ') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text[2] == ' ' || text[text.size() - 2] == ' ') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VTable::isCellWellFormatted(const Row &p_row,
|
||||
const Cell &p_cell,
|
||||
const CellInfo &p_info,
|
||||
int p_targetWidth,
|
||||
VTable::Alignment p_align) const
|
||||
{
|
||||
Q_ASSERT(p_align != Alignment::None);
|
||||
|
||||
const QString &text = p_cell.m_text;
|
||||
if (text.size() < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text[1] != ' ' || text[text.size() - 1] != ' ') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip alignment check of empty cell.
|
||||
if (p_info.m_coreOffset > 0) {
|
||||
int leftSpaces = p_info.m_coreOffset - 2;
|
||||
int rightSpaces = text.size() - p_info.m_coreOffset - p_info.m_coreLength - 1;
|
||||
switch (p_align) {
|
||||
case Alignment::Left:
|
||||
if (leftSpaces > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case Alignment::Center:
|
||||
if (qAbs(leftSpaces - rightSpaces) > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case Alignment::Right:
|
||||
if (rightSpaces > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the width of the text without two spaces around.
|
||||
int cellWidth = calculateTextWidth(p_row.m_block,
|
||||
p_cell.m_offset + 2,
|
||||
p_cell.m_length - 3);
|
||||
if (!equalWidth(cellWidth, p_targetWidth, m_spaceWidth)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VTable::write()
|
||||
{
|
||||
bool changed = false;
|
||||
QTextCursor cursor = m_editor->textCursorW();
|
||||
int cursorBlock = -1, cursorPib = -1;
|
||||
|
||||
// Write the table row by row.
|
||||
for (auto & row : m_rows) {
|
||||
bool needChange = false;
|
||||
for (const auto & cell : row.m_cells) {
|
||||
if (!cell.m_formattedText.isEmpty()) {
|
||||
needChange = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needChange) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
changed = true;
|
||||
cursorBlock = cursor.blockNumber();
|
||||
cursorPib = cursor.positionInBlock();
|
||||
|
||||
cursor.beginEditBlock();
|
||||
}
|
||||
|
||||
// Construct the block text.
|
||||
QString newBlockText;
|
||||
int firstOffset = row.m_cells.first().m_offset;
|
||||
if (firstOffset > 0) {
|
||||
// Get the prefix text.
|
||||
QString text = row.m_block.text();
|
||||
newBlockText = text.left(firstOffset);
|
||||
}
|
||||
|
||||
for (auto & cell : row.m_cells) {
|
||||
int pos = newBlockText.size();
|
||||
if (cell.m_formattedText.isEmpty()) {
|
||||
newBlockText += cell.m_text;
|
||||
} else {
|
||||
newBlockText += cell.m_formattedText;
|
||||
}
|
||||
|
||||
if (cell.m_cursorCoreOffset > -1) {
|
||||
// Cursor in this cell.
|
||||
cursorPib = pos + cell.m_cursorCoreOffset + 2;
|
||||
if (cursorPib >= newBlockText.size()) {
|
||||
cursorPib = newBlockText.size() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newBlockText += "|";
|
||||
|
||||
// Replace the whole block.
|
||||
cursor.setPosition(row.m_block.position());
|
||||
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
|
||||
cursor.insertText(newBlockText);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
qDebug() << "write formatted table with cursor block" << cursorBlock;
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursorW(cursor);
|
||||
|
||||
// Restore the cursor.
|
||||
QTextBlock block = m_editor->documentW()->findBlockByNumber(cursorBlock);
|
||||
if (block.isValid()) {
|
||||
int pos = block.position() + cursorPib;
|
||||
QTextCursor cur = m_editor->textCursorW();
|
||||
cur.setPosition(pos);
|
||||
m_editor->setTextCursorW(cur);
|
||||
}
|
||||
}
|
||||
}
|
181
src/vtable.h
Normal file
181
src/vtable.h
Normal file
@ -0,0 +1,181 @@
|
||||
#ifndef VTABLE_H
|
||||
#define VTABLE_H
|
||||
|
||||
#include <QTextBlock>
|
||||
|
||||
#include "markdownhighlighterdata.h"
|
||||
|
||||
class VEditor;
|
||||
|
||||
class VTable
|
||||
{
|
||||
public:
|
||||
struct Cell
|
||||
{
|
||||
Cell()
|
||||
: m_offset(-1),
|
||||
m_length(0),
|
||||
m_cursorCoreOffset(-1)
|
||||
{
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_offset = -1;
|
||||
m_length = 0;
|
||||
m_text.clear();
|
||||
m_formattedText.clear();
|
||||
m_cursorCoreOffset = -1;
|
||||
}
|
||||
|
||||
// Start offset within block, including the starting border |.
|
||||
int m_offset;
|
||||
|
||||
// Length of this cell, till next border |.
|
||||
int m_length;
|
||||
|
||||
// Text like "| vnote ".
|
||||
QString m_text;
|
||||
|
||||
// Formatted text, such as "| vnote ".
|
||||
// It is empty if it does not need formatted.
|
||||
QString m_formattedText;
|
||||
|
||||
// If cursor is within this cell, this will not be -1.
|
||||
int m_cursorCoreOffset;
|
||||
};
|
||||
|
||||
struct Row
|
||||
{
|
||||
Row()
|
||||
{
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return m_block.isValid();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_block = QTextBlock();
|
||||
m_cells.clear();
|
||||
}
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
QString cells;
|
||||
for (auto & cell : m_cells) {
|
||||
cells += QString(" (%1, %2 [%3])").arg(cell.m_offset)
|
||||
.arg(cell.m_length)
|
||||
.arg(cell.m_text);
|
||||
}
|
||||
|
||||
return QString("row %1 %2").arg(m_block.blockNumber()).arg(cells);
|
||||
}
|
||||
|
||||
QTextBlock m_block;
|
||||
QVector<Cell> m_cells;
|
||||
};
|
||||
|
||||
enum Alignment
|
||||
{
|
||||
None,
|
||||
Left,
|
||||
Center,
|
||||
Right
|
||||
};
|
||||
|
||||
VTable(VEditor *p_editor, const VTableBlock &p_block);
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
void format();
|
||||
|
||||
// Write a formatted table.
|
||||
void write();
|
||||
|
||||
VTable::Row *header() const;
|
||||
|
||||
VTable::Row *delimiter() const;
|
||||
|
||||
private:
|
||||
// Used to hold info about a cell when formatting a column.
|
||||
struct CellInfo
|
||||
{
|
||||
CellInfo()
|
||||
: m_coreOffset(0),
|
||||
m_coreLength(0),
|
||||
m_coreWidth(0)
|
||||
{
|
||||
}
|
||||
|
||||
// The offset of the core content within the cell.
|
||||
// Will be 0 if it is an empty cell.
|
||||
int m_coreOffset;
|
||||
|
||||
// The length of the core content.
|
||||
// Will be 0 if it is an empty cell.
|
||||
int m_coreLength;
|
||||
|
||||
// Pixel width of the core content.
|
||||
int m_coreWidth;
|
||||
};
|
||||
|
||||
void parseFromTableBlock(const VTableBlock &p_block);
|
||||
|
||||
void clear();
|
||||
|
||||
bool parseOneRow(const QTextBlock &p_block,
|
||||
const QVector<int> &p_borders,
|
||||
int &p_borderIdx,
|
||||
Row &p_row) const;
|
||||
|
||||
int calculateColumnCount() const;
|
||||
|
||||
// When called with i, the (i - 1) column must have been formatted.
|
||||
void formatOneColumn(int p_idx, int p_cursorRowIdx, int p_cursorPib);
|
||||
|
||||
void fetchCellInfoOfColumn(int p_idx,
|
||||
QVector<CellInfo> &p_cellsInfo,
|
||||
int &p_targetWidth) const;
|
||||
|
||||
void calculateBasicWidths(const QTextBlock &p_block, int p_borderPos);
|
||||
|
||||
int calculateTextWidth(const QTextBlock &p_block, int p_pib, int p_length) const;
|
||||
|
||||
bool isHeaderRow(int p_idx) const;
|
||||
|
||||
bool isDelimiterRow(int p_idx) const;
|
||||
|
||||
// @p_nrSpaces: number of spaces to fill core content.
|
||||
QString generateFormattedText(const QString &p_core,
|
||||
int p_nrSpaces = 0,
|
||||
Alignment p_align = Alignment::Left) const;
|
||||
|
||||
VTable::Alignment getColumnAlignment(int p_idx) const;
|
||||
|
||||
bool isDelimiterCellWellFormatted(const Cell &p_cell,
|
||||
const CellInfo &p_info,
|
||||
int p_targetWidth) const;
|
||||
|
||||
bool isCellWellFormatted(const Row &p_row,
|
||||
const Cell &p_cell,
|
||||
const CellInfo &p_info,
|
||||
int p_targetWidth,
|
||||
VTable::Alignment p_align) const;
|
||||
|
||||
VEditor *m_editor;
|
||||
|
||||
// Header, delimiter, and body.
|
||||
QVector<Row> m_rows;
|
||||
|
||||
int m_spaceWidth;
|
||||
int m_minusWidth;
|
||||
int m_colonWidth;
|
||||
int m_defaultDelimiterWidth;
|
||||
|
||||
static const QString c_defaultDelimiter;
|
||||
};
|
||||
|
||||
#endif // VTABLE_H
|
54
src/vtablehelper.cpp
Normal file
54
src/vtablehelper.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
#include "vtablehelper.h"
|
||||
|
||||
#include "veditor.h"
|
||||
#include "vtable.h"
|
||||
|
||||
VTableHelper::VTableHelper(VEditor *p_editor, QObject *p_parent)
|
||||
: QObject(p_parent),
|
||||
m_editor(p_editor)
|
||||
{
|
||||
}
|
||||
|
||||
void VTableHelper::updateTableBlocks(const QVector<VTableBlock> &p_blocks)
|
||||
{
|
||||
if (m_editor->isReadOnlyW() || !m_editor->isModified()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = currentCursorTableBlock(p_blocks);
|
||||
if (idx == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
VTable table(m_editor, p_blocks[idx]);
|
||||
if (!table.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
table.format();
|
||||
|
||||
table.write();
|
||||
}
|
||||
|
||||
int VTableHelper::currentCursorTableBlock(const QVector<VTableBlock> &p_blocks) const
|
||||
{
|
||||
// Binary search.
|
||||
int curPos = m_editor->textCursorW().position();
|
||||
|
||||
int first = 0, last = p_blocks.size() - 1;
|
||||
while (first <= last) {
|
||||
int mid = (first + last) / 2;
|
||||
const VTableBlock &block = p_blocks[mid];
|
||||
if (block.m_startPos <= curPos && block.m_endPos >= curPos) {
|
||||
return mid;
|
||||
}
|
||||
|
||||
if (block.m_startPos > curPos) {
|
||||
last = mid - 1;
|
||||
} else {
|
||||
first = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
26
src/vtablehelper.h
Normal file
26
src/vtablehelper.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef VTABLEHELPER_H
|
||||
#define VTABLEHELPER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "markdownhighlighterdata.h"
|
||||
|
||||
class VEditor;
|
||||
|
||||
class VTableHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VTableHelper(VEditor *p_editor, QObject *p_parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void updateTableBlocks(const QVector<VTableBlock> &p_blocks);
|
||||
|
||||
private:
|
||||
// Return the block index which contains the cursor.
|
||||
int currentCursorTableBlock(const QVector<VTableBlock> &p_blocks) const;
|
||||
|
||||
VEditor *m_editor;
|
||||
};
|
||||
|
||||
#endif // VTABLEHELPER_H
|
@ -75,7 +75,7 @@ struct VPreviewedImageInfo
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return QString("previewed image (%1): [%2, %3] padding %4 inline %5 (%6,%7) bg(%8)")
|
||||
return QString("previewed image (%1): [%2, %3) padding %4 inline %5 (%6,%7) bg(%8)")
|
||||
.arg(m_imageName)
|
||||
.arg(m_startPos)
|
||||
.arg(m_endPos)
|
||||
|
Loading…
x
Reference in New Issue
Block a user