support full-text search (#1733)

This commit is contained in:
Le Tan 2021-04-13 20:53:08 +08:00 committed by GitHub
parent 372f092919
commit fe827e74f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 3100 additions and 114 deletions

View File

@ -5,28 +5,36 @@
#include <QCoreApplication>
#include <QDebug>
#define TR(x) QCoreApplication::translate("main", (x))
#include <widgets/mainwindow.h>
using vnotex::MainWindow;
CommandLineOptions::ParseResult CommandLineOptions::parse(const QStringList &p_arguments)
{
QCommandLineParser parser;
parser.setApplicationDescription(TR("A pleasant note-taking platform."));
parser.setApplicationDescription(MainWindow::tr("A pleasant note-taking platform."));
const auto helpOpt = parser.addHelpOption();
const auto versionOpt = parser.addVersionOption();
// Positional arguments.
parser.addPositionalArgument("paths", TR("Files or folders to open."));
parser.addPositionalArgument("paths", MainWindow::tr("Files or folders to open."));
const QCommandLineOption verboseOpt("verbose", TR("Print more logs."));
const QCommandLineOption verboseOpt("verbose", MainWindow::tr("Print more logs."));
parser.addOption(verboseOpt);
// WebEngine options.
// No need to handle them. Just add them to the parser to avoid parse error.
QCommandLineOption webRemoteDebuggingPortOpt("remote-debugging-port",
TR("WebEngine remote debugging port."),
"port_number");
webRemoteDebuggingPortOpt.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(webRemoteDebuggingPortOpt);
{
QCommandLineOption webRemoteDebuggingPortOpt("remote-debugging-port",
MainWindow::tr("WebEngine remote debugging port."),
"port_number");
webRemoteDebuggingPortOpt.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(webRemoteDebuggingPortOpt);
QCommandLineOption webNoSandboxOpt("no-sandbox", MainWindow::tr("WebEngine without sandbox."));
webNoSandboxOpt.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(webNoSandboxOpt);
}
if (!parser.parse(p_arguments)) {
m_errorMsg = parser.errorText();

View File

@ -42,6 +42,7 @@ HEADERS += \
$$PWD/filelocator.h \
$$PWD/fileopenparameters.h \
$$PWD/htmltemplatehelper.h \
$$PWD/location.h \
$$PWD/logger.h \
$$PWD/mainconfig.h \
$$PWD/markdowneditorconfig.h \

View File

@ -23,6 +23,8 @@ namespace vnotex
CloseTab,
NavigationDock,
OutlineDock,
SearchDock,
LocationListDock,
NavigationMode,
LocateNode,
VerticalSplit,

View File

@ -29,6 +29,9 @@ namespace vnotex
// Open as read-only.
bool m_readOnly = false;
// If m_lineNumber > -1, it indicates the line to scroll to after opening the file.
int m_lineNumber = -1;
};
}

View File

@ -64,12 +64,14 @@ namespace vnotex
enum FindOption
{
None = 0,
FindNone = 0,
FindBackward = 0x1U,
CaseSensitive = 0x2U,
WholeWordOnly = 0x4U,
RegularExpression = 0x8U,
IncrementalSearch = 0x10U
IncrementalSearch = 0x10U,
// Used in full-text search.
FuzzySearch = 0x20U
};
Q_DECLARE_FLAGS(FindOptions, FindOption);

72
src/core/location.h Normal file
View File

@ -0,0 +1,72 @@
#ifndef LOCATION_H
#define LOCATION_H
#include <QDebug>
namespace vnotex
{
struct Location
{
friend QDebug operator<<(QDebug p_dbg, const Location &p_loc)
{
QDebugStateSaver saver(p_dbg);
p_dbg.nospace() << p_loc.m_path << ":" << p_loc.m_lineNumber;
return p_dbg;
}
// TODO: support encoding like buffer/notebook.
QString m_path;
QString m_displayPath;
int m_lineNumber = -1;
};
enum class LocationType
{
Buffer,
File,
Folder,
Notebook
};
struct ComplexLocation
{
struct Line
{
Line() = default;
Line(int p_lineNumber, const QString &p_text)
: m_lineNumber(p_lineNumber),
m_text(p_text)
{
}
int m_lineNumber = -1;
QString m_text;
};
void addLine(int p_lineNumber, const QString &p_text)
{
m_lines.push_back(Line(p_lineNumber, p_text));
}
friend QDebug operator<<(QDebug p_dbg, const ComplexLocation &p_loc)
{
QDebugStateSaver saver(p_dbg);
p_dbg.nospace() << static_cast<int>(p_loc.m_type) << p_loc.m_path << p_loc.m_displayPath;
return p_dbg;
}
LocationType m_type = LocationType::File;
QString m_path;
QString m_displayPath;
QVector<Line> m_lines;
};
}
#endif // LOCATION_H

View File

@ -62,6 +62,8 @@ void SessionConfig::init()
}
m_exportOption.fromJson(sessionJobj[QStringLiteral("export_option")].toObject());
m_searchOption.fromJson(sessionJobj[QStringLiteral("search_option")].toObject());
}
void SessionConfig::loadCore(const QJsonObject &p_session)
@ -177,6 +179,7 @@ QJsonObject SessionConfig::toJson() const
obj[QStringLiteral("notebooks")] = saveNotebooks();
obj[QStringLiteral("state_geometry")] = saveStateAndGeometry();
obj[QStringLiteral("export_option")] = m_exportOption.toJson();
obj[QStringLiteral("search_option")] = m_searchOption.toJson();
return obj;
}
@ -289,6 +292,16 @@ void SessionConfig::setExportOption(const ExportOption &p_option)
updateConfig(m_exportOption, p_option, this);
}
const SearchOption &SessionConfig::getSearchOption() const
{
return m_searchOption;
}
void SessionConfig::setSearchOption(const SearchOption &p_option)
{
updateConfig(m_searchOption, p_option, this);
}
void SessionConfig::loadStateAndGeometry(const QJsonObject &p_session)
{
const auto obj = p_session.value(QStringLiteral("state_geometry")).toObject();

View File

@ -7,6 +7,7 @@
#include <QVector>
#include <export/exportdata.h>
#include <search/searchdata.h>
namespace vnotex
{
@ -87,6 +88,9 @@ namespace vnotex
const ExportOption &getExportOption() const;
void setExportOption(const ExportOption &p_option);
const SearchOption &getSearchOption() const;
void setSearchOption(const SearchOption &p_option);
private:
void loadCore(const QJsonObject &p_session);
@ -123,6 +127,8 @@ namespace vnotex
int m_minimizeToSystemTray = -1;
ExportOption m_exportOption;
SearchOption m_searchOption;
};
} // ns vnotex

View File

@ -8,6 +8,7 @@
#include "buffermgr.h"
#include "configmgr.h"
#include "coreconfig.h"
#include "location.h"
#include "fileopenparameters.h"

View File

