mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
551 lines
18 KiB
C++
551 lines
18 KiB
C++
#include "vsearcher.h"
|
|
|
|
#include <QtWidgets>
|
|
#include <QCoreApplication>
|
|
|
|
#include "vlineedit.h"
|
|
#include "utils/vutils.h"
|
|
#include "utils/viconutils.h"
|
|
#include "vsearch.h"
|
|
#include "vsearchresulttree.h"
|
|
#include "vmainwindow.h"
|
|
#include "veditarea.h"
|
|
#include "vdirectorytree.h"
|
|
#include "vdirectory.h"
|
|
#include "vnotebookselector.h"
|
|
#include "vnotebook.h"
|
|
#include "vnote.h"
|
|
#include "vconfigmanager.h"
|
|
|
|
extern VMainWindow *g_mainWin;
|
|
|
|
extern VNote *g_vnote;
|
|
|
|
extern VConfigManager *g_config;
|
|
|
|
VSearcher::VSearcher(QWidget *p_parent)
|
|
: QWidget(p_parent),
|
|
m_inSearch(false),
|
|
m_askedToStop(false),
|
|
m_search(this)
|
|
{
|
|
setupUI();
|
|
|
|
initUIFields();
|
|
|
|
handleInputChanged();
|
|
|
|
connect(&m_search, &VSearch::resultItemAdded,
|
|
m_results, &VSearchResultTree::addResultItem);
|
|
connect(&m_search, &VSearch::finished,
|
|
this, &VSearcher::handleSearchFinished);
|
|
}
|
|
|
|
void VSearcher::setupUI()
|
|
{
|
|
// Search button.
|
|
m_searchBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/search.svg"), "", this);
|
|
m_searchBtn->setToolTip(tr("Search"));
|
|
m_searchBtn->setProperty("FlatBtn", true);
|
|
connect(m_searchBtn, &QPushButton::clicked,
|
|
this, &VSearcher::startSearch);
|
|
|
|
// Clear button.
|
|
m_clearBtn = new QPushButton(VIconUtils::buttonDangerIcon(":/resources/icons/clear_search.svg"), "", this);
|
|
m_clearBtn->setToolTip(tr("Clear Results"));
|
|
m_clearBtn->setProperty("FlatBtn", true);
|
|
connect(m_clearBtn, &QPushButton::clicked,
|
|
this, [this]() {
|
|
m_results->clearResults();
|
|
});
|
|
|
|
// Advanced button.
|
|
m_advBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/search_advanced.svg"),
|
|
"",
|
|
this);
|
|
m_advBtn->setToolTip(tr("Advanced Settings"));
|
|
m_advBtn->setProperty("FlatBtn", true);
|
|
m_advBtn->setCheckable(true);
|
|
connect(m_advBtn, &QPushButton::toggled,
|
|
this, [this](bool p_checked) {
|
|
m_advWidget->setVisible(p_checked);
|
|
});
|
|
|
|
// Console button.
|
|
m_consoleBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/search_console.svg"),
|
|
"",
|
|
this);
|
|
m_consoleBtn->setToolTip(tr("Console"));
|
|
m_consoleBtn->setProperty("FlatBtn", true);
|
|
m_consoleBtn->setCheckable(true);
|
|
connect(m_consoleBtn, &QPushButton::toggled,
|
|
this, [this](bool p_checked) {
|
|
m_consoleEdit->setVisible(p_checked);
|
|
});
|
|
|
|
m_numLabel = new QLabel(this);
|
|
|
|
QHBoxLayout *btnLayout = new QHBoxLayout();
|
|
btnLayout->addWidget(m_searchBtn);
|
|
btnLayout->addWidget(m_clearBtn);
|
|
btnLayout->addWidget(m_advBtn);
|
|
btnLayout->addWidget(m_consoleBtn);
|
|
btnLayout->addStretch();
|
|
btnLayout->addWidget(m_numLabel);
|
|
btnLayout->setContentsMargins(0, 0, 3, 0);
|
|
|
|
// Keyword.
|
|
m_keywordCB = VUtils::getComboBox(this);
|
|
m_keywordCB->setEditable(true);
|
|
m_keywordCB->setLineEdit(new VLineEdit(this));
|
|
m_keywordCB->setToolTip(tr("Keywords to search for"));
|
|
m_keywordCB->lineEdit()->setPlaceholderText(tr("Supports space, &&, and ||"));
|
|
connect(m_keywordCB, &QComboBox::currentTextChanged,
|
|
this, &VSearcher::handleInputChanged);
|
|
connect(m_keywordCB->lineEdit(), &QLineEdit::returnPressed,
|
|
this, &VSearcher::animateSearchClick);
|
|
m_keywordCB->completer()->setCaseSensitivity(Qt::CaseSensitive);
|
|
|
|
// Scope.
|
|
m_searchScopeCB = VUtils::getComboBox(this);
|
|
m_searchScopeCB->setToolTip(tr("Scope to search"));
|
|
connect(m_searchScopeCB, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
|
this, &VSearcher::handleInputChanged);
|
|
|
|
// Object.
|
|
m_searchObjectCB = VUtils::getComboBox(this);
|
|
m_searchObjectCB->setToolTip(tr("Object to search"));
|
|
connect(m_searchObjectCB, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
|
this, &VSearcher::handleInputChanged);
|
|
|
|
// Target.
|
|
m_searchTargetCB = VUtils::getComboBox(this);
|
|
m_searchTargetCB->setToolTip(tr("Target to search"));
|
|
connect(m_searchTargetCB, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
|
this, &VSearcher::handleInputChanged);
|
|
|
|
// Pattern.
|
|
m_filePatternCB = VUtils::getComboBox(this);
|
|
m_filePatternCB->setEditable(true);
|
|
m_filePatternCB->setLineEdit(new VLineEdit(this));
|
|
m_filePatternCB->setToolTip(tr("Wildcard pattern to filter the files to be searched"));
|
|
m_filePatternCB->completer()->setCaseSensitivity(Qt::CaseSensitive);
|
|
|
|
// Engine.
|
|
m_searchEngineCB = VUtils::getComboBox(this);
|
|
m_searchEngineCB->setToolTip(tr("Engine to execute the search"));
|
|
|
|
// Case sensitive.
|
|
m_caseSensitiveCB = new QCheckBox(tr("&Case sensitive"), this);
|
|
|
|
// Whole word only.
|
|
m_wholeWordOnlyCB = new QCheckBox(tr("&Whole word only"), this);
|
|
|
|
// Fuzzy search.
|
|
m_fuzzyCB = new QCheckBox(tr("&Fuzzy search"), this);
|
|
m_fuzzyCB->setToolTip(tr("Not available for content search"));
|
|
connect(m_fuzzyCB, &QCheckBox::stateChanged,
|
|
this, [this](int p_state) {
|
|
bool checked = p_state == Qt::Checked;
|
|
m_wholeWordOnlyCB->setEnabled(!checked);
|
|
});
|
|
|
|
// Regular expression.
|
|
m_regularExpressionCB = new QCheckBox(tr("Re&gular expression"), this);
|
|
connect(m_regularExpressionCB, &QCheckBox::stateChanged,
|
|
this, [this](int p_state) {
|
|
bool checked = p_state == Qt::Checked;
|
|
m_wholeWordOnlyCB->setEnabled(!checked);
|
|
m_fuzzyCB->setEnabled(!checked);
|
|
});
|
|
|
|
QFormLayout *advLayout = new QFormLayout();
|
|
advLayout->addRow(tr("File pattern:"), m_filePatternCB);
|
|
advLayout->addRow(tr("Engine:"), m_searchEngineCB);
|
|
advLayout->addRow(m_caseSensitiveCB);
|
|
advLayout->addRow(m_wholeWordOnlyCB);
|
|
advLayout->addRow(m_fuzzyCB);
|
|
advLayout->addRow(m_regularExpressionCB);
|
|
advLayout->setContentsMargins(0, 0, 3, 0);
|
|
|
|
m_advWidget = new QWidget(this);
|
|
m_advWidget->setLayout(advLayout);
|
|
m_advWidget->hide();
|
|
|
|
// Progress bar.
|
|
m_proBar = new QProgressBar(this);
|
|
m_proBar->setRange(0, 0);
|
|
|
|
// Cancel button.
|
|
m_cancelBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/close.svg"),
|
|
"",
|
|
this);
|
|
m_cancelBtn->setToolTip(tr("Cancel"));
|
|
m_cancelBtn->setProperty("FlatBtn", true);
|
|
connect(m_cancelBtn, &QPushButton::clicked,
|
|
this, [this]() {
|
|
if (m_inSearch) {
|
|
appendLogLine(tr("Cancelling the export..."));
|
|
m_askedToStop = true;
|
|
m_search.stop();
|
|
}
|
|
});
|
|
|
|
QHBoxLayout *proLayout = new QHBoxLayout();
|
|
proLayout->addWidget(m_proBar);
|
|
proLayout->addWidget(m_cancelBtn);
|
|
proLayout->setContentsMargins(0, 0, 3, 0);
|
|
|
|
// Console.
|
|
m_consoleEdit = new QPlainTextEdit(this);
|
|
m_consoleEdit->setReadOnly(true);
|
|
m_consoleEdit->setLineWrapMode(QPlainTextEdit::WidgetWidth);
|
|
m_consoleEdit->setProperty("LineEdit", true);
|
|
m_consoleEdit->setPlaceholderText(tr("Output logs will be shown here"));
|
|
m_consoleEdit->setMaximumHeight(m_searchScopeCB->height() * 2);
|
|
m_consoleEdit->hide();
|
|
|
|
// List.
|
|
m_results = new VSearchResultTree(this);
|
|
m_results->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
|
connect(m_results, &VSearchResultTree::countChanged,
|
|
this, [this](int p_count) {
|
|
m_clearBtn->setEnabled(p_count > 0);
|
|
updateNumLabel(p_count);
|
|
});
|
|
|
|
QFormLayout *formLayout = new QFormLayout();
|
|
formLayout->addRow(tr("Keywords:"), m_keywordCB);
|
|
formLayout->addRow(tr("Scope:"), m_searchScopeCB);
|
|
formLayout->addRow(tr("Object:"), m_searchObjectCB);
|
|
formLayout->addRow(tr("Target:"), m_searchTargetCB);
|
|
formLayout->setContentsMargins(0, 0, 3, 0);
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout();
|
|
mainLayout->addLayout(btnLayout);
|
|
mainLayout->addLayout(formLayout);
|
|
mainLayout->addWidget(m_advWidget);
|
|
mainLayout->addLayout(proLayout);
|
|
mainLayout->addWidget(m_consoleEdit);
|
|
mainLayout->addWidget(m_results);
|
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
setLayout(mainLayout);
|
|
}
|
|
|
|
void VSearcher::initUIFields()
|
|
{
|
|
VSearchConfig config = VSearchConfig::fromConfig(g_config->getSearchOptions());
|
|
|
|
// Scope.
|
|
m_searchScopeCB->addItem(tr("Current Note"), VSearchConfig::CurrentNote);
|
|
m_searchScopeCB->addItem(tr("Opened Notes"), VSearchConfig::OpenedNotes);
|
|
m_searchScopeCB->addItem(tr("Current Folder"), VSearchConfig::CurrentFolder);
|
|
m_searchScopeCB->addItem(tr("Current Notebook"), VSearchConfig::CurrentNotebook);
|
|
m_searchScopeCB->addItem(tr("All Notebooks"), VSearchConfig::AllNotebooks);
|
|
m_searchScopeCB->setCurrentIndex(m_searchScopeCB->findData(config.m_scope));
|
|
|
|
// Object.
|
|
m_searchObjectCB->addItem(tr("Name"), VSearchConfig::Name);
|
|
m_searchObjectCB->addItem(tr("Content"), VSearchConfig::Content);
|
|
m_searchObjectCB->addItem(tr("Outline"), VSearchConfig::Outline);
|
|
m_searchObjectCB->setCurrentIndex(m_searchObjectCB->findData(config.m_object));
|
|
|
|
// Target.
|
|
m_searchTargetCB->addItem(tr("Note"), VSearchConfig::Note);
|
|
m_searchTargetCB->addItem(tr("Folder"), VSearchConfig::Folder);
|
|
m_searchTargetCB->addItem(tr("Notebook"), VSearchConfig::Notebook);
|
|
m_searchTargetCB->addItem(tr("All"),
|
|
VSearchConfig::Note
|
|
| VSearchConfig:: Folder
|
|
| VSearchConfig::Notebook);
|
|
m_searchTargetCB->setCurrentIndex(m_searchTargetCB->findData(config.m_target));
|
|
|
|
// Engine.
|
|
m_searchEngineCB->addItem(tr("Internal"), VSearchConfig::Internal);
|
|
m_searchEngineCB->setCurrentIndex(m_searchEngineCB->findData(config.m_engine));
|
|
|
|
// Pattern.
|
|
m_filePatternCB->setCurrentText(config.m_pattern);
|
|
|
|
m_caseSensitiveCB->setChecked(config.m_option & VSearchConfig::CaseSensitive);
|
|
m_wholeWordOnlyCB->setChecked(config.m_option & VSearchConfig::WholeWordOnly);
|
|
m_fuzzyCB->setChecked(config.m_option & VSearchConfig::Fuzzy);
|
|
m_regularExpressionCB->setChecked(config.m_option & VSearchConfig::RegularExpression);
|
|
|
|
setProgressVisible(false);
|
|
|
|
m_clearBtn->setEnabled(false);
|
|
}
|
|
|
|
void VSearcher::updateItemToComboBox(QComboBox *p_comboBox)
|
|
{
|
|
QString text = p_comboBox->currentText();
|
|
if (!text.isEmpty() && p_comboBox->findText(text) == -1) {
|
|
p_comboBox->addItem(text);
|
|
}
|
|
}
|
|
|
|
void VSearcher::setProgressVisible(bool p_visible)
|
|
{
|
|
m_proBar->setVisible(p_visible);
|
|
m_cancelBtn->setVisible(p_visible);
|
|
}
|
|
|
|
void VSearcher::appendLogLine(const QString &p_text)
|
|
{
|
|
m_consoleEdit->appendPlainText(">>> " + p_text);
|
|
m_consoleEdit->ensureCursorVisible();
|
|
QCoreApplication::sendPostedEvents();
|
|
}
|
|
|
|
void VSearcher::showMessage(const QString &p_text) const
|
|
{
|
|
g_mainWin->showStatusMessage(p_text);
|
|
QCoreApplication::sendPostedEvents();
|
|
}
|
|
|
|
void VSearcher::handleInputChanged()
|
|
{
|
|
bool readyToSearch = true;
|
|
|
|
// Keyword.
|
|
QString keyword = m_keywordCB->currentText();
|
|
readyToSearch = !keyword.isEmpty();
|
|
|
|
if (readyToSearch) {
|
|
// Other targets are only available for Name.
|
|
int obj = m_searchObjectCB->currentData().toInt();
|
|
if (obj != VSearchConfig::Name) {
|
|
int target = m_searchTargetCB->currentData().toInt();
|
|
if (!(target & VSearchConfig::Note)) {
|
|
readyToSearch = false;
|
|
}
|
|
}
|
|
|
|
if (readyToSearch && obj == VSearchConfig::Outline) {
|
|
// Outline is available only for CurrentNote and OpenedNotes.
|
|
int scope = m_searchScopeCB->currentData().toInt();
|
|
if (scope != VSearchConfig::CurrentNote
|
|
&& scope != VSearchConfig::OpenedNotes) {
|
|
readyToSearch = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_searchBtn->setEnabled(readyToSearch);
|
|
}
|
|
|
|
void VSearcher::startSearch()
|
|
{
|
|
if (m_inSearch) {
|
|
return;
|
|
}
|
|
|
|
m_searchBtn->setEnabled(false);
|
|
setProgressVisible(true);
|
|
m_results->clearResults();
|
|
m_askedToStop = false;
|
|
m_inSearch = true;
|
|
m_consoleEdit->clear();
|
|
appendLogLine(tr("Search started."));
|
|
|
|
updateItemToComboBox(m_keywordCB);
|
|
updateItemToComboBox(m_filePatternCB);
|
|
|
|
QSharedPointer<VSearchConfig> config(new VSearchConfig(m_searchScopeCB->currentData().toInt(),
|
|
m_searchObjectCB->currentData().toInt(),
|
|
m_searchTargetCB->currentData().toInt(),
|
|
m_searchEngineCB->currentData().toInt(),
|
|
getSearchOption(),
|
|
m_keywordCB->currentText(),
|
|
m_filePatternCB->currentText()));
|
|
m_search.setConfig(config);
|
|
|
|
g_config->setSearchOptions(config->toConfig());
|
|
|
|
QSharedPointer<VSearchResult> result;
|
|
switch (config->m_scope) {
|
|
case VSearchConfig::CurrentNote:
|
|
{
|
|
QVector<VFile *> files;
|
|
files.append(g_mainWin->getCurrentFile());
|
|
if (files[0]) {
|
|
QString msg(tr("Search current note %1.").arg(files[0]->getName()));
|
|
appendLogLine(msg);
|
|
showMessage(msg);
|
|
}
|
|
|
|
result = m_search.search(files);
|
|
break;
|
|
}
|
|
|
|
case VSearchConfig::OpenedNotes:
|
|
{
|
|
QVector<VEditTabInfo> tabs = g_mainWin->getEditArea()->getAllTabsInfo();
|
|
QVector<VFile *> files;
|
|
files.reserve(tabs.size());
|
|
for (auto const & ta : tabs) {
|
|
files.append(ta.m_editTab->getFile());
|
|
}
|
|
|
|
result = m_search.search(files);
|
|
break;
|
|
}
|
|
|
|
case VSearchConfig::CurrentFolder:
|
|
{
|
|
VDirectory *dir = g_mainWin->getDirectoryTree()->currentDirectory();
|
|
if (dir) {
|
|
QString msg(tr("Search current folder %1.").arg(dir->getName()));
|
|
appendLogLine(msg);
|
|
showMessage(msg);
|
|
}
|
|
|
|
result = m_search.search(dir);
|
|
break;
|
|
}
|
|
|
|
case VSearchConfig::CurrentNotebook:
|
|
{
|
|
QVector<VNotebook *> notebooks;
|
|
notebooks.append(g_mainWin->getNotebookSelector()->currentNotebook());
|
|
if (notebooks[0]) {
|
|
QString msg(tr("Search current notebook %1.").arg(notebooks[0]->getName()));
|
|
appendLogLine(msg);
|
|
showMessage(msg);
|
|
}
|
|
|
|
result = m_search.search(notebooks);
|
|
break;
|
|
}
|
|
|
|
case VSearchConfig::AllNotebooks:
|
|
{
|
|
const QVector<VNotebook *> ¬ebooks = g_vnote->getNotebooks();
|
|
result = m_search.search(notebooks);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
handleSearchFinished(result);
|
|
}
|
|
|
|
void VSearcher::handleSearchFinished(const QSharedPointer<VSearchResult> &p_result)
|
|
{
|
|
Q_ASSERT(m_inSearch);
|
|
Q_ASSERT(p_result->m_state != VSearchState::Idle);
|
|
|
|
qDebug() << "handleSearchFinished" << (int)p_result->m_state;
|
|
|
|
QString msg;
|
|
switch (p_result->m_state) {
|
|
case VSearchState::Busy:
|
|
msg = tr("Search is on going.");
|
|
appendLogLine(msg);
|
|
return;
|
|
|
|
case VSearchState::Success:
|
|
msg = tr("Search succeeded.");
|
|
appendLogLine(msg);
|
|
showMessage(msg);
|
|
break;
|
|
|
|
case VSearchState::Fail:
|
|
msg = tr("Search failed.");
|
|
appendLogLine(msg);
|
|
showMessage(msg);
|
|
VUtils::showMessage(QMessageBox::Warning,
|
|
tr("Warning"),
|
|
tr("Search failed."),
|
|
p_result->m_errMsg,
|
|
QMessageBox::Ok,
|
|
QMessageBox::Ok,
|
|
this);
|
|
break;
|
|
|
|
case VSearchState::Cancelled:
|
|
Q_ASSERT(m_askedToStop);
|
|
msg = tr("User cancelled the search. Aborted!");
|
|
appendLogLine(msg);
|
|
showMessage(msg);
|
|
m_askedToStop = false;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (p_result->m_state != VSearchState::Fail
|
|
&& p_result->hasError()) {
|
|
VUtils::showMessage(QMessageBox::Warning,
|
|
tr("Warning"),
|
|
tr("Errors found during search."),
|
|
p_result->m_errMsg,
|
|
QMessageBox::Ok,
|
|
QMessageBox::Ok,
|
|
this);
|
|
}
|
|
|
|
m_search.clear();
|
|
|
|
m_inSearch = false;
|
|
m_searchBtn->setEnabled(true);
|
|
setProgressVisible(false);
|
|
}
|
|
|
|
void VSearcher::animateSearchClick()
|
|
{
|
|
m_searchBtn->animateClick();
|
|
}
|
|
|
|
int VSearcher::getSearchOption() const
|
|
{
|
|
int ret = VSearchConfig::NoneOption;
|
|
|
|
if (m_caseSensitiveCB->isChecked()) {
|
|
ret |= VSearchConfig::CaseSensitive;
|
|
}
|
|
|
|
if (m_wholeWordOnlyCB->isChecked()) {
|
|
ret |= VSearchConfig::WholeWordOnly;
|
|
}
|
|
|
|
if (m_fuzzyCB->isChecked()) {
|
|
ret |= VSearchConfig::Fuzzy;
|
|
}
|
|
|
|
if (m_regularExpressionCB->isChecked()) {
|
|
ret |= VSearchConfig::RegularExpression;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void VSearcher::updateNumLabel(int p_count)
|
|
{
|
|
m_numLabel->setText(tr("%1 Items").arg(p_count));
|
|
}
|
|
|
|
void VSearcher::focusToSearch()
|
|
{
|
|
m_keywordCB->setFocus(Qt::OtherFocusReason);
|
|
}
|
|
|
|
void VSearcher::showNavigation()
|
|
{
|
|
VNavigationMode::showNavigation(m_results);
|
|
}
|
|
|
|
bool VSearcher::handleKeyNavigation(int p_key, bool &p_succeed)
|
|
{
|
|
static bool secondKey = false;
|
|
return VNavigationMode::handleKeyNavigation(m_results,
|
|
secondKey,
|
|
p_key,
|
|
p_succeed);
|
|
}
|