mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-04 21:39:52 +08:00
support full-text search (#1733)
This commit is contained in:
parent
372f092919
commit
fe827e74f0
@ -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();
|
||||
|
@ -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 \
|
||||
|
@ -23,6 +23,8 @@ namespace vnotex
|
||||
CloseTab,
|
||||
NavigationDock,
|
||||
OutlineDock,
|
||||
SearchDock,
|
||||
LocationListDock,
|
||||
NavigationMode,
|
||||
LocateNode,
|
||||
VerticalSplit,
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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
72
src/core/location.h
Normal 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
|
@ -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();
|
||||
|
@ -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
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "buffermgr.h"
|
||||
#include "configmgr.h"
|
||||
#include "coreconfig.h"
|
||||
#include "location.h"
|
||||
|
||||
#include "fileopenparameters.h"
|
||||
|
||||
|
@ -16,6 +16,7 @@ namespace vnotex
|
||||
struct FileOpenParameters;
|
||||
class Event;
|
||||
class Notebook;
|
||||
struct ComplexLocation;
|
||||
|
||||
class VNoteX : public QObject
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>
|
||||
|
14
src/data/core/icons/buffer.svg
Normal file
14
src/data/core/icons/buffer.svg
Normal 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 |
1
src/data/core/icons/cancel.svg
Normal file
1
src/data/core/icons/cancel.svg
Normal 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 |
@ -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 |
10
src/data/core/icons/search.svg
Normal file
10
src/data/core/icons/search.svg
Normal 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 |
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -233,6 +233,11 @@
|
||||
"fg" : "@base#icon#inactive#fg"
|
||||
}
|
||||
},
|
||||
"locationlist" : {
|
||||
"node_icon" : {
|
||||
"fg" : "@base#icon#fg"
|
||||
}
|
||||
},
|
||||
"viewsplit" : {
|
||||
"action_button" : {
|
||||
"fg" : "@base#icon#inactive#fg",
|
||||
|
@ -92,6 +92,11 @@
|
||||
"fg" : "@base#icon#disabled#fg"
|
||||
}
|
||||
},
|
||||
"locationlist" : {
|
||||
"node_icon" : {
|
||||
"fg" : "@base#icon#fg"
|
||||
}
|
||||
},
|
||||
"viewsplit" : {
|
||||
"action_button" : {
|
||||
"fg" : "#808080",
|
||||
|
@ -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;
|
||||
|
@ -229,6 +229,11 @@
|
||||
"fg" : "@base#icon#inactive#fg"
|
||||
}
|
||||
},
|
||||
"locationlist" : {
|
||||
"node_icon" : {
|
||||
"fg" : "@base#icon#fg"
|
||||
}
|
||||
},
|
||||
"viewsplit" : {
|
||||
"action_button" : {
|
||||
"fg" : "@base#icon#inactive#fg",
|
||||
|
@ -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
|
||||
}
|
||||
|
259
src/search/filesearchengine.cpp
Normal file
259
src/search/filesearchengine.cpp
Normal 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);
|
||||
}
|
||||
}
|
98
src/search/filesearchengine.h
Normal file
98
src/search/filesearchengine.h
Normal 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
|
57
src/search/isearchengine.h
Normal file
57
src/search/isearchengine.h
Normal 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
17
src/search/search.pri
Normal 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
47
src/search/searchdata.cpp
Normal 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
110
src/search/searchdata.h
Normal 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
501
src/search/searcher.cpp
Normal 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
93
src/search/searcher.h
Normal 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
|
54
src/search/searchresultitem.cpp
Normal file
54
src/search/searchresultitem.cpp
Normal 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);
|
||||
}
|
42
src/search/searchresultitem.h
Normal file
42
src/search/searchresultitem.h
Normal 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
247
src/search/searchtoken.cpp
Normal 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
86
src/search/searchtoken.h
Normal 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
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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]() {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -77,7 +77,7 @@ namespace vnotex
|
||||
|
||||
QCheckBox *m_incrementalSearchCheckBox = nullptr;
|
||||
|
||||
FindOptions m_options = FindOption::None;
|
||||
FindOptions m_options = FindOption::FindNone;
|
||||
|
||||
QTimer *m_findTextTimer = nullptr;
|
||||
|
||||
|
180
src/widgets/locationlist.cpp
Normal file
180
src/widgets/locationlist.cpp
Normal 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()));
|
||||
}
|
||||
}
|
74
src/widgets/locationlist.h
Normal file
74
src/widgets/locationlist.h
Normal 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
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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."));
|
||||
|
@ -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]() {
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
43
src/widgets/searchinfoprovider.cpp
Normal file
43
src/widgets/searchinfoprovider.cpp
Normal 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;
|
||||
}
|
36
src/widgets/searchinfoprovider.h
Normal file
36
src/widgets/searchinfoprovider.h
Normal 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
526
src/widgets/searchpanel.cpp
Normal 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
144
src/widgets/searchpanel.h
Normal 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
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 \
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user