@ -16,6 +16,7 @@ namespace vnotex
struct FileOpenParameters;
class Event;
class Notebook;
struct ComplexLocation;
class VNoteX : public QObject
{

View File

@ -39,7 +39,7 @@ namespace vnotex
private:
int m_outlineAutoExpandedLevel = 6;
FindOptions m_findAndReplaceOptions = FindOption::None;
FindOptions m_findAndReplaceOptions = FindOption::FindNone;
int m_nodeExplorerViewOrder = 0;

View File

@ -35,8 +35,8 @@
<file>icons/remove_notebook.svg</file>
<file>icons/close_notebook.svg</file>
<file>icons/recycle_bin.svg</file>
<file>icons/saved.svg</file>
<file>icons/save_editor.svg</file>
<file>icons/buffer.svg</file>
<file>icons/attachment_editor.svg</file>
<file>icons/attachment_full_editor.svg</file>
<file>icons/split_menu.svg</file>
@ -74,6 +74,8 @@
<file>icons/stay_on_top.svg</file>
<file>icons/outline_editor.svg</file>
<file>icons/find_replace_editor.svg</file>
<file>icons/search.svg</file>
<file>icons/cancel.svg</file>
<file>icons/section_number_editor.svg</file>
<file>icons/sort.svg</file>
<file>logo/vnote.svg</file>

View File

@ -0,0 +1,14 @@
<?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 style="fill:#000000" d="M112,64v16v320h16V80h304v337.143c0,8.205-6.652,14.857-14.857,14.857H94.857C86.652,432,80,425.348,80,417.143V128h16v-16
H64v305.143C64,434.157,77.843,448,94.857,448h322.285C434.157,448,448,434.157,448,417.143V64H112z"/>
<rect style="fill:#000000" x="160" y="112" width="128" height="16"/>
<rect style="fill:#000000" x="160" y="192" width="240" height="16"/>
<rect style="fill:#000000" x="160" y="272" width="192" height="16"/>
<rect style="fill:#000000" x="160" y="352" width="240" height="16"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1018 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1616726105805" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1116" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m0 820c-205.4 0-372-166.6-372-372 0-89 31.3-170.8 83.5-234.8l523.3 523.3C682.8 852.7 601 884 512 884z m288.5-137.2L277.2 223.5C341.2 171.3 423 140 512 140c205.4 0 372 166.6 372 372 0 89-31.3 170.8-83.5 234.8z" p-id="1117" fill="#000000"></path></svg>

After

Width:  |  Height:  |  Size: 713 B

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1609209772514" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3065" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M819.2 104.96H168.96c-45.1712 0-81.92 36.7488-81.92 81.92v650.24c0 45.1712 36.7488 81.92 81.92 81.92h650.24c45.1712 0 81.92-36.7488 81.92-81.92V186.88c0-45.1712-36.7488-81.92-81.92-81.92zM488.96 207.36v79.36c0 25.44896 20.63104 46.08 46.08 46.08s46.08-20.63104 46.08-46.08V207.36h43.52v176.64H366.08V207.36h122.88z m309.76 609.28H189.44V207.36h74.24v197.12c0 45.1712 36.7488 81.92 81.92 81.92h299.52c45.1712 0 81.92-36.7488 81.92-81.92V207.36h71.68v609.28z" p-id="3066" fill="#000000"></path></svg>

Before

Width:  |  Height:  |  Size: 875 B

View File

@ -0,0 +1,10 @@
<?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" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#000000" d="M445,386.7l-84.8-85.9c13.8-24.1,21-50.9,21-77.9c0-87.6-71.2-158.9-158.6-158.9C135.2,64,64,135.3,64,222.9
c0,87.6,71.2,158.9,158.6,158.9c27.9,0,55.5-7.7,80.1-22.4l84.4,85.6c1.9,1.9,4.6,3.1,7.3,3.1c2.7,0,5.4-1.1,7.3-3.1l43.3-43.8
C449,397.1,449,390.7,445,386.7z M222.6,125.9c53.4,0,96.8,43.5,96.8,97c0,53.5-43.4,97-96.8,97c-53.4,0-96.8-43.5-96.8-97
C125.8,169.4,169.2,125.9,222.6,125.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 905 B

View File

@ -17,6 +17,8 @@
"CloseTab" : "Ctrl+G, X",
"NavigationDock" : "Ctrl+G, 1",
"OutlineDock" : "Ctrl+G, 2",
"SearchDock" : "Ctrl+G, 3",
"LocationListDock" : "Ctrl+G, 4",
"NavigationMode" : "Ctrl+G, W",
"LocateNode" : "Ctrl+G, D",
"VerticalSplit" : "Ctrl+G, \\",
@ -260,7 +262,7 @@
"//comment" : "Whether insert the file name as title on new file",
"insert_file_name_as_title" : true,
"//comment" : "none/read/edit",
"section_number" : "read",
"section_number" : "none",
"//comment" : "Base level to start section numbering",
"section_number_base_level" : 2,
"//comment" : "Style of the section number in edit mode",

View File

@ -412,6 +412,24 @@ vnotex--MainWindow QLabel#MainWindowTipsLabel {
}
/* QLineEdit */
QLineEdit[EmbeddedLineEdit="true"] {
border: none;
padding: 0px;
margin: 0px;
color: @widgets#qlineedit#fg;
background-color: transparent;
}
QLineEdit[EmbeddedLineEdit="true"]:focus {
border: none;
background-color: @widgets#qlineedit#focus#bg;
}
QLineEdit[EmbeddedLineEdit="true"]:hover {
border: none;
background-color: @widgets#qlineedit#hover#bg;
}
QLineEdit {
border: 1px solid @widgets#qlineedit#border;
padding: 3px;

View File

@ -233,6 +233,11 @@
"fg" : "@base#icon#inactive#fg"
}
},
"locationlist" : {
"node_icon" : {
"fg" : "@base#icon#fg"
}
},
"viewsplit" : {
"action_button" : {
"fg" : "@base#icon#inactive#fg",

View File

@ -92,6 +92,11 @@
"fg" : "@base#icon#disabled#fg"
}
},
"locationlist" : {
"node_icon" : {
"fg" : "@base#icon#fg"
}
},
"viewsplit" : {
"action_button" : {
"fg" : "#808080",

View File

@ -412,6 +412,24 @@ vnotex--MainWindow QLabel#MainWindowTipsLabel {
}
/* QLineEdit */
QLineEdit[EmbeddedLineEdit="true"] {
border: none;
padding: 0px;
margin: 0px;
color: @widgets#qlineedit#fg;
background-color: transparent;
}
QLineEdit[EmbeddedLineEdit="true"]:focus {
border: none;
background-color: @widgets#qlineedit#focus#bg;
}
QLineEdit[EmbeddedLineEdit="true"]:hover {
border: none;
background-color: @widgets#qlineedit#hover#bg;
}
QLineEdit {
border: 1px solid @widgets#qlineedit#border;
padding: 3px;

View File

@ -229,6 +229,11 @@
"fg" : "@base#icon#inactive#fg"
}
},
"locationlist" : {
"node_icon" : {
"fg" : "@base#icon#fg"
}
},
"viewsplit" : {
"action_button" : {
"fg" : "@base#icon#inactive#fg",

View File

@ -89,8 +89,9 @@ int main(int argc, char *argv[])
break;
case CommandLineOptions::Error:
showMessageOnCommandLineIfAvailable(cmdOptions.m_errorMsg);
return -1;
fprintf(stderr, "%s\n", qPrintable(cmdOptions.m_errorMsg));
// Arguments to WebEngineView will be unknown ones. So just let it go.
break;
case CommandLineOptions::VersionRequested:
{
@ -244,6 +245,6 @@ void showMessageOnCommandLineIfAvailable(const QString &p_msg)
MessageBoxHelper::notify(MessageBoxHelper::Information,
QString("<pre>%1</pre>").arg(p_msg));
#else
printf("%s\n", qPrintable(p_msg));
fprintf(stderr, "%s\n", qPrintable(p_msg));
#endif
}

View File

@ -0,0 +1,259 @@
#include "filesearchengine.h"
#include <QFile>
#include <QMimeDatabase>
#include <QDebug>
#include "searchresultitem.h"
using namespace vnotex;
FileSearchEngineWorker::FileSearchEngineWorker(QObject *p_parent)
: QThread(p_parent)
{
}
void FileSearchEngineWorker::setData(const QVector<SearchSecondPhaseItem> &p_items,
const QSharedPointer<SearchOption> &p_option,
const SearchToken &p_token)
{
m_items = p_items;
m_option = p_option;
m_token = p_token;
}
void FileSearchEngineWorker::stop()
{
m_askedToStop.store(1);
}
bool FileSearchEngineWorker::isAskedToStop() const
{
return m_askedToStop.load() == 1;
}
void FileSearchEngineWorker::run()
{
const int c_batchSize = 100;
QMimeDatabase mimeDatabase;
m_state = SearchState::Busy;
m_results.clear();
int nr = 0;
for (const auto &item : m_items) {
if (isAskedToStop()) {
m_state = SearchState::Stopped;
break;
}
const QMimeType mimeType = mimeDatabase.mimeTypeForFile(item.m_filePath);
if (mimeType.isValid() && !mimeType.inherits(QStringLiteral("text/plain"))) {
appendError(tr("Skip binary file (%1)").arg(item.m_filePath));
continue;
}
searchFile(item.m_filePath, item.m_displayPath);
if (++nr >= c_batchSize) {
nr = 0;
processBatchResults();
}
}
processBatchResults();
if (m_state == SearchState::Busy) {
m_state = SearchState::Finished;
}
}
void FileSearchEngineWorker::appendError(const QString &p_err)
{
m_errors.append(p_err);
}
void FileSearchEngineWorker::searchFile(const QString &p_filePath, const QString &p_displayPath)
{
QFile file(p_filePath);
if (!file.open(QIODevice::ReadOnly)) {
return;
}
const bool shouldStartBatchMode = m_token.shouldStartBatchMode();
if (shouldStartBatchMode) {
m_token.startBatchMode();
}
QSharedPointer<SearchResultItem> resultItem;
int lineNum = 1;
QTextStream ins(&file);
while (!ins.atEnd()) {
if (isAskedToStop()) {
m_state = SearchState::Stopped;
break;
}
const auto lineText = ins.readLine();
bool matched = false;
if (!shouldStartBatchMode) {
matched = m_token.matched(lineText);
} else {
matched = m_token.matchedInBatchMode(lineText);
}
if (matched) {
if (resultItem) {
resultItem->addLine(lineNum, lineText);
} else {
resultItem = SearchResultItem::createFileItem(p_filePath, p_displayPath, lineNum, lineText);
}
}
if (shouldStartBatchMode && m_token.readyToEndBatchMode()) {
break;
}
++lineNum;
}
if (shouldStartBatchMode) {
bool allMatched = m_token.readyToEndBatchMode();
m_token.endBatchMode();
if (!allMatched) {
// This file does not meet all the tokens.
resultItem.reset();
}
}
if (resultItem) {
m_results.append(resultItem);
}
}
void FileSearchEngineWorker::processBatchResults()
{
if (!m_results.isEmpty()) {
emit resultItemsReady(m_results);
m_results.clear();
}
}
FileSearchEngine::FileSearchEngine()
{
}
FileSearchEngine::~FileSearchEngine()
{
stopInternal();
clearInternal();
}
void FileSearchEngine::search(const QSharedPointer<SearchOption> &p_option,
const SearchToken &p_token,
const QVector<SearchSecondPhaseItem> &p_items)
{
int numThread = QThread::idealThreadCount();
if (numThread < 1) {
numThread = 1;
}
Q_ASSERT(!p_items.isEmpty());
if (p_items.size() < numThread) {
numThread = 1;
}
clearWorkers();
m_workers.reserve(numThread);
const int totalSize = p_items.size();
const int step = totalSize / numThread;
int remain = totalSize % numThread;
int start = 0;
for (int i = 0; i < numThread && start < totalSize; ++i) {
int len = step;
if (remain) {
++len;
--remain;
}
if (start + len > totalSize) {
len = totalSize - start;
}
auto th = QSharedPointer<FileSearchEngineWorker>::create();
th->setData(p_items.mid(start, len), p_option, p_token);
connect(th.data(), &FileSearchEngineWorker::finished,
this, &FileSearchEngine::handleWorkerFinished);
connect(th.data(), &FileSearchEngineWorker::resultItemsReady,
this, &FileSearchEngine::resultItemsAdded);
m_workers.append(th);
th->start();
start += len;
}
}
void FileSearchEngine::stop()
{
stopInternal();
}
void FileSearchEngine::stopInternal()
{
for (const auto &th : m_workers) {
th->stop();
}
}
void FileSearchEngine::clear()
{
clearInternal();
}
void FileSearchEngine::clearInternal()
{
clearWorkers();
}
void FileSearchEngine::clearWorkers()
{
for (const auto &th : m_workers) {
th->quit();
th->wait();
}
m_workers.clear();
m_numOfFinishedWorkers = 0;
}
void FileSearchEngine::handleWorkerFinished()
{
++m_numOfFinishedWorkers;
if (m_numOfFinishedWorkers == m_workers.size()) {
SearchState state = SearchState::Finished;
for (const auto &th : m_workers) {
if (th->m_state == SearchState::Failed) {
if (state != SearchState::Stopped) {
state = SearchState::Failed;
}
} else if (th->m_state == SearchState::Stopped) {
state = SearchState::Stopped;
}
for (const auto &err : th->m_errors) {
emit logRequested(err);
}
Q_ASSERT(th->isFinished());
}
m_workers.clear();
m_numOfFinishedWorkers = 0;
emit finished(state);
}
}

View File

@ -0,0 +1,98 @@
#ifndef SEARCHENGINE_H
#define SEARCHENGINE_H
#include "isearchengine.h"
#include <QThread>
#include <QRegularExpression>
#include <QAtomicInt>
#include <QVector>
#include "searchtoken.h"
#include "searchdata.h"
namespace vnotex
{
struct SearchResultItem;
class FileSearchEngineWorker : public QThread
{
Q_OBJECT
friend class FileSearchEngine;
public:
explicit FileSearchEngineWorker(QObject *p_parent = nullptr);
~FileSearchEngineWorker() = default;
void setData(const QVector<SearchSecondPhaseItem> &p_items,
const QSharedPointer<SearchOption> &p_option,
const SearchToken &p_token);
public slots:
void stop();
signals:
void resultItemsReady(const QVector<QSharedPointer<SearchResultItem>> &p_items);
protected:
void run() Q_DECL_OVERRIDE;
private:
void appendError(const QString &p_err);
void searchFile(const QString &p_filePath, const QString &p_displayPath);
void processBatchResults();
bool isAskedToStop() const;
QAtomicInt m_askedToStop = 0;
QVector<SearchSecondPhaseItem> m_items;
SearchToken m_token;
QSharedPointer<SearchOption> m_option;
SearchState m_state = SearchState::Idle;
QStringList m_errors;
QVector<QSharedPointer<SearchResultItem>> m_results;
};
class FileSearchEngine : public ISearchEngine
{
Q_OBJECT
public:
FileSearchEngine();
~FileSearchEngine();
void search(const QSharedPointer<SearchOption> &p_option,
const SearchToken &p_token,
const QVector<SearchSecondPhaseItem> &p_items);
void stop() Q_DECL_OVERRIDE;
void clear() Q_DECL_OVERRIDE;
private slots:
void handleWorkerFinished();
private:
void clearWorkers();
// Need non-virtual version of this.
void stopInternal();
// Need non-virtual version of this.
void clearInternal();
int m_numOfFinishedWorkers = 0;
QVector<QSharedPointer<FileSearchEngineWorker>> m_workers;
};
}
#endif // SEARCHENGINE_H

View File

@ -0,0 +1,57 @@
#ifndef ISEARCHENGINE_H
#define ISEARCHENGINE_H
#include <QObject>
#include <QVector>
#include <QList>
#include <QSharedPointer>
#include "searchdata.h"
namespace vnotex
{
struct SearchResultItem;
class SearchToken;
struct SearchSecondPhaseItem
{
SearchSecondPhaseItem() = default;
SearchSecondPhaseItem(const QString &p_filePath, const QString &p_displayPath)
: m_filePath(p_filePath),
m_displayPath(p_displayPath)
{
}
QString m_filePath;
QString m_displayPath;
};
class ISearchEngine : public QObject
{
Q_OBJECT
public:
ISearchEngine() = default;
virtual ~ISearchEngine() = default;
virtual void search(const QSharedPointer<SearchOption> &p_option,
const SearchToken &p_token,
const QVector<SearchSecondPhaseItem> &p_items) = 0;
virtual void stop() = 0;
virtual void clear() = 0;
signals:
void finished(SearchState p_state);
void resultItemsAdded(const QVector<QSharedPointer<SearchResultItem>> &p_items);
void logRequested(const QString &p_log);
};
}
#endif // ISEARCHENGINE_H

17
src/search/search.pri Normal file
View File

@ -0,0 +1,17 @@
QT += widgets
HEADERS += \
$$PWD/filesearchengine.h \
$$PWD/isearchengine.h \
$$PWD/searchdata.h \
$$PWD/searcher.h \
$$PWD/searchresultitem.h \
$$PWD/searchtoken.h
SOURCES += \
$$PWD/filesearchengine.cpp \
$$PWD/searchdata.cpp \
$$PWD/searcher.cpp \
$$PWD/searchresultitem.cpp \
$$PWD/searchtoken.cpp

47
src/search/searchdata.cpp Normal file
View File

@ -0,0 +1,47 @@
#include "searchdata.h"
#include <QJsonObject>
using namespace vnotex;
SearchOption::SearchOption()
: m_objects(SearchObject::SearchName | SearchObject::SearchContent),
m_targets(SearchTarget::SearchFile | SearchTarget::SearchFolder)
{
}
QJsonObject SearchOption::toJson() const
{
QJsonObject obj;
obj["file_pattern"] = m_filePattern;
obj["scope"] = static_cast<int>(m_scope);
obj["objects"] = static_cast<int>(m_objects);
obj["targets"] = static_cast<int>(m_targets);
obj["engine"] = static_cast<int>(m_engine);
obj["find_options"] = static_cast<int>(m_findOptions);
return obj;
}
void SearchOption::fromJson(const QJsonObject &p_obj)
{
if (p_obj.isEmpty()) {
return;
}
m_filePattern = p_obj["file_pattern"].toString();
m_scope = static_cast<SearchScope>(p_obj["scope"].toInt());
m_objects = static_cast<SearchObjects>(p_obj["objects"].toInt());
m_targets = static_cast<SearchTargets>(p_obj["targets"].toInt());
m_engine = static_cast<SearchEngine>(p_obj["engine"].toInt());
m_findOptions = static_cast<FindOptions>(p_obj["find_options"].toInt());
}
bool SearchOption::operator==(const SearchOption &p_other) const
{
return m_filePattern == p_other.m_filePattern
&& m_scope == p_other.m_scope
&& m_objects == p_other.m_objects
&& m_targets == p_other.m_targets
&& m_engine == p_other.m_engine
&& m_findOptions == p_other.m_findOptions;
}

110
src/search/searchdata.h Normal file
View File

@ -0,0 +1,110 @@
#ifndef SEARCHOPTION_H
#define SEARCHOPTION_H
#include <QObject>
#include <QString>
#include <core/global.h>
namespace vnotex
{
enum class SearchState
{
Idle = 0,
Busy,
Finished,
Failed,
Stopped
};
class SearchTranslate : public QObject
{
Q_OBJECT
};
inline QString SearchStateToString(SearchState p_state)
{
switch (p_state) {
case SearchState::Idle:
return SearchTranslate::tr("Idle");
case SearchState::Busy:
return SearchTranslate::tr("Busy");
case SearchState::Finished:
return SearchTranslate::tr("Finished");
case SearchState::Failed:
return SearchTranslate::tr("Failed");
case SearchState::Stopped:
return SearchTranslate::tr("Stopped");
}
return QString();
}
enum class SearchScope
{
Buffers = 0,
CurrentFolder,
CurrentNotebook,
AllNotebooks
};
enum SearchObject
{
ObjectNone = 0,
SearchName = 0x1UL,
SearchContent = 0x2UL,
SearchOutline = 0x4UL,
SearchTag = 0x8UL,
SearchPath = 0x10UL
};
Q_DECLARE_FLAGS(SearchObjects, SearchObject);
enum SearchTarget
{
TargetNone = 0,
SearchFile = 0x1UL,
SearchFolder = 0x2UL,
SearchNotebook = 0x4UL
};
Q_DECLARE_FLAGS(SearchTargets, SearchTarget);
enum class SearchEngine
{
Internal = 0
};
struct SearchOption
{
SearchOption();
QJsonObject toJson() const;
void fromJson(const QJsonObject &p_obj);
bool operator==(const SearchOption &p_other) const;
QString m_keyword;
QString m_filePattern;
SearchScope m_scope = SearchScope::CurrentNotebook;
// *nix requests to init in the constructor.
SearchObjects m_objects;
// *nix requests to init in the constructor.
SearchTargets m_targets;
SearchEngine m_engine = SearchEngine::Internal;
FindOptions m_findOptions = FindOption::FindNone;
};
}
Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::SearchObjects);
Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::SearchTargets);
#endif // SEARCHOPTION_H

501
src/search/searcher.cpp Normal file
View File

