TagExplorer: add explorer for tags

This commit is contained in:
Le Tan 2018-06-16 09:01:34 +08:00
parent f94169053e
commit 13c2d143bb
13 changed files with 690 additions and 8 deletions

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<path fill="#000000" d="M464,32H304L48,320l160,160l256-288V32z M448,184L208.125,456L72.062,320L311.587,48H448V184z"/>
<path fill="#000000" d="M368,160c17.645,0,32-14.355,32-32s-14.355-32-32-32s-32,14.355-32,32S350.355,160,368,160z M368,112
c8.836,0,16,7.163,16,16s-7.164,16-16,16s-16-7.163-16-16S359.164,112,368,112z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 810 B

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<path fill="#000000" d="M448,64V32H288L32,320l160,160l23.471-23.904L240,480l240-272V64H448z M192,457.371L54.39,320L294.621,48H432v16v16
v105.377l-216.555,247.99l-11.34,11.363L192,457.371z M464,201.377L240,457.371l-13.182-12.65L448,192V80h16V201.377z"/>
<path fill="#000000" d="M352,160c17.645,0,32-14.355,32-32s-14.355-32-32-32s-32,14.355-32,32S334.355,160,352,160z M352,112
c8.836,0,16,7.163,16,16s-7.164,16-16,16s-16-7.163-16-16S343.164,112,352,112z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 947 B

View File

@ -139,7 +139,8 @@ SOURCES += main.cpp\
utils/vprocessutils.cpp \
vtagpanel.cpp \
valltagspanel.cpp \
vtaglabel.cpp
vtaglabel.cpp \
vtagexplorer.cpp
HEADERS += vmainwindow.h \
vdirectorytree.h \
@ -272,7 +273,8 @@ HEADERS += vmainwindow.h \
utils/vprocessutils.h \
vtagpanel.h \
valltagspanel.h \
vtaglabel.h
vtaglabel.h \
vtagexplorer.h
RESOURCES += \
vnote.qrc \

View File

