support search

- Ctrl+E C to toggle the search dock;
This commit is contained in:
Le Tan 2018-03-09 22:47:53 +08:00
parent d404360fee
commit a2ee5413a1
41 changed files with 2668 additions and 269 deletions

View File

@ -75,7 +75,7 @@ void VExportDialog::setupUI()
// Notes to export.
m_srcCB = VUtils::getComboBox();
m_srcCB->setToolTip(tr("Choose notes to export"));
m_srcCB->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
m_srcCB->setSizeAdjustPolicy(QComboBox::AdjustToContents);
connect(m_srcCB, SIGNAL(currentIndexChanged(int)),
this, SLOT(handleCurrentSrcChanged(int)));
@ -580,7 +580,7 @@ void VExportDialog::startExport()
if (opt.m_outputSuffix.isEmpty()
|| opt.m_cmd.isEmpty()
|| opt.m_allInOne && opt.m_folderSep.isEmpty()) {
|| (opt.m_allInOne && opt.m_folderSep.isEmpty())) {
appendLogLine(tr("Invalid configurations for custom export."));
m_inExport = false;
m_exportBtn->setEnabled(true);
@ -1274,11 +1274,11 @@ int VExportDialog::doExportCustomAllInOne(const QList<QString> &p_files,
QWidget *VExportDialog::setupCustomAdvancedSettings()
{
// Source format.
m_customSrcFormatCB = VUtils::getComboBox();
m_customSrcFormatCB = VUtils::getComboBox(this);
m_customSrcFormatCB->setToolTip(tr("Choose format of the input"));
// Output suffix.
m_customSuffixEdit = new VLineEdit();
m_customSuffixEdit = new VLineEdit(this);
m_customSuffixEdit->setPlaceholderText(tr("Without the preceding dot"));
m_customSuffixEdit->setToolTip(tr("Suffix of the output file without the preceding dot"));
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
@ -1288,11 +1288,12 @@ QWidget *VExportDialog::setupCustomAdvancedSettings()
QLabel *tipsLabel = new QLabel(tr("<span><span style=\"font-weight:bold;\">%0</span> for the input file; "
"<span style=\"font-weight:bold;\">%1</span> for the output file; "
"<span style=\"font-weight:bold;\">%2</span> for the rendering CSS style file; "
"<span style=\"font-weight:bold;\">%3</span> for the input file directory.</span>"));
"<span style=\"font-weight:bold;\">%3</span> for the input file directory.</span>"),
this);
tipsLabel->setWordWrap(true);
// Enable All In One.
m_customAllInOneCB = new QCheckBox(tr("Enable All In One"));
m_customAllInOneCB = new QCheckBox(tr("Enable All In One"), this);
m_customAllInOneCB->setToolTip(tr("Pass a list of input files to the custom command"));
connect(m_customAllInOneCB, &QCheckBox::stateChanged,
this, [this](int p_state) {
@ -1302,13 +1303,13 @@ QWidget *VExportDialog::setupCustomAdvancedSettings()
});
// Input directory separator.
m_customFolderSepEdit = new VLineEdit();
m_customFolderSepEdit = new VLineEdit(this);
m_customFolderSepEdit->setPlaceholderText(tr("Separator to concatenate input files directories"));
m_customFolderSepEdit->setToolTip(tr("Separator to concatenate input files directories"));
m_customFolderSepEdit->setEnabled(false);
// Target file name for all in one.
m_customTargetFileNameEdit = new VLineEdit();
m_customTargetFileNameEdit = new VLineEdit(this);
m_customTargetFileNameEdit->setPlaceholderText(tr("Empty to use the name of the first source file"));
m_customTargetFileNameEdit->setToolTip(tr("Name of the generated All-In-One file"));
validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
@ -1317,7 +1318,7 @@ QWidget *VExportDialog::setupCustomAdvancedSettings()
m_customTargetFileNameEdit->setEnabled(false);
// Cmd edit.
m_customCmdEdit = new QPlainTextEdit();
m_customCmdEdit = new QPlainTextEdit(this);
m_customCmdEdit->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
QString cmdExamp("pandoc --resource-path=.:\"%3\" --css=\"%2\" -s -o \"%1\" \"%0\"");
m_customCmdEdit->setPlaceholderText(cmdExamp);
@ -1348,12 +1349,12 @@ QWidget *VExportDialog::setupCustomAdvancedSettings()
QWidget *wid = new QWidget();
wid->setLayout(advLayout);
m_customCmdEdit->setMaximumHeight(100);
m_customCmdEdit->setMaximumHeight(m_customSrcFormatCB->height() * 3);
return wid;
}
int VExportDialog::outputAsHTML(QString &p_outputFolder,
int VExportDialog::outputAsHTML(const QString &p_outputFolder,
QString *p_errMsg,
QList<QString> *p_outputFiles)
{

View File

@ -368,7 +368,7 @@ private:
ExportFormat currentFormat() const;
int outputAsHTML(QString &p_outputFolder,
int outputAsHTML(const QString &p_outputFolder,
QString *p_errMsg = NULL,
QList<QString> *p_outputFiles = NULL);

View File

@ -49,7 +49,7 @@ void VFindReplaceDialog::setupUI()
m_replaceFindBtn->setProperty("FlatBtn", true);
m_replaceAllBtn = new QPushButton(tr("Replace A&ll"));
m_replaceAllBtn->setProperty("FlatBtn", true);
m_advancedBtn = new QPushButton(tr("&Advanced >>"));
m_advancedBtn = new QPushButton(tr("&Advanced >>>"));
m_advancedBtn->setProperty("FlatBtn", true);
m_advancedBtn->setCheckable(true);
@ -199,9 +199,9 @@ void VFindReplaceDialog::handleFindTextChanged(const QString &p_text)
void VFindReplaceDialog::advancedBtnToggled(bool p_checked)
{
if (p_checked) {
m_advancedBtn->setText("B&asic <<");
m_advancedBtn->setText(tr("B&asic <<<"));
} else {
m_advancedBtn->setText("&Advanced <<");
m_advancedBtn->setText(tr("&Advanced >>>"));
}
m_caseSensitiveCheck->setVisible(p_checked);

View File

@ -48,6 +48,7 @@ private slots:
private:
void setupUI();
// Bit OR of FindOption
uint m_options;
bool m_replaceAvailable;

34
src/isearchengine.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef ISEARCHENGINE_H
#define ISEARCHENGINE_H
#include <QObject>
#include <QVector>
#include "vsearchconfig.h"
// Abstract class for search engine.
class ISearchEngine : public QObject
{
Q_OBJECT
public:
explicit ISearchEngine(QObject *p_parent = nullptr)
: QObject(p_parent)
{
}
virtual void search(const QSharedPointer<VSearchConfig> &p_config,
const QSharedPointer<VSearchResult> &p_result) = 0;
virtual void stop() = 0;
virtual void clear() = 0;
signals:
void finished(const QSharedPointer<VSearchResult> &p_result);
void resultItemAdded(const QSharedPointer<VSearchResultItem> &p_item);
protected:
QSharedPointer<VSearchResult> m_result;
};
#endif // ISEARCHENGINE_H

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#C9302C" d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4
L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1
c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1
c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 995 B

View File

@ -0,0 +1,15 @@
<?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">
<g>
<path style="fill:#000000" d="M398.6,169.2c-0.9-2.2-2-4.3-3.5-6.1l-83.8-91.7c-1.9-2.1-4.2-3.6-6.7-4.9c-2.9-1.5-6.1-2.1-9.5-2.1H135.2
c-12.4,0-22.7,10.6-22.7,23.9v335.2c0,13.4,10.3,24.9,22.7,24.9h243.1c12.4,0,22.2-11.5,22.2-24.9V179.4
C400.5,175.8,400,172.3,398.6,169.2z M160.5,178.6c0-1.5,1.8-2.1,3.4-2.1h70.8c1.6,0,2.8,0.6,2.8,2.1v10.8c0,1.4-1.1,3.1-2.8,3.1
h-70.8c-1.6,0-3.4-1.7-3.4-3.1V178.6z M160.5,306.6c0-1.5,1.8-2.1,3.4-2.1h122.2c1.6,0,2.4,0.6,2.4,2.1v10.8c0,1.4-0.7,3.1-2.4,3.1
H163.9c-1.6,0-3.4-1.7-3.4-3.1V306.6z M320.5,381.4c0,1.4-0.7,3.1-2.4,3.1H163.9c-1.6,0-3.4-1.7-3.4-3.1v-10.8
c0-1.5,1.8-2.1,3.4-2.1h154.2c1.6,0,2.4,0.6,2.4,2.1V381.4z M352.5,253.4c0,1.4-0.7,3.1-2.4,3.1H163.9c-1.6,0-3.4-1.7-3.4-3.1
v-10.8c0-1.5,1.8-2.1,3.4-2.1h186.2c1.6,0,2.4,0.6,2.4,2.1V253.4z M305.6,177.5c-5.6,0-11.1-5.2-11.1-11.3v-66l71.2,77.3H305.6z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#000000" d="M445,386.7l-84.8-85.9c13.8-24.1,21-50.9,21-77.9c0-87.6-71.2-158.9-158.6-158.9C135.2,64,64,135.3,64,222.9
c0,87.6,71.2,158.9,158.6,158.9c27.9,0,55.5-7.7,80.1-22.4l84.4,85.6c1.9,1.9,4.6,3.1,7.3,3.1c2.7,0,5.4-1.1,7.3-3.1l43.3-43.8
C449,397.1,449,390.7,445,386.7z M222.6,125.9c53.4,0,96.8,43.5,96.8,97c0,53.5-43.4,97-96.8,97c-53.4,0-96.8-43.5-96.8-97
C125.8,169.4,169.2,125.9,222.6,125.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 905 B

View File

@ -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="M32,376h283.35c6.186-14.112,20.281-24,36.65-24s30.465,9.888,36.65,24H480v32h-91.35c-6.186,14.112-20.281,24-36.65,24
s-30.465-9.888-36.65-24H32"/>
<path style="fill:#000000" d="M32,240h91.35c6.186-14.112,20.281-24,36.65-24s30.465,9.888,36.65,24H480v32H196.65c-6.186,14.112-20.281,24-36.65,24
s-30.465-9.888-36.65-24H32"/>
<path style="fill:#000000" d="M32,104h283.35c6.186-14.112,20.281-24,36.65-24s30.465,9.888,36.65,24H480v32h-91.35c-6.186,14.112-20.281,24-36.65,24
s-30.465-9.888-36.65-24H32"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1022 B

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<path style="fill:#000000" d="M468.7,64H43.3c-6,0-11.3,5-11.3,11.1v265.7c0,6.2,5.2,11.1,11.3,11.1h425.4c6,0,11.3-5,11.3-11.1V75.1
C480,69,474.8,64,468.7,64z M448,320H64V96h384V320z"/>
<path style="fill:#000000" d="M302.5,448c28-0.5,41.5-3.9,29-12.5c-12.5-8.7-28.5-15.3-29-22.5c-0.3-3.7-1.7-45-1.7-45H256h-44.8c0,0-1.5,41.3-1.7,45
c-0.5,7.1-16.5,13.8-29,22.5c-12.5,8.7,1,12,29,12.5H302.5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 890 B

View File

@ -183,64 +183,18 @@ QDockWidget::close-button:hover, QDockWidget::float-button:hover {
/* End DockWidget */
/* QPushButton */
QPushButton {
color: @pushbutton_fg;
background: @pushbutton_bg;
border: 1px solid @pushbutton_border;
padding: 3px;
min-width: 80px;
}
QPushButton:focus {
background-color: @pushbutton_focus_bg;
}
QPushButton:pressed {
background-color: @pushbutton_pressed_bg;
}
QPushButton:checked {
background-color: @pushbutton_checked_bg;
}
QPushButton:checked:hover {
background-color: @pushbutton_hover_bg;
}
QPushButton:hover {
background-color: @pushbutton_hover_bg;
}
QPushButton:flat {
border: none;
}
QPushButton:default {
border: 1px solid @pushbutton_default_border;
}
QPushButton:disabled {
color: @pushbutton_disabled_fg;
background-color: @pushbutton_disabled_bg;
}
QPushButton::menu-indicator {
image: url(arrow_dropdown.svg);
width: 16px;
height: 16px;
}
QPushButton[SpecialBtn="true"] {
color: @pushbutton_specialbtn_fg;
background: @pushbutton_specialbtn_bg;
}
QPushButton[SpecialBtn="true"]:focus {
background-color: @pushbutton_specialbtn_focus_bg;
QPushButton[SpecialBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_specialbtn_bg;
}
QPushButton[SpecialBtn="true"]:pressed {
background-color: @pushbutton_specialbtn_pressed_bg;
QPushButton[SpecialBtn="true"]:focus {
background-color: @pushbutton_specialbtn_focus_bg;
}
QPushButton[SpecialBtn="true"]:checked {
@ -255,16 +209,15 @@ QPushButton[SpecialBtn="true"]:hover {
background-color: @pushbutton_specialbtn_hover_bg;
}
QPushButton[SpecialBtn="true"]:pressed {
background-color: @pushbutton_specialbtn_pressed_bg;
}
QPushButton[SpecialBtn="true"]:disabled {
color: @pushbutton_disabled_fg;
background-color: @pushbutton_disabled_bg;
}
QPushButton[SpecialBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_specialbtn_bg;
}
QPushButton[CornerBtn="true"] {
padding: 4px -2px 4px -2px;
margin: 0px;
@ -277,14 +230,19 @@ QPushButton[CornerBtn="true"]::menu-indicator {
image: none;
}
QPushButton[CornerBtn="true"]:hover {
background-color: @pushbutton_cornerbtn_hover_bg;
QPushButton[CornerBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[CornerBtn="true"]:focus {
background-color: @pushbutton_cornerbtn_focus_bg;
}
QPushButton[CornerBtn="true"]:hover {
background-color: @pushbutton_cornerbtn_hover_bg;
}
QPushButton[CornerBtn="true"]:pressed {
background-color: @pushbutton_cornerbtn_pressed_bg;
}
@ -294,11 +252,6 @@ QPushButton[CornerBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[CornerBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[StatusBtn="true"] {
font: bold;
padding: 0px 2px 0px 2px;
@ -308,14 +261,19 @@ QPushButton[StatusBtn="true"] {
min-width: -1;
}
QPushButton[StatusBtn="true"]:hover {
background-color: @pushbutton_statusbtn_hover_bg;
QPushButton[StatusBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[StatusBtn="true"]:focus {
background-color: @pushbutton_statusbtn_focus_bg;;
}
QPushButton[StatusBtn="true"]:hover {
background-color: @pushbutton_statusbtn_hover_bg;
}
QPushButton[StatusBtn="true"]:pressed {
background-color: @pushbutton_statusbtn_pressed_bg;
}
@ -325,11 +283,6 @@ QPushButton[StatusBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[StatusBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[FlatBtn="true"] {
padding: 4px;
margin: 0px;
@ -338,14 +291,19 @@ QPushButton[FlatBtn="true"] {
min-width: -1;
}
QPushButton[FlatBtn="true"]:hover {
background-color: @pushbutton_flatbtn_hover_bg;
QPushButton[FlatBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[FlatBtn="true"]:focus {
background-color: @pushbutton_flatbtn_focus_bg;
}
QPushButton[FlatBtn="true"]:hover {
background-color: @pushbutton_flatbtn_hover_bg;
}
QPushButton[FlatBtn="true"]:pressed {
background-color: @pushbutton_flatbtn_pressed_bg;
}
@ -355,11 +313,6 @@ QPushButton[FlatBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[FlatBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[SelectionBtn="true"] {
padding: 4px 10px 4px 10px;
border: none;
@ -369,14 +322,19 @@ QPushButton[SelectionBtn="true"] {
min-width: -1;
}
QPushButton[SelectionBtn="true"]:hover {
background-color: @pushbutton_selectionbtn_hover_bg;
QPushButton[SelectionBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[SelectionBtn="true"]:focus {
background-color: @pushbutton_selectionbtn_focus_bg;
}
QPushButton[SelectionBtn="true"]:hover {
background-color: @pushbutton_selectionbtn_hover_bg;
}
QPushButton[SelectionBtn="true"]:pressed {
background-color: @pushbutton_selectionbtn_pressed_bg;
}
@ -386,11 +344,6 @@ QPushButton[SelectionBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[SelectionBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[TitleBtn="true"] {
padding: 4px;
margin: 0px;
@ -399,14 +352,19 @@ QPushButton[TitleBtn="true"] {
min-width: -1;
}
QPushButton[TitleBtn="true"]:hover {
background-color: @pushbutton_titlebtn_hover_bg;
QPushButton[TitleBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_titlebtn_bg;
}
QPushButton[TitleBtn="true"]:focus {
background-color: @pushbutton_titlebtn_focus_bg;
}
QPushButton[TitleBtn="true"]:hover {
background-color: @pushbutton_titlebtn_hover_bg;
}
QPushButton[TitleBtn="true"]:pressed {
background-color: @pushbutton_titlebtn_pressed_bg;
}
@ -416,11 +374,6 @@ QPushButton[TitleBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[TitleBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_titlebtn_bg;
}
QPushButton[DangerBtn="true"] {
color: @pushbutton_dangerbtn_fg;
border: none;
@ -428,14 +381,23 @@ QPushButton[DangerBtn="true"] {
min-width: -1;
}
QPushButton[DangerBtn="true"]:hover {
background-color: @pushbutton_dangerbtn_hover_bg;
QPushButton[DangerBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_dangerbtn_bg;
}
QPushButton[DangerBtn="true"]:focus {
background-color: @pushbutton_dangerbtn_focus_bg;
}
QPushButton[DangerBtn="true"]:checked {
background-color: @pushbutton_dangerbtn_pressed_bg;
}
QPushButton[DangerBtn="true"]:hover {
background-color: @pushbutton_dangerbtn_hover_bg;
}
QPushButton[DangerBtn="true"]:pressed {
background-color: @pushbutton_dangerbtn_pressed_bg;
}
@ -445,11 +407,6 @@ QPushButton[DangerBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[DangerBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_dangerbtn_bg;
}
QPushButton[ToolBoxActiveBtn="true"] {
padding: 4px 10px 4px 4px;
margin: 0px;
@ -459,6 +416,11 @@ QPushButton[ToolBoxActiveBtn="true"] {
min-width: -1;
}
QPushButton[ToolBoxActiveBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_toolboxbtn_active_bg;
}
QPushButton[ToolBoxActiveBtn="true"]:focus {
background-color: @pushbutton_toolboxbtn_active_focus_bg;
}
@ -476,11 +438,6 @@ QPushButton[ToolBoxActiveBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[ToolBoxActiveBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_toolboxbtn_active_bg;
}
QPushButton[AvatarBtn="true"] {
padding: 2px 4px 2px 4px;
margin: 0px;
@ -489,14 +446,19 @@ QPushButton[AvatarBtn="true"] {
min-width: -1;
}
QPushButton[AvatarBtn="true"]:hover {
background-color: @pushbutton_avatarbtn_hover_bg;
QPushButton[AvatarBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[AvatarBtn="true"]:focus {
background-color: @pushbutton_avatarbtn_focus_bg;;
}
QPushButton[AvatarBtn="true"]:hover {
background-color: @pushbutton_avatarbtn_hover_bg;
}
QPushButton[AvatarBtn="true"]:pressed {
background-color: @pushbutton_avatarbtn_pressed_bg;
}
@ -506,9 +468,51 @@ QPushButton[AvatarBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[AvatarBtn="true"]:default {
QPushButton {
color: @pushbutton_fg;
background: @pushbutton_bg;
border: 1px solid @pushbutton_border;
padding: 3px;
min-width: 80px;
}
QPushButton:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton:focus {
background-color: @pushbutton_focus_bg;
}
QPushButton:checked {
background-color: @pushbutton_checked_bg;
}
QPushButton:checked:hover {
background-color: @pushbutton_hover_bg;
}
QPushButton:flat {
border: none;
}
QPushButton:hover {
background-color: @pushbutton_hover_bg;
}
QPushButton:pressed {
background-color: @pushbutton_pressed_bg;
}
QPushButton:disabled {
color: @pushbutton_disabled_fg;
background-color: @pushbutton_disabled_bg;
}
QPushButton::menu-indicator {
image: url(arrow_dropdown.svg);
width: 16px;
height: 16px;
}
VButtonMenuItem {
@ -544,11 +548,11 @@ VButtonMenuItem[Heading6="true"] {
font-size: 14pt;
}
VButtonMenuItem:hover {
VButtonMenuItem:focus {
background-color: @menubar_item_selected_bg;
}
VButtonMenuItem:focus {
VButtonMenuItem:hover {
background-color: @menubar_item_selected_bg;
}

View File

@ -183,64 +183,18 @@ QDockWidget::close-button:hover, QDockWidget::float-button:hover {
/* End DockWidget */
/* QPushButton */
QPushButton {
color: @pushbutton_fg;
background: @pushbutton_bg;
border: 1px solid @pushbutton_border;
padding: 3px;
min-width: 80px;
}
QPushButton:focus {
background-color: @pushbutton_focus_bg;
}
QPushButton:pressed {
background-color: @pushbutton_pressed_bg;
}
QPushButton:checked {
background-color: @pushbutton_checked_bg;
}
QPushButton:checked:hover {
background-color: @pushbutton_hover_bg;
}
QPushButton:hover {
background-color: @pushbutton_hover_bg;
}
QPushButton:flat {
border: none;
}
QPushButton:default {
border: 1px solid @pushbutton_default_border;
}
QPushButton:disabled {
color: @pushbutton_disabled_fg;
background-color: @pushbutton_disabled_bg;
}
QPushButton::menu-indicator {
image: url(arrow_dropdown.svg);
width: 16px;
height: 16px;
}
QPushButton[SpecialBtn="true"] {
color: @pushbutton_specialbtn_fg;
background: @pushbutton_specialbtn_bg;
}
QPushButton[SpecialBtn="true"]:focus {
background-color: @pushbutton_specialbtn_focus_bg;
QPushButton[SpecialBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_specialbtn_bg;
}
QPushButton[SpecialBtn="true"]:pressed {
background-color: @pushbutton_specialbtn_pressed_bg;
QPushButton[SpecialBtn="true"]:focus {
background-color: @pushbutton_specialbtn_focus_bg;
}
QPushButton[SpecialBtn="true"]:checked {
@ -255,16 +209,15 @@ QPushButton[SpecialBtn="true"]:hover {
background-color: @pushbutton_specialbtn_hover_bg;
}
QPushButton[SpecialBtn="true"]:pressed {
background-color: @pushbutton_specialbtn_pressed_bg;
}
QPushButton[SpecialBtn="true"]:disabled {
color: @pushbutton_disabled_fg;
background-color: @pushbutton_disabled_bg;
}
QPushButton[SpecialBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_specialbtn_bg;
}
QPushButton[CornerBtn="true"] {
padding: 4px -2px 4px -2px;
margin: 0px;
@ -277,14 +230,19 @@ QPushButton[CornerBtn="true"]::menu-indicator {
image: none;
}
QPushButton[CornerBtn="true"]:hover {
background-color: @pushbutton_cornerbtn_hover_bg;
QPushButton[CornerBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[CornerBtn="true"]:focus {
background-color: @pushbutton_cornerbtn_focus_bg;
}
QPushButton[CornerBtn="true"]:hover {
background-color: @pushbutton_cornerbtn_hover_bg;
}
QPushButton[CornerBtn="true"]:pressed {
background-color: @pushbutton_cornerbtn_pressed_bg;
}
@ -294,11 +252,6 @@ QPushButton[CornerBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[CornerBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[StatusBtn="true"] {
font: bold;
padding: 0px 2px 0px 2px;
@ -308,14 +261,19 @@ QPushButton[StatusBtn="true"] {
min-width: -1;
}
QPushButton[StatusBtn="true"]:hover {
background-color: @pushbutton_statusbtn_hover_bg;
QPushButton[StatusBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[StatusBtn="true"]:focus {
background-color: @pushbutton_statusbtn_focus_bg;;
}
QPushButton[StatusBtn="true"]:hover {
background-color: @pushbutton_statusbtn_hover_bg;
}
QPushButton[StatusBtn="true"]:pressed {
background-color: @pushbutton_statusbtn_pressed_bg;
}
@ -325,11 +283,6 @@ QPushButton[StatusBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[StatusBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[FlatBtn="true"] {
padding: 4px;
margin: 0px;
@ -338,14 +291,19 @@ QPushButton[FlatBtn="true"] {
min-width: -1;
}
QPushButton[FlatBtn="true"]:hover {
background-color: @pushbutton_flatbtn_hover_bg;
QPushButton[FlatBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[FlatBtn="true"]:focus {
background-color: @pushbutton_flatbtn_focus_bg;
}
QPushButton[FlatBtn="true"]:hover {
background-color: @pushbutton_flatbtn_hover_bg;
}
QPushButton[FlatBtn="true"]:pressed {
background-color: @pushbutton_flatbtn_pressed_bg;
}
@ -355,11 +313,6 @@ QPushButton[FlatBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[FlatBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[SelectionBtn="true"] {
padding: 4px 10px 4px 10px;
border: none;
@ -369,14 +322,19 @@ QPushButton[SelectionBtn="true"] {
min-width: -1;
}
QPushButton[SelectionBtn="true"]:hover {
background-color: @pushbutton_selectionbtn_hover_bg;
QPushButton[SelectionBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[SelectionBtn="true"]:focus {
background-color: @pushbutton_selectionbtn_focus_bg;
}
QPushButton[SelectionBtn="true"]:hover {
background-color: @pushbutton_selectionbtn_hover_bg;
}
QPushButton[SelectionBtn="true"]:pressed {
background-color: @pushbutton_selectionbtn_pressed_bg;
}
@ -386,11 +344,6 @@ QPushButton[SelectionBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[SelectionBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[TitleBtn="true"] {
padding: 4px;
margin: 0px;
@ -399,14 +352,19 @@ QPushButton[TitleBtn="true"] {
min-width: -1;
}
QPushButton[TitleBtn="true"]:hover {
background-color: @pushbutton_titlebtn_hover_bg;
QPushButton[TitleBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_titlebtn_bg;
}
QPushButton[TitleBtn="true"]:focus {
background-color: @pushbutton_titlebtn_focus_bg;
}
QPushButton[TitleBtn="true"]:hover {
background-color: @pushbutton_titlebtn_hover_bg;
}
QPushButton[TitleBtn="true"]:pressed {
background-color: @pushbutton_titlebtn_pressed_bg;
}
@ -416,11 +374,6 @@ QPushButton[TitleBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[TitleBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_titlebtn_bg;
}
QPushButton[DangerBtn="true"] {
color: @pushbutton_dangerbtn_fg;
border: none;
@ -428,21 +381,25 @@ QPushButton[DangerBtn="true"] {
min-width: -1;
}
QPushButton[DangerBtn="true"]:hover {
background-color: @pushbutton_dangerbtn_hover_bg;
QPushButton[DangerBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_dangerbtn_bg;
}
QPushButton[DangerBtn="true"]:focus {
background-color: @pushbutton_dangerbtn_focus_bg;
}
QPushButton[DangerBtn="true"]:pressed {
QPushButton[DangerBtn="true"]:checked {
background-color: @pushbutton_dangerbtn_pressed_bg;
}
QPushButton[DangerBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_dangerbtn_bg;
QPushButton[DangerBtn="true"]:hover {
background-color: @pushbutton_dangerbtn_hover_bg;
}
QPushButton[DangerBtn="true"]:pressed {
background-color: @pushbutton_dangerbtn_pressed_bg;
}
QPushButton[DangerBtn="true"]:disabled {
@ -459,28 +416,28 @@ QPushButton[ToolBoxActiveBtn="true"] {
min-width: -1;
}
QPushButton[ToolBoxActiveBtn="true"]:focus {
background-color: @pushbutton_toolboxbtn_active_focus_bg;
QPushButton[ToolBoxActiveBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_toolboxbtn_active_bg;
}
QPushButton[ToolBoxActiveBtn="true"]:hover {
background-color: @pushbutton_toolboxbtn_active_hover_bg;
QPushButton[ToolBoxActiveBtn="true"]:focus {
background-color: @pushbutton_toolboxbtn_active_focus_bg;
}
QPushButton[ToolBoxActiveBtn="true"]:pressed {
background-color: @pushbutton_toolboxbtn_active_pressed_bg;
}
QPushButton[ToolBoxActiveBtn="true"]:hover {
background-color: @pushbutton_toolboxbtn_active_hover_bg;
}
QPushButton[ToolBoxActiveBtn="true"]:disabled {
color: @pushbutton_disabled_fg;
background-color: @pushbutton_disabled_bg;
}
QPushButton[ToolBoxActiveBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: @pushbutton_toolboxbtn_active_bg;
}
QPushButton[AvatarBtn="true"] {
padding: 2px 4px 2px 4px;
margin: 0px;
@ -489,14 +446,19 @@ QPushButton[AvatarBtn="true"] {
min-width: -1;
}
QPushButton[AvatarBtn="true"]:hover {
background-color: @pushbutton_avatarbtn_hover_bg;
}
QPushButton[AvatarBtn="true"]:focus {
background-color: @pushbutton_avatarbtn_focus_bg;;
}
QPushButton[AvatarBtn="true"]:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton[AvatarBtn="true"]:hover {
background-color: @pushbutton_avatarbtn_hover_bg;
}
QPushButton[AvatarBtn="true"]:pressed {
background-color: @pushbutton_avatarbtn_pressed_bg;
}
@ -506,9 +468,51 @@ QPushButton[AvatarBtn="true"]:disabled {
background-color: @pushbutton_disabled_bg;
}
QPushButton[AvatarBtn="true"]:default {
QPushButton {
color: @pushbutton_fg;
background: @pushbutton_bg;
border: 1px solid @pushbutton_border;
padding: 3px;
min-width: 80px;
}
QPushButton:focus {
background-color: @pushbutton_focus_bg;
}
QPushButton:checked {
background-color: @pushbutton_checked_bg;
}
QPushButton:checked:hover {
background-color: @pushbutton_hover_bg;
}
QPushButton:flat {
border: none;
}
QPushButton:default {
border: 1px solid @pushbutton_default_border;
background-color: transparent;
}
QPushButton:hover {
background-color: @pushbutton_hover_bg;
}
QPushButton:pressed {
background-color: @pushbutton_pressed_bg;
}
QPushButton:disabled {
color: @pushbutton_disabled_fg;
background-color: @pushbutton_disabled_bg;
}
QPushButton::menu-indicator {
image: url(arrow_dropdown.svg);
width: 16px;
height: 16px;
}
VButtonMenuItem {
@ -544,11 +548,11 @@ VButtonMenuItem[Heading6="true"] {
font-size: 14pt;
}
VButtonMenuItem:hover {
VButtonMenuItem:focus {
background-color: @menubar_item_selected_bg;
}
VButtonMenuItem:focus {
VButtonMenuItem:hover {
background-color: @menubar_item_selected_bg;
}

View File

@ -149,6 +149,9 @@ enable_compact_mode=true
; Whether enable tools dock widget
tools_dock_checked=true
; Whether enable search dock widget
search_dock_checked=false
; Whether show menu bar
menu_bar_checked=true
@ -201,6 +204,10 @@ single_click_close_previous_tab=true
; Whether enable auto wildcard match in simple search like list and tree widgets
enable_wildcard_in_simple_search=true
; Search options
; scope,object,target,engine,option,pattern
search_options=4,2,7,0,0,""
[export]
; Path of the wkhtmltopdf tool
wkhtmltopdf=wkhtmltopdf
@ -279,6 +286,8 @@ Find=Ctrl+F
FindNext=F3
; Find previous occurence
FindPrevious=Shift+F3
; Advanced find
AdvancedFind=Ctrl+Alt+F
; Recover last closed file
LastClosedFile=Ctrl+Shift+T
; Activate next tab
@ -311,6 +320,8 @@ OnePanelView=P
DiscardAndRead=Q
; Toggle Tools dock widget
ToolsDock=T
; Toggle Search dock widget
SearchDock=C
; Close current note
CloseNote=X
; Show shortcuts help document

View File

@ -113,7 +113,11 @@ SOURCES += main.cpp\
vstyleditemdelegate.cpp \
vtreewidget.cpp \
dialog/vexportdialog.cpp \
vexporter.cpp
vexporter.cpp \
vsearcher.cpp \
vsearch.cpp \
vsearchresulttree.cpp \
vsearchengine.cpp
HEADERS += vmainwindow.h \
vdirectorytree.h \
@ -214,7 +218,13 @@ HEADERS += vmainwindow.h \
vtreewidget.h \
dialog/vexportdialog.h \
vexporter.h \
vwordcountinfo.h
vwordcountinfo.h \
vsearcher.h \
vsearch.h \
vsearchresulttree.h \
isearchengine.h \
vsearchconfig.h \
vsearchengine.h
RESOURCES += \
vnote.qrc \

View File

@ -522,13 +522,18 @@ bool VUtils::isImageURLText(const QString &p_url)
qreal VUtils::calculateScaleFactor()
{
// const qreal refHeight = 1152;
// const qreal refWidth = 2048;
const qreal refDpi = 96;
static qreal factor = -1;
if (factor < 0) {
const qreal refDpi = 96;
qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
qreal factor = dpi / refDpi;
return factor < 1 ? 1 : factor;
factor = dpi / refDpi;
if (factor < 1) {
factor = 1;
}
}
return factor;
}
bool VUtils::realEqual(qreal p_a, qreal p_b)

View File

@ -104,8 +104,6 @@ void VConfigManager::initialize()
curRenderBackgroundColor = getConfigFromSettings("global",
"current_render_background_color").toString();
m_toolsDockChecked = getConfigFromSettings("global", "tools_dock_checked").toBool();
m_findCaseSensitive = getConfigFromSettings("global",
"find_case_sensitive").toBool();
m_findWholeWordOnly = getConfigFromSettings("global",
@ -498,11 +496,9 @@ QString VConfigManager::fetchDirConfigFilePath(const QString &p_path)
if (!dir.rename(c_obsoleteDirConfigFile, c_dirConfigFile)) {
fileName = c_obsoleteDirConfigFile;
}
qDebug() << "rename old directory config file:" << fileName;
}
QString filePath = QDir::cleanPath(dir.filePath(fileName));
qDebug() << "use directory config file:" << filePath;
return filePath;
}
@ -1473,6 +1469,7 @@ void VConfigManager::resetConfigurations()
void VConfigManager::resetLayoutConfigurations()
{
resetDefaultConfig("global", "tools_dock_checked");
resetDefaultConfig("global", "search_dock_checked");
resetDefaultConfig("global", "menu_bar_checked");
resetDefaultConfig("global", "enable_compact_mode");

View File

@ -184,6 +184,9 @@ public:
bool getToolsDockChecked() const;
void setToolsDockChecked(bool p_checked);
bool getSearchDockChecked() const;
void setSearchDockChecked(bool p_checked);
const QByteArray &getMainWindowGeometry() const;
void setMainWindowGeometry(const QByteArray &p_geometry);
@ -466,6 +469,9 @@ public:
QStringList getCustomExport() const;
void setCustomExport(const QStringList &p_exp);
QStringList getSearchOptions() const;
void setSearchOptions(const QStringList &p_opts);
private:
// Look up a config from user and default settings.
QVariant getConfigFromSettings(const QString &section, const QString &key) const;
@ -605,8 +611,6 @@ private:
QString curRenderBackgroundColor;
bool m_toolsDockChecked;
QByteArray m_mainWindowGeometry;
QByteArray m_mainWindowState;
QByteArray m_mainSplitterState;
@ -1153,14 +1157,26 @@ inline void VConfigManager::setCurRenderBackgroundColor(const QString &colorName
inline bool VConfigManager::getToolsDockChecked() const
{
return m_toolsDockChecked;
return getConfigFromSettings("global", "tools_dock_checked").toBool();
}
inline void VConfigManager::setToolsDockChecked(bool p_checked)
{
m_toolsDockChecked = p_checked;
setConfigToSettings("global", "tools_dock_checked",
m_toolsDockChecked);
setConfigToSettings("global",
"tools_dock_checked",
p_checked);
}
inline bool VConfigManager::getSearchDockChecked() const
{
return getConfigFromSettings("global", "search_dock_checked").toBool();
}
inline void VConfigManager::setSearchDockChecked(bool p_checked)
{
setConfigToSettings("global",
"search_dock_checked",
p_checked);
}
inline const QByteArray& VConfigManager::getMainWindowGeometry() const
@ -2146,4 +2162,15 @@ inline void VConfigManager::setCustomExport(const QStringList &p_exp)
{
setConfigToSettings("export", "custom_export", p_exp);
}
inline QStringList VConfigManager::getSearchOptions() const
{
return getConfigFromSettings("global",
"search_options").toStringList();
}
inline void VConfigManager::setSearchOptions(const QStringList &p_opts)
{
setConfigToSettings("global", "search_options", p_opts);
}
#endif // VCONFIGMANAGER_H

View File

@ -191,12 +191,14 @@ void VDirectoryTree::setNotebook(VNotebook *p_notebook)
void VDirectoryTree::fillTreeItem(QTreeWidgetItem *p_item, VDirectory *p_directory)
{
static QIcon itemIcon = VIconUtils::treeViewIcon(":/resources/icons/dir_item.svg");
int col = 0;
QString name = p_directory->getName();
p_item->setText(col, name);
p_item->setToolTip(col, name);
p_item->setData(col, Qt::UserRole, QVariant::fromValue(p_directory));
p_item->setIcon(col, VIconUtils::treeViewIcon(":/resources/icons/dir_item.svg"));
p_item->setIcon(col, itemIcon);
}
void VDirectoryTree::updateDirectoryTree()

View File

@ -188,6 +188,7 @@ void VEdit::insertLink()
"",
linkText,
linkUrl,
false,
this);
if (dialog.exec() == QDialog::Accepted) {
linkText = dialog.getLinkText();

View File

@ -570,6 +570,20 @@ VEditTab *VEditArea::getTab(int p_winIdx, int p_tabIdx) const
return win->getTab(p_tabIdx);
}
VEditTab *VEditArea::getTab(const VFile *p_file) const
{
int nrWin = splitter->count();
for (int winIdx = 0; winIdx < nrWin; ++winIdx) {
VEditWindow *win = getWindow(winIdx);
int tabIdx = win->findTabByFile(p_file);
if (tabIdx != -1) {
return win->getTab(tabIdx);
}
}
return NULL;
}
QVector<VEditTabInfo> VEditArea::getAllTabsInfo() const
{
QVector<VEditTabInfo> tabs;

View File

@ -44,6 +44,9 @@ public:
// Return the @p_tabIdx tab in the @p_winIdx window.
VEditTab *getTab(int p_winIdx, int p_tabIdx) const;
// Return the tab for @p_file file.
VEditTab *getTab(const VFile *p_file) const;
// Return VEditTabInfo of all edit tabs.
QVector<VEditTabInfo> getAllTabsInfo() const;

View File

@ -38,6 +38,7 @@
#include "dialog/vtipsdialog.h"
#include "vcart.h"
#include "dialog/vexportdialog.h"
#include "vsearcher.h"
extern VConfigManager *g_config;
@ -148,6 +149,7 @@ void VMainWindow::registerCaptainAndNavigationTargets()
m_captain->registerNavigationTarget(m_toolBox);
m_captain->registerNavigationTarget(outline);
m_captain->registerNavigationTarget(m_snippetList);
m_captain->registerNavigationTarget(m_searcher);
// Register Captain mode targets.
m_captain->registerCaptainTarget(tr("AttachmentList"),
@ -174,6 +176,10 @@ void VMainWindow::registerCaptainAndNavigationTargets()
g_config->getCaptainShortcutKeySequence("ToolsDock"),
this,
toggleToolsDockByCaptain);
m_captain->registerCaptainTarget(tr("SearchDock"),
g_config->getCaptainShortcutKeySequence("SearchDock"),
this,
toggleSearchDockByCaptain);
m_captain->registerCaptainTarget(tr("CloseNote"),
g_config->getCaptainShortcutKeySequence("CloseNote"),
this,
@ -1073,6 +1079,17 @@ void VMainWindow::initEditMenu()
connect(m_findReplaceAct, &QAction::triggered,
this, &VMainWindow::openFindDialog);
QAction *advFindAct = new QAction(tr("Advanced Find"), this);
advFindAct->setToolTip(tr("Advanced find within VNote"));
keySeq = g_config->getShortcutKeySequence("AdvancedFind");
qDebug() << "set AdvancedFind shortcut to" << keySeq;
advFindAct->setShortcut(QKeySequence(keySeq));
connect(advFindAct, &QAction::triggered,
this, [this]() {
m_searchDock->setVisible(true);
m_searcher->focusToSearch();
});
m_findNextAct = new QAction(tr("Find Next"), this);
m_findNextAct->setToolTip(tr("Find next occurence"));
keySeq = g_config->getShortcutKeySequence("FindNext");
@ -1188,6 +1205,8 @@ void VMainWindow::initEditMenu()
QMenu *findReplaceMenu = editMenu->addMenu(tr("Find/Replace"));
findReplaceMenu->setToolTipsVisible(true);
findReplaceMenu->addAction(m_findReplaceAct);
findReplaceMenu->addAction(advFindAct);
findReplaceMenu->addSeparator();
findReplaceMenu->addAction(m_findNextAct);
findReplaceMenu->addAction(m_findPreviousAct);
findReplaceMenu->addAction(m_replaceAct);
@ -1268,9 +1287,22 @@ void VMainWindow::initEditMenu()
void VMainWindow::initDockWindows()
{
toolDock = new QDockWidget(tr("Tools"), this);
toolDock->setObjectName("ToolsDock");
toolDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
setTabPosition(Qt::LeftDockWidgetArea, QTabWidget::West);
setTabPosition(Qt::RightDockWidgetArea, QTabWidget::East);
setTabPosition(Qt::TopDockWidgetArea, QTabWidget::North);
setTabPosition(Qt::BottomDockWidgetArea, QTabWidget::South);
setDockNestingEnabled(true);
initToolsDock();
initSearchDock();
}
void VMainWindow::initToolsDock()
{
m_toolDock = new QDockWidget(tr("Tools"), this);
m_toolDock->setObjectName("ToolsDock");
m_toolDock->setAllowedAreas(Qt::AllDockWidgetAreas);
// Outline tree.
outline = new VOutline(this);
@ -1298,16 +1330,35 @@ void VMainWindow::initDockWindows()
":/resources/icons/cart.svg",
tr("Cart"));
toolDock->setWidget(m_toolBox);
addDockWidget(Qt::RightDockWidgetArea, toolDock);
m_toolDock->setWidget(m_toolBox);
addDockWidget(Qt::RightDockWidgetArea, m_toolDock);
QAction *toggleAct = toolDock->toggleViewAction();
QAction *toggleAct = m_toolDock->toggleViewAction();
toggleAct->setToolTip(tr("Toggle the tools dock widget"));
VUtils::fixTextWithCaptainShortcut(toggleAct, "ToolsDock");
m_viewMenu->addAction(toggleAct);
}
void VMainWindow::initSearchDock()
{
m_searchDock = new QDockWidget(tr("Search"), this);
m_searchDock->setObjectName("SearchDock");
m_searchDock->setAllowedAreas(Qt::AllDockWidgetAreas);
m_searcher = new VSearcher(this);
m_searchDock->setWidget(m_searcher);
addDockWidget(Qt::RightDockWidgetArea, m_searchDock);
QAction *toggleAct = m_searchDock->toggleViewAction();
toggleAct->setToolTip(tr("Toggle the search dock widget"));
VUtils::fixTextWithCaptainShortcut(toggleAct, "SearchDock");
m_viewMenu->addAction(toggleAct);
}
void VMainWindow::importNoteFromFile()
{
static QString lastPath = QDir::homePath();
@ -2162,7 +2213,8 @@ void VMainWindow::saveStateAndGeometry()
{
g_config->setMainWindowGeometry(saveGeometry());
g_config->setMainWindowState(saveState());
g_config->setToolsDockChecked(toolDock->isVisible());
g_config->setToolsDockChecked(m_toolDock->isVisible());
g_config->setSearchDockChecked(m_searchDock->isVisible());
if (m_panelViewState == PanelViewState::CompactMode) {
g_config->setNaviSplitterState(m_naviSplitter->saveState());
@ -2181,11 +2233,14 @@ void VMainWindow::restoreStateAndGeometry()
if (!geometry.isEmpty()) {
restoreGeometry(geometry);
}
const QByteArray &state = g_config->getMainWindowState();
if (!state.isEmpty()) {
restoreState(state);
}
toolDock->setVisible(g_config->getToolsDockChecked());
m_toolDock->setVisible(g_config->getToolsDockChecked());
m_searchDock->setVisible(g_config->getSearchDockChecked());
const QByteArray &splitterState = g_config->getMainSplitterState();
if (!splitterState.isEmpty()) {
@ -2260,6 +2315,32 @@ bool VMainWindow::locateFile(VFile *p_file)
return ret;
}
bool VMainWindow::locateDirectory(VDirectory *p_directory)
{
bool ret = false;
if (!p_directory) {
return ret;
}
VNotebook *notebook = p_directory->getNotebook();
if (notebookSelector->locateNotebook(notebook)) {
while (directoryTree->currentNotebook() != notebook) {
QCoreApplication::sendPostedEvents();
}
ret = directoryTree->locateDirectory(p_directory);
}
// Open the directory and file panels after location.
if (m_panelViewState == PanelViewState::CompactMode) {
compactModeView();
} else {
twoPanelView();
}
return ret;
}
void VMainWindow::handleFindDialogTextChanged(const QString &p_text, uint /* p_options */)
{
bool enabled = true;
@ -2659,7 +2740,21 @@ bool VMainWindow::toggleToolsDockByCaptain(void *p_target, void *p_data)
{
Q_UNUSED(p_data);
VMainWindow *obj = static_cast<VMainWindow *>(p_target);
obj->toolDock->setVisible(!obj->toolDock->isVisible());
obj->m_toolDock->setVisible(!obj->m_toolDock->isVisible());
return true;
}
bool VMainWindow::toggleSearchDockByCaptain(void *p_target, void *p_data)
{
Q_UNUSED(p_data);
VMainWindow *obj = static_cast<VMainWindow *>(p_target);
bool visible = obj->m_searchDock->isVisible();
obj->m_searchDock->setVisible(!visible);
if (!visible) {
obj->m_searcher->focusToSearch();
return false;
}
return true;
}

View File

@ -39,6 +39,7 @@ class VButtonWithWidget;
class VAttachmentList;
class VSnippetList;
class VCart;
class VSearcher;
class QPrinter;
enum class PanelViewState
@ -60,6 +61,9 @@ public:
// Returns true if the location succeeds.
bool locateFile(VFile *p_file);
// Returns true if the location succeeds.
bool locateDirectory(VDirectory *p_directory);
VFileList *getFileList() const;
VEditArea *getEditArea() const;
@ -68,6 +72,10 @@ public:
VCart *getCart() const;
VDirectoryTree *getDirectoryTree() const;
VNotebookSelector *getNotebookSelector() const;
// View and edit the information of @p_file, which is an orphan file.
void editOrphanFileInfo(VFile *p_file);
@ -205,6 +213,10 @@ private:
void initDockWindows();
void initToolsDock();
void initSearchDock();
void initRenderBackgroundMenu(QMenu *menu);
void initRenderStyleMenu(QMenu *p_menu);
@ -280,6 +292,8 @@ private:
static bool toggleToolsDockByCaptain(void *p_target, void *p_data);
static bool toggleSearchDockByCaptain(void *p_target, void *p_data);
static bool closeFileByCaptain(void *p_target, void *p_data);
static bool shortcutsHelpByCaptain(void *p_target, void *p_data);
@ -311,7 +325,9 @@ private:
VEditArea *editArea;
QDockWidget *toolDock;
QDockWidget *m_toolDock;
QDockWidget *m_searchDock;
// Tool box in the dock widget.
VToolBox *m_toolBox;
@ -324,6 +340,9 @@ private:
// View and manage cart.
VCart *m_cart;
// Advanced search.
VSearcher *m_searcher;
VFindReplaceDialog *m_findReplaceDialog;
VVimCmdLineEdit *m_vimCmd;
@ -453,4 +472,13 @@ inline VCart *VMainWindow::getCart() const
return m_cart;
}
inline VDirectoryTree *VMainWindow::getDirectoryTree() const
{
return directoryTree;
}
inline VNotebookSelector *VMainWindow::getNotebookSelector() const
{
return notebookSelector;
}
#endif // VMAINWINDOW_H

View File

@ -95,6 +95,8 @@ VMdEditor::VMdEditor(VFile *p_file,
connect(this, &VTextEdit::cursorPositionChanged,
this, &VMdEditor::updateCurrentHeader);
setDisplayScaleFactor(VUtils::calculateScaleFactor());
updateFontAndPalette();
updateConfig();

View File

@ -314,6 +314,17 @@ VDirectory *VNote::getInternalDirectory(const QString &p_path)
}
VNotebook *VNote::getNotebook(const QString &p_path)
{
for (auto & nb : m_notebooks) {
if (VUtils::equalPath(nb->getPath(), p_path)) {
return nb;
}
}
return NULL;
}
void VNote::freeOrphanFiles()
{
for (int i = 0; i < m_externalFiles.size();) {

View File

@ -95,6 +95,10 @@ public:
// Otherwise, returns NULL.
VDirectory *getInternalDirectory(const QString &p_path);
// Given the path of a file, try to find it in all notebooks.
// Returns a VNotebook struct if it is the root folder of a notebook.
VNotebook *getNotebook(const QString &p_path);
void freeOrphanFiles();
// @p_renderBg: background color, empty to not specify given color.

View File

@ -243,5 +243,10 @@
<file>resources/themes/v_moonlight/arrow_dropdown_disabled.svg</file>
<file>resources/themes/v_pure/arrow_dropdown_disabled.svg</file>
<file>resources/themes/v_white/arrow_dropdown_disabled.svg</file>
<file>resources/icons/clear_search.svg</file>
<file>resources/icons/search.svg</file>
<file>resources/icons/search_advanced.svg</file>
<file>resources/icons/search_console.svg</file>
<file>resources/icons/note_item.svg</file>
</qresource>
</RCC>

View File

@ -7,7 +7,6 @@
#include <QString>
#include <QStyleFactory>
#include <QWidgetAction>
#include <QLabel>
#include "veditwindow.h"
#include "vnotefile.h"

418
src/vsearch.cpp Normal file
View File

@ -0,0 +1,418 @@
#include "vsearch.h"
#include "utils/vutils.h"
#include "vfile.h"
#include "vdirectory.h"
#include "vnotebook.h"
#include "veditarea.h"
#include "vmainwindow.h"
#include "vtableofcontent.h"
#include "vsearchengine.h"
extern VMainWindow *g_mainWin;
VSearch::VSearch(QObject *p_parent)
: QObject(p_parent),
m_askedToStop(false),
m_engine(NULL)
{
}
QSharedPointer<VSearchResult> VSearch::search(const QVector<VFile *> &p_files)
{
Q_ASSERT(!askedToStop());
QSharedPointer<VSearchResult> result(new VSearchResult(this));
if (p_files.isEmpty()) {
return result;
}
if (!testTarget(VSearchConfig::Note)) {
qDebug() << "search is not applicable for note";
result->m_state = VSearchState::Success;
return result;
}
result->m_state = VSearchState::Busy;
for (auto const & it : p_files) {
if (!it) {
continue;
}
searchFirstPhase(it, result, true);
if (askedToStop()) {
qDebug() << "asked to cancel the search";
result->m_state = VSearchState::Cancelled;
break;
}
}
if (result->m_state == VSearchState::Busy) {
result->m_state = VSearchState::Success;
}
return result;
}
QSharedPointer<VSearchResult> VSearch::search(VDirectory *p_directory)
{
Q_ASSERT(!askedToStop());
QSharedPointer<VSearchResult> result(new VSearchResult(this));
if (!p_directory) {
return result;
}
if ((!testTarget(VSearchConfig::Note)
&& !testTarget(VSearchConfig::Folder))
|| testObject(VSearchConfig::Outline)) {
qDebug() << "search is not applicable for folder";
result->m_state = VSearchState::Success;
return result;
}
result->m_state = VSearchState::Busy;
searchFirstPhase(p_directory, result);
if (result->hasSecondPhaseItems()) {
searchSecondPhase(result);
} else if (result->m_state == VSearchState::Busy) {
result->m_state = VSearchState::Success;
}
return result;
}
QSharedPointer<VSearchResult> VSearch::search(const QVector<VNotebook *> &p_notebooks)
{
Q_ASSERT(!askedToStop());
QSharedPointer<VSearchResult> result(new VSearchResult(this));
if (p_notebooks.isEmpty()) {
return result;
}
if (testObject(VSearchConfig::Outline)) {
qDebug() << "search is not applicable for notebook";
result->m_state = VSearchState::Success;
return result;
}
result->m_state = VSearchState::Busy;
for (auto const & nb : p_notebooks) {
if (!nb) {
continue;
}
searchFirstPhase(nb, result);
if (askedToStop()) {
qDebug() << "asked to cancel the search";
result->m_state = VSearchState::Cancelled;
break;
}
}
if (result->hasSecondPhaseItems()) {
searchSecondPhase(result);
} else if (result->m_state == VSearchState::Busy) {
result->m_state = VSearchState::Success;
}
return result;
}
void VSearch::searchFirstPhase(VFile *p_file,
const QSharedPointer<VSearchResult> &p_result,
bool p_searchContent)
{
Q_ASSERT(testTarget(VSearchConfig::Note));
QString name = p_file->getName();
if (!m_patternReg.isEmpty()) {
if (!matchOneLine(name, m_patternReg)) {
return;
}
}
QString filePath = p_file->fetchPath();
if (testObject(VSearchConfig::Name)) {
if (matchOneLine(name, m_searchReg)) {
VSearchResultItem *item = new VSearchResultItem(VSearchResultItem::Note,
VSearchResultItem::LineNumber,
name,
filePath);
QSharedPointer<VSearchResultItem> pitem(item);
emit resultItemAdded(pitem);
}
}
if (testObject(VSearchConfig::Outline)) {
VSearchResultItem *item = searchForOutline(p_file);
if (item) {
QSharedPointer<VSearchResultItem> pitem(item);
emit resultItemAdded(pitem);
}
}
if (testObject(VSearchConfig::Content)) {
// Search content in first phase.
if (p_searchContent) {
VSearchResultItem *item = searchForContent(p_file);
if (item) {
QSharedPointer<VSearchResultItem> pitem(item);
emit resultItemAdded(pitem);
}
} else {
// Add an item for second phase process.
p_result->addSecondPhaseItem(filePath);
}
}
}
void VSearch::searchFirstPhase(VDirectory *p_directory,
const QSharedPointer<VSearchResult> &p_result)
{
if (!testTarget(VSearchConfig::Note)
&& !testTarget(VSearchConfig::Folder)) {
return;
}
bool opened = p_directory->isOpened();
if (!opened && !p_directory->open()) {
p_result->logError(QString("Fail to open folder %1.").arg(p_directory->fetchRelativePath()));
p_result->m_state = VSearchState::Fail;
return;
}
if (testTarget(VSearchConfig::Folder)
&& testObject(VSearchConfig::Name)) {
QString text = p_directory->getName();
if (matchOneLine(text, m_searchReg)) {
VSearchResultItem *item = new VSearchResultItem(VSearchResultItem::Folder,
VSearchResultItem::LineNumber,
text,
p_directory->fetchPath());
QSharedPointer<VSearchResultItem> pitem(item);
emit resultItemAdded(pitem);
}
}
// Search files.
if (testTarget(VSearchConfig::Note)) {
for (auto const & file : p_directory->getFiles()) {
if (askedToStop()) {
qDebug() << "asked to cancel the search";
p_result->m_state = VSearchState::Cancelled;
goto exit;
}
searchFirstPhase(file, p_result);
}
}
// Search subfolders.
for (auto const & dir : p_directory->getSubDirs()) {
if (askedToStop()) {
qDebug() << "asked to cancel the search";
p_result->m_state = VSearchState::Cancelled;
goto exit;
}
searchFirstPhase(dir, p_result);
}
exit:
if (!opened) {
p_directory->close();
}
}
void VSearch::searchFirstPhase(VNotebook *p_notebook,
const QSharedPointer<VSearchResult> &p_result)
{
bool opened = p_notebook->isOpened();
if (!opened && !p_notebook->open()) {
p_result->logError(QString("Fail to open notebook %1.").arg(p_notebook->getName()));
p_result->m_state = VSearchState::Fail;
return;
}
if (testTarget(VSearchConfig::Notebook)
&& testObject(VSearchConfig::Name)) {
QString text = p_notebook->getName();
if (matchOneLine(text, m_searchReg)) {
VSearchResultItem *item = new VSearchResultItem(VSearchResultItem::Notebook,
VSearchResultItem::LineNumber,
text,
p_notebook->getPath());
QSharedPointer<VSearchResultItem> pitem(item);
emit resultItemAdded(pitem);
}
}
if (!testTarget(VSearchConfig::Note)
&& !testTarget(VSearchConfig::Folder)) {
goto exit;
}
// Search for subfolders.
for (auto const & dir : p_notebook->getRootDir()->getSubDirs()) {
if (askedToStop()) {
qDebug() << "asked to cancel the search";
p_result->m_state = VSearchState::Cancelled;
goto exit;
}
searchFirstPhase(dir, p_result);
}
exit:
if (!opened) {
p_notebook->close();
}
}
VSearchResultItem *VSearch::searchForOutline(const VFile *p_file) const
{
VEditTab *tab = g_mainWin->getEditArea()->getTab(p_file);
if (!tab) {
return NULL;
}
const VTableOfContent &toc = tab->getOutline();
const QVector<VTableOfContentItem> &table = toc.getTable();
VSearchResultItem *item = NULL;
for (auto const & it: table) {
if (it.isEmpty()) {
continue;
}
if (!matchOneLine(it.m_name, m_searchReg)) {
continue;
}
if (!item) {
item = new VSearchResultItem(VSearchResultItem::Note,
VSearchResultItem::OutlineIndex,
p_file->getName(),
p_file->fetchPath());
}
VSearchResultSubItem sitem(it.m_index, it.m_name);
item->m_matches.append(sitem);
}
return item;
}
VSearchResultItem *VSearch::searchForContent(const VFile *p_file) const
{
Q_ASSERT(p_file->isOpened());
const QString &content = p_file->getContent();
if (content.isEmpty()) {
return NULL;
}
VSearchResultItem *item = NULL;
int lineNum = 1;
int pos = 0;
int size = content.size();
QRegExp newLineReg = QRegExp("\\n|\\r\\n|\\r");
Qt::CaseSensitivity cs = testOption(VSearchConfig::CaseSensitive)
? Qt::CaseSensitive : Qt::CaseInsensitive;
while (pos < size) {
int idx = content.indexOf(newLineReg, pos);
if (idx == -1) {
idx = size;
}
if (idx > pos) {
QString lineText = content.mid(pos, idx - pos);
bool matched = false;
if (m_contentSearchReg.isEmpty()) {
matched = lineText.contains(m_config->m_keyword, cs);
} else {
matched = (m_contentSearchReg.indexIn(lineText) != -1);
}
if (matched) {
if (!item) {
item = new VSearchResultItem(VSearchResultItem::Note,
VSearchResultItem::LineNumber,
p_file->getName(),
p_file->fetchPath());
}
VSearchResultSubItem sitem(lineNum, lineText);
item->m_matches.append(sitem);
}
}
if (idx == size) {
break;
}
pos = idx + newLineReg.matchedLength();
++lineNum;
}
return item;
}
void VSearch::searchSecondPhase(const QSharedPointer<VSearchResult> &p_result)
{
delete m_engine;
m_engine = NULL;
switch (m_config->m_engine) {
case VSearchConfig::Internal:
{
m_engine = new VSearchEngine(this);
m_engine->search(m_config, p_result);
break;
}
default:
p_result->m_state = VSearchState::Success;
break;
}
if (m_engine) {
connect(m_engine, &ISearchEngine::finished,
this, &VSearch::finished);
connect(m_engine, &ISearchEngine::resultItemAdded,
this, &VSearch::resultItemAdded);
}
}
void VSearch::clear()
{
m_config.clear();
if (m_engine) {
m_engine->clear();
delete m_engine;
m_engine = NULL;
}
m_askedToStop = false;
}
void VSearch::stop()
{
qDebug() << "VSearch asked to stop";
m_askedToStop = true;
if (m_engine) {
m_engine->stop();
}
}

162
src/vsearch.h Normal file
View File

@ -0,0 +1,162 @@
#ifndef VSEARCH_H
#define VSEARCH_H
#include <QObject>
#include <QString>
#include <QSharedPointer>
#include <QRegExp>
#include <QCoreApplication>
#include "vsearchconfig.h"
class VFile;
class VDirectory;
class VNotebook;
class ISearchEngine;
class VSearch : public QObject
{
Q_OBJECT
public:
explicit VSearch(QObject *p_parent = nullptr);
void setConfig(QSharedPointer<VSearchConfig> p_config);
// Search list of files for CurrentNote and OpenedNotes.
QSharedPointer<VSearchResult> search(const QVector<VFile *> &p_files);
// Search folder for CurrentFolder.
QSharedPointer<VSearchResult> search(VDirectory *p_directory);
// Search folder for CurrentNotebook and AllNotebooks.
QSharedPointer<VSearchResult> search(const QVector<VNotebook *> &p_notebooks);
// Clear resources after a search completed.
void clear();
void stop();
signals:
// Emitted when a new item added as result.
void resultItemAdded(const QSharedPointer<VSearchResultItem> &p_item);
// Emitted when async task finished.
void finished(const QSharedPointer<VSearchResult> &p_result);
private:
bool askedToStop() const;
// @p_searchContent: whether search content in first phase.
void searchFirstPhase(VFile *p_file,
const QSharedPointer<VSearchResult> &p_result,
bool p_searchContent = false);
void searchFirstPhase(VDirectory *p_directory,
const QSharedPointer<VSearchResult> &p_result);
void searchFirstPhase(VNotebook *p_notebook,
const QSharedPointer<VSearchResult> &p_result);
bool testTarget(VSearchConfig::Target p_target) const;
bool testObject(VSearchConfig::Object p_object) const;
bool testOption(VSearchConfig::Option p_option) const;
bool matchOneLine(const QString &p_text, const QRegExp &p_reg) const;
VSearchResultItem *searchForOutline(const VFile *p_file) const;
VSearchResultItem *searchForContent(const VFile *p_file) const;
void searchSecondPhase(const QSharedPointer<VSearchResult> &p_result);
bool m_askedToStop;
QSharedPointer<VSearchConfig> m_config;
ISearchEngine *m_engine;
// Search reg used for name, outline, tag.
QRegExp m_searchReg;
// Search reg used for content.
// We use raw string to speed up if it is empty.
QRegExp m_contentSearchReg;
// Wildcard reg to for file name pattern.
QRegExp m_patternReg;
};
inline bool VSearch::askedToStop() const
{
QCoreApplication::processEvents();
return m_askedToStop;
}
inline void VSearch::setConfig(QSharedPointer<VSearchConfig> p_config)
{
m_config = p_config;
// Compile reg.
const QString &keyword = m_config->m_keyword;
m_contentSearchReg = QRegExp();
if (keyword.isEmpty()) {
m_searchReg = QRegExp();
return;
}
Qt::CaseSensitivity cs = testOption(VSearchConfig::CaseSensitive)
? Qt::CaseSensitive : Qt::CaseInsensitive;
if (testOption(VSearchConfig::RegularExpression)) {
m_searchReg = QRegExp(keyword, cs);
m_contentSearchReg = QRegExp(keyword, cs);
} else {
if (testOption(VSearchConfig::Fuzzy)) {
QString wildcardText(keyword.size() * 2 + 1, '*');
for (int i = 0, j = 1; i < keyword.size(); ++i, j += 2) {
wildcardText[j] = keyword[i];
}
m_searchReg = QRegExp(wildcardText, cs, QRegExp::Wildcard);
} else {
QString pattern = QRegExp::escape(keyword);
if (testOption(VSearchConfig::WholeWordOnly)) {
pattern = "\\b" + pattern + "\\b";
// We only use m_contentSearchReg when WholeWordOnly is checked.
m_contentSearchReg = QRegExp(pattern, cs);
}
m_searchReg = QRegExp(pattern, cs);
}
}
if (m_config->m_pattern.isEmpty()) {
m_patternReg = QRegExp();
} else {
m_patternReg = QRegExp(m_config->m_pattern, Qt::CaseInsensitive, QRegExp::Wildcard);
}
}
inline bool VSearch::testTarget(VSearchConfig::Target p_target) const
{
return p_target & m_config->m_target;
}
inline bool VSearch::testObject(VSearchConfig::Object p_object) const
{
return p_object & m_config->m_object;
}
inline bool VSearch::testOption(VSearchConfig::Option p_option) const
{
return p_option & m_config->m_option;
}
inline bool VSearch::matchOneLine(const QString &p_text, const QRegExp &p_reg) const
{
return p_reg.indexIn(p_text) != -1;
}
#endif // VSEARCH_H

273
src/vsearchconfig.h Normal file
View File

@ -0,0 +1,273 @@
#ifndef VSEARCHCONFIG_H
#define VSEARCHCONFIG_H
#include <QString>
#include <QStringList>
#include <QSharedPointer>
struct VSearchConfig
{
enum Scope
{
NoneScope = 0,
CurrentNote,
OpenedNotes,
CurrentFolder,
CurrentNotebook,
AllNotebooks
};
enum Object
{
NoneObject = 0,
Name = 0x1UL,
Content = 0x2UL,
Outline = 0x4UL,
Tag = 0x8UL
};
enum Target
{
NoneTarget = 0,
Note = 0x1UL,
Folder = 0x2UL,
Notebook = 0x4UL
};
enum Engine
{
Internal = 0
};
enum Option
{
NoneOption = 0,
CaseSensitive = 0x1UL,
WholeWordOnly = 0x2UL,
Fuzzy = 0x4UL,
RegularExpression = 0x8UL
};
VSearchConfig()
: VSearchConfig(Scope::NoneScope,
Object::NoneObject,
Target::NoneTarget,
Engine::Internal,
Option::NoneOption,
"",
"")
{
}
VSearchConfig(int p_scope,
int p_object,
int p_target,
int p_engine,
int p_option,
const QString &p_keyword,
const QString &p_pattern)
: m_scope(p_scope),
m_object(p_object),
m_target(p_target),
m_engine(p_engine),
m_option(p_option),
m_keyword(p_keyword),
m_pattern(p_pattern)
{
}
QStringList toConfig() const
{
QStringList str;
str << QString::number(m_scope);
str << QString::number(m_object);
str << QString::number(m_target);
str << QString::number(m_engine);
str << QString::number(m_option);
str << m_pattern;
return str;
}
static VSearchConfig fromConfig(const QStringList &p_str)
{
VSearchConfig config;
if (p_str.size() != 6) {
return config;
}
config.m_scope = p_str[0].toInt();
config.m_object = p_str[1].toInt();
config.m_target = p_str[2].toInt();
config.m_engine = p_str[3].toInt();
config.m_option = p_str[4].toInt();
config.m_pattern = p_str[5];
return config;
}
int m_scope;
int m_object;
int m_target;
int m_engine;
int m_option;
QString m_keyword;
// Wildcard pattern to filter file.
QString m_pattern;
};
struct VSearchResultSubItem
{
VSearchResultSubItem()
: m_lineNumber(-1)
{
}
VSearchResultSubItem(int p_lineNumber,
const QString &p_text)
: m_lineNumber(p_lineNumber),
m_text(p_text)
{
}
int m_lineNumber;
QString m_text;
};
struct VSearchResultItem
{
enum ItemType
{
None = 0,
Note,
Folder,
Notebook
};
enum MatchType
{
LineNumber = 0,
OutlineIndex
};
VSearchResultItem()
: m_type(ItemType::None),
m_matchType(MatchType::LineNumber)
{
}
VSearchResultItem(VSearchResultItem::ItemType p_type,
VSearchResultItem::MatchType p_matchType,
const QString &p_text,
const QString &p_path)
: m_type(p_type),
m_matchType(p_matchType),
m_text(p_text),
m_path(p_path)
{
}
bool isEmpty() const
{
return m_type == ItemType::None;
}
QString toString() const
{
return QString("item text: [%1] path: [%2] subitems: %3")
.arg(m_text)
.arg(m_path)
.arg(m_matches.size());
}
ItemType m_type;
MatchType m_matchType;
// Text to displayed. If empty, use @m_path instead.
QString m_text;
// Path of the target.
QString m_path;
// Matched places within this item.
QList<VSearchResultSubItem> m_matches;
};
class VSearch;
enum class VSearchState
{
Idle = 0,
Busy,
Success,
Fail,
Cancelled
};
struct VSearchResult
{
friend class VSearch;
VSearchResult(VSearch *p_search)
: m_state(VSearchState::Idle),
m_search(p_search)
{
}
bool hasError() const
{
return !m_errMsg.isEmpty();
}
void logError(const QString &p_err)
{
if (m_errMsg.isEmpty()) {
m_errMsg = p_err;
} else {
m_errMsg = "\n" + p_err;
}
}
void addSecondPhaseItem(const QString &p_item)
{
m_secondPhaseItems.append(p_item);
}
QString toString() const
{
QString str = QString("search result: state %1 err %2")
.arg((int)m_state)
.arg(!m_errMsg.isEmpty());
return str;
}
bool hasSecondPhaseItems() const
{
return !m_secondPhaseItems.isEmpty();
}
VSearchState m_state;
QString m_errMsg;
QStringList m_secondPhaseItems;
private:
VSearch *m_search;
};
#endif // VSEARCHCONFIG_H

255
src/vsearchengine.cpp Normal file
View File

@ -0,0 +1,255 @@
#include "vsearchengine.h"
#include <QDebug>
#include <QFile>
#include <QMimeDatabase>
#include "utils/vutils.h"
VSearchEngineWorker::VSearchEngineWorker(QObject *p_parent)
: QThread(p_parent),
m_stop(0),
m_state(VSearchState::Idle)
{
}
void VSearchEngineWorker::setData(const QStringList &p_files,
const QRegExp &p_reg,
const QString &p_keyword,
Qt::CaseSensitivity p_cs)
{
m_files = p_files;
m_reg = p_reg;
m_keyword = p_keyword;
m_caseSensitivity = p_cs;
}
void VSearchEngineWorker::stop()
{
m_stop.store(1);
}
void VSearchEngineWorker::run()
{
qDebug() << "worker" << QThread::currentThreadId() << m_files.size();
QMimeDatabase mimeDatabase;
m_state = VSearchState::Busy;
for (auto const & fileName : m_files) {
if (m_stop.load() == 1) {
m_state = VSearchState::Cancelled;
qDebug() << "worker" << QThread::currentThreadId() << "is asked to stop";
break;
}
const QMimeType mimeType = mimeDatabase.mimeTypeForFile(fileName);
if (mimeType.isValid() && !mimeType.inherits(QStringLiteral("text/plain"))) {
appendError(tr("Skip binary file %1.").arg(fileName));
continue;
}
VSearchResultItem *item = searchFile(fileName);
if (item) {
emit resultItemReady(item);
}
}
if (m_state == VSearchState::Busy) {
m_state = VSearchState::Success;
}
}
VSearchResultItem *VSearchEngineWorker::searchFile(const QString &p_fileName)
{
QFile file(p_fileName);
if (!file.open(QIODevice::ReadOnly)) {
return NULL;
}
int lineNum = 1;
VSearchResultItem *item = NULL;
QString line;
QTextStream in(&file);
while (!in.atEnd()) {
if (m_stop.load() == 1) {
m_state = VSearchState::Cancelled;
qDebug() << "worker" << QThread::currentThreadId() << "is asked to stop";
break;
}
bool matched = false;
line = in.readLine();
if (m_reg.isEmpty()) {
if (line.contains(m_keyword, m_caseSensitivity)) {
matched = true;
}
} else if (m_reg.indexIn(line) != -1) {
matched = true;
}
if (matched) {
if (!item) {
item = new VSearchResultItem(VSearchResultItem::Note,
VSearchResultItem::LineNumber,
VUtils::fileNameFromPath(p_fileName),
p_fileName);
}
VSearchResultSubItem sitem(lineNum, line);
item->m_matches.append(sitem);
}
++lineNum;
}
return item;
}
VSearchEngine::VSearchEngine(QObject *p_parent)
: ISearchEngine(p_parent),
m_finishedWorkers(0)
{
}
void VSearchEngine::search(const QSharedPointer<VSearchConfig> &p_config,
const QSharedPointer<VSearchResult> &p_result)
{
int numThread = QThread::idealThreadCount();
if (numThread < 1) {
numThread = 1;
}
const QStringList items = p_result->m_secondPhaseItems;
Q_ASSERT(!items.isEmpty());
if (items.size() < numThread) {
numThread = items.size();
}
m_result = p_result;
QRegExp reg = compileRegExpFromConfig(p_config);
Qt::CaseSensitivity cs = (p_config->m_option & VSearchConfig::CaseSensitive)
? Qt::CaseSensitive : Qt::CaseInsensitive;
clearAllWorkers();
m_workers.reserve(numThread);
m_finishedWorkers = 0;
int totalSize = m_result->m_secondPhaseItems.size();
int step = totalSize / numThread;
int remain = totalSize % numThread;
for (int i = 0; i < numThread; ++i) {
int start = i * step;
if (start >= totalSize) {
break;
}
int len = step;
if (remain) {
++len;
--remain;
}
if (start + len > totalSize) {
len = totalSize - start;
}
VSearchEngineWorker *th = new VSearchEngineWorker(this);
th->setData(m_result->m_secondPhaseItems.mid(start, len),
reg,
p_config->m_keyword,
cs);
connect(th, &VSearchEngineWorker::finished,
this, &VSearchEngine::handleWorkerFinished);
connect(th, &VSearchEngineWorker::resultItemReady,
this, [this](VSearchResultItem *p_item) {
emit resultItemAdded(QSharedPointer<VSearchResultItem>(p_item));
});
m_workers.append(th);
th->start();
}
qDebug() << "schedule tasks to threads" << m_workers.size() << totalSize << step;
}
QRegExp VSearchEngine::compileRegExpFromConfig(const QSharedPointer<VSearchConfig> &p_config) const
{
const QString &keyword = p_config->m_keyword;
Qt::CaseSensitivity cs = (p_config->m_option & VSearchConfig::CaseSensitive)
? Qt::CaseSensitive : Qt::CaseInsensitive;
if (p_config->m_option & VSearchConfig::RegularExpression) {
return QRegExp(keyword, cs);
} else if (p_config->m_option & VSearchConfig::WholeWordOnly) {
QString pattern = QRegExp::escape(keyword);
pattern = "\\b" + pattern + "\\b";
return QRegExp(pattern, cs);
} else {
return QRegExp();
}
}
void VSearchEngine::stop()
{
qDebug() << "VSearchEngine asked to stop";
for (auto const & th : m_workers) {
th->stop();
}
}
void VSearchEngine::handleWorkerFinished()
{
++m_finishedWorkers;
qDebug() << m_finishedWorkers << "workers finished";
if (m_finishedWorkers == m_workers.size()) {
VSearchState state = VSearchState::Success;
for (auto const & th : m_workers) {
if (th->m_state == VSearchState::Fail) {
if (state != VSearchState::Cancelled) {
state = VSearchState::Fail;
}
} else if (th->m_state == VSearchState::Cancelled) {
state = VSearchState::Cancelled;
}
if (!th->m_error.isEmpty()) {
m_result->logError(th->m_error);
}
Q_ASSERT(th->isFinished());
th->deleteLater();
}
m_workers.clear();
m_finishedWorkers = 0;
m_result->m_state = state;
qDebug() << "SearchEngine finished" << (int)state;
emit finished(m_result);
}
}
void VSearchEngine::clear()
{
clearAllWorkers();
m_finishedWorkers = 0;
m_result.clear();
}
void VSearchEngine::clearAllWorkers()
{
for (auto const & th : m_workers) {
th->quit();
th->wait();
delete th;
}
m_workers.clear();
}

89
src/vsearchengine.h Normal file
View File

@ -0,0 +1,89 @@
#ifndef VSEARCHENGINE_H
#define VSEARCHENGINE_H
#include <QThread>
#include <QRegExp>
#include <QAtomicInt>
#include "isearchengine.h"
class VSearchEngineWorker : public QThread
{
Q_OBJECT
friend class VSearchEngine;
public:
explicit VSearchEngineWorker(QObject *p_parent = nullptr);
void setData(const QStringList &p_files,
const QRegExp &p_reg,
const QString &p_keyword,
Qt::CaseSensitivity p_cs);
public slots:
void stop();
signals:
void resultItemReady(VSearchResultItem *p_item);
protected:
void run() Q_DECL_OVERRIDE;
private:
void appendError(const QString &p_err);
VSearchResultItem *searchFile(const QString &p_fileName);
QAtomicInt m_stop;
QStringList m_files;
QRegExp m_reg;
QString m_keyword;
Qt::CaseSensitivity m_caseSensitivity;
VSearchState m_state;
QString m_error;
};
inline void VSearchEngineWorker::appendError(const QString &p_err)
{
if (m_error.isEmpty()) {
m_error = p_err;
} else {
m_error = "\n" + p_err;
}
}
class VSearchEngine : public ISearchEngine
{
Q_OBJECT
public:
explicit VSearchEngine(QObject *p_parent = nullptr);
void search(const QSharedPointer<VSearchConfig> &p_config,
const QSharedPointer<VSearchResult> &p_result) Q_DECL_OVERRIDE;
void stop() Q_DECL_OVERRIDE;
void clear() Q_DECL_OVERRIDE;
private slots:
void handleWorkerFinished();
private:
// Returns an empty object if raw string is preferred.
QRegExp compileRegExpFromConfig(const QSharedPointer<VSearchConfig> &p_config) const;
void clearAllWorkers();
int m_finishedWorkers;
QVector<VSearchEngineWorker *> m_workers;
};
#endif // VSEARCHENGINE_H

548
src/vsearcher.cpp Normal file
View File

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

108
src/vsearcher.h Normal file
View File

@ -0,0 +1,108 @@
#ifndef VSEARCHER_H
#define VSEARCHER_H
#include <QWidget>
#include <QSharedPointer>
#include "vsearch.h"
#include "vnavigationmode.h"
class QComboBox;
class QCheckBox;
class QPushButton;
class QLabel;
class VSearchResultTree;
class QProgressBar;
class QPlainTextEdit;
class VSearcher : public QWidget, public VNavigationMode
{
Q_OBJECT
public:
explicit VSearcher(QWidget *p_parent = nullptr);
void focusToSearch();
// Implementations for VNavigationMode.
void showNavigation() Q_DECL_OVERRIDE;
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
private slots:
void handleSearchFinished(const QSharedPointer<VSearchResult> &p_result);
private:
void startSearch();
void setupUI();
void initUIFields();
void setProgressVisible(bool p_visible);
void appendLogLine(const QString &p_text);
void handleInputChanged();
void animateSearchClick();
void updateItemToComboBox(QComboBox *p_comboBox);
// Get the OR of the search options.
int getSearchOption() const;
void updateNumLabel(int p_count);
void showMessage(const QString &p_text) const;
QComboBox *m_keywordCB;
// All notebooks, current notebook, and so on.
QComboBox *m_searchScopeCB;
// Name, content, tag.
QComboBox *m_searchObjectCB;
// Notebook, folder, note.
QComboBox *m_searchTargetCB;
QComboBox *m_filePatternCB;
QComboBox *m_searchEngineCB;
QCheckBox *m_caseSensitiveCB;
QCheckBox *m_wholeWordOnlyCB;
QCheckBox *m_fuzzyCB;
QCheckBox *m_regularExpressionCB;
QPushButton *m_searchBtn;
QPushButton *m_clearBtn;
QPushButton *m_advBtn;
QPushButton *m_consoleBtn;
QLabel *m_numLabel;
QWidget *m_advWidget;
VSearchResultTree *m_results;
QProgressBar *m_proBar;
QPushButton *m_cancelBtn;
QPlainTextEdit *m_consoleEdit;
bool m_inSearch;
bool m_askedToStop;
VSearch m_search;
};
#endif // VSEARCHER_H

143
src/vsearchresulttree.cpp Normal file
View File

@ -0,0 +1,143 @@
#include "vsearchresulttree.h"
#include "utils/viconutils.h"
#include "vnote.h"
#include "vmainwindow.h"
#include "vnotebookselector.h"
extern VNote *g_vnote;
extern VMainWindow *g_mainWin;
VSearchResultTree::VSearchResultTree(QWidget *p_parent)
: VTreeWidget(p_parent)
{
setColumnCount(1);
setHeaderHidden(true);
setExpandsOnDoubleClick(false);
setSimpleSearchMatchFlags(getSimpleSearchMatchFlags() & ~Qt::MatchRecursive);
m_noteIcon = VIconUtils::treeViewIcon(":/resources/icons/note_item.svg");
m_folderIcon = VIconUtils::treeViewIcon(":/resources/icons/dir_item.svg");
m_notebookIcon = VIconUtils::treeViewIcon(":/resources/icons/notebook_item.svg");
connect(this, &VTreeWidget::itemActivated,
this, &VSearchResultTree::handleItemActivated);
}
void VSearchResultTree::updateResults(const QList<QSharedPointer<VSearchResultItem> > &p_items)
{
clearResults();
for (auto const & it : p_items) {
appendItem(it);
}
emit countChanged(topLevelItemCount());
}
void VSearchResultTree::addResultItem(const QSharedPointer<VSearchResultItem> &p_item)
{
appendItem(p_item);
emit countChanged(topLevelItemCount());
}
void VSearchResultTree::clearResults()
{
clearAll();
m_data.clear();
emit countChanged(topLevelItemCount());
}
void VSearchResultTree::appendItem(const QSharedPointer<VSearchResultItem> &p_item)
{
m_data.append(p_item);
QTreeWidgetItem *item = new QTreeWidgetItem(this);
item->setData(0, Qt::UserRole, m_data.size() - 1);
item->setText(0, p_item->m_text.isEmpty() ? p_item->m_path : p_item->m_text);
item->setToolTip(0, p_item->m_path);
switch (p_item->m_type) {
case VSearchResultItem::Note:
item->setIcon(0, m_noteIcon);
break;
case VSearchResultItem::Folder:
item->setIcon(0, m_folderIcon);
break;
case VSearchResultItem::Notebook:
item->setIcon(0, m_notebookIcon);
break;
default:
break;
}
for (auto const & it: p_item->m_matches) {
QTreeWidgetItem *subItem = new QTreeWidgetItem(item);
QString text;
if (it.m_lineNumber > -1) {
text = QString("[%1] %2").arg(it.m_lineNumber).arg(it.m_text);
} else {
text = it.m_text;
}
subItem->setText(0, text);
subItem->setToolTip(0, it.m_text);
}
}
void VSearchResultTree::handleItemActivated(QTreeWidgetItem *p_item, int p_column)
{
Q_UNUSED(p_column);
if (!p_item) {
return;
}
QTreeWidgetItem *topItem = p_item;
if (p_item->parent()) {
topItem = p_item->parent();
}
int idx = topItem->data(0, Qt::UserRole).toInt();
Q_ASSERT(idx >= 0 && idx < m_data.size());
const QSharedPointer<VSearchResultItem> &resItem = m_data[idx];
switch (resItem->m_type) {
case VSearchResultItem::Note:
{
QStringList files(resItem->m_path);
g_mainWin->openFiles(files);
break;
}
case VSearchResultItem::Folder:
{
VDirectory *dir = g_vnote->getInternalDirectory(resItem->m_path);
if (dir) {
g_mainWin->locateDirectory(dir);
}
break;
}
case VSearchResultItem::Notebook:
{
VNotebook *nb = g_vnote->getNotebook(resItem->m_path);
if (nb) {
g_mainWin->getNotebookSelector()->locateNotebook(nb);
}
break;
}
default:
break;
}
}

39
src/vsearchresulttree.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef VSEARCHRESULTTREE_H
#define VSEARCHRESULTTREE_H
#include <QIcon>
#include "vtreewidget.h"
#include "vsearch.h"
class VSearchResultTree : public VTreeWidget
{
Q_OBJECT
public:
explicit VSearchResultTree(QWidget *p_parent = nullptr);
void updateResults(const QList<QSharedPointer<VSearchResultItem> > &p_items);
void clearResults();
public slots:
void addResultItem(const QSharedPointer<VSearchResultItem> &p_item);
signals:
void countChanged(int p_count);
private slots:
void handleItemActivated(QTreeWidgetItem *p_item, int p_column);
private:
void appendItem(const QSharedPointer<VSearchResultItem> &p_item);
QVector<QSharedPointer<VSearchResultItem> > m_data;
QIcon m_noteIcon;
QIcon m_folderIcon;
QIcon m_notebookIcon;
};
#endif // VSEARCHRESULTTREE_H

View File

@ -47,6 +47,10 @@ public:
void setNavigationKeyEnabled(bool p_enabled);
void setMatchFlags(Qt::MatchFlags p_flags);
Qt::MatchFlags getMatchFlags() const;
signals:
// Search mode is triggered.
void triggered(bool p_inSearchMode);
@ -101,4 +105,14 @@ inline void VSimpleSearchInput::setNavigationKeyEnabled(bool p_enabled)
{
m_navigationKeyEnabled = p_enabled;
}
inline void VSimpleSearchInput::setMatchFlags(Qt::MatchFlags p_flags)
{
m_matchFlags = p_flags;
}
inline Qt::MatchFlags VSimpleSearchInput::getMatchFlags() const
{
return m_matchFlags;
}
#endif // VSIMPLESEARCHINPUT_H

View File

@ -43,6 +43,8 @@ void VTextEdit::init()
{
setAcceptRichText(false);
m_defaultCursorWidth = 1;
m_lineNumberType = LineNumberType::None;
m_blockImageEnabled = false;
@ -60,6 +62,8 @@ void VTextEdit::init()
docLayout->setVirtualCursorBlockWidth(VIRTUAL_CURSOR_BLOCK_WIDTH);
docLayout->setCursorWidth(m_defaultCursorWidth);
connect(docLayout, &VTextDocumentLayout::cursorBlockWidthUpdated,
this, [this](int p_width) {
if (p_width != cursorWidth()
@ -361,7 +365,7 @@ void VTextEdit::setCursorBlockMode(CursorBlock p_mode)
layout->setCursorBlockMode(m_cursorBlockMode);
layout->clearLastCursorBlockWidth();
setCursorWidth(m_cursorBlockMode != CursorBlock::None ? VIRTUAL_CURSOR_BLOCK_WIDTH
: 1);
: m_defaultCursorWidth);
layout->updateBlockByNumber(textCursor().blockNumber());
}
}
@ -388,3 +392,12 @@ void VTextEdit::relayout()
{
getLayout()->relayout();
}
void VTextEdit::setDisplayScaleFactor(qreal p_factor)
{
m_defaultCursorWidth = p_factor + 0.5;
setCursorWidth(m_cursorBlockMode != CursorBlock::None ? VIRTUAL_CURSOR_BLOCK_WIDTH
: m_defaultCursorWidth);
getLayout()->setCursorWidth(m_defaultCursorWidth);
}

View File

@ -65,6 +65,8 @@ public:
void relayout();
void setDisplayScaleFactor(qreal p_factor);
protected:
void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE;
@ -91,6 +93,8 @@ private:
CursorBlock m_cursorBlockMode;
bool m_highlightCursorLineBlock;
int m_defaultCursorWidth;
};
inline void VTextEdit::setLineNumberType(LineNumberType p_type)

View File

@ -21,6 +21,10 @@ public:
// Clear tree widget as well as other data.
void clearAll();
void setSimpleSearchMatchFlags(Qt::MatchFlags p_flags);
Qt::MatchFlags getSimpleSearchMatchFlags() const;
// Implement ISimpleSearch.
virtual QList<void *> searchItems(const QString &p_text,
Qt::MatchFlags p_flags) const Q_DECL_OVERRIDE;
@ -65,4 +69,14 @@ private:
QTimer *m_searchColdTimer;
};
inline void VTreeWidget::setSimpleSearchMatchFlags(Qt::MatchFlags p_flags)
{
m_searchInput->setMatchFlags(p_flags);
}
inline Qt::MatchFlags VTreeWidget::getSimpleSearchMatchFlags() const
{
return m_searchInput->getMatchFlags();
}
#endif // VTREEWIDGET_H