@ -0,0 +1,501 @@
#include "searcher.h"
#include <QCoreApplication>
#include <QDebug>
#include <buffer/buffer.h>
#include <core/file.h>
#include <notebook/node.h>
#include <notebook/notebook.h>
#include "searchresultitem.h"
#include "filesearchengine.h"
using namespace vnotex;
Searcher::Searcher(QObject *p_parent)
: QObject(p_parent)
{
}
void Searcher::clear()
{
m_option.clear();
if (m_engine) {
m_engine->clear();
m_engine.reset();
}
m_askedToStop = false;
}
void Searcher::stop()
{
m_askedToStop = true;
if (m_engine) {
m_engine->stop();
}
}
SearchState Searcher::search(const QSharedPointer<SearchOption> &p_option, const QList<Buffer *> &p_buffers)
{
if (!(p_option->m_targets & SearchTarget::SearchFile)) {
// Only File target is applicable.
return SearchState::Finished;
}
if (!prepare(p_option)) {
return SearchState::Failed;
}
emit logRequested(tr("Searching %n buffer(s)", "", p_buffers.size()));
emit progressUpdated(0, p_buffers.size());
for (int i = 0; i < p_buffers.size(); ++i) {
if (!p_buffers[i]) {
continue;
}
if (isAskedToStop()) {
return SearchState::Stopped;
}
auto file = p_buffers[i]->getFile();
if (!firstPhaseSearch(file.data())) {
return SearchState::Failed;
}
emit progressUpdated(i + 1, p_buffers.size());
}
return SearchState::Finished;
}
SearchState Searcher::search(const QSharedPointer<SearchOption> &p_option, Node *p_folder)
{
Q_ASSERT(p_folder->isContainer());
if (!(p_option->m_targets & (SearchTarget::SearchFile | SearchTarget::SearchFolder))) {
// Only File/Folder target is applicable.
return SearchState::Finished;
}
if (!prepare(p_option)) {
return SearchState::Failed;
}
emit logRequested(tr("Searching folder (%1)").arg(p_folder->getName()));
QVector<SearchSecondPhaseItem> secondPhaseItems;
if (!firstPhaseSearchFolder(p_folder, secondPhaseItems)) {
return SearchState::Failed;
}
if (isAskedToStop()) {
return SearchState::Stopped;
}
if (!secondPhaseItems.isEmpty()) {
// Do second phase search.
if (!secondPhaseSearch(secondPhaseItems)) {
return SearchState::Failed;
}
if (isAskedToStop()) {
return SearchState::Stopped;
}
return SearchState::Busy;
}
return SearchState::Finished;
}
SearchState Searcher::search(const QSharedPointer<SearchOption> &p_option, const QVector<Notebook *> &p_notebooks)
{
if (!prepare(p_option)) {
return SearchState::Failed;
}
QVector<SearchSecondPhaseItem> secondPhaseItems;
emit progressUpdated(0, p_notebooks.size());
for (int i = 0; i < p_notebooks.size(); ++i) {
if (isAskedToStop()) {
return SearchState::Stopped;
}
emit logRequested(tr("Searching notebook (%1)").arg(p_notebooks[i]->getName()));
if (!firstPhaseSearch(p_notebooks[i], secondPhaseItems)) {
return SearchState::Failed;
}
emit progressUpdated(i + 1, p_notebooks.size());
}
if (isAskedToStop()) {
return SearchState::Stopped;
}
if (!secondPhaseItems.isEmpty()) {
// Do second phase search.
if (!secondPhaseSearch(secondPhaseItems)) {
return SearchState::Failed;
}
if (isAskedToStop()) {
return SearchState::Stopped;
}
return SearchState::Busy;
}
return SearchState::Finished;
}
bool Searcher::prepare(const QSharedPointer<SearchOption> &p_option)
{
Q_ASSERT(!m_option);
m_option = p_option;
if (!SearchToken::compile(m_option->m_keyword, m_option->m_findOptions, m_token)) {
emit logRequested(tr("Failed to compile tokens (%1)").arg(m_option->m_keyword));
return false;
}
if (m_option->m_filePattern.isEmpty()) {
m_filePattern = QRegularExpression();
} else {
m_filePattern = QRegularExpression(QRegularExpression::wildcardToRegularExpression(m_option->m_filePattern), QRegularExpression::CaseInsensitiveOption);
}
return true;
}
bool Searcher::isAskedToStop() const
{
QCoreApplication::sendPostedEvents();
return m_askedToStop;
}
static QString tryGetRelativePath(const File *p_file)
{
const auto node = p_file->getNode();
if (node) {
return node->fetchPath();
}
return p_file->getFilePath();
}
bool Searcher::firstPhaseSearch(const File *p_file)
{
if (!p_file) {
return true;
}
Q_ASSERT(testTarget(SearchTarget::SearchFile));
const auto name = p_file->getName();
if (!isFilePatternMatched(name)) {
return true;
}
const auto filePath = p_file->getFilePath();
const auto relativePath = tryGetRelativePath(p_file);
if (testObject(SearchObject::SearchName)) {
if (isTokenMatched(name)) {
emit resultItemAdded(SearchResultItem::createBufferItem(filePath, relativePath, -1, name));
}
}
if (testObject(SearchObject::SearchPath)) {
if (isTokenMatched(relativePath)) {
emit resultItemAdded(SearchResultItem::createBufferItem(filePath, relativePath, -1, name));
}
}
if (testObject(SearchObject::SearchOutline)) {
emit logRequested(tr("Searching outline is not supported yet"));
}
if (testObject(SearchObject::SearchTag)) {
emit logRequested(tr("Searching tag is not supported yet"));
}
// Make SearchContent always the last one to check.
if (testObject(SearchObject::SearchContent)) {
if (!searchContent(p_file)) {
return false;
}
}
return true;
}
bool Searcher::isFilePatternMatched(const QString &p_name) const
{
if (m_option->m_filePattern.isEmpty()) {
return true;
}
return m_filePattern.match(p_name).hasMatch();
}
bool Searcher::testTarget(SearchTarget p_target) const
{
return m_option->m_targets & p_target;
}
bool Searcher::testObject(SearchObject p_object) const
{
return m_option->m_objects & p_object;
}
bool Searcher::isTokenMatched(const QString &p_text) const
{
return m_token.matched(p_text);
}
bool Searcher::searchContent(const File *p_file)
{
const auto content = p_file->read();
if (content.isEmpty()) {
return true;
}
const bool shouldStartBatchMode = m_token.shouldStartBatchMode();
if (shouldStartBatchMode) {
m_token.startBatchMode();
}
const auto filePath = p_file->getFilePath();
const auto relativePath = tryGetRelativePath(p_file);
QSharedPointer<SearchResultItem> resultItem;
int lineNum = 1;
int pos = 0;
int contentSize = content.size();
QRegularExpression newlineRegExp("\\n|\\r\\n|\\r");
while (pos < contentSize) {
if (isAskedToStop()) {
break;
}
QRegularExpressionMatch match;
int idx = content.indexOf(newlineRegExp, pos, &match);
if (idx == -1) {
idx = contentSize;
}
if (idx > pos) {
QString lineText = content.mid(pos, idx - pos);
bool matched = false;
if (!shouldStartBatchMode) {
matched = m_token.matched(lineText);
} else {
matched = m_token.matchedInBatchMode(lineText);
}
if (matched) {
if (resultItem) {
resultItem->addLine(lineNum, lineText);
} else {
resultItem = SearchResultItem::createBufferItem(filePath, relativePath, lineNum, lineText);
}
}
}
if (idx == contentSize) {
break;
}
if (shouldStartBatchMode && m_token.readyToEndBatchMode()) {
break;
}
pos = idx + match.capturedLength();
++lineNum;
}
if (shouldStartBatchMode) {
bool allMatched = m_token.readyToEndBatchMode();
m_token.endBatchMode();
if (!allMatched) {
// This file does not meet all the tokens.
resultItem.reset();
}
}
if (resultItem) {
emit resultItemAdded(resultItem);
}
return true;
}
bool Searcher::firstPhaseSearchFolder(Node *p_node, QVector<SearchSecondPhaseItem> &p_secondPhaseItems)
{
if (!p_node) {
return true;
}
Q_ASSERT(p_node->isContainer());
Q_ASSERT(testTarget(SearchTarget::SearchFile) || testTarget(SearchTarget::SearchFolder));
p_node->load();
if (testTarget(SearchTarget::SearchFolder)) {
const auto name = p_node->getName();
const auto folderPath = p_node->fetchAbsolutePath();
const auto relativePath = p_node->fetchPath();
if (testObject(SearchObject::SearchName)) {
if (isTokenMatched(name)) {
emit resultItemAdded(SearchResultItem::createFolderItem(folderPath, relativePath));
}
}
if (testObject(SearchObject::SearchPath)) {
if (isTokenMatched(relativePath)) {
emit resultItemAdded(SearchResultItem::createFolderItem(folderPath, relativePath));
}
}
}
// Search children.
const auto &children = p_node->getChildrenRef();
for (const auto &child : children) {
if (isAskedToStop()) {
return true;
}
if (child->hasContent() && testTarget(SearchTarget::SearchFile)) {
if (!firstPhaseSearch(child.data(), p_secondPhaseItems)) {
return false;
}
}
if (child->isContainer()) {
if (!firstPhaseSearchFolder(child.data(), p_secondPhaseItems)) {
return false;
}
}
}
return true;
}
bool Searcher::firstPhaseSearch(Node *p_node, QVector<SearchSecondPhaseItem> &p_secondPhaseItems)
{
if (!p_node) {
return true;
}
Q_ASSERT(testTarget(SearchTarget::SearchFile));
const auto name = p_node->getName();
if (!isFilePatternMatched(name)) {
return true;
}
const auto filePath = p_node->fetchAbsolutePath();
const auto relativePath = p_node->fetchPath();
if (testObject(SearchObject::SearchName)) {
if (isTokenMatched(name)) {
emit resultItemAdded(SearchResultItem::createFileItem(filePath, relativePath, -1, name));
}
}
if (testObject(SearchObject::SearchPath)) {
if (isTokenMatched(relativePath)) {
emit resultItemAdded(SearchResultItem::createFileItem(filePath, relativePath, -1, name));
}
}
if (testObject(SearchObject::SearchOutline)) {
emit logRequested(tr("Searching outline is not supported yet"));
}
if (testObject(SearchObject::SearchTag)) {
emit logRequested(tr("Searching tag is not supported yet"));
}
if (testObject(SearchObject::SearchContent)) {
p_secondPhaseItems.push_back(SearchSecondPhaseItem(filePath, relativePath));
}
return true;
}
bool Searcher::firstPhaseSearch(Notebook *p_notebook, QVector<SearchSecondPhaseItem> &p_secondPhaseItems)
{
if (!p_notebook) {
return true;
}
if (testTarget(SearchTarget::SearchNotebook)) {
if (testObject(SearchObject::SearchName)) {
const auto name = p_notebook->getName();
if (isTokenMatched(name)) {
emit resultItemAdded(SearchResultItem::createNotebookItem(p_notebook->getRootFolderAbsolutePath(),
name));
}
}
}
if (!testTarget(SearchTarget::SearchFile) && !testTarget(SearchTarget::SearchFolder)) {
return true;
}
auto rootNode = p_notebook->getRootNode();
Q_ASSERT(rootNode->isLoaded());
const auto &children = rootNode->getChildrenRef();
for (const auto &child : children) {
if (isAskedToStop()) {
return true;
}
if (child->hasContent() && testTarget(SearchTarget::SearchFile)) {
if (!firstPhaseSearch(child.data(), p_secondPhaseItems)) {
return false;
}
}
if (child->isContainer()) {
if (!firstPhaseSearchFolder(child.data(), p_secondPhaseItems)) {
return false;
}
}
}
return true;
}
bool Searcher::secondPhaseSearch(const QVector<SearchSecondPhaseItem> &p_secondPhaseItems)
{
Q_ASSERT(!p_secondPhaseItems.isEmpty());
qDebug() << "secondPhaseSearch" << p_secondPhaseItems.size();
createSearchEngine();
m_engine->search(m_option, m_token, p_secondPhaseItems);
connect(m_engine.data(), &ISearchEngine::finished,
this, &Searcher::finished);
connect(m_engine.data(), &ISearchEngine::logRequested,
this, &Searcher::logRequested);
connect(m_engine.data(), &ISearchEngine::resultItemsAdded,
this, &Searcher::resultItemsAdded);
return true;
}
void Searcher::createSearchEngine()
{
Q_ASSERT(m_option->m_engine == SearchEngine::Internal);
m_engine.reset(new FileSearchEngine());
}

93
src/search/searcher.h Normal file
View File

@ -0,0 +1,93 @@
#ifndef SEARCHER_H
#define SEARCHER_H
#include <QObject>
#include <QSharedPointer>
#include <QScopedPointer>
#include <QRegularExpression>
#include "searchdata.h"
#include "searchtoken.h"
#include "isearchengine.h"
namespace vnotex
{
class Buffer;
class File;
struct SearchResultItem;
class Node;
class Notebook;
class Searcher : public QObject
{
Q_OBJECT
public:
explicit Searcher(QObject *p_parent = nullptr);
void clear();
void stop();
SearchState search(const QSharedPointer<SearchOption> &p_option, const QList<Buffer *> &p_buffers);
SearchState search(const QSharedPointer<SearchOption> &p_option, Node *p_folder);
SearchState search(const QSharedPointer<SearchOption> &p_option, const QVector<Notebook *> &p_notebooks);
signals:
void progressUpdated(int p_val, int p_maximum);
void logRequested(const QString &p_log);
void resultItemAdded(const QSharedPointer<SearchResultItem> &p_item);
void resultItemsAdded(const QVector<QSharedPointer<SearchResultItem>> &p_items);
void finished(SearchState p_state);
private:
bool isAskedToStop() const;
bool prepare(const QSharedPointer<SearchOption> &p_option);
// Return false if there is failure.
// Always search content at first phase.
bool firstPhaseSearch(const File *p_file);
// Return false if there is failure.
bool firstPhaseSearchFolder(Node *p_node, QVector<SearchSecondPhaseItem> &p_secondPhaseItems);
// Return false if there is failure.
bool firstPhaseSearch(Node *p_node, QVector<SearchSecondPhaseItem> &p_secondPhaseItems);
// Return false if there is failure.
bool firstPhaseSearch(Notebook *p_notebook, QVector<SearchSecondPhaseItem> &p_secondPhaseItems);
// Return false if there is failure.
bool secondPhaseSearch(const QVector<SearchSecondPhaseItem> &p_secondPhaseItems);
bool isFilePatternMatched(const QString &p_name) const;
bool testTarget(SearchTarget p_target) const;
bool testObject(SearchObject p_object) const;
bool isTokenMatched(const QString &p_text) const;
bool searchContent(const File *p_file);
void createSearchEngine();
QSharedPointer<SearchOption> m_option;
SearchToken m_token;
QRegularExpression m_filePattern;
bool m_askedToStop = false;
QScopedPointer<ISearchEngine> m_engine;
};
}
#endif // SEARCHER_H

View File

@ -0,0 +1,54 @@
#include "searchresultitem.h"
using namespace vnotex;
QSharedPointer<SearchResultItem> SearchResultItem::createBufferItem(const QString &p_targetPath,
const QString &p_displayPath,
int p_lineNumber,
const QString &p_text)
{
auto item = QSharedPointer<SearchResultItem>::create();
item->m_location.m_type = LocationType::Buffer;
item->m_location.m_path = p_targetPath;
item->m_location.m_displayPath = p_displayPath;
item->m_location.addLine(p_lineNumber, p_text);
return item;
}
QSharedPointer<SearchResultItem> SearchResultItem::createFileItem(const QString &p_targetPath,
const QString &p_displayPath,
int p_lineNumber,
const QString &p_text)
{
auto item = QSharedPointer<SearchResultItem>::create();
item->m_location.m_type = LocationType::File;
item->m_location.m_path = p_targetPath;
item->m_location.m_displayPath = p_displayPath;
item->m_location.addLine(p_lineNumber, p_text);
return item;
}
QSharedPointer<SearchResultItem> SearchResultItem::createFolderItem(const QString &p_targetPath,
const QString &p_displayPath)
{
auto item = QSharedPointer<SearchResultItem>::create();
item->m_location.m_type = LocationType::Folder;
item->m_location.m_path = p_targetPath;
item->m_location.m_displayPath = p_displayPath;
return item;
}
QSharedPointer<SearchResultItem> SearchResultItem::createNotebookItem(const QString &p_targetPath,
const QString &p_displayPath)
{
auto item = QSharedPointer<SearchResultItem>::create();
item->m_location.m_type = LocationType::Notebook;
item->m_location.m_path = p_targetPath;
item->m_location.m_displayPath = p_displayPath;
return item;
}
void SearchResultItem::addLine(int p_lineNumber, const QString &p_text)
{
m_location.addLine(p_lineNumber, p_text);
}

View File

@ -0,0 +1,42 @@
#ifndef SEARCHRESULTITEM_H
#define SEARCHRESULTITEM_H
#include <QSharedPointer>
#include <QString>
#include <QDebug>
#include <core/location.h>
namespace vnotex
{
struct SearchResultItem
{
friend QDebug operator<<(QDebug p_dbg, const SearchResultItem &p_item)
{
p_dbg << p_item.m_location;
return p_dbg;
}
void addLine(int p_lineNumber, const QString &p_text);
static QSharedPointer<SearchResultItem> createBufferItem(const QString &p_targetPath,
const QString &p_displayPath,
int p_lineNumber,
const QString &p_text);
static QSharedPointer<SearchResultItem> createFileItem(const QString &p_targetPath,
const QString &p_displayPath,
int p_lineNumber,
const QString &p_text);
static QSharedPointer<SearchResultItem> createFolderItem(const QString &p_targetPath,
const QString &p_displayPath);
static QSharedPointer<SearchResultItem> createNotebookItem(const QString &p_targetPath,
const QString &p_displayPath);
ComplexLocation m_location;
};
}
#endif // SEARCHRESULTITEM_H