@ -194,6 +194,9 @@ public:
const QByteArray getNotebookSplitterState() const;
void setNotebookSplitterState(const QByteArray &p_state);
const QByteArray getTagExplorerSplitterState() const;
void setTagExplorerSplitterState(const QByteArray &p_state);
bool getFindCaseSensitive() const;
void setFindCaseSensitive(bool p_enabled);
@ -1304,6 +1307,19 @@ inline void VConfigManager::setNotebookSplitterState(const QByteArray &p_state)
p_state);
}
inline const QByteArray VConfigManager::getTagExplorerSplitterState() const
{
return getConfigFromSessionSettings("geometry",
"tag_explorer_splitter_state").toByteArray();
}
inline void VConfigManager::setTagExplorerSplitterState(const QByteArray &p_state)
{
setConfigToSessionSettings("geometry",
"tag_explorer_splitter_state",
p_state);
}
inline bool VConfigManager::getFindCaseSensitive() const
{
return m_findCaseSensitive;

View File

@ -460,7 +460,7 @@ void VHistoryList::openItem(const QListWidgetItem *p_item) const
g_mainWin->openFiles(files);
}
void VHistoryList::locateCurrentItem()
void VHistoryList::locateCurrentItem() const
{
auto item = m_itemList->currentItem();
if (!item) {

View File

@ -48,7 +48,7 @@ private slots:
void unpinSelectedItems();
void locateCurrentItem();
void locateCurrentItem() const;
// Add selected files to Cart.
void addFileToCart() const;

View File

@ -48,6 +48,7 @@
#include "vhistorylist.h"
#include "vexplorer.h"
#include "vlistue.h"
#include "vtagexplorer.h"
extern VConfigManager *g_config;
@ -291,6 +292,13 @@ void VMainWindow::setupNaviBox()
m_naviBox->addItem(m_explorer,
":/resources/icons/explorer.svg",
tr("Explorer"));
m_tagExplorer = new VTagExplorer();
m_naviBox->addItem(m_tagExplorer,
":/resources/icons/tag_explorer.svg",
tr("Tags"));
connect(m_notebookSelector, &VNotebookSelector::curNotebookChanged,
m_tagExplorer, &VTagExplorer::setNotebook);
}
void VMainWindow::setupNotebookPanel()
@ -2177,17 +2185,18 @@ void VMainWindow::saveStateAndGeometry()
g_config->setSearchDockChecked(m_searchDock->isVisible());
g_config->setNotebookSplitterState(m_nbSplitter->saveState());
g_config->setMainSplitterState(m_mainSplitter->saveState());
m_tagExplorer->saveStateAndGeometry();
g_config->setNaviBoxCurrentIndex(m_naviBox->currentIndex());
}
void VMainWindow::restoreStateAndGeometry()
{
const QByteArray &geometry = g_config->getMainWindowGeometry();
const QByteArray geometry = g_config->getMainWindowGeometry();
if (!geometry.isEmpty()) {
restoreGeometry(geometry);
}
const QByteArray &state = g_config->getMainWindowState();
const QByteArray state = g_config->getMainWindowState();
if (!state.isEmpty()) {
restoreState(state);
}
@ -2195,12 +2204,12 @@ void VMainWindow::restoreStateAndGeometry()
m_toolDock->setVisible(g_config->getToolsDockChecked());
m_searchDock->setVisible(g_config->getSearchDockChecked());
const QByteArray &splitterState = g_config->getMainSplitterState();
const QByteArray splitterState = g_config->getMainSplitterState();
if (!splitterState.isEmpty()) {
m_mainSplitter->restoreState(splitterState);
}
const QByteArray &nbSplitterState = g_config->getNotebookSplitterState();
const QByteArray nbSplitterState = g_config->getNotebookSplitterState();
if (!nbSplitterState.isEmpty()) {
m_nbSplitter->restoreState(nbSplitterState);
}

View File

@ -44,6 +44,7 @@ class QPrinter;
class VUniversalEntry;
class VHistoryList;
class VExplorer;
class VTagExplorer;
enum class PanelViewState
{
@ -462,6 +463,8 @@ private:
VExplorer *m_explorer;
VTagExplorer *m_tagExplorer;
// Interval of the shared memory timer in ms.
static const int c_sharedMemTimerInterval;
};

View File

@ -259,5 +259,7 @@
<file>resources/themes/v_detorte/v_detorte_codeblock.css</file>
<file>resources/themes/v_detorte/v_detorte_mermaid.css</file>
<file>resources/icons/tags.svg</file>
<file>resources/icons/tag_explorer.svg</file>
<file>resources/icons/tag.svg</file>
</qresource>
</RCC>

View File

@ -442,3 +442,18 @@ bool VNotebook::addTag(const QString &p_tag)
return true;
}
void VNotebook::removeTag(const QString &p_tag)
{
if (p_tag.isEmpty() || m_tags.isEmpty()) {
return;
}
int nr = m_tags.removeAll(p_tag);
if (nr > 0) {
if (!writeConfigNotebook()) {
qWarning() << "fail to update config of notebook" << m_name
<< "in directory" << m_path;
}
}
}

View File

