vnote/src/voutline.cpp

373 lines
10 KiB
C++

#include "voutline.h"
#include <QtWidgets>
#include <QCoreApplication>
#include "utils/vutils.h"
#include "vnote.h"
#include "vfile.h"
#include "vtreewidget.h"
#include "utils/viconutils.h"
#include "vconfigmanager.h"
#include "vmainwindow.h"
extern VNote *g_vnote;
extern VConfigManager *g_config;
extern VMainWindow *g_mainWin;
#define STATIC_EXPANDED_LEVEL 6
VOutline::VOutline(QWidget *parent)
: QWidget(parent),
VNavigationMode(),
m_muted(false)
{
setupUI();
m_expandTimer = new QTimer(this);
m_expandTimer->setSingleShot(true);
m_expandTimer->setInterval(1000);
connect(m_expandTimer, &QTimer::timeout,
this, [this]() {
// Auto adjust items after current header change.
int level = g_config->getOutlineExpandedLevel();
if (level == STATIC_EXPANDED_LEVEL) {
return;
}
expandTree(level);
QTreeWidgetItem *curItem = m_tree->currentItem();
if (curItem) {
m_tree->scrollToItem(curItem);
}
});
}
void VOutline::setupUI()
{
m_deLevelBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/decrease_outline_level.svg"),
"",
this);
m_deLevelBtn->setToolTip(tr("Decrease Expanded Level"));
m_deLevelBtn->setProperty("FlatBtn", true);
m_deLevelBtn->setEnabled(false);
connect(m_deLevelBtn, &QPushButton::clicked,
this, [this]() {
int level = g_config->getOutlineExpandedLevel() - 1;
if (level <= 0) {
level = 1;
} else {
g_config->setOutlineExpandedLevel(level);
expandTree(level);
}
g_mainWin->showStatusMessage(tr("Set Outline Expanded Level to %1").arg(level));
});
m_inLevelBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/increase_outline_level.svg"),
"",
this);
m_inLevelBtn->setToolTip(tr("Increase Expanded Level"));
m_inLevelBtn->setProperty("FlatBtn", true);
m_inLevelBtn->setEnabled(false);
connect(m_inLevelBtn, &QPushButton::clicked,
this, [this]() {
int level = g_config->getOutlineExpandedLevel() + 1;
if (level >= 7) {
level = 6;
} else {
g_config->setOutlineExpandedLevel(level);
expandTree(level);
}
g_mainWin->showStatusMessage(tr("Set Outline Expanded Level to %1").arg(level));
});
QHBoxLayout *btnLayout = new QHBoxLayout();
btnLayout->addStretch();
btnLayout->addWidget(m_deLevelBtn);
btnLayout->addWidget(m_inLevelBtn);
btnLayout->setContentsMargins(0, 0, 0, 0);
m_tree = new VTreeWidget(this);
m_tree->setColumnCount(1);
m_tree->setHeaderHidden(true);
m_tree->setSelectionMode(QAbstractItemView::SingleSelection);
// TODO: jump to the header when user click the same item twice.
connect(m_tree, &QTreeWidget::currentItemChanged,
this, [this](QTreeWidgetItem *p_cur, QTreeWidgetItem *p_pre) {
Q_UNUSED(p_pre);
activateItem(p_cur);
});
connect(m_tree, &QTreeWidget::itemClicked,
this, [this](QTreeWidgetItem *p_item, int p_col) {
Q_UNUSED(p_col);
// Will duplicate the signal. That's fine.
activateItem(p_item, true);
});
QVBoxLayout *layout = new QVBoxLayout();
layout->addLayout(btnLayout);
layout->addWidget(m_tree);
layout->setContentsMargins(3, 0, 3, 0);
setLayout(layout);
}
void VOutline::updateOutline(const VTableOfContent &p_outline)
{
if (p_outline == m_outline) {
return;
}
// Clear current header
m_currentHeader.clear();
m_outline = p_outline;
updateTreeFromOutline(m_tree, m_outline);
updateButtonsState();
expandTree(g_config->getOutlineExpandedLevel());
}
void VOutline::updateTreeFromOutline(QTreeWidget *p_treeWidget,
const VTableOfContent &p_outline)
{
p_treeWidget->clear();
if (p_outline.isEmpty()) {
return;
}
const QVector<VTableOfContentItem> &headers = p_outline.getTable();
int idx = 0;
updateTreeByLevel(p_treeWidget, headers, idx, NULL, NULL, 1);
}
void VOutline::updateTreeByLevel(QTreeWidget *p_treeWidget,
const QVector<VTableOfContentItem> &p_headers,
int &p_index,
QTreeWidgetItem *p_parent,
QTreeWidgetItem *p_last,
int p_level)
{
while (p_index < p_headers.size()) {
const VTableOfContentItem &header = p_headers[p_index];
QTreeWidgetItem *item;
if (header.m_level == p_level) {
if (p_parent) {
item = new QTreeWidgetItem(p_parent);
} else {
item = new QTreeWidgetItem(p_treeWidget);
}
fillItem(item, header);
p_last = item;
++p_index;
} else if (header.m_level < p_level) {
return;
} else {
updateTreeByLevel(p_treeWidget, p_headers, p_index, p_last, NULL, p_level + 1);
}
}
}
void VOutline::fillItem(QTreeWidgetItem *p_item, const VTableOfContentItem &p_header)
{
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"));
}
}
void VOutline::expandTree(int p_expandedLevel)
{
int topCount = m_tree->topLevelItemCount();
if (topCount == 0) {
return;
}
m_tree->collapseAll();
// Get the base level.
const VTableOfContentItem *header = getHeaderFromItem(m_tree->topLevelItem(0), m_outline);
if (!header) {
return;
}
int baseLevel = header->m_level;
int levelToBeExpanded = p_expandedLevel - baseLevel;
for (int i = 0; i < topCount; ++i) {
expandTreeOne(m_tree->topLevelItem(i), levelToBeExpanded);
}
}
void VOutline::expandTreeOne(QTreeWidgetItem *p_item, int p_levelToBeExpanded)
{
if (p_levelToBeExpanded <= 0) {
return;
}
// Expand this item.
p_item->setExpanded(true);
int nrChild = p_item->childCount();
for (int i = 0; i < nrChild; ++i) {
expandTreeOne(p_item->child(i), p_levelToBeExpanded - 1);
}
}
void VOutline::activateItem(QTreeWidgetItem *p_item, bool p_focusEditArea)
{
if (!p_item) {
return;
}
const VTableOfContentItem *header = getHeaderFromItem(p_item, m_outline);
Q_ASSERT(header);
m_currentHeader.update(m_outline.getFile(), header->m_index);
if (!header->isEmpty() && !m_muted) {
emit outlineItemActivated(m_currentHeader);
if (p_focusEditArea) {
g_mainWin->focusEditArea();
}
}
}
void VOutline::updateCurrentHeader(const VHeaderPointer &p_header)
{
if (p_header == m_currentHeader
|| !m_outline.isMatched(p_header)) {
return;
}
// Item change should not emit the signal.
m_muted = true;
m_currentHeader = p_header;
selectHeader(m_tree, m_outline, m_currentHeader);
m_muted = false;
m_expandTimer->start();
}
void VOutline::selectHeader(QTreeWidget *p_treeWidget,
const VTableOfContent &p_outline,
const VHeaderPointer &p_header)
{
p_treeWidget->setCurrentItem(NULL);
if (!p_outline.getItem(p_header)) {
return;
}
int nrTop = p_treeWidget->topLevelItemCount();
for (int i = 0; i < nrTop; ++i) {
if (selectHeaderOne(p_treeWidget, p_treeWidget->topLevelItem(i), p_outline, p_header)) {
return;
}
}
}
bool VOutline::selectHeaderOne(QTreeWidget *p_treeWidget,
QTreeWidgetItem *p_item,
const VTableOfContent &p_outline,
const VHeaderPointer &p_header)
{
if (!p_item) {
return false;
}
const VTableOfContentItem *header = getHeaderFromItem(p_item, p_outline);
if (!header) {
return false;
}
if (header->isMatched(p_header)) {
p_treeWidget->setCurrentItem(p_item);
return true;
}
int nrChild = p_item->childCount();
for (int i = 0; i < nrChild; ++i) {
if (selectHeaderOne(p_treeWidget, p_item->child(i), p_outline, p_header)) {
return true;
}
}
return false;
}
void VOutline::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Return:
V_FALLTHROUGH;
case Qt::Key_Enter:
{
QTreeWidgetItem *item = m_tree->currentItem();
if (item) {
item->setExpanded(!item->isExpanded());
}
return;
}
default:
break;
}
QWidget::keyPressEvent(event);
}
void VOutline::showNavigation()
{
VNavigationMode::showNavigation(m_tree);
}
bool VOutline::handleKeyNavigation(int p_key, bool &p_succeed)
{
static bool secondKey = false;
bool ret = VNavigationMode::handleKeyNavigation(m_tree,
secondKey,
p_key,
p_succeed);
if (ret && p_succeed && !secondKey) {
g_mainWin->focusEditArea();
}
return ret;
}
const VTableOfContentItem *VOutline::getHeaderFromItem(QTreeWidgetItem *p_item,
const VTableOfContent &p_outline)
{
int index = p_item->data(0, Qt::UserRole).toInt();
return p_outline.getItem(index);
}
void VOutline::focusInEvent(QFocusEvent *p_event)
{
QWidget::focusInEvent(p_event);
m_tree->setFocus();
}
void VOutline::updateButtonsState()
{
bool empty = m_outline.isEmpty();
m_deLevelBtn->setEnabled(!empty);
m_inLevelBtn->setEnabled(!empty);
}