247
src/search/searchtoken.cpp Normal file
View File

@ -0,0 +1,247 @@
#include "searchtoken.h"
#include <QCommandLineParser>
#include <QDebug>
#include <utils/processutils.h>
#include <widgets/searchpanel.h>
using namespace vnotex;
QScopedPointer<QCommandLineParser> SearchToken::s_parser;
void SearchToken::clear()
{
m_type = Type::PlainText;
m_operator = Operator::And;
m_caseSensitivity = Qt::CaseInsensitive;
m_keywords.clear();
m_regularExpressions.clear();
m_matchedConstraintsInBatchMode.clear();
m_matchedConstraintsCountInBatchMode = 0;
}
void SearchToken::append(const QString &p_text)
{
m_keywords.append(p_text);
}
void SearchToken::append(const QRegularExpression &p_regExp)
{
m_regularExpressions.append(p_regExp);
}
bool SearchToken::matched(const QString &p_text) const
{
const int consSize = constraintSize();
if (consSize == 0) {
return false;
}
bool isMatched = m_operator == Operator::And ? true : false;
for (int i = 0; i < consSize; ++i) {
bool consMatched = false;
if (m_type == Type::PlainText) {
consMatched = p_text.contains(m_keywords[i], m_caseSensitivity);
} else {
consMatched = p_text.contains(m_regularExpressions[i]);
}
if (consMatched) {
if (m_operator == Operator::Or) {
isMatched = true;
break;
}
} else if (m_operator == Operator::And) {
isMatched = false;
break;
}
}
return isMatched;
}
int SearchToken::constraintSize() const
{
return (m_type == Type::PlainText ? m_keywords.size() : m_regularExpressions.size());
}
bool SearchToken::shouldStartBatchMode() const
{
return constraintSize() > 1;
}
void SearchToken::startBatchMode()
{
m_matchedConstraintsInBatchMode.fill(false, constraintSize());
m_matchedConstraintsCountInBatchMode = 0;
}
bool SearchToken::matchedInBatchMode(const QString &p_text)
{
bool isMatched = false;
const int consSize = m_matchedConstraintsInBatchMode.size();
for (int i = 0; i < consSize; ++i) {
if (m_matchedConstraintsInBatchMode[i]) {
continue;
}
bool consMatched = false;
if (m_type == Type::PlainText) {
consMatched = p_text.contains(m_keywords[i], m_caseSensitivity);
} else {
consMatched = p_text.contains(m_regularExpressions[i]);
}
if (consMatched) {
m_matchedConstraintsInBatchMode[i] = true;
++m_matchedConstraintsCountInBatchMode;
isMatched = true;
}
}
return isMatched;
}
bool SearchToken::readyToEndBatchMode() const
{
if (m_operator == Operator::And) {
// We need all the tokens matched.
if (m_matchedConstraintsCountInBatchMode == m_matchedConstraintsInBatchMode.size()) {
return true;
}
} else {
// We only need one match.
if (m_matchedConstraintsCountInBatchMode > 0) {
return true;
}
}
return false;
}
void SearchToken::endBatchMode()
{
m_matchedConstraintsInBatchMode.clear();
m_matchedConstraintsCountInBatchMode = 0;
}
bool SearchToken::isEmpty() const
{
return constraintSize() == 0;
}
void SearchToken::createCommandLineParser()
{
if (s_parser) {
return;
}
s_parser.reset(new QCommandLineParser());
s_parser->setApplicationDescription(SearchPanel::tr("Full-text search."));
QCommandLineOption caseSensitiveOpt(QStringList() << "c" << "case-sensitive", SearchPanel::tr("Search in case sensitive."));
s_parser->addOption(caseSensitiveOpt);
QCommandLineOption regularExpressionOpt(QStringList() << "r" << "regular-expression", SearchPanel::tr("Search by regular expression."));
s_parser->addOption(regularExpressionOpt);
QCommandLineOption wholeWordOnlyOpt(QStringList() << "w" << "whole-word-only", SearchPanel::tr("Search whole word only."));
s_parser->addOption(wholeWordOnlyOpt);
QCommandLineOption fuzzySearchOpt(QStringList() << "f" << "fuzzy-search", SearchPanel::tr("Do a fuzzy search (not applicable to content search)."));
s_parser->addOption(fuzzySearchOpt);
QCommandLineOption orOpt(QStringList() << "o" << "or", SearchPanel::tr("Do an OR combination of keywords."));
s_parser->addOption(orOpt);
s_parser->addPositionalArgument("keywords", SearchPanel::tr("Keywords to search."));
}
bool SearchToken::compile(const QString &p_keyword, FindOptions p_options, SearchToken &p_token)
{
p_token.clear();
if (p_keyword.isEmpty()) {
return false;
}
createCommandLineParser();
auto caseSensitivity = p_options & FindOption::CaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
bool isRegularExpression = p_options & FindOption::RegularExpression;
bool isWholeWordOnly = p_options & FindOption::WholeWordOnly;
bool isFuzzySearch = p_options & FindOption::FuzzySearch;
auto args = ProcessUtils::parseCombinedArgString(p_keyword);
// The parser needs the first arg to be the application name.
args.prepend("vnotex");
if (!s_parser->parse(args))
{
return false;
}
if (s_parser->isSet("c")) {
caseSensitivity = Qt::CaseSensitive;
}
if (s_parser->isSet("r")) {
isRegularExpression = true;
}
if (s_parser->isSet("w")) {
isWholeWordOnly = true;
}
if (s_parser->isSet("f")) {
isFuzzySearch = true;
}
args = s_parser->positionalArguments();
if (args.isEmpty()) {
return false;
}
p_token.m_caseSensitivity = caseSensitivity;
if (isRegularExpression || isWholeWordOnly || isFuzzySearch) {
p_token.m_type = Type::RegularExpression;
} else {
p_token.m_type = Type::PlainText;
}
p_token.m_operator = s_parser->isSet("o") ? Operator::Or : Operator::And;
auto patternOptions = caseSensitivity == Qt::CaseInsensitive ? QRegularExpression::CaseInsensitiveOption
: QRegularExpression::NoPatternOption;
for (const auto &ar : args) {
if (ar.isEmpty()) {
continue;
}
if (isRegularExpression) {
p_token.append(QRegularExpression(ar, patternOptions));
} else if (isFuzzySearch) {
// ABC -> *A*B*C*.
QString wildcardText(ar.size() * 2 + 1, '*');
for (int i = 0, j = 1; i < ar.size(); ++i, j += 2) {
wildcardText[j] = ar[i];
}
p_token.append(QRegularExpression(QRegularExpression::wildcardToRegularExpression(wildcardText),
patternOptions));
} else if (isWholeWordOnly) {
auto pattern = QRegularExpression::escape(ar);
pattern = "\\b" + pattern + "\\b";
p_token.append(QRegularExpression(pattern, patternOptions));
} else {
p_token.append(ar);
}
}
return !p_token.isEmpty();
}
QString SearchToken::getHelpText()
{
createCommandLineParser();
auto text = s_parser->helpText();
// Skip the first line containing the application name.
return text.mid(text.indexOf('\n') + 1);
}

86
src/search/searchtoken.h Normal file
View File

@ -0,0 +1,86 @@
#ifndef SEARCHTOKEN_H
#define SEARCHTOKEN_H
#include <QString>
#include <QVector>
#include <QRegularExpression>
#include <QBitArray>
#include <QPair>
#include <QScopedPointer>
#include <core/global.h>
class QCommandLineParser;
namespace vnotex
{
class SearchToken
{
public:
enum class Type
{
PlainText,
RegularExpression
};
enum class Operator
{
And,
Or
};
void clear();
void append(const QString &p_text);
void append(const QRegularExpression &p_regExp);
// Whether @p_text is matched.
bool matched(const QString &p_text) const;
int constraintSize() const;
bool isEmpty() const;
bool shouldStartBatchMode() const;
// Batch Mode: use a list of text string to match the same token.
void startBatchMode();
// Match one string in batch mode.
// Return true if @p_text is matched.
bool matchedInBatchMode(const QString &p_text);
bool readyToEndBatchMode() const;
void endBatchMode();
// Compile tokens from keyword.
// Support some magic switchs in the keyword which will suppress the given options.
static bool compile(const QString &p_keyword, FindOptions p_options, SearchToken &p_token);
static QString getHelpText();
private:
static void createCommandLineParser();
Type m_type = Type::PlainText;
Operator m_operator = Operator::And;
Qt::CaseSensitivity m_caseSensitivity = Qt::CaseInsensitive;
QStringList m_keywords;
QVector<QRegularExpression> m_regularExpressions;
// [i] is true only if m_keywords[i] or m_regularExpressions[i] is matched.
QBitArray m_matchedConstraintsInBatchMode;
int m_matchedConstraintsCountInBatchMode = 0;
static QScopedPointer<QCommandLineParser> s_parser;
};
}
#endif // SEARCHTOKEN_H

View File

@ -45,6 +45,8 @@ include($$PWD/utils/utils.pri)
include($$PWD/export/export.pri)
include($$PWD/search/search.pri)
include($$PWD/core/core.pri)
include($$PWD/widgets/widgets.pri)

View File

@ -22,6 +22,9 @@
#include <QMenu>
#include <QDebug>
#include <QLineEdit>
#include <QLayout>
#include <core/global.h>
using namespace vnotex;
@ -361,3 +364,8 @@ void WidgetUtils::selectBaseName(QLineEdit *p_lineEdit)
int dotIndex = text.lastIndexOf(QLatin1Char('.'));
p_lineEdit->setSelection(0, (dotIndex == -1) ? text.size() : dotIndex);
}
void WidgetUtils::setContentsMargins(QLayout *p_layout)
{
p_layout->setContentsMargins(CONTENTS_MARGIN, CONTENTS_MARGIN, CONTENTS_MARGIN, CONTENTS_MARGIN);
}

View File

@ -18,6 +18,7 @@ class QListView;
class QMenu;
class QShortcut;
class QLineEdit;
class QLayout;
namespace vnotex
{
@ -81,6 +82,8 @@ namespace vnotex
// Select the base name part of the line edit content.
static void selectBaseName(QLineEdit *p_lineEdit);
static void setContentsMargins(QLayout *p_layout);
private:
static void resizeToHideScrollBar(QScrollArea *p_scroll, bool p_vertical, bool p_horizontal);
};

View File

@ -204,7 +204,7 @@ void AttachmentPopup::setupUI()
QToolButton *AttachmentPopup::createButton()
{
auto btn = new QToolButton(this);
btn->setProperty(PropertyDefs::s_actionToolButton, true);
btn->setProperty(PropertyDefs::c_actionToolButton, true);
return btn;
}

View File

@ -26,7 +26,7 @@ void Dialog::setCentralWidget(QWidget *p_widget)
{
Q_ASSERT(!m_centralWidget && p_widget);
m_centralWidget = p_widget;
m_centralWidget->setProperty(PropertyDefs::s_dialogCentralWidget, true);
m_centralWidget->setProperty(PropertyDefs::c_dialogCentralWidget, true);
m_layout->addWidget(m_centralWidget);
}
@ -115,7 +115,7 @@ void Dialog::setInformationText(const QString &p_text, InformationLevel p_level)
break;
}
WidgetUtils::setPropertyDynamically(m_infoTextEdit, PropertyDefs::s_state, level);
WidgetUtils::setPropertyDynamically(m_infoTextEdit, PropertyDefs::c_state, level);
if (needResize) {
WidgetUtils::updateSize(this);
}

View File