@ -60,6 +60,8 @@ public:
bool addTag(const QString &p_tag);
void removeTag(const QString &p_tag);
bool hasTag(const QString &p_tag) const;
static VNotebook *createNotebook(const QString &p_name,

497
src/vtagexplorer.cpp Normal file
View File

@ -0,0 +1,497 @@
#include "vtagexplorer.h"
#include <QtWidgets>
#include "utils/viconutils.h"
#include "vmainwindow.h"
#include "vlistwidget.h"
#include "vnotebook.h"
#include "vconfigmanager.h"
#include "vsearch.h"
#include "vnote.h"
#include "vcart.h"
#include "vhistorylist.h"
#include "vnotefile.h"
#include "utils/vutils.h"
extern VMainWindow *g_mainWin;
extern VConfigManager *g_config;
extern VNote *g_vnote;
#define MAX_DISPLAY_LENGTH 10
VTagExplorer::VTagExplorer(QWidget *p_parent)
: QWidget(p_parent),
m_uiInitialized(false),
m_notebook(NULL),
m_notebookChanged(true),
m_search(NULL)
{
}
void VTagExplorer::setupUI()
{
if (m_uiInitialized) {
return;
}
m_uiInitialized = true;
m_notebookLabel = new QLabel(tr("Tags"), this);
m_notebookLabel->setProperty("TitleLabel", true);
m_tagList = new VListWidget(this);
m_tagList->setAttribute(Qt::WA_MacShowFocusRect, false);
connect(m_tagList, &QListWidget::itemActivated,
this, [this](const QListWidgetItem *p_item) {
QString tag;
if (p_item) {
tag = p_item->text();
}
bool ret = activateTag(tag);
if (ret && !tag.isEmpty() && m_fileList->count() == 0) {
promptToRemoveEmptyTag(tag);
}
});
m_tagLabel = new QLabel(tr("Notes"), this);
m_tagLabel->setProperty("TitleLabel", true);
m_fileList = new VListWidget(this);
m_fileList->setAttribute(Qt::WA_MacShowFocusRect, false);
m_fileList->setContextMenuPolicy(Qt::CustomContextMenu);
m_fileList->setSelectionMode(QAbstractItemView::ExtendedSelection);
connect(m_fileList, &QListWidget::itemActivated,
this, &VTagExplorer::openFileItem);
connect(m_fileList, &QListWidget::customContextMenuRequested,
this, &VTagExplorer::handleFileListContextMenuRequested);
QWidget *fileWidget = new QWidget(this);
QVBoxLayout *fileLayout = new QVBoxLayout();
fileLayout->addWidget(m_tagLabel);
fileLayout->addWidget(m_fileList);
fileLayout->setContentsMargins(0, 0, 0, 0);
fileWidget->setLayout(fileLayout);
m_splitter = new QSplitter(this);
m_splitter->setOrientation(Qt::Vertical);
m_splitter->setObjectName("TagExplorerSplitter");
m_splitter->addWidget(m_tagList);
m_splitter->addWidget(fileWidget);
m_splitter->setStretchFactor(0, 0);
m_splitter->setStretchFactor(1, 1);
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addWidget(m_notebookLabel);
mainLayout->addWidget(m_splitter);
mainLayout->setContentsMargins(0, 0, 0, 0);
setLayout(mainLayout);
restoreStateAndGeometry();
}
void VTagExplorer::showEvent(QShowEvent *p_event)
{
setupUI();
QWidget::showEvent(p_event);
updateContent();
}
void VTagExplorer::focusInEvent(QFocusEvent *p_event)
{
setupUI();
QWidget::focusInEvent(p_event);
m_tagList->setFocus();
}
void VTagExplorer::setNotebook(VNotebook *p_notebook)
{
if (p_notebook == m_notebook) {
return;
}
setupUI();
m_notebook = p_notebook;
m_notebookChanged = true;
if (!isVisible()) {
return;
}
updateContent();
}
void VTagExplorer::updateContent()
{
if (m_notebook) {
updateNotebookLabel();
const QStringList &tags = m_notebook->getTags();
if (m_notebookChanged || tagListObsolete(tags)) {
updateTagList(tags);
}
} else {
clear();
}
m_notebookChanged = false;
}
void VTagExplorer::clear()
{
setupUI();
m_fileList->clearAll();
m_tagList->clearAll();
updateTagLabel("");
updateNotebookLabel();
}
void VTagExplorer::updateNotebookLabel()
{
QString text = tr("Tags");
QString tooltip;
if (m_notebook) {
QString name = m_notebook->getName();
tooltip = name;
if (name.size() > MAX_DISPLAY_LENGTH) {
name = name.left(MAX_DISPLAY_LENGTH) + QStringLiteral("...");
}
text = tr("Tags (%1)").arg(name);
}
m_notebookLabel->setText(text);
m_notebookLabel->setToolTip(tooltip);
}
bool VTagExplorer::tagListObsolete(const QStringList &p_tags) const
{
if (m_tagList->count() != p_tags.size()) {
return true;
}
for (int i = 0; i < p_tags.size(); ++i) {
if (p_tags[i] != m_tagList->item(i)->text()) {
return true;
}
}
return false;
}
void VTagExplorer::updateTagLabel(const QString &p_tag)
{
QString text = tr("Notes");
QString tooltip;
if (!p_tag.isEmpty()) {
QString name = p_tag;
tooltip = name;
if (name.size() > MAX_DISPLAY_LENGTH) {
name = name.left(MAX_DISPLAY_LENGTH) + QStringLiteral("...");
}
text = tr("Notes (%1)").arg(name);
}
m_tagLabel->setText(text);
m_tagLabel->setToolTip(tooltip);
}
bool VTagExplorer::activateTag(const QString &p_tag)
{
updateTagLabel(p_tag);
m_fileList->clearAll();
if (p_tag.isEmpty()) {
return false;
}
// Search this tag within current notebook.
g_mainWin->showStatusMessage(tr("Searching for tag \"%1\"").arg(p_tag));
QVector<VNotebook *> notebooks;
notebooks.append(m_notebook);
getVSearch()->clear();
QSharedPointer<VSearchConfig> config(new VSearchConfig(VSearchConfig::CurrentNotebook,
VSearchConfig::Tag,
VSearchConfig::Note,
VSearchConfig::Internal,
VSearchConfig::NoneOption,
p_tag,
QString()));
getVSearch()->setConfig(config);
QSharedPointer<VSearchResult> result = getVSearch()->search(notebooks);
bool ret = result->m_state == VSearchState::Success;
handleSearchFinished(result);
return ret;
}
void VTagExplorer::updateTagList(const QStringList &p_tags)
{
// Clear.
m_tagList->clearAll();
activateTag("");
for (auto const & tag : p_tags) {
addTagItem(tag);
}
if (m_tagList->count() > 0) {
m_tagList->setCurrentRow(0, QItemSelectionModel::ClearAndSelect);
}
}
void VTagExplorer::addTagItem(const QString &p_tag)
{
QListWidgetItem *item = new QListWidgetItem(VIconUtils::treeViewIcon(":/resources/icons/tag.svg"),
p_tag);
item->setToolTip(p_tag);
m_tagList->addItem(item);
}
void VTagExplorer::saveStateAndGeometry()
{
if (!m_uiInitialized) {
return;
}
g_config->setTagExplorerSplitterState(m_splitter->saveState());
}
void VTagExplorer::restoreStateAndGeometry()
{
const QByteArray state = g_config->getTagExplorerSplitterState();
if (!state.isEmpty()) {
m_splitter->restoreState(state);
}
}
void VTagExplorer::initVSearch()
{
m_search = new VSearch(this);
connect(m_search, &VSearch::resultItemAdded,
this, &VTagExplorer::handleSearchItemAdded);
connect(m_search, &VSearch::resultItemsAdded,
this, &VTagExplorer::handleSearchItemsAdded);
connect(m_search, &VSearch::finished,
this, &VTagExplorer::handleSearchFinished);
m_noteIcon = VIconUtils::treeViewIcon(":/resources/icons/note_item.svg");
}
void VTagExplorer::handleSearchItemAdded(const QSharedPointer<VSearchResultItem> &p_item)
{
appendItemToFileList(p_item);
}
void VTagExplorer::appendItemToFileList(const QSharedPointer<VSearchResultItem> &p_item)
{
Q_ASSERT(p_item->m_type == VSearchResultItem::Note);
QListWidgetItem *item = new QListWidgetItem(m_noteIcon,
p_item->m_text.isEmpty() ? p_item->m_path : p_item->m_text);
item->setData(Qt::UserRole, p_item->m_path);
item->setToolTip(p_item->m_path);
m_fileList->addItem(item);
}
void VTagExplorer::handleSearchItemsAdded(const QList<QSharedPointer<VSearchResultItem> > &p_items)
{
for (auto const & it : p_items) {
appendItemToFileList(it);
}
}
void VTagExplorer::handleSearchFinished(const QSharedPointer<VSearchResult> &p_result)
{
Q_ASSERT(p_result->m_state != VSearchState::Idle);
QString msg;
switch (p_result->m_state) {
case VSearchState::Busy:
// Only synchronized search.
Q_ASSERT(false);
msg = tr("Invalid busy state when searching for tag");
break;
case VSearchState::Success:
qDebug() << "search succeeded";
msg = tr("Search for tag succeeded");
break;
case VSearchState::Fail:
qDebug() << "search failed";
msg = tr("Search for tag failed");
break;
case VSearchState::Cancelled:
qDebug() << "search cancelled";
msg = tr("Search for tag calcelled");
break;
default:
break;
}
m_search->clear();
if (!msg.isEmpty()) {
g_mainWin->showStatusMessage(msg);
}
}
void VTagExplorer::openFileItem(QListWidgetItem *p_item) const
{
if (!p_item) {
return;
}
QStringList files;
files << getFilePath(p_item);
g_mainWin->openFiles(files);
}
void VTagExplorer::openSelectedFileItems() const
{
QStringList files;
QList<QListWidgetItem *> selectedItems = m_fileList->selectedItems();
for (auto it : selectedItems) {
files << getFilePath(it);
}
if (!files.isEmpty()) {
g_mainWin->openFiles(files);
}
}
QString VTagExplorer::getFilePath(const QListWidgetItem *p_item) const
{
return p_item->data(Qt::UserRole).toString();
}
void VTagExplorer::handleFileListContextMenuRequested(QPoint p_pos)
{
QListWidgetItem *item = m_fileList->itemAt(p_pos);
if (!item) {
return;
}
QMenu menu(this);
menu.setToolTipsVisible(true);
QAction *openAct = new QAction(tr("&Open"), &menu);
openAct->setToolTip(tr("Open selected notes"));
connect(openAct, &QAction::triggered,
this, &VTagExplorer::openSelectedFileItems);
menu.addAction(openAct);
QList<QListWidgetItem *> selectedItems = m_fileList->selectedItems();
if (selectedItems.size() == 1) {
QAction *locateAct = new QAction(VIconUtils::menuIcon(":/resources/icons/locate_note.svg"),
tr("&Locate To Folder"),
&menu);
locateAct->setToolTip(tr("Locate the folder of current note"));
connect(locateAct, &QAction::triggered,
this, &VTagExplorer::locateCurrentFileItem);
menu.addAction(locateAct);
}
menu.addSeparator();
QAction *addToCartAct = new QAction(VIconUtils::menuIcon(":/resources/icons/cart.svg"),
tr("Add To Cart"),
&menu);
addToCartAct->setToolTip(tr("Add selected notes to Cart for further processing"));
connect(addToCartAct, &QAction::triggered,
this, &VTagExplorer::addFileToCart);
menu.addAction(addToCartAct);
QAction *pinToHistoryAct = new QAction(VIconUtils::menuIcon(":/resources/icons/pin.svg"),
tr("Pin To History"),
&menu);
pinToHistoryAct->setToolTip(tr("Pin selected notes to History"));
connect(pinToHistoryAct, &QAction::triggered,
this, &VTagExplorer::pinFileToHistory);
menu.addAction(pinToHistoryAct);
menu.exec(m_fileList->mapToGlobal(p_pos));
}
void VTagExplorer::locateCurrentFileItem() const
{
auto item = m_fileList->currentItem();
if (!item) {
return;
}
VFile *file = g_vnote->getInternalFile(getFilePath(item));
if (file) {
g_mainWin->locateFile(file);
}
}
void VTagExplorer::addFileToCart() const
{
QList<QListWidgetItem *> items = m_fileList->selectedItems();
VCart *cart = g_mainWin->getCart();
for (int i = 0; i < items.size(); ++i) {
cart->addFile(getFilePath(items[i]));
}
g_mainWin->showStatusMessage(tr("%1 %2 added to Cart")
.arg(items.size())
.arg(items.size() > 1 ? tr("notes") : tr("note")));
}
void VTagExplorer::pinFileToHistory() const
{
QList<QListWidgetItem *> items = m_fileList->selectedItems();
QStringList files;
for (int i = 0; i < items.size(); ++i) {
files << getFilePath(items[i]);
}
g_mainWin->getHistoryList()->pinFiles(files);
g_mainWin->showStatusMessage(tr("%1 %2 pinned to History")
.arg(items.size())
.arg(items.size() > 1 ? tr("notes") : tr("note")));
}
void VTagExplorer::promptToRemoveEmptyTag(const QString &p_tag)
{
Q_ASSERT(!p_tag.isEmpty());
int ret = VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Empty tag detected! Do you want to remove it?"),
tr("The tag <span style=\"%1\">%2</span> seems not to "
"be assigned to any note currently.")
.arg(g_config->c_dataTextStyle)
.arg(p_tag),
QMessageBox::Ok | QMessageBox::Cancel,
QMessageBox::Cancel,
this,
MessageBoxType::Danger);
if (ret == QMessageBox::Cancel) {
return;
}
// Remove the tag from m_notebook.
m_notebook->removeTag(p_tag);
updateContent();
}

