#include "vtagexplorer.h" #include #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" #include "vnavigationmode.h" #include "vcaptain.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); } }); QVBoxLayout *tagLayout = new QVBoxLayout(); tagLayout->addWidget(m_notebookLabel); tagLayout->addWidget(m_tagList); tagLayout->setContentsMargins(0, 0, 0, 0); QWidget *tagWidget = new QWidget(this); tagWidget->setLayout(tagLayout); m_tagLabel = new QLabel(tr("Notes"), this); m_tagLabel->setProperty("TitleLabel", true); m_splitBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/split_window.svg"), "", this); m_splitBtn->setToolTip(tr("Split")); m_splitBtn->setCheckable(true); m_splitBtn->setProperty("CornerBtn", true); m_splitBtn->setFocusPolicy(Qt::NoFocus); connect(m_splitBtn, &QPushButton::clicked, this, [this](bool p_checked) { g_config->setEnableSplitTagFileList(p_checked); setupFileListSplitOut(p_checked); }); QHBoxLayout *titleLayout = new QHBoxLayout(); titleLayout->addWidget(m_tagLabel); titleLayout->addWidget(m_splitBtn); titleLayout->addStretch(); titleLayout->setContentsMargins(0, 0, 0, 0); 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); QVBoxLayout *fileLayout = new QVBoxLayout(); fileLayout->addLayout(titleLayout); fileLayout->addWidget(m_fileList); fileLayout->setContentsMargins(0, 0, 0, 0); QWidget *fileWidget = new QWidget(this); fileWidget->setLayout(fileLayout); m_splitter = new QSplitter(this); m_splitter->setObjectName("TagExplorerSplitter"); m_splitter->addWidget(tagWidget); m_splitter->addWidget(fileWidget); setupFileListSplitOut(g_config->getEnableSplitTagFileList()); QVBoxLayout *mainLayout = new QVBoxLayout(); 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 notebooks; notebooks.append(m_notebook); getVSearch()->clear(); // We could not use WholeWordOnly here, since "c#" won't match a word. int opts = VSearchConfig::CaseSensitive | VSearchConfig::RegularExpression; QString pattern = QRegExp::escape(p_tag); pattern = "^" + pattern + "$"; QSharedPointer config(new VSearchConfig(VSearchConfig::CurrentNotebook, VSearchConfig::Tag, VSearchConfig::Note, VSearchConfig::Internal, opts, pattern, QString())); getVSearch()->setConfig(config); QSharedPointer 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 &p_item) { appendItemToFileList(p_item); } void VTagExplorer::appendItemToFileList(const QSharedPointer &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 > &p_items) { for (auto const & it : p_items) { appendItemToFileList(it); } } void VTagExplorer::handleSearchFinished(const QSharedPointer &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 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 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 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 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 %2 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(); } void VTagExplorer::registerNavigationTarget() { setupUI(); VNavigationModeListWidgetWrapper *tagWrapper = new VNavigationModeListWidgetWrapper(m_tagList, this); g_mainWin->getCaptain()->registerNavigationTarget(tagWrapper); VNavigationModeListWidgetWrapper *fileWrapper = new VNavigationModeListWidgetWrapper(m_fileList, this); g_mainWin->getCaptain()->registerNavigationTarget(fileWrapper); } void VTagExplorer::setupFileListSplitOut(bool p_enabled) { m_splitBtn->setChecked(p_enabled); if (p_enabled) { m_splitter->setOrientation(Qt::Horizontal); m_splitter->setStretchFactor(0, 1); m_splitter->setStretchFactor(1, 1); } else { m_splitter->setOrientation(Qt::Vertical); m_splitter->setStretchFactor(0, 1); m_splitter->setStretchFactor(1, 2); } }