@ -387,8 +387,8 @@ void ExportDialog::rejectedButtonClicked()
{
if (m_exportOngoing) {
// Just cancel the export.
appendLog(tr("Cancelling the export."));
m_exporter->stop();
appendLog(tr("Cancelling the export"));
getExporter()->stop();
} else {
Dialog::rejectedButtonClicked();
}

View File

@ -71,7 +71,7 @@ void ManageNotebooksDialog::setupUI()
});
m_deleteNotebookBtn = new QPushButton(tr("Delete (DANGER)"), infoWidget);
WidgetUtils::setPropertyDynamically(m_deleteNotebookBtn, PropertyDefs::s_dangerButton, true);
WidgetUtils::setPropertyDynamically(m_deleteNotebookBtn, PropertyDefs::c_dangerButton, true);
btnLayout->addWidget(m_deleteNotebookBtn);
connect(m_deleteNotebookBtn, &QPushButton::clicked,
this, [this]() {

View File

@ -29,7 +29,7 @@ void ScrollDialog::setCentralWidget(QWidget *p_widget)
{
Q_ASSERT(!m_centralWidget && p_widget);
m_centralWidget = p_widget;
m_centralWidget->setProperty(PropertyDefs::s_dialogCentralWidget, true);
m_centralWidget->setProperty(PropertyDefs::c_dialogCentralWidget, true);
m_scrollArea->setWidget(p_widget);
}

View File

@ -55,7 +55,7 @@ void FindAndReplaceWidget::setupUI()
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
auto iconFile = themeMgr.getIconFile(QStringLiteral("close.svg"));
auto closeBtn = new QToolButton(this);
closeBtn->setProperty(PropertyDefs::s_actionToolButton, true);
closeBtn->setProperty(PropertyDefs::c_actionToolButton, true);
titleLayout->addWidget(closeBtn);
auto closeAct = new QAction(IconUtils::fetchIcon(iconFile), QString(), closeBtn);
@ -223,7 +223,7 @@ void FindAndReplaceWidget::updateFindOptions()
return;
}
FindOptions options = FindOption::None;
FindOptions options = FindOption::FindNone;
if (m_caseSensitiveCheckBox->isChecked()) {
options |= FindOption::CaseSensitive;

View File

@ -77,7 +77,7 @@ namespace vnotex
QCheckBox *m_incrementalSearchCheckBox = nullptr;
FindOptions m_options = FindOption::None;
FindOptions m_options = FindOption::FindNone;
QTimer *m_findTextTimer = nullptr;

View File

@ -0,0 +1,180 @@
#include "locationlist.h"
#include <QVBoxLayout>
#include <QToolButton>
#include "treewidget.h"
#include "widgetsfactory.h"
#include "titlebar.h"
#include <core/vnotex.h>
#include <utils/iconutils.h>
using namespace vnotex;
QIcon LocationList::s_bufferIcon;
QIcon LocationList::s_fileIcon;
QIcon LocationList::s_folderIcon;
QIcon LocationList::s_notebookIcon;
LocationList::LocationList(QWidget *p_parent)
: QFrame(p_parent)
{
setupUI();
}
void LocationList::setupUI()
{
auto mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
{
setupTitleBar(QString(), this);
mainLayout->addWidget(m_titleBar);
}
m_tree = new TreeWidget(TreeWidget::Flag::None, this);
// When updated, pay attention to the Columns enum.
m_tree->setHeaderLabels(QStringList() << tr("Path") << tr("Line") << tr("Text"));
TreeWidget::showHorizontalScrollbar(m_tree);
connect(m_tree, &QTreeWidget::itemActivated,
this, [this](QTreeWidgetItem *p_item, int p_col) {
Q_UNUSED(p_col);
if (!m_callback) {
return;
}
m_callback(getItemLocation(p_item));
});
mainLayout->addWidget(m_tree);
setFocusProxy(m_tree);
}
const QIcon &LocationList::getItemIcon(LocationType p_type)
{
if (s_bufferIcon.isNull()) {
// Init.
const QString nodeIconFgName = "widgets#locationlist#node_icon#fg";
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
const auto fg = themeMgr.paletteColor(nodeIconFgName);
s_bufferIcon = IconUtils::fetchIcon(themeMgr.getIconFile("buffer.svg"), fg);
s_fileIcon = IconUtils::fetchIcon(themeMgr.getIconFile("file_node.svg"), fg);
s_folderIcon = IconUtils::fetchIcon(themeMgr.getIconFile("folder_node.svg"), fg);
s_notebookIcon = IconUtils::fetchIcon(themeMgr.getIconFile("notebook_default.svg"), fg);
}
switch (p_type) {
case LocationType::Buffer:
return s_bufferIcon;
case LocationType::File:
return s_fileIcon;
case LocationType::Folder:
return s_folderIcon;
case LocationType::Notebook:
Q_FALLTHROUGH();
default:
return s_notebookIcon;
}
}
NavigationModeWrapper<QTreeWidget, QTreeWidgetItem> *LocationList::getNavigationModeWrapper()
{
if (!m_navigationWrapper) {
m_navigationWrapper.reset(new NavigationModeWrapper<QTreeWidget, QTreeWidgetItem>(m_tree));
}
return m_navigationWrapper.data();
}
void LocationList::setupTitleBar(const QString &p_title, QWidget *p_parent)
{
m_titleBar = new TitleBar(p_title, true, TitleBar::Action::None, p_parent);
{
auto clearBtn = m_titleBar->addActionButton(QStringLiteral("clear.svg"), tr("Clear"));
connect(clearBtn, &QToolButton::triggered,
this, &LocationList::clear);
}
}
void LocationList::clear()
{
m_tree->clear();
m_callback = LocationCallback();
updateItemsCountLabel();
}
void LocationList::setItemLocationLineAndText(QTreeWidgetItem *p_item, const ComplexLocation::Line &p_line)
{
p_item->setData(Columns::LineColumn, Qt::UserRole, p_line.m_lineNumber);
if (p_line.m_lineNumber != -1) {
p_item->setText(Columns::LineColumn, QString::number(p_line.m_lineNumber));
}
p_item->setText(Columns::TextColumn, p_line.m_text);
}
void LocationList::addLocation(const ComplexLocation &p_location)
{
auto item = new QTreeWidgetItem(m_tree);
item->setText(Columns::PathColumn, p_location.m_displayPath);
item->setData(Columns::PathColumn, Qt::UserRole, p_location.m_path);
item->setIcon(Columns::PathColumn, getItemIcon(p_location.m_type));
if (p_location.m_lines.size() == 1) {
setItemLocationLineAndText(item, p_location.m_lines[0]);
} else if (p_location.m_lines.size() > 1) {
// Add sub items.
for (const auto &line : p_location.m_lines) {
auto subItem = new QTreeWidgetItem(item);
setItemLocationLineAndText(subItem, line);
}
item->setExpanded(true);
}
updateItemsCountLabel();
}
void LocationList::startSession(const LocationCallback &p_callback)
{
m_callback = p_callback;
}
Location LocationList::getItemLocation(const QTreeWidgetItem *p_item) const
{
Location loc;
if (!p_item) {
return loc;
}
auto paItem = p_item->parent() ? p_item->parent() : p_item;
loc.m_path = paItem->data(Columns::PathColumn, Qt::UserRole).toString();
loc.m_displayPath = paItem->text(Columns::PathColumn);
auto lineNumberData = p_item->data(Columns::LineColumn, Qt::UserRole);
if (lineNumberData.isValid()) {
loc.m_lineNumber = lineNumberData.toInt();
}
return loc;
}
void LocationList::updateItemsCountLabel()
{
const auto cnt = m_tree->topLevelItemCount();
if (cnt == 0) {
m_titleBar->setInfoLabel("");
} else {
m_titleBar->setInfoLabel(tr("%n Item(s)", "", m_tree->topLevelItemCount()));
}
}

View File

@ -0,0 +1,74 @@
#ifndef LOCATIONLIST_H
#define LOCATIONLIST_H
#include <functional>
#include <QFrame>
#include <QScopedPointer>
#include <QSharedPointer>
#include <QIcon>
#include <core/location.h>
#include "navigationmodewrapper.h"
namespace vnotex
{
class TitleBar;
class LocationList : public QFrame
{
Q_OBJECT
public:
typedef std::function<void(const Location &)> LocationCallback;
explicit LocationList(QWidget *p_parent = nullptr);
NavigationModeWrapper<QTreeWidget, QTreeWidgetItem> *getNavigationModeWrapper();
void clear();
void addLocation(const ComplexLocation &p_location);
// Start a new session of the location list to set a callback for activation handling.
void startSession(const LocationCallback &p_callback);
private:
enum Columns
{
PathColumn = 0,
LineColumn,
TextColumn
};
void setupUI();
void setupTitleBar(const QString &p_title, QWidget *p_parent = nullptr);
void setItemLocationLineAndText(QTreeWidgetItem *p_item, const ComplexLocation::Line &p_line);
const QIcon &getItemIcon(LocationType p_type);
Location getItemLocation(const QTreeWidgetItem *p_item) const;
void updateItemsCountLabel();
TitleBar *m_titleBar = nullptr;
QTreeWidget *m_tree = nullptr;
QScopedPointer<NavigationModeWrapper<QTreeWidget, QTreeWidgetItem>> m_navigationWrapper;
LocationCallback m_callback;
static QIcon s_bufferIcon;
static QIcon s_fileIcon;
static QIcon s_folderIcon;
static QIcon s_notebookIcon;
};
}
#endif // LOCATIONLIST_H

View File

@ -38,6 +38,10 @@
#include "messageboxhelper.h"
#include "systemtrayhelper.h"
#include "titletoolbar.h"
#include "locationlist.h"
#include "searchpanel.h"
#include <notebook/notebook.h>
#include "searchinfoprovider.h"
using namespace vnotex;
@ -193,10 +197,14 @@ void MainWindow::setupDocks()
setupOutlineDock();
setupSearchDock();
for (int i = 1; i < m_docks.size(); ++i) {
tabifyDockWidget(m_docks[i - 1], m_docks[i]);
}
setupLocationListDock();
for (auto dock : m_docks) {
connect(dock, &QDockWidget::visibilityChanged,
this, [this]() {
@ -205,8 +213,7 @@ void MainWindow::setupDocks()
});
}
// Activate the first dock.
activateDock(m_docks[0]);
activateDock(m_docks[DockIndex::NavigationDock]);
}
void MainWindow::activateDock(QDockWidget *p_dock)
@ -257,6 +264,53 @@ void MainWindow::setupOutlineDock()
addDockWidget(Qt::LeftDockWidgetArea, dock);
}
void MainWindow::setupSearchDock()
{
auto dock = new QDockWidget(tr("Search"), this);
m_docks.push_back(dock);
dock->setObjectName(QStringLiteral("SearchDock.vnotex"));
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
setupSearchPanel();
dock->setWidget(m_searchPanel);
dock->setFocusProxy(m_searchPanel);
addDockWidget(Qt::LeftDockWidgetArea, dock);
}
void MainWindow::setupSearchPanel()
{
m_searchPanel = new SearchPanel(
QSharedPointer<SearchInfoProvider>::create(m_viewArea,
m_notebookExplorer,
&VNoteX::getInst().getNotebookMgr()),
this);
m_searchPanel->setObjectName("SearchPanel.vnotex");
}
void MainWindow::setupLocationListDock()
{
auto dock = new QDockWidget(tr("Location List"), this);
m_docks.push_back(dock);
dock->setObjectName(QStringLiteral("LocationListDock.vnotex"));
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
setupLocationList();
dock->setWidget(m_locationList);
dock->setFocusProxy(m_locationList);
addDockWidget(Qt::BottomDockWidgetArea, dock);
dock->hide();
}
void MainWindow::setupLocationList()
{
m_locationList = new LocationList(this);
m_locationList->setObjectName("LocationList.vnotex");
NavigationModeMgr::getInst().registerNavigationTarget(m_locationList->getNavigationModeWrapper());
}
void MainWindow::setupNavigationToolBox()
{
m_navigationToolBox = new ToolBox(this);
@ -522,34 +576,30 @@ void MainWindow::closeOnQuit()
void MainWindow::setupShortcuts()
{
const auto &coreConfig = ConfigMgr::getInst().getCoreConfig();
// Focus Navigation dock.
{
auto keys = coreConfig.getShortcut(CoreConfig::Shortcut::NavigationDock);
auto shortcut = WidgetUtils::createShortcut(keys, this);
if (shortcut) {
auto dock = m_docks[DockIndex::NavigationDock];
dock->setToolTip(QString("%1\t%2").arg(dock->windowTitle(),
QKeySequence(keys).toString(QKeySequence::NativeText)));
connect(shortcut, &QShortcut::activated,
this, [this]() {
activateDock(m_docks[DockIndex::NavigationDock]);
});
}
}
// Focus Outline dock.
{
auto keys = coreConfig.getShortcut(CoreConfig::Shortcut::OutlineDock);
auto shortcut = WidgetUtils::createShortcut(keys, this);
if (shortcut) {
auto dock = m_docks[DockIndex::OutlineDock];
dock->setToolTip(QString("%1\t%2").arg(dock->windowTitle(),
QKeySequence(keys).toString(QKeySequence::NativeText)));
connect(shortcut, &QShortcut::activated,
this, [this]() {
activateDock(m_docks[DockIndex::OutlineDock]);
});
}
setupDockActivateShortcut(m_docks[DockIndex::NavigationDock],
coreConfig.getShortcut(CoreConfig::Shortcut::NavigationDock));
setupDockActivateShortcut(m_docks[DockIndex::OutlineDock],
coreConfig.getShortcut(CoreConfig::Shortcut::OutlineDock));
setupDockActivateShortcut(m_docks[DockIndex::SearchDock],
coreConfig.getShortcut(CoreConfig::Shortcut::SearchDock));
setupDockActivateShortcut(m_docks[DockIndex::LocationListDock],
coreConfig.getShortcut(CoreConfig::Shortcut::LocationListDock));
}
void MainWindow::setupDockActivateShortcut(QDockWidget *p_dock, const QString &p_keys)
{
auto shortcut = WidgetUtils::createShortcut(p_keys, this);
if (shortcut) {
p_dock->setToolTip(QString("%1\t%2").arg(p_dock->windowTitle(),
QKeySequence(p_keys).toString(QKeySequence::NativeText)));
connect(shortcut, &QShortcut::activated,
this, [this, p_dock]() {
activateDock(p_dock);
});
}
}
@ -684,3 +734,17 @@ void MainWindow::setTipsAreaVisible(bool p_visible)
m_tipsLabel->hide();
}
}
LocationList *MainWindow::getLocationList() const
{
return m_locationList;
}
void MainWindow::setLocationListVisible(bool p_visible)
{
if (p_visible) {
activateDock(m_docks[DockIndex::LocationListDock]);
} else {
m_docks[DockIndex::LocationListDock]->hide();
}
}

View File

@ -19,6 +19,8 @@ namespace vnotex
class ViewArea;
class Event;
class OutlineViewer;
class LocationList;
class SearchPanel;
enum { RESTART_EXIT_CODE = 1000 };
@ -55,6 +57,10 @@ namespace vnotex
void openFiles(const QStringList &p_files);
LocationList *getLocationList() const;
void setLocationListVisible(bool p_visible);
signals:
void mainWindowStarted();
@ -85,7 +91,9 @@ namespace vnotex
enum DockIndex
{
NavigationDock = 0,
OutlineDock
OutlineDock,
SearchDock,
LocationListDock
};
void setupUI();
@ -100,6 +108,14 @@ namespace vnotex
void setupOutlineDock();
void setupSearchDock();
void setupSearchPanel();
void setupLocationListDock();
void setupLocationList();
void setupNotebookExplorer(QWidget *p_parent = nullptr);
void setupDocks();
@ -129,6 +145,8 @@ namespace vnotex
void setTipsAreaVisible(bool p_visible);
void setupDockActivateShortcut(QDockWidget *p_dock, const QString &p_keys);
ToolBarHelper m_toolBarHelper;
StatusBarHelper m_statusBarHelper;
@ -143,6 +161,10 @@ namespace vnotex
OutlineViewer *m_outlineViewer = nullptr;
LocationList *m_locationList = nullptr;
SearchPanel *m_searchPanel = nullptr;
QVector<QDockWidget *> m_docks;
bool m_layoutReset = false;

View File