113
src/vtagexplorer.h Normal file
View File

@ -0,0 +1,113 @@
#ifndef VTAGEXPLORER_H
#define VTAGEXPLORER_H
#include <QWidget>
#include <QIcon>
#include "vsearchconfig.h"
class QLabel;
class VListWidget;
class QListWidgetItem;
class QSplitter;
class VNotebook;
class VSearch;
class VTagExplorer : public QWidget
{
Q_OBJECT
public:
explicit VTagExplorer(QWidget *p_parent = nullptr);
void clear();
void setNotebook(VNotebook *p_notebook);
void saveStateAndGeometry();
protected:
void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
void focusInEvent(QFocusEvent *p_event) Q_DECL_OVERRIDE;
private slots:
void handleSearchItemAdded(const QSharedPointer<VSearchResultItem> &p_item);
void handleSearchItemsAdded(const QList<QSharedPointer<VSearchResultItem> > &p_items);
void handleSearchFinished(const QSharedPointer<VSearchResult> &p_result);
void openFileItem(QListWidgetItem *p_item) const;
void openSelectedFileItems() const;
void locateCurrentFileItem() const;
void addFileToCart() const;
void pinFileToHistory() const;
void handleFileListContextMenuRequested(QPoint p_pos);
private:
void setupUI();
void updateNotebookLabel();
void updateTagLabel(const QString &p_tag);
bool tagListObsolete(const QStringList &p_tags) const;
void updateTagList(const QStringList &p_tags);
void updateContent();
// Return ture if succeeded.
bool activateTag(const QString &p_tag);
void addTagItem(const QString &p_tag);
void restoreStateAndGeometry();
VSearch *getVSearch() const;
void initVSearch();
void appendItemToFileList(const QSharedPointer<VSearchResultItem> &p_item);
QString getFilePath(const QListWidgetItem *p_item) const;
void promptToRemoveEmptyTag(const QString &p_tag);
bool m_uiInitialized;
QLabel *m_notebookLabel;
QLabel *m_tagLabel;
VListWidget *m_tagList;
VListWidget *m_fileList;
QSplitter *m_splitter;
VNotebook *m_notebook;
bool m_notebookChanged;
QIcon m_noteIcon;
VSearch *m_search;
};
inline VSearch *VTagExplorer::getVSearch() const
{
if (m_search) {
return m_search;
}
const_cast<VTagExplorer *>(this)->initVSearch();
return m_search;
}
#endif // VTAGEXPLORER_H