@ -840,7 +840,9 @@ void MarkdownViewWindow::zoom(bool p_zoomIn)
void MarkdownViewWindow::handleFindTextChanged(const QString &p_text, FindOptions p_options)
{
if (m_mode == Mode::Read) {
adapter()->findText(p_text, p_options);
if (p_options & FindOption::IncrementalSearch) {
adapter()->findText(p_text, p_options);
}
} else {
TextViewWindowHelper::handleFindTextChanged(this, p_text, p_options);
}
@ -849,9 +851,7 @@ void MarkdownViewWindow::handleFindTextChanged(const QString &p_text, FindOption
void MarkdownViewWindow::handleFindNext(const QString &p_text, FindOptions p_options)
{
if (m_mode == Mode::Read) {
if (p_options & FindOption::IncrementalSearch) {
adapter()->findText(p_text, p_options);
}
adapter()->findText(p_text, p_options);
} else {
TextViewWindowHelper::handleFindNext(this, p_text, p_options);
}
@ -880,7 +880,7 @@ void MarkdownViewWindow::handleFindAndReplaceWidgetClosed()
if (m_editor) {
TextViewWindowHelper::handleFindAndReplaceWidgetClosed(this);
} else {
adapter()->findText("", FindOption::None);
adapter()->findText("", FindOption::FindNone);
}
}

View File

@ -45,7 +45,7 @@ NotebookExplorer::NotebookExplorer(QWidget *p_parent)
void NotebookExplorer::setupUI()
{
auto mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
WidgetUtils::setContentsMargins(mainLayout);
// Title bar.
auto titleBar = setupTitleBar(this);
@ -88,6 +88,7 @@ TitleBar *NotebookExplorer::setupTitleBar(QWidget *p_parent)
const auto &widgetConfig = ConfigMgr::getInst().getWidgetConfig();
auto titleBar = new TitleBar(tr("Notebook"),
false,
TitleBar::Action::Menu,
p_parent);
titleBar->setWhatsThis(tr("This title bar contains buttons and menu to manage notebooks and notes."));

View File

@ -941,7 +941,7 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent)
case Action::Properties:
act = new QAction(generateMenuActionIcon("properties.svg"),
tr("&Properties"),
tr("&Properties (Rename)"),
p_parent);
connect(act, &QAction::triggered,
this, [this]() {

View File

@ -3,6 +3,8 @@
#include <QVBoxLayout>
#include <QToolButton>
#include <core/global.h>
#include <utils/widgetutils.h>
#include "outlineviewer.h"
using namespace vnotex;
@ -22,6 +24,7 @@ OutlinePopup::OutlinePopup(QToolButton *p_btn, QWidget *p_parent)
void OutlinePopup::setupUI()
{
auto mainLayout = new QVBoxLayout(this);
WidgetUtils::setContentsMargins(mainLayout);
m_viewer = new OutlineViewer(tr("Outline"), this);
mainLayout->addWidget(m_viewer);

View File

@ -9,6 +9,8 @@
#include <QToolTip>
#include <QDebug>
#include <utils/widgetutils.h>
#include "treewidget.h"
#include "titlebar.h"
@ -50,8 +52,7 @@ OutlineViewer::OutlineViewer(const QString &p_title, QWidget *p_parent)
void OutlineViewer::setupUI(const QString &p_title)
{
auto mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
WidgetUtils::setContentsMargins(mainLayout);
{
auto titleBar = setupTitleBar(p_title, this);
@ -88,7 +89,7 @@ NavigationModeWrapper<QTreeWidget, QTreeWidgetItem> *OutlineViewer::getNavigatio
TitleBar *OutlineViewer::setupTitleBar(const QString &p_title, QWidget *p_parent)
{
auto titleBar = new TitleBar(p_title, TitleBar::Action::None, p_parent);
auto titleBar = new TitleBar(p_title, false, TitleBar::Action::None, p_parent);
auto decreaseBtn = titleBar->addActionButton(QStringLiteral("decrease_outline_level.svg"), tr("Decrease Expansion Level"));
connect(decreaseBtn, &QToolButton::clicked,

View File

@ -22,7 +22,7 @@ namespace vnotex
{
Q_OBJECT
public:
explicit OutlineViewer(const QString &p_title, QWidget *p_parent = nullptr);
OutlineViewer(const QString &p_title, QWidget *p_parent = nullptr);
void setOutlineProvider(const QSharedPointer<OutlineProvider> &p_provider);

View File

@ -2,18 +2,20 @@
using namespace vnotex;
const char *PropertyDefs::s_actionToolButton = "ActionToolButton";
const char *PropertyDefs::c_actionToolButton = "ActionToolButton";
const char *PropertyDefs::s_toolButtonWithoutMenuIndicator = "NoMenuIndicator";
const char *PropertyDefs::c_toolButtonWithoutMenuIndicator = "NoMenuIndicator";
const char *PropertyDefs::s_dangerButton = "DangerButton";
const char *PropertyDefs::c_dangerButton = "DangerButton";
const char *PropertyDefs::s_dialogCentralWidget = "DialogCentralWidget";
const char *PropertyDefs::c_dialogCentralWidget = "DialogCentralWidget";
const char *PropertyDefs::s_viewSplitCornerWidget = "ViewSplitCornerWidget";
const char *PropertyDefs::c_viewSplitCornerWidget = "ViewSplitCornerWidget";
const char *PropertyDefs::s_state = "State";
const char *PropertyDefs::c_state = "State";
const char *PropertyDefs::s_viewWindowToolBar = "ViewWindowToolBar";
const char *PropertyDefs::c_viewWindowToolBar = "ViewWindowToolBar";
const char *PropertyDefs::s_consoleTextEdit = "ConsoleTextEdit";
const char *PropertyDefs::c_consoleTextEdit = "ConsoleTextEdit";
const char *PropertyDefs::c_embeddedLineEdit = "EmbeddedLineEdit";

View File

@ -9,22 +9,24 @@ namespace vnotex
public:
PropertyDefs() = delete;
static const char *s_actionToolButton;
static const char *c_actionToolButton;
static const char *s_toolButtonWithoutMenuIndicator;
static const char *c_toolButtonWithoutMenuIndicator;
static const char *s_dangerButton;
static const char *c_dangerButton;
static const char *s_dialogCentralWidget;
static const char *c_dialogCentralWidget;
static const char *s_viewSplitCornerWidget;
static const char *c_viewSplitCornerWidget;
static const char *s_viewWindowToolBar;
static const char *c_viewWindowToolBar;
static const char *s_consoleTextEdit;
static const char *c_consoleTextEdit;
static const char *c_embeddedLineEdit;
// Values: info/warning/error.
static const char *s_state;
static const char *c_state;
};
}

View File

@ -0,0 +1,43 @@
#include "searchinfoprovider.h"
#include "viewarea.h"
#include "notebookexplorer.h"
#include "notebookmgr.h"
using namespace vnotex;
SearchInfoProvider::SearchInfoProvider(const ViewArea *p_viewArea,
const NotebookExplorer *p_notebookExplorer,
const NotebookMgr *p_notebookMgr)
: m_viewArea(p_viewArea),
m_notebookExplorer(p_notebookExplorer),
m_notebookMgr(p_notebookMgr)
{
}
QList<Buffer *> SearchInfoProvider::getBuffers() const
{
return m_viewArea->getAllBuffersInViewSplits();
}
Node *SearchInfoProvider::getCurrentFolder() const
{
return m_notebookExplorer->currentExploredFolderNode();
}
Notebook *SearchInfoProvider::getCurrentNotebook() const
{
return m_notebookExplorer->currentNotebook().data();
}
QVector<Notebook *> SearchInfoProvider::getNotebooks() const
{
auto notebooks = m_notebookMgr->getNotebooks();
QVector<Notebook *> nbs;
nbs.reserve(notebooks.size());
for (const auto &nb : notebooks) {
nbs.push_back(nb.data());
}
return nbs;
}

View File

@ -0,0 +1,36 @@
#ifndef SEARCHINFOPROVIDER_H
#define SEARCHINFOPROVIDER_H
#include "searchpanel.h"
namespace vnotex
{
class ViewArea;
class NotebookExplorer;
class NotebookMgr;
class SearchInfoProvider : public ISearchInfoProvider
{
public:
SearchInfoProvider(const ViewArea *p_viewArea,
const NotebookExplorer *p_notebookExplorer,
const NotebookMgr *p_notebookMgr);
QList<Buffer *> getBuffers() const Q_DECL_OVERRIDE;
Node *getCurrentFolder() const Q_DECL_OVERRIDE;
Notebook *getCurrentNotebook() const Q_DECL_OVERRIDE;
QVector<Notebook *> getNotebooks() const Q_DECL_OVERRIDE;
private:
const ViewArea *m_viewArea = nullptr;
const NotebookExplorer *m_notebookExplorer = nullptr;
const NotebookMgr *m_notebookMgr = nullptr;
};
}
#endif // SEARCHINFOPROVIDER_H

526
src/widgets/searchpanel.cpp Normal file
View File

@ -0,0 +1,526 @@
#include "searchpanel.h"
#include <QVBoxLayout>
#include <QToolButton>
#include <QLabel>
#include <QFormLayout>
#include <QComboBox>
#include <QCheckBox>
#include <QLineEdit>
#include <QCompleter>
#include <QGridLayout>
#include <QProgressBar>
#include <QPlainTextEdit>
#include <QCoreApplication>
#include <QRadioButton>
#include <QButtonGroup>
#include <core/configmgr.h>
#include <core/sessionconfig.h>
#include <core/vnotex.h>
#include <core/fileopenparameters.h>
#include <notebook/node.h>
#include <notebook/notebook.h>
#include "widgetsfactory.h"
#include "titlebar.h"
#include "propertydefs.h"
#include "mainwindow.h"
#include <search/searchtoken.h>
#include <search/searchresultitem.h>
#include <utils/widgetutils.h>
#include "locationlist.h"
using namespace vnotex;
SearchPanel::SearchPanel(const QSharedPointer<ISearchInfoProvider> &p_provider, QWidget *p_parent)
: QFrame(p_parent),
m_provider(p_provider)
{
qRegisterMetaType<QVector<QSharedPointer<SearchResultItem>>>("QVector<QSharedPointer<SearchResultItem>>");
setupUI();
initOptions();
restoreFields(*m_option);
}
void SearchPanel::setupUI()
{
auto mainLayout = new QVBoxLayout(this);
WidgetUtils::setContentsMargins(mainLayout);
{
auto titleBar = setupTitleBar(QString(), this);
mainLayout->addWidget(titleBar);
}
auto inputsLayout = new QFormLayout();
mainLayout->addLayout(inputsLayout);
m_keywordComboBox = WidgetsFactory::createComboBox(this);
m_keywordComboBox->setToolTip(SearchToken::getHelpText());
m_keywordComboBox->setEditable(true);
m_keywordComboBox->setLineEdit(WidgetsFactory::createLineEdit(this));
m_keywordComboBox->lineEdit()->setProperty(PropertyDefs::c_embeddedLineEdit, true);
m_keywordComboBox->completer()->setCaseSensitivity(Qt::CaseSensitive);
connect(m_keywordComboBox->lineEdit(), &QLineEdit::returnPressed,
this, [this]() {
m_searchBtn->animateClick();
});
inputsLayout->addRow(tr("Keyword:"), m_keywordComboBox);
m_searchScopeComboBox = WidgetsFactory::createComboBox(this);
m_searchScopeComboBox->addItem(tr("Buffers"), static_cast<int>(SearchScope::Buffers));
m_searchScopeComboBox->addItem(tr("Current Folder"), static_cast<int>(SearchScope::CurrentFolder));
m_searchScopeComboBox->addItem(tr("Current Notebook"), static_cast<int>(SearchScope::CurrentNotebook));
m_searchScopeComboBox->addItem(tr("All Notebooks"), static_cast<int>(SearchScope::AllNotebooks));
inputsLayout->addRow(tr("Scope:"), m_searchScopeComboBox);
setupSearchObject(inputsLayout);
setupSearchTarget(inputsLayout);
m_filePatternComboBox = WidgetsFactory::createComboBox(this);
m_filePatternComboBox->setEditable(true);
m_filePatternComboBox->setLineEdit(WidgetsFactory::createLineEdit(this));
m_filePatternComboBox->lineEdit()->setPlaceholderText(tr("Wildcard pattern of files and folders to search"));
m_filePatternComboBox->lineEdit()->setProperty(PropertyDefs::c_embeddedLineEdit, true);
m_filePatternComboBox->completer()->setCaseSensitivity(Qt::CaseSensitive);
inputsLayout->addRow(tr("File pattern:"), m_filePatternComboBox);
setupFindOption(inputsLayout);
{
m_progressBar = new QProgressBar(this);
m_progressBar->setRange(0, 0);
m_progressBar->hide();
mainLayout->addWidget(m_progressBar);
}
mainLayout->addStretch();
}
TitleBar *SearchPanel::setupTitleBar(const QString &p_title, QWidget *p_parent)
{
auto titleBar = new TitleBar(p_title, false, TitleBar::Action::None, p_parent);
titleBar->setActionButtonsAlwaysShown(true);
{
m_searchBtn = titleBar->addActionButton(QStringLiteral("search.svg"), tr("Search"));
connect(m_searchBtn, &QToolButton::triggered,
this, &SearchPanel::startSearch);
auto cancelBtn = titleBar->addActionButton(QStringLiteral("cancel.svg"), tr("Cancel"));
connect(cancelBtn, &QToolButton::triggered,
this, &SearchPanel::stopSearch);
auto closeLocationListBtn = titleBar->addActionButton(QStringLiteral("close.svg"), tr("Close Location List"));
connect(closeLocationListBtn, &QToolButton::triggered,
this, [this]() {
});
}
return titleBar;
}
void SearchPanel::setupSearchObject(QFormLayout *p_layout)
{
auto gridLayout = new QGridLayout();
p_layout->addRow(tr("Object:"), gridLayout);
m_searchObjectNameCheckBox = WidgetsFactory::createCheckBox(tr("Name"), this);
gridLayout->addWidget(m_searchObjectNameCheckBox, 0, 0);
m_searchObjectContentCheckBox = WidgetsFactory::createCheckBox(tr("Content"), this);
gridLayout->addWidget(m_searchObjectContentCheckBox, 0, 1);
m_searchObjectOutlineCheckBox = WidgetsFactory::createCheckBox(tr("Outline"), this);
gridLayout->addWidget(m_searchObjectOutlineCheckBox, 0, 2);
m_searchObjectTagCheckBox = WidgetsFactory::createCheckBox(tr("Tag"), this);
gridLayout->addWidget(m_searchObjectTagCheckBox, 1, 0);
m_searchObjectPathCheckBox = WidgetsFactory::createCheckBox(tr("Path"), this);
gridLayout->addWidget(m_searchObjectPathCheckBox, 1, 1);
}
void SearchPanel::setupSearchTarget(QFormLayout *p_layout)
{
auto gridLayout = new QGridLayout();
p_layout->addRow(tr("Target:"), gridLayout);
m_searchTargetFileCheckBox = WidgetsFactory::createCheckBox(tr("File"), this);
gridLayout->addWidget(m_searchTargetFileCheckBox, 0, 0);
m_searchTargetFolderCheckBox = WidgetsFactory::createCheckBox(tr("Folder"), this);
gridLayout->addWidget(m_searchTargetFolderCheckBox, 0, 1);
m_searchTargetNotebookCheckBox = WidgetsFactory::createCheckBox(tr("Notebook"), this);
gridLayout->addWidget(m_searchTargetNotebookCheckBox, 0, 2);
}
void SearchPanel::setupFindOption(QFormLayout *p_layout)
{
auto gridLayout = new QGridLayout();
p_layout->addRow(tr("Option:"), gridLayout);
m_caseSensitiveCheckBox = WidgetsFactory::createCheckBox(tr("&Case sensitive"), this);
gridLayout->addWidget(m_caseSensitiveCheckBox, 0, 0);
{
QButtonGroup *btnGroup = new QButtonGroup(this);
m_plainTextRadioBtn = WidgetsFactory::createRadioButton(tr("&Plain text"), this);
btnGroup->addButton(m_plainTextRadioBtn);
gridLayout->addWidget(m_plainTextRadioBtn, 1, 0);
m_wholeWordOnlyRadioBtn = WidgetsFactory::createRadioButton(tr("&Whole word only"), this);
btnGroup->addButton(m_wholeWordOnlyRadioBtn);
gridLayout->addWidget(m_wholeWordOnlyRadioBtn, 1, 1);
m_fuzzySearchRadioBtn = WidgetsFactory::createRadioButton(tr("&Fuzzy search"), this);
btnGroup->addButton(m_fuzzySearchRadioBtn);
gridLayout->addWidget(m_fuzzySearchRadioBtn, 2, 0);
m_regularExpressionRadioBtn = WidgetsFactory::createRadioButton(tr("Re&gular expression"), this);
btnGroup->addButton(m_regularExpressionRadioBtn);
gridLayout->addWidget(m_regularExpressionRadioBtn, 2, 1);
}
}
void SearchPanel::initOptions()
{
// Read it from config.
m_option = QSharedPointer<SearchOption>::create(ConfigMgr::getInst().getSessionConfig().getSearchOption());
connect(VNoteX::getInst().getMainWindow(), &MainWindow::mainWindowClosedOnQuit,
this, [this]() {
saveFields(*m_option);
ConfigMgr::getInst().getSessionConfig().setSearchOption(*m_option);
});
}
void SearchPanel::restoreFields(const SearchOption &p_option)
{
m_keywordComboBox->setEditText(p_option.m_keyword);
m_filePatternComboBox->setEditText(p_option.m_filePattern);
{
int idx = m_searchScopeComboBox->findData(static_cast<int>(p_option.m_scope));
if (idx != -1) {
m_searchScopeComboBox->setCurrentIndex(idx);
}
}
{
m_searchObjectNameCheckBox->setChecked(p_option.m_objects & SearchObject::SearchName);
m_searchObjectContentCheckBox->setChecked(p_option.m_objects & SearchObject::SearchContent);
m_searchObjectOutlineCheckBox->setChecked(p_option.m_objects & SearchObject::SearchOutline);
m_searchObjectTagCheckBox->setChecked(p_option.m_objects & SearchObject::SearchTag);
m_searchObjectPathCheckBox->setChecked(p_option.m_objects & SearchObject::SearchPath);
}
{
m_searchTargetFileCheckBox->setChecked(p_option.m_targets & SearchTarget::SearchFile);
m_searchTargetFolderCheckBox->setChecked(p_option.m_targets & SearchTarget::SearchFolder);
m_searchTargetNotebookCheckBox->setChecked(p_option.m_targets & SearchTarget::SearchNotebook);
}
{
m_plainTextRadioBtn->setChecked(true);
m_caseSensitiveCheckBox->setChecked(p_option.m_findOptions & FindOption::CaseSensitive);
m_wholeWordOnlyRadioBtn->setChecked(p_option.m_findOptions & FindOption::WholeWordOnly);
m_fuzzySearchRadioBtn->setChecked(p_option.m_findOptions & FindOption::FuzzySearch);
m_regularExpressionRadioBtn->setChecked(p_option.m_findOptions & FindOption::RegularExpression);
}
}
void SearchPanel::updateUIOnSearch()
{
if (m_searchOngoing) {
m_progressBar->setMaximum(0);
m_progressBar->show();
} else {
m_progressBar->hide();
}
}
void SearchPanel::startSearch()
{
if (m_searchOngoing) {
return;
}
// On start.
{
clearLog();
m_searchOngoing = true;
updateUIOnSearch();
prepareLocationList();
}
saveFields(*m_option);
auto state = search(m_option);
// On end.
handleSearchFinished(state);
}
void SearchPanel::handleSearchFinished(SearchState p_state)
{
Q_ASSERT(m_searchOngoing);
Q_ASSERT(p_state != SearchState::Idle);
if (p_state != SearchState::Busy) {
appendLog(tr("Search finished: %1").arg(SearchStateToString(p_state)));
getSearcher()->clear();
m_searchOngoing = false;
updateUIOnSearch();
}
}
void SearchPanel::stopSearch()
{
if (!m_searchOngoing) {
return;
}
getSearcher()->stop();
}
void SearchPanel::appendLog(const QString &p_text)
{
if (p_text.isEmpty()) {
return;
}
if (!m_infoTextEdit) {
m_infoTextEdit = WidgetsFactory::createPlainTextConsole(this);
m_infoTextEdit->setMaximumHeight(m_infoTextEdit->minimumSizeHint().height());
static_cast<QVBoxLayout *>(layout())->insertWidget(layout()->count() - 1, m_infoTextEdit);
}
m_infoTextEdit->appendPlainText(">>> " + p_text);
m_infoTextEdit->ensureCursorVisible();
m_infoTextEdit->show();
QCoreApplication::sendPostedEvents();
}
void SearchPanel::clearLog()
{
if (!m_infoTextEdit) {
return;
}
m_infoTextEdit->clear();
m_infoTextEdit->hide();
}
void SearchPanel::saveFields(SearchOption &p_option)
{
p_option.m_keyword = m_keywordComboBox->currentText().trimmed();
p_option.m_filePattern = m_filePatternComboBox->currentText().trimmed();
p_option.m_scope = static_cast<SearchScope>(m_searchScopeComboBox->currentData().toInt());
{
p_option.m_objects = SearchObject::ObjectNone;
if (m_searchObjectNameCheckBox->isChecked()) {
p_option.m_objects |= SearchObject::SearchName;
}
if (m_searchObjectContentCheckBox->isChecked()) {
p_option.m_objects |= SearchObject::SearchContent;
}
if (m_searchObjectOutlineCheckBox->isChecked()) {
p_option.m_objects |= SearchObject::SearchOutline;
}
if (m_searchObjectTagCheckBox->isChecked()) {
p_option.m_objects |= SearchObject::SearchTag;
}
if (m_searchObjectPathCheckBox->isChecked()) {
p_option.m_objects |= SearchObject::SearchPath;
}
}
{
p_option.m_targets = SearchTarget::TargetNone;
if (m_searchTargetFileCheckBox->isChecked()) {
p_option.m_targets |= SearchTarget::SearchFile;
}
if (m_searchTargetFolderCheckBox->isChecked()) {
p_option.m_targets |= SearchTarget::SearchFolder;
}
if (m_searchTargetNotebookCheckBox->isChecked()) {
p_option.m_targets |= SearchTarget::SearchNotebook;
}
}
p_option.m_engine = SearchEngine::Internal;
{
p_option.m_findOptions = FindOption::FindNone;
if (m_caseSensitiveCheckBox->isChecked()) {
p_option.m_findOptions |= FindOption::CaseSensitive;
}
if (m_wholeWordOnlyRadioBtn->isChecked()) {
p_option.m_findOptions |= FindOption::WholeWordOnly;
}
if (m_fuzzySearchRadioBtn->isChecked()) {
p_option.m_findOptions |= FindOption::FuzzySearch;
}
if (m_regularExpressionRadioBtn->isChecked()) {
p_option.m_findOptions |= FindOption::RegularExpression;
}
}
}
SearchState SearchPanel::search(const QSharedPointer<SearchOption> &p_option)
{
if (!isSearchOptionValid(*p_option)) {
return SearchState::Failed;
}
SearchState state = SearchState::Finished;
switch (p_option->m_scope) {
case SearchScope::Buffers:
{
auto buffers = m_provider->getBuffers();
if (buffers.isEmpty()) {
break;
}
state = getSearcher()->search(p_option, buffers);
break;
}
case SearchScope::CurrentFolder:
{
auto notebook = m_provider->getCurrentNotebook();
if (!notebook) {
break;
}
auto folder = m_provider->getCurrentFolder();
if (folder && (folder->isRoot() || notebook->isRecycleBinNode(folder))) {
folder = nullptr;
}
if (!folder) {
break;
}
state = getSearcher()->search(p_option, folder);
break;
}
case SearchScope::CurrentNotebook:
{
auto notebook = m_provider->getCurrentNotebook();
if (!notebook) {
break;
}
QVector<Notebook *> notebooks;
notebooks.push_back(notebook);
state = getSearcher()->search(p_option, notebooks);
break;
}
case SearchScope::AllNotebooks:
{
auto notebooks = m_provider->getNotebooks();
if (notebooks.isEmpty()) {
break;
}
state = getSearcher()->search(p_option, notebooks);
break;
}
}
return state;
}
bool SearchPanel::isSearchOptionValid(const SearchOption &p_option)
{
if (p_option.m_keyword.isEmpty()) {
appendLog(tr("Invalid keyword"));
return false;
}
if (p_option.m_objects == SearchObject::ObjectNone) {
appendLog(tr("No object specified"));
return false;
}
if (p_option.m_targets == SearchTarget::TargetNone) {
appendLog(tr("No target specified"));
return false;
}
if (p_option.m_findOptions & FindOption::FuzzySearch
&& p_option.m_objects & SearchObject::SearchContent) {
appendLog(tr("Fuzzy search is not allowed when searching content"));
return false;
}
return true;
}
Searcher *SearchPanel::getSearcher()
{
if (!m_searcher) {
m_searcher = new Searcher(this);
connect(m_searcher, &Searcher::progressUpdated,
this, &SearchPanel::updateProgress);
connect(m_searcher, &Searcher::logRequested,
this, &SearchPanel::appendLog);
connect(m_searcher, &Searcher::resultItemAdded,
this, [this](const QSharedPointer<SearchResultItem> &p_item) {
m_locationList->addLocation(p_item->m_location);
});
connect(m_searcher, &Searcher::resultItemsAdded,
this, [this](const QVector<QSharedPointer<SearchResultItem>> &p_items) {
for (const auto &item : p_items) {
m_locationList->addLocation(item->m_location);
}
});
connect(m_searcher, &Searcher::finished,
this, &SearchPanel::handleSearchFinished);
}
return m_searcher;
}
void SearchPanel::updateProgress(int p_val, int p_maximum)
{
m_progressBar->setMaximum(p_maximum);
m_progressBar->setValue(p_val);
}
void SearchPanel::prepareLocationList()
{
auto mainWindow = VNoteX::getInst().getMainWindow();
mainWindow->setLocationListVisible(true);
if (!m_locationList) {
m_locationList = mainWindow->getLocationList();
}
m_locationList->clear();
m_locationList->startSession([this](const Location &p_location) {
handleLocationActivated(p_location);
});
}
void SearchPanel::handleLocationActivated(const Location &p_location)
{
qDebug() << "location activated" << p_location;
// TODO: decode the path of location and handle different types of destination.
auto paras = QSharedPointer<FileOpenParameters>::create();
paras->m_lineNumber = p_location.m_lineNumber;
emit VNoteX::getInst().openFileRequested(p_location.m_path, paras);
}

144
src/widgets/searchpanel.h Normal file
View File

@ -0,0 +1,144 @@
#ifndef SEARCHPANEL_H
#define SEARCHPANEL_H
#include <QFrame>
#include <QSharedPointer>
#include <QList>
#include <search/searchdata.h>
#include <search/searcher.h>
class QComboBox;
class QCheckBox;
class QFormLayout;
class QProgressBar;
class QToolButton;
class QPlainTextEdit;
class QRadioButton;
class QButtonGroup;
namespace vnotex
{
class TitleBar;
class Buffer;
class Node;
class Notebook;
class LocationList;
struct Location;
class ISearchInfoProvider
{
public:
ISearchInfoProvider() = default;
virtual ~ISearchInfoProvider() = default;
virtual QList<Buffer *> getBuffers() const = 0;
virtual Node *getCurrentFolder() const = 0;
virtual Notebook *getCurrentNotebook() const = 0;
virtual QVector<Notebook *> getNotebooks() const = 0;
};
class SearchPanel : public QFrame
{
Q_OBJECT
public:
explicit SearchPanel(const QSharedPointer<ISearchInfoProvider> &p_provider, QWidget *p_parent = nullptr);
private slots:
void startSearch();
void stopSearch();
void handleSearchFinished(SearchState p_state);
void updateProgress(int p_val, int p_maximum);
void appendLog(const QString &p_text);
private:
void setupUI();
TitleBar *setupTitleBar(const QString &p_title, QWidget *p_parent = nullptr);
void setupSearchObject(QFormLayout *p_layout);
void setupSearchTarget(QFormLayout *p_layout);
void setupFindOption(QFormLayout *p_layout);
void initOptions();
void restoreFields(const SearchOption &p_option);
void saveFields(SearchOption &p_option);
void updateUIOnSearch();
void clearLog();
SearchState search(const QSharedPointer<SearchOption> &p_option);
bool isSearchOptionValid(const SearchOption &p_option);
Searcher *getSearcher();
void prepareLocationList();
void handleLocationActivated(const Location &p_location);
QSharedPointer<ISearchInfoProvider> m_provider;
QToolButton *m_searchBtn = nullptr;
QComboBox *m_keywordComboBox = nullptr;
QComboBox *m_searchScopeComboBox = nullptr;
QCheckBox *m_searchObjectNameCheckBox = nullptr;
QCheckBox *m_searchObjectContentCheckBox = nullptr;
QCheckBox *m_searchObjectOutlineCheckBox = nullptr;
QCheckBox *m_searchObjectTagCheckBox = nullptr;
QCheckBox *m_searchObjectPathCheckBox = nullptr;
QCheckBox *m_searchTargetFileCheckBox = nullptr;
QCheckBox *m_searchTargetFolderCheckBox = nullptr;
QCheckBox *m_searchTargetNotebookCheckBox = nullptr;
QComboBox *m_filePatternComboBox = nullptr;
QCheckBox *m_caseSensitiveCheckBox = nullptr;
// WholeWordOnly/RegularExpression/FuzzySearch is exclusive.
QRadioButton *m_plainTextRadioBtn = nullptr;
QRadioButton *m_wholeWordOnlyRadioBtn = nullptr;
QRadioButton *m_fuzzySearchRadioBtn = nullptr;
QRadioButton *m_regularExpressionRadioBtn = nullptr;
QProgressBar *m_progressBar = nullptr;
QPlainTextEdit *m_infoTextEdit = nullptr;
QSharedPointer<SearchOption> m_option;
bool m_searchOngoing = false;
Searcher *m_searcher = nullptr;
LocationList *m_locationList = nullptr;
};
}
#endif // SEARCHPANEL_H

View File

@ -23,19 +23,21 @@ const QString TitleBar::c_menuIconForegroundName = "widgets#titlebar#menu_icon#f
const QString TitleBar::c_menuIconDisabledForegroundName = "widgets#titlebar#menu_icon#disabled#fg";
TitleBar::TitleBar(const QString &p_title,
bool p_hasInfoLabel,
TitleBar::Actions p_actionFlags,
QWidget *p_parent)
: QWidget(p_parent)
{
setupUI(p_title, p_actionFlags);
setupUI(p_title, p_hasInfoLabel, p_actionFlags);
}
void TitleBar::setupUI(const QString &p_title, TitleBar::Actions p_actionFlags)
void TitleBar::setupUI(const QString &p_title, bool p_hasInfoLabel, TitleBar::Actions p_actionFlags)
{
auto mainLayout = new QHBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
// Title label.
// Should always add it even if title is empty. Otherwise, we could not catch the hover event to show actions.
{
auto titleLabel = new QLabel(p_title, this);
titleLabel->setProperty(c_titleProp, true);
@ -56,13 +58,20 @@ void TitleBar::setupUI(const QString &p_title, TitleBar::Actions p_actionFlags)
setActionButtonsVisible(false);
}
// Info label.
if (p_hasInfoLabel) {
m_infoLabel = new QLabel(this);
m_infoLabel->setProperty(c_titleProp, true);
mainLayout->addWidget(m_infoLabel);
}
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
}
QToolButton *TitleBar::newActionButton(const QString &p_iconName, const QString &p_text, QWidget *p_parent)
{
auto btn = new QToolButton(p_parent);
btn->setProperty(PropertyDefs::s_actionToolButton, true);
btn->setProperty(PropertyDefs::c_actionToolButton, true);
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
auto iconFile = themeMgr.getIconFile(p_iconName);
@ -99,7 +108,7 @@ void TitleBar::enterEvent(QEvent *p_event)
void TitleBar::leaveEvent(QEvent *p_event)
{
QWidget::leaveEvent(p_event);
setActionButtonsVisible(m_alwaysShowActionButtons);
setActionButtonsVisible(m_actionButtonsForcedShown || m_actionButtonsAlwaysShown);
}
void TitleBar::setActionButtonsVisible(bool p_visible)
@ -165,12 +174,12 @@ QToolButton *TitleBar::addActionButton(const QString &p_iconName, const QString
btn->setMenu(p_menu);
connect(p_menu, &QMenu::aboutToShow,
this, [this]() {
m_alwaysShowActionButtons = true;
m_actionButtonsForcedShown = true;
setActionButtonsVisible(true);
});
connect(p_menu, &QMenu::aboutToHide,
this, [this]() {
m_alwaysShowActionButtons = false;
m_actionButtonsForcedShown = false;
setActionButtonsVisible(false);
});
return btn;
@ -180,3 +189,17 @@ QHBoxLayout *TitleBar::actionButtonLayout() const
{
return static_cast<QHBoxLayout *>(m_buttonWidget->layout());
}
void TitleBar::setInfoLabel(const QString &p_info)
{
Q_ASSERT(m_infoLabel);
if (m_infoLabel) {
m_infoLabel->setText(p_info);
}
}
void TitleBar::setActionButtonsAlwaysShown(bool p_shown)
{
m_actionButtonsAlwaysShown = p_shown;
setActionButtonsVisible(m_actionButtonsForcedShown || m_actionButtonsAlwaysShown);
}

View File

@ -7,6 +7,7 @@
class QToolButton;
class QHBoxLayout;
class QLabel;
namespace vnotex
{
@ -23,6 +24,7 @@ namespace vnotex
Q_DECLARE_FLAGS(Actions, Action)
TitleBar(const QString &p_title,
bool p_hasInfoLabel,
TitleBar::Actions p_actionFlags,
QWidget *p_parent = nullptr);
@ -46,13 +48,17 @@ namespace vnotex
void addMenuSeparator();
void setInfoLabel(const QString &p_info);
void setActionButtonsAlwaysShown(bool p_shown);
protected:
void enterEvent(QEvent *p_event) Q_DECL_OVERRIDE;
void leaveEvent(QEvent *p_event) Q_DECL_OVERRIDE;
private:
void setupUI(const QString &p_title, TitleBar::Actions p_actionFlags);
void setupUI(const QString &p_title, bool p_hasInfoLabel, TitleBar::Actions p_actionFlags);
void setupActionButtons(TitleBar::Actions p_actionFlags);
@ -64,11 +70,15 @@ namespace vnotex
static QIcon generateMenuActionIcon(const QString &p_iconName);
QLabel *m_infoLabel = nullptr;
QVector<QToolButton *> m_actionButtons;
QWidget *m_buttonWidget = nullptr;
bool m_alwaysShowActionButtons = false;
bool m_actionButtonsAlwaysShown = false;
bool m_actionButtonsForcedShown = false;
QMenu *m_menu = nullptr;

View File

@ -92,7 +92,7 @@ void TitleToolBar::addTitleBarIcons(const QIcon &p_minimizeIcon,
m_window->close();
});
auto btn = static_cast<QToolButton *>(widgetForAction(closeAct));
btn->setProperty(PropertyDefs::s_dangerButton, true);
btn->setProperty(PropertyDefs::c_dangerButton, true);
}
updateMaximizeAct();

View File

@ -48,8 +48,9 @@ QToolBar *ToolBarHelper::setupFileToolBar(MainWindow *p_win, QToolBar *p_toolBar
auto toolBtn = dynamic_cast<QToolButton *>(tb->widgetForAction(act));
Q_ASSERT(toolBtn);
toolBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toolBtn->setPopupMode(QToolButton::InstantPopup);
toolBtn->setProperty(PropertyDefs::s_toolButtonWithoutMenuIndicator, true);
toolBtn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
auto newMenu = WidgetsFactory::createMenu(tb);
toolBtn->setMenu(newMenu);
@ -149,7 +150,7 @@ QToolBar *ToolBarHelper::setupFileToolBar(MainWindow *p_win, QToolBar *p_toolBar
auto btn = dynamic_cast<QToolButton *>(tb->widgetForAction(act));
Q_ASSERT(btn);
btn->setPopupMode(QToolButton::InstantPopup);
btn->setProperty(PropertyDefs::s_toolButtonWithoutMenuIndicator, true);
btn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
auto newMenu = WidgetsFactory::createMenu(tb);
btn->setMenu(newMenu);
@ -269,7 +270,7 @@ QToolBar *ToolBarHelper::setupSettingsToolBar(MainWindow *p_win, QToolBar *p_too
auto btn = dynamic_cast<QToolButton *>(tb->widgetForAction(act));
Q_ASSERT(btn);
btn->setPopupMode(QToolButton::InstantPopup);
btn->setProperty(PropertyDefs::s_toolButtonWithoutMenuIndicator, true);
btn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
auto menu = WidgetsFactory::createMenu(tb);
btn->setMenu(menu);
@ -348,7 +349,7 @@ QToolBar *ToolBarHelper::setupSettingsToolBar(MainWindow *p_win, QToolBar *p_too
auto btn = dynamic_cast<QToolButton *>(tb->widgetForAction(act));
Q_ASSERT(btn);
btn->setPopupMode(QToolButton::InstantPopup);
btn->setProperty(PropertyDefs::s_toolButtonWithoutMenuIndicator, true);
btn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
auto menu = WidgetsFactory::createMenu(tb);
btn->setMenu(menu);

View File

@ -10,6 +10,7 @@
#include <QDropEvent>
#include <QTimer>
#include <QApplication>
#include <QSet>
#include "viewwindow.h"
#include "mainwindow.h"
@ -655,9 +656,7 @@ bool ViewArea::removeWorkspaceInViewSplit(ViewSplit *p_split, bool p_insertNew)
{
// Close all the ViewWindows.
setCurrentViewSplit(p_split, true);
auto wins = getAllViewWindows(p_split, [](ViewWindow *) {
return true;
});
auto wins = getAllViewWindows(p_split);
for (const auto win : wins) {
if (!closeViewWindow(win, false, false)) {
return false;
@ -963,7 +962,7 @@ void ViewArea::dropEvent(QDropEvent *p_event)
QWidget::dropEvent(p_event);
}
QVector<ViewWindow *> ViewArea::getAllViewWindows(ViewSplit *p_split, const ViewSplit::ViewWindowSelector &p_func)
QVector<ViewWindow *> ViewArea::getAllViewWindows(ViewSplit *p_split, const ViewSplit::ViewWindowSelector &p_func) const
{
QVector<ViewWindow *> wins;
p_split->forEachViewWindow([p_func, &wins](ViewWindow *p_win) {
@ -974,3 +973,24 @@ QVector<ViewWindow *> ViewArea::getAllViewWindows(ViewSplit *p_split, const View
});
return wins;
}
QVector<ViewWindow *> ViewArea::getAllViewWindows(ViewSplit *p_split) const
{
return getAllViewWindows(p_split, [](ViewWindow *) {
return true;
});
}
QList<Buffer *> ViewArea::getAllBuffersInViewSplits() const
{
QSet<Buffer *> bufferSet;
for (auto split : m_splits) {
auto wins = getAllViewWindows(split);
for (auto win : wins) {
bufferSet.insert(win->getBuffer());
}
}
return bufferSet.values();
}

View File

@ -68,6 +68,9 @@ namespace vnotex
QSize sizeHint() const Q_DECL_OVERRIDE;
// Not all Workspace. Just all ViewSplits.
QList<Buffer *> getAllBuffersInViewSplits() const;
public slots:
void openBuffer(Buffer *p_buffer, const QSharedPointer<FileOpenParameters> &p_paras);
@ -198,7 +201,9 @@ namespace vnotex
void checkCurrentViewWindowChange();
QVector<ViewWindow *> getAllViewWindows(ViewSplit *p_split, const ViewSplit::ViewWindowSelector &p_func);
QVector<ViewWindow *> getAllViewWindows(ViewSplit *p_split, const ViewSplit::ViewWindowSelector &p_func) const;
QVector<ViewWindow *> getAllViewWindows(ViewSplit *p_split) const;
QLayout *m_mainLayout = nullptr;

View File

@ -109,7 +109,7 @@ void ViewSplit::setupCornerWidget()
// Container.
auto widget = new QWidget(this);
widget->setProperty(PropertyDefs::s_viewSplitCornerWidget, true);
widget->setProperty(PropertyDefs::c_viewSplitCornerWidget, true);
auto layout = new QHBoxLayout(widget);
layout->setContentsMargins(0, 0, 0, 0);
@ -117,7 +117,7 @@ void ViewSplit::setupCornerWidget()
{
m_windowListButton = new QToolButton(this);
m_windowListButton->setPopupMode(QToolButton::InstantPopup);
m_windowListButton->setProperty(PropertyDefs::s_actionToolButton, true);
m_windowListButton->setProperty(PropertyDefs::c_actionToolButton, true);
auto act = new QAction(s_windowListIcon, tr("Windows List"), m_windowListButton);
m_windowListButton->setDefaultAction(act);
@ -141,7 +141,7 @@ void ViewSplit::setupCornerWidget()
{
m_menuButton = new QToolButton(this);
m_menuButton->setPopupMode(QToolButton::InstantPopup);
m_menuButton->setProperty(PropertyDefs::s_actionToolButton, true);
m_menuButton->setProperty(PropertyDefs::c_actionToolButton, true);
auto act = new QAction(s_menuIcon, tr("Workspaces and Splits"), m_menuButton);
m_menuButton->setDefaultAction(act);
@ -607,7 +607,7 @@ void ViewSplit::mousePressEvent(QMouseEvent *p_event)
emit focused(this);
}
bool ViewSplit::forEachViewWindow(const ViewWindowSelector &p_func)
bool ViewSplit::forEachViewWindow(const ViewWindowSelector &p_func) const
{
int cnt = getViewWindowCount();
for (int i = 0; i < cnt; ++i) {

View File

@ -56,7 +56,7 @@ namespace vnotex
// @p_func: return true if going well, return false to stop the iteration.
// Return false if there is a break.
bool forEachViewWindow(const ViewWindowSelector &p_func);
bool forEachViewWindow(const ViewWindowSelector &p_func) const;
QVector<ViewWindowNavigationModeInfo> getNavigationModeInfo() const;

View File

@ -128,7 +128,7 @@ void ViewWindow::initIcons()
}
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
const QString savedIconName("saved.svg");
const QString savedIconName("buffer.svg");
const QString unsavedIconFg("base#icon#warning#fg");
s_savedIcon = IconUtils::fetchIcon(themeMgr.getIconFile(savedIconName));
s_modifiedIcon = IconUtils::fetchIcon(themeMgr.getIconFile(savedIconName),
@ -1083,6 +1083,6 @@ void ViewWindow::read(bool p_save)
QToolBar *ViewWindow::createToolBar(QWidget *p_parent)
{
auto toolBar = new QToolBar(p_parent);
toolBar->setProperty(PropertyDefs::s_viewWindowToolBar, true);
toolBar->setProperty(PropertyDefs::c_viewWindowToolBar, true);
return toolBar;
}

View File

@ -121,7 +121,7 @@ QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action)
auto toolBtn = dynamic_cast<QToolButton *>(p_tb->widgetForAction(act));
Q_ASSERT(toolBtn);
toolBtn->setPopupMode(QToolButton::InstantPopup);
toolBtn->setProperty(PropertyDefs::s_toolButtonWithoutMenuIndicator, true);
toolBtn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
auto menu = WidgetsFactory::createMenu(p_tb);
@ -282,7 +282,7 @@ QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action)
auto toolBtn = dynamic_cast<QToolButton *>(p_tb->widgetForAction(act));
Q_ASSERT(toolBtn);
toolBtn->setPopupMode(QToolButton::InstantPopup);
toolBtn->setProperty(PropertyDefs::s_toolButtonWithoutMenuIndicator, true);
toolBtn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
auto menu = new AttachmentPopup(toolBtn, p_tb);
toolBtn->setMenu(menu);
@ -297,7 +297,7 @@ QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action)
auto toolBtn = dynamic_cast<QToolButton *>(p_tb->widgetForAction(act));
Q_ASSERT(toolBtn);
toolBtn->setPopupMode(QToolButton::InstantPopup);
toolBtn->setProperty(PropertyDefs::s_toolButtonWithoutMenuIndicator, true);
toolBtn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
addButtonShortcut(toolBtn, editorConfig.getShortcut(Shortcut::Outline), viewWindow);
@ -322,7 +322,7 @@ QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action)
auto toolBtn = dynamic_cast<QToolButton *>(p_tb->widgetForAction(act));
Q_ASSERT(toolBtn);
toolBtn->setPopupMode(QToolButton::InstantPopup);
toolBtn->setProperty(PropertyDefs::s_toolButtonWithoutMenuIndicator, true);
toolBtn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
auto menu = WidgetsFactory::createMenu(p_tb);

View File

@ -43,6 +43,7 @@ SOURCES += \
$$PWD/lineedit.cpp \
$$PWD/lineeditdelegate.cpp \
$$PWD/listwidget.cpp \
$$PWD/locationlist.cpp \
$$PWD/mainwindow.cpp \
$$PWD/markdownviewwindow.cpp \
$$PWD/navigationmodemgr.cpp \
@ -50,6 +51,8 @@ SOURCES += \
$$PWD/outlineprovider.cpp \
$$PWD/outlineviewer.cpp \
$$PWD/propertydefs.cpp \
$$PWD/searchinfoprovider.cpp \
$$PWD/searchpanel.cpp \
$$PWD/systemtrayhelper.cpp \
$$PWD/textviewwindow.cpp \
$$PWD/toolbarhelper.cpp \
@ -130,6 +133,7 @@ HEADERS += \
$$PWD/lineedit.h \
$$PWD/lineeditdelegate.h \
$$PWD/listwidget.h \
$$PWD/locationlist.h \
$$PWD/mainwindow.h \
$$PWD/markdownviewwindow.h \
$$PWD/navigationmodemgr.h \
@ -138,6 +142,8 @@ HEADERS += \
$$PWD/outlineprovider.h \
$$PWD/outlineviewer.h \
$$PWD/propertydefs.h \
$$PWD/searchinfoprovider.h \
$$PWD/searchpanel.h \
$$PWD/systemtrayhelper.h \
$$PWD/textviewwindow.h \
$$PWD/textviewwindowhelper.h \

View File

@ -9,6 +9,7 @@
#include <QToolButton>
#include <QFormLayout>
#include <QPlainTextEdit>
#include <QRadioButton>
#include "lineedit.h"
#include "combobox.h"
@ -52,6 +53,11 @@ QCheckBox *WidgetsFactory::createCheckBox(const QString &p_text, QWidget *p_pare
return new QCheckBox(p_text, p_parent);
}
QRadioButton *WidgetsFactory::createRadioButton(const QString &p_text, QWidget *p_parent)
{
return new QRadioButton(p_text, p_parent);
}
QSpinBox *WidgetsFactory::createSpinBox(QWidget *p_parent)
{
return new QSpinBox(p_parent);

View File

@ -12,6 +12,7 @@ class QToolButton;
class QDoubleSpinBox;
class QFormLayout;
class QPlainTextEdit;
class QRadioButton;
namespace vnotex
{
@ -32,6 +33,8 @@ namespace vnotex
static QCheckBox *createCheckBox(const QString &p_text, QWidget *p_parent = nullptr);
static QRadioButton *createRadioButton(const QString &p_text, QWidget *p_parent = nullptr);
static QSpinBox *createSpinBox(QWidget *p_parent = nullptr);
static QDoubleSpinBox *createDoubleSpinBox(QWidget *p_parent = nullptr);

View File

@ -18,6 +18,7 @@ include($$CORE_FOLDER/core.pri)
include($$SRC_FOLDER/widgets/widgets.pri)
include($$SRC_FOLDER/utils/utils.pri)
include($$SRC_FOLDER/export/export.pri)
include($$SRC_FOLDER/search/search.pri)
SOURCES += \
test_notebook.cpp