UniversalEntry: add q to list all notebooks and a to search name of folder/note

This commit is contained in:
Le Tan 2018-03-29 19:47:47 +08:00
parent 4727d0aa98
commit b46a8f4f39
22 changed files with 1198 additions and 29 deletions

73
src/iuniversalentry.h Normal file
View File

@ -0,0 +1,73 @@
#ifndef IUNIVERSALENTRY_H
#define IUNIVERSALENTRY_H
#include <QObject>
#include <QString>
// Interface for one Universal Entry.
// An entry may be used for different keys, in which case we could use an ID to
// identify.
class IUniversalEntry : public QObject
{
Q_OBJECT
public:
enum State
{
Idle,
Busy,
Success,
Fail,
Cancelled
};
IUniversalEntry(QObject *p_parent = nullptr)
: QObject(p_parent),
m_initialized(false),
m_widgetParent(NULL)
{
}
// Return a description string for the entry.
virtual QString description(int p_id) const = 0;
// Return the widget to show to user.
virtual QWidget *widget(int p_id) = 0;
virtual void processCommand(int p_id, const QString &p_cmd) = 0;
// This UE is not used now. Free resources.
virtual void clear(int p_id) = 0;
// UE is hidden by the user.
virtual void entryHidden(int p_id) = 0;
// Select next item.
virtual void selectNextItem(int p_id, bool p_forward) = 0;
// Activate current item.
virtual void activate(int p_id) = 0;
// Ask the UE to stop asynchronously.
virtual void askToStop(int p_id) = 0;
void setWidgetParent(QWidget *p_parent)
{
m_widgetParent = p_parent;
}
signals:
void widgetUpdated();
void stateUpdated(IUniversalEntry::State p_state);
void requestHideUniversalEntry();
protected:
virtual void init() = 0;
bool m_initialized;
QWidget *m_widgetParent;
};
#endif // IUNIVERSALENTRY_H

View File

@ -115,6 +115,10 @@ template_title_flash_dark_fg=@master_bg
search_hit_item_fg=@selected_fg
search_hit_item_bg=@master_dark_bg
; Universal Entry CMD Edit border color
ue_cmd_busy_border=#3F51B5
ue_cmd_fail_border=@danger_bg
[widgets]
; Widget color attributes.
@ -351,3 +355,7 @@ headerview_checked_bg=@selected_bg
progressbar_bg=@edit_bg
progressbar_border_bg=@border_bg
progressbar_chunk_bg=@master_dark_bg
universalentry_border_bg=@border_bg
doublerowitem_second_row_label_fg=#6C6C6C

View File

@ -590,6 +590,15 @@ VTabIndicator QLabel[TabIndicatorLabel="true"] {
background: transparent;
}
VDoubleRowItemWidget QLabel[FirstRowLabel="true"] {
font-size: 10pt;
}
VDoubleRowItemWidget QLabel[SecondRowLabel="true"] {
font-size: 8pt;
color: @doublerowitem_second_row_label_fg;
}
QLabel {
border: none;
color: @label_fg;
@ -1255,3 +1264,8 @@ QProgressBar::chunk {
width: $20px;
}
/* End QProgressBar */
VUniversalEntry {
background: transparent;
border: 1px solid @universalentry_border_bg;
}

View File

@ -83,6 +83,10 @@ template_title_flash_dark_fg=#00897B
search_hit_item_fg=@selected_fg
search_hit_item_bg=#80CBC4
; Universal Entry CMD Edit border color
ue_cmd_busy_border=#3F51B5
ue_cmd_fail_border=@danger_bg
[widgets]
; Widget color attributes.

View File

@ -109,6 +109,10 @@ template_title_flash_dark_fg=@master_bg
search_hit_item_fg=@selected_fg
search_hit_item_bg=#CCE7E4
; Universal Entry CMD Edit border color
ue_cmd_busy_border=#3F51B5
ue_cmd_fail_border=@danger_bg
[widgets]
; Widget color attributes.
@ -344,3 +348,7 @@ headerview_checked_bg=@selected_bg
progressbar_bg=@edit_bg
progressbar_border_bg=@border_bg
progressbar_chunk_bg=@master_light_bg
universalentry_border_bg=@border_bg
doublerowitem_second_row_label_fg=#6C6C6C

View File

@ -590,6 +590,15 @@ VTabIndicator QLabel[TabIndicatorLabel="true"] {
background: transparent;
}
VDoubleRowItemWidget QLabel[FirstRowLabel="true"] {
font-size: 10pt;
}
VDoubleRowItemWidget QLabel[SecondRowLabel="true"] {
font-size: 8pt;
color: @doublerowitem_second_row_label_fg;
}
QLabel {
border: none;
color: @label_fg;
@ -1254,3 +1263,8 @@ QProgressBar::chunk {
width: $20px;
}
/* End QProgressBar */
VUniversalEntry {
background: transparent;
border: 1px solid @universalentry_border_bg;
}

View File

@ -118,7 +118,10 @@ SOURCES += main.cpp\
vsearch.cpp \
vsearchresulttree.cpp \
vsearchengine.cpp \
vuniversalentry.cpp
vuniversalentry.cpp \
vlistwidgetdoublerows.cpp \
vdoublerowitemwidget.cpp \
vsearchue.cpp
HEADERS += vmainwindow.h \
vdirectorytree.h \
@ -226,7 +229,11 @@ HEADERS += vmainwindow.h \
isearchengine.h \
vsearchconfig.h \
vsearchengine.h \
vuniversalentry.h
vuniversalentry.h \
iuniversalentry.h \
vlistwidgetdoublerows.h \
vdoublerowitemwidget.h \
vsearchue.h
RESOURCES += \
vnote.qrc \

View File

@ -0,0 +1,37 @@
#include "vdoublerowitemwidget.h"
#include <QVariant>
#include <QLabel>
#include <QVBoxLayout>
VDoubleRowItemWidget::VDoubleRowItemWidget(QWidget *p_parent)
: QWidget(p_parent)
{
m_firstLabel = new QLabel(this);
m_firstLabel->setProperty("FirstRowLabel", true);
m_secondLabel = new QLabel(this);
m_secondLabel->setProperty("SecondRowLabel", true);
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(m_firstLabel);
layout->addWidget(m_secondLabel);
layout->addStretch();
layout->setContentsMargins(3, 0, 0, 0);
layout->setSpacing(0);
setLayout(layout);
}
void VDoubleRowItemWidget::setText(const QString &p_firstText,
const QString &p_secondText)
{
m_firstLabel->setText(p_firstText);
if (!p_secondText.isEmpty()) {
m_secondLabel->setText(p_secondText);
m_secondLabel->show();
} else {
m_secondLabel->hide();
}
}

View File

@ -0,0 +1,21 @@
#ifndef VDOUBLEROWITEMWIDGET_H
#define VDOUBLEROWITEMWIDGET_H
#include <QWidget>
class QLabel;
class VDoubleRowItemWidget : public QWidget
{
Q_OBJECT
public:
explicit VDoubleRowItemWidget(QWidget *p_parent = nullptr);
void setText(const QString &p_firstText, const QString &p_secondText);
private:
QLabel *m_firstLabel;
QLabel *m_secondLabel;
};
#endif // VDOUBLEROWITEMWIDGET_H

View File

@ -9,9 +9,10 @@
#include "utils/vimnavigationforwidget.h"
#include "vstyleditemdelegate.h"
VListWidget::VListWidget(QWidget *parent)
: QListWidget(parent),
ISimpleSearch()
VListWidget::VListWidget(QWidget *p_parent)
: QListWidget(p_parent),
ISimpleSearch(),
m_fitContent(false)
{
m_searchInput = new VSimpleSearchInput(this, this);
connect(m_searchInput, &VSimpleSearchInput::triggered,
@ -142,8 +143,19 @@ int VListWidget::totalNumberOfItems()
void VListWidget::selectNextItem(bool p_forward)
{
Q_UNUSED(p_forward);
Q_ASSERT(false);
if (count() == 0) {
return;
}
int cur = currentRow();
cur = cur + (p_forward ? 1 : -1);
if (cur < 0) {
cur = 0;
} else if (cur >= count()) {
cur = count() - 1;
}
setCurrentRow(cur);
}
void VListWidget::sortListWidget(QListWidget *p_list, const QVector<int> &p_sortedIdx)
@ -161,3 +173,34 @@ void VListWidget::sortListWidget(QListWidget *p_list, const QVector<int> &p_sort
p_list->insertItem(i, it);
}
}
QSize VListWidget::sizeHint() const
{
if (count() == 0 || !m_fitContent) {
return QListWidget::sizeHint();
} else {
// Adjust size to content.
int cnt = count();
int hei = 0;
int wid = sizeHintForColumn(0) + 10;
for (int i = 0; i < cnt; ++i) {
hei += sizeHintForRow(i);
}
hei += 2 * cnt;
// Scrollbar.
QScrollBar *verBar = verticalScrollBar();
QScrollBar *horBar = horizontalScrollBar();
if (verBar && (verBar->minimum() != verBar->maximum())) {
wid += verBar->width();
}
if (horBar && (horBar->minimum() != horBar->maximum())) {
hei += horBar->height();
}
return QSize(wid, hei);
}
}

View File

@ -12,11 +12,11 @@ class VStyledItemDelegate;
class VListWidget : public QListWidget, public ISimpleSearch
{
public:
explicit VListWidget(QWidget *parent = Q_NULLPTR);
explicit VListWidget(QWidget *p_parent = Q_NULLPTR);
// Clear list widget as well as other data.
// clear() is not virtual to override.
void clearAll();
virtual void clearAll();
// Implement ISimpleSearch.
virtual QList<void *> searchItems(const QString &p_text,
@ -32,9 +32,13 @@ public:
virtual void selectNextItem(bool p_forward) Q_DECL_OVERRIDE;
QSize sizeHint() const Q_DECL_OVERRIDE;
// Sort @p_list according to @p_sortedIdx.
static void sortListWidget(QListWidget *p_list, const QVector<int> &p_sortedIdx);
void setFitContent(bool p_enabled);
private slots:
void handleSearchModeTriggered(bool p_inSearchMode, bool p_focus);
@ -50,6 +54,13 @@ private:
VSimpleSearchInput *m_searchInput;
VStyledItemDelegate *m_delegate;
// Whether fit the size to content.
bool m_fitContent;
};
inline void VListWidget::setFitContent(bool p_enabled)
{
m_fitContent = p_enabled;
}
#endif // VLISTWIDGET_H

View File

@ -0,0 +1,55 @@
#include "vlistwidgetdoublerows.h"
#include <QListWidgetItem>
#include <QScrollBar>
#include "vdoublerowitemwidget.h"
VListWidgetDoubleRows::VListWidgetDoubleRows(QWidget *p_parent)
: VListWidget(p_parent)
{
}
QListWidgetItem *VListWidgetDoubleRows::addItem(const QIcon &p_icon,
const QString &p_firstRow,
const QString &p_secondRow)
{
VDoubleRowItemWidget *itemWidget = new VDoubleRowItemWidget(this);
itemWidget->setText(p_firstRow, p_secondRow);
QSize sz = itemWidget->sizeHint();
QSize iconSz = iconSize();
if (!iconSz.isValid()) {
iconSz = QSize(sz.height(), sz.height());
setIconSize(iconSz);
}
sz.setHeight(sz.height() * 1.25);
QListWidgetItem *item = new QListWidgetItem();
if (!p_icon.isNull()) {
item->setIcon(p_icon);
sz.setWidth(sz.width() + iconSz.width());
sz.setHeight(qMax(sz.height(), iconSz.height()));
}
item->setSizeHint(sz);
VListWidget::addItem(item);
VListWidget::setItemWidget(item, itemWidget);
return item;
}
void VListWidgetDoubleRows::clearAll()
{
// Delete the item widget for each item.
int cnt = count();
for (int i = 0; i < cnt; ++i) {
QWidget *wid = itemWidget(item(i));
removeItemWidget(item(i));
delete wid;
}
VListWidget::clearAll();
setIconSize(QSize());
}

View File

@ -0,0 +1,24 @@
#ifndef VLISTWIDGETDOUBLEROWS_H
#define VLISTWIDGETDOUBLEROWS_H
#include "vlistwidget.h"
#include <QIcon>
class QListWidgetItem;
class VListWidgetDoubleRows : public VListWidget
{
Q_OBJECT
public:
explicit VListWidgetDoubleRows(QWidget *p_parent = nullptr);
QListWidgetItem *addItem(const QIcon &p_icon,
const QString &p_firstRow,
const QString &p_secondRow);
void clearAll() Q_DECL_OVERRIDE;
};
#endif // VLISTWIDGETDOUBLEROWS_H

View File

@ -40,6 +40,7 @@
#include "dialog/vexportdialog.h"
#include "vsearcher.h"
#include "vuniversalentry.h"
#include "vsearchue.h"
extern VConfigManager *g_config;
@ -3164,15 +3165,7 @@ VNotebook *VMainWindow::getCurrentNotebook() const
void VMainWindow::activateUniversalEntry()
{
if (!m_ue) {
m_ue = new VUniversalEntry(this);
m_ue->hide();
m_ue->setWindowFlags(Qt::Popup
| Qt::FramelessWindowHint
| Qt::NoDropShadowWindowHint);
connect(m_ue, &VUniversalEntry::exited,
this, [this]() {
m_captain->setCaptainModeEnabled(true);
});
initUniversalEntry();
}
m_captain->setCaptainModeEnabled(false);
@ -3191,3 +3184,21 @@ void VMainWindow::activateUniversalEntry()
m_ue->show();
m_ue->raise();
}
void VMainWindow::initUniversalEntry()
{
m_ue = new VUniversalEntry(this);
m_ue->hide();
m_ue->setWindowFlags(Qt::Popup
| Qt::FramelessWindowHint
| Qt::NoDropShadowWindowHint);
connect(m_ue, &VUniversalEntry::exited,
this, [this]() {
m_captain->setCaptainModeEnabled(true);
});
// Register entries.
VSearchUE *searchUE = new VSearchUE(this);
m_ue->registerEntry('q', searchUE, VSearchUE::Name_Notebook_AllNotebook);
m_ue->registerEntry('a', searchUE, VSearchUE::Name_FolderNote_AllNotebook);
}

View File

@ -282,6 +282,8 @@ private:
void updateEditReadAct(const VEditTab *p_tab);
void initUniversalEntry();
// Captain mode functions.
// Popup the attachment list if it is enabled.

View File

@ -382,6 +382,7 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
case Qt::Key_Enter:
// Fall through.
V_FALLTHROUGH;
case Qt::Key_Return:
{
if (handleKeyReturn(p_event)) {

View File

@ -45,3 +45,8 @@ const QString &VMetaWordLineEdit::getEvaluatedText() const
{
return m_evaluatedText;
}
QString VMetaWordLineEdit::evaluateText(const QString &p_text) const
{
return g_mwMgr->evaluate(p_text);
}

View File

@ -16,6 +16,8 @@ public:
// Return the evaluated text.
const QString &getEvaluatedText() const;
QString evaluateText(const QString &p_text) const;
private slots:
void handleTextChanged(const QString &p_text);

384
src/vsearchue.cpp Normal file
View File

@ -0,0 +1,384 @@
#include "vsearchue.h"
#include <QDebug>
#include <QVector>
#include "vlistwidgetdoublerows.h"
#include "vnotebook.h"
#include "vnote.h"
#include "vsearch.h"
#include "utils/viconutils.h"
#include "vmainwindow.h"
#include "vnotebookselector.h"
#include "vnotefile.h"
extern VNote *g_vnote;
extern VMainWindow *g_mainWin;
VSearchUE::VSearchUE(QObject *p_parent)
: IUniversalEntry(p_parent),
m_search(NULL),
m_inSearch(false),
m_listWidget(NULL),
m_id(ID::Name_Notebook_AllNotebook)
{
}
QString VSearchUE::description(int p_id) const
{
switch (p_id) {
case ID::Name_Notebook_AllNotebook:
return tr("List and search all notebooks");
case ID::Name_FolderNote_AllNotebook:
return tr("Search the name of folders/notes in all notebooks");
default:
Q_ASSERT(false);
return tr("Invalid ID %1").arg(p_id);
}
}
void VSearchUE::init()
{
if (m_initialized) {
return;
}
Q_ASSERT(m_widgetParent);
m_initialized = true;
m_search = new VSearch(this);
connect(m_search, &VSearch::resultItemAdded,
this, &VSearchUE::handleSearchItemAdded);
connect(m_search, &VSearch::finished,
this, &VSearchUE::handleSearchFinished);
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");
m_listWidget = new VListWidgetDoubleRows(m_widgetParent);
m_listWidget->setFitContent(true);
m_listWidget->hide();
connect(m_listWidget, &VListWidgetDoubleRows::itemActivated,
this, &VSearchUE::activateItem);
}
QWidget *VSearchUE::widget(int p_id)
{
init();
switch (p_id) {
case ID::Name_Notebook_AllNotebook:
V_FALLTHROUGH;
case ID::Name_FolderNote_AllNotebook:
return m_listWidget;
default:
Q_ASSERT(false);
return NULL;
}
}
void VSearchUE::processCommand(int p_id, const QString &p_cmd)
{
qDebug() << "processCommand" << p_id << p_cmd;
init();
clear(-1);
m_inSearch = true;
m_id = p_id;
emit stateUpdated(State::Busy);
switch (p_id) {
case ID::Name_Notebook_AllNotebook:
searchNameOfAllNotebooks(p_cmd);
break;
case ID::Name_FolderNote_AllNotebook:
searchNameOfFolderNoteInAllNotebooks(p_cmd);
break;
default:
Q_ASSERT(false);
break;
}
widget(p_id)->updateGeometry();
emit widgetUpdated();
}
void VSearchUE::searchNameOfAllNotebooks(const QString &p_cmd)
{
const QVector<VNotebook *> &notebooks = g_vnote->getNotebooks();
if (p_cmd.isEmpty()) {
// List all the notebooks.
for (auto const & nb : notebooks) {
QSharedPointer<VSearchResultItem> item(new VSearchResultItem(VSearchResultItem::Notebook,
VSearchResultItem::LineNumber,
nb->getName(),
nb->getPath()));
handleSearchItemAdded(item);
}
m_inSearch = false;
emit stateUpdated(State::Success);
} else {
// Do a fuzzy search against the name of the notebooks.
VSearchConfig::Option opt = VSearchConfig::Fuzzy;
QSharedPointer<VSearchConfig> config(new VSearchConfig(VSearchConfig::AllNotebooks,
VSearchConfig::Name,
VSearchConfig::Notebook,
VSearchConfig::Internal,
opt,
p_cmd,
QString()));
m_search->setConfig(config);
QSharedPointer<VSearchResult> result = m_search->search(notebooks);
handleSearchFinished(result);
}
}
void VSearchUE::searchNameOfFolderNoteInAllNotebooks(const QString &p_cmd)
{
const QVector<VNotebook *> &notebooks = g_vnote->getNotebooks();
if (p_cmd.isEmpty()) {
m_inSearch = false;
emit stateUpdated(State::Success);
} else {
VSearchConfig::Option opt = VSearchConfig::NoneOption;
QSharedPointer<VSearchConfig> config(new VSearchConfig(VSearchConfig::AllNotebooks,
VSearchConfig::Name,
VSearchConfig::Folder | VSearchConfig::Note,
VSearchConfig::Internal,
opt,
p_cmd,
QString()));
m_search->setConfig(config);
QSharedPointer<VSearchResult> result = m_search->search(notebooks);
handleSearchFinished(result);
}
}
void VSearchUE::clear(int p_id)
{
Q_UNUSED(p_id);
stopSearch();
m_data.clear();
m_listWidget->clearAll();
}
void VSearchUE::entryHidden(int p_id)
{
Q_UNUSED(p_id);
}
void VSearchUE::handleSearchItemAdded(const QSharedPointer<VSearchResultItem> &p_item)
{
switch (m_id) {
case ID::Name_Notebook_AllNotebook:
V_FALLTHROUGH;
case ID::Name_FolderNote_AllNotebook:
appendItemToList(p_item);
break;
default:
break;
}
}
void VSearchUE::appendItemToList(const QSharedPointer<VSearchResultItem> &p_item)
{
static int itemAdded = 0;
m_data.append(p_item);
QString first, second;
if (p_item->m_text.isEmpty()) {
first = p_item->m_path;
} else {
first = p_item->m_text;
second = p_item->m_path;
}
QIcon *icon = NULL;
switch (p_item->m_type) {
case VSearchResultItem::Note:
icon = &m_noteIcon;
break;
case VSearchResultItem::Folder:
icon = &m_folderIcon;
break;
case VSearchResultItem::Notebook:
icon = &m_notebookIcon;
break;
default:
break;
}
QListWidgetItem *item = m_listWidget->addItem(*icon, first, second);
item->setData(Qt::UserRole, m_data.size() - 1);
item->setToolTip(p_item->m_path);
if (m_listWidget->currentRow() == -1) {
m_listWidget->setCurrentRow(0);
}
if (++itemAdded >= 10) {
itemAdded = 0;
m_listWidget->updateGeometry();
emit widgetUpdated();
}
}
void VSearchUE::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;
IUniversalEntry::State state = State::Idle;
switch (p_result->m_state) {
case VSearchState::Busy:
qDebug() << "search is ongoing";
state = State::Busy;
return;
case VSearchState::Success:
qDebug() << "search succeeded";
state = State::Success;
break;
case VSearchState::Fail:
qDebug() << "search failed";
state = State::Fail;
break;
case VSearchState::Cancelled:
qDebug() << "search cancelled";
state = State::Cancelled;
break;
default:
break;
}
m_search->clear();
m_inSearch = false;
widget(m_id)->updateGeometry();
emit widgetUpdated();
emit stateUpdated(state);
}
void VSearchUE::stopSearch()
{
if (m_inSearch) {
m_search->stop();
while (m_inSearch) {
VUtils::sleepWait(100);
qDebug() << "sleep wait for search to stop";
}
}
}
const QSharedPointer<VSearchResultItem> &VSearchUE::itemResultData(const QListWidgetItem *p_item) const
{
Q_ASSERT(p_item);
int idx = p_item->data(Qt::UserRole).toInt();
Q_ASSERT(idx >= 0 && idx < m_data.size());
return m_data[idx];
}
void VSearchUE::activateItem(const QListWidgetItem *p_item)
{
if (!p_item) {
return;
}
emit requestHideUniversalEntry();
const QSharedPointer<VSearchResultItem> &resItem = itemResultData(p_item);
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;
}
}
void VSearchUE::selectNextItem(int p_id, bool p_forward)
{
switch (p_id) {
case ID::Name_Notebook_AllNotebook:
V_FALLTHROUGH;
case ID::Name_FolderNote_AllNotebook:
{
// Could not use postEvent method here which will induce infinite recursion.
m_listWidget->selectNextItem(p_forward);
break;
}
default:
Q_ASSERT(false);
}
}
void VSearchUE::activate(int p_id)
{
switch (p_id) {
case ID::Name_Notebook_AllNotebook:
V_FALLTHROUGH;
case ID::Name_FolderNote_AllNotebook:
{
activateItem(m_listWidget->currentItem());
break;
}
default:
Q_ASSERT(false);
}
}
void VSearchUE::askToStop(int p_id)
{
Q_UNUSED(p_id);
m_search->stop();
}

85
src/vsearchue.h Normal file
View File

@ -0,0 +1,85 @@
#ifndef VSEARCHUE_H
#define VSEARCHUE_H
#include "iuniversalentry.h"
#include <QIcon>
#include "vsearchconfig.h"
class VListWidgetDoubleRows;
class QListWidgetItem;
// Universal Entry to list and search all the notebooks.
class VSearchUE : public IUniversalEntry
{
Q_OBJECT
public:
enum ID
{
// List and search the name of all notebooks.
Name_Notebook_AllNotebook = 0,
// Search the name of the folder/note in all the notebooks.
Name_FolderNote_AllNotebook
};
explicit VSearchUE(QObject *p_parent = nullptr);
QString description(int p_id) const Q_DECL_OVERRIDE;
QWidget *widget(int p_id) Q_DECL_OVERRIDE;
void processCommand(int p_id, const QString &p_cmd) Q_DECL_OVERRIDE;
void clear(int p_id) Q_DECL_OVERRIDE;
void entryHidden(int p_id) Q_DECL_OVERRIDE;
void selectNextItem(int p_id, bool p_forward) Q_DECL_OVERRIDE;
void activate(int p_id) Q_DECL_OVERRIDE;
void askToStop(int p_id) Q_DECL_OVERRIDE;
protected:
void init() Q_DECL_OVERRIDE;
private slots:
void handleSearchItemAdded(const QSharedPointer<VSearchResultItem> &p_item);
void handleSearchFinished(const QSharedPointer<VSearchResult> &p_result);
private:
void searchNameOfAllNotebooks(const QString &p_cmd);
void searchNameOfFolderNoteInAllNotebooks(const QString &p_cmd);
// Stop the search synchronously.
void stopSearch();
void appendItemToList(const QSharedPointer<VSearchResultItem> &p_item);
void activateItem(const QListWidgetItem *p_item);
const QSharedPointer<VSearchResultItem> &itemResultData(const QListWidgetItem *p_item) const;
VSearch *m_search;
bool m_inSearch;
// Current instance ID.
int m_id;
QVector<QSharedPointer<VSearchResultItem> > m_data;
QIcon m_noteIcon;
QIcon m_folderIcon;
QIcon m_notebookIcon;
VListWidgetDoubleRows *m_listWidget;
};
#endif // VSEARCHUE_H

View File

@ -5,31 +5,119 @@
#include <QDebug>
#include <QHideEvent>
#include <QShowEvent>
#include <QPaintEvent>
#include <QKeyEvent>
#include <QListWidgetItem>
#include <QStyleOption>
#include <QPainter>
#include <QTimer>
#include <QAtomicInt>
#include "vlineedit.h"
#include "vmetawordlineedit.h"
#include "utils/vutils.h"
#include "vlistwidget.h"
#include "vpalette.h"
#define MINIMUM_WIDTH 200
#define CMD_EDIT_INTERVAL 500
extern VPalette *g_palette;
VUniversalEntryContainer::VUniversalEntryContainer(QWidget *p_parent)
: QWidget(p_parent),
m_widget(NULL)
{
m_layout = new QVBoxLayout();
m_layout->setContentsMargins(0, 0, 0, 0);
setLayout(m_layout);
}
void VUniversalEntryContainer::clear()
{
if (m_widget) {
m_layout->removeWidget(m_widget);
m_widget->hide();
m_widget = NULL;
}
adjustSizeByWidget();
}
void VUniversalEntryContainer::setWidget(QWidget *p_widget)
{
if (m_widget != p_widget) {
clear();
m_widget = p_widget;
m_layout->addWidget(m_widget);
m_widget->show();
}
adjustSizeByWidget();
}
void VUniversalEntryContainer::adjustSizeByWidget()
{
updateGeometry();
}
QSize VUniversalEntryContainer::sizeHint() const
{
if (m_widget) {
return m_widget->sizeHint();
} else {
return QWidget::sizeHint();
}
}
VUniversalEntry::VUniversalEntry(QWidget *p_parent)
: QWidget(p_parent),
m_availableRect(0, 0, MINIMUM_WIDTH, MINIMUM_WIDTH)
m_availableRect(0, 0, MINIMUM_WIDTH, MINIMUM_WIDTH),
m_lastEntry(NULL),
m_inQueue(0),
m_pendingCommand(false)
{
m_minimumWidth = MINIMUM_WIDTH * VUtils::calculateScaleFactor() + 0.5;
m_cmdTimer = new QTimer(this);
m_cmdTimer->setSingleShot(true);
m_cmdTimer->setInterval(CMD_EDIT_INTERVAL);
connect(m_cmdTimer, &QTimer::timeout,
this, [this]() {
processCommand();
});
setupUI();
m_infoWidget = new VListWidget(this);
m_infoWidget->setFitContent(true);
m_container->setWidget(m_infoWidget);
m_cmdStyleSheet = m_cmdEdit->styleSheet();
}
void VUniversalEntry::setupUI()
{
m_cmdEdit = new VLineEdit(this);
m_cmdEdit = new VMetaWordLineEdit(this);
m_cmdEdit->setPlaceholderText(tr("Welcome to Universal Entry"));
connect(m_cmdEdit, &VMetaWordLineEdit::textEdited,
this, [this]() {
m_cmdTimer->stop();
m_cmdTimer->start();
});
m_layout = new QVBoxLayout();
m_layout->addWidget(m_cmdEdit);
m_container = new VUniversalEntryContainer(this);
m_layout->setContentsMargins(0, 0, 0, 0);
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addWidget(m_cmdEdit);
mainLayout->addWidget(m_container);
setLayout(m_layout);
mainLayout->setContentsMargins(1, 1, 1, 1);
mainLayout->setSpacing(0);
setLayout(mainLayout);
setMinimumWidth(m_minimumWidth);
}
@ -37,6 +125,10 @@ void VUniversalEntry::setupUI()
void VUniversalEntry::hideEvent(QHideEvent *p_event)
{
QWidget::hideEvent(p_event);
if (m_lastEntry) {
m_lastEntry->m_entry->entryHidden(m_lastEntry->m_id);
}
emit exited();
}
@ -45,7 +137,6 @@ void VUniversalEntry::showEvent(QShowEvent *p_event)
QWidget::showEvent(p_event);
m_cmdEdit->setFocus();
m_cmdEdit->selectAll();
}
void VUniversalEntry::setAvailableRect(const QRect &p_rect)
@ -55,4 +146,193 @@ void VUniversalEntry::setAvailableRect(const QRect &p_rect)
if (m_availableRect.width() < m_minimumWidth) {
m_availableRect.setWidth(m_minimumWidth);
}
setMaximumSize(m_availableRect.size());
}
void VUniversalEntry::registerEntry(QChar p_key, IUniversalEntry *p_entry, int p_id)
{
Q_ASSERT(!m_entries.contains(p_key));
p_entry->setParent(this);
p_entry->setWidgetParent(this);
connect(p_entry, &IUniversalEntry::widgetUpdated,
this, [this]() {
m_container->adjustSizeByWidget();
adjustSize();
});
connect(p_entry, &IUniversalEntry::stateUpdated,
this, &VUniversalEntry::updateState);
connect(p_entry, &IUniversalEntry::requestHideUniversalEntry,
this, &VUniversalEntry::hide);
m_entries.insert(p_key, Entry(p_entry, p_id));
m_infoWidget->addItem(QString("%1: %2").arg(p_key)
.arg(p_entry->description(p_id)));
m_infoWidget->updateGeometry();
}
void VUniversalEntry::processCommand()
{
if (!m_inQueue.testAndSetRelaxed(0, 1)) {
// There is already a job in queue.
qDebug() << "already a job in queue, pend a new job and ask to stop";
m_pendingCommand = true;
if (m_lastEntry) {
m_lastEntry->m_entry->askToStop(m_lastEntry->m_id);
}
return;
}
redo:
QString cmd = m_cmdEdit->getEvaluatedText();
processCommand(cmd);
if (m_pendingCommand && cmd != m_cmdEdit->getEvaluatedText()) {
// Handle pending job.
qDebug() << "handle pending job" << cmd;
m_pendingCommand = false;
goto redo;
}
m_inQueue.store(0);
}
void VUniversalEntry::processCommand(const QString &p_cmd)
{
if (p_cmd.isEmpty()) {
clear();
return;
}
auto it = m_entries.find(p_cmd[0]);
if (it == m_entries.end()) {
clear();
return;
}
const Entry &entry = it.value();
if (m_lastEntry && m_lastEntry != &entry) {
m_lastEntry->m_entry->clear(m_lastEntry->m_id);
}
m_lastEntry = &entry;
m_container->setWidget(entry.m_entry->widget(entry.m_id));
adjustSize();
entry.m_entry->processCommand(entry.m_id, p_cmd.mid(1));
}
void VUniversalEntry::clear()
{
if (m_lastEntry) {
m_lastEntry->m_entry->clear(m_lastEntry->m_id);
m_lastEntry = NULL;
}
m_container->setWidget(m_infoWidget);
adjustSize();
}
void VUniversalEntry::keyPressEvent(QKeyEvent *p_event)
{
int modifiers = p_event->modifiers();
bool forward = true;
switch (p_event->key()) {
case Qt::Key_BracketLeft:
if (VUtils::isControlModifierForVim(modifiers)) {
// Hide.
hide();
return;
}
break;
// Up/Down Ctrl+K/J to navigate to next item.
case Qt::Key_Up:
forward = false;
V_FALLTHROUGH;
case Qt::Key_Down:
if (m_lastEntry) {
m_lastEntry->m_entry->selectNextItem(m_lastEntry->m_id, forward);
return;
}
break;
case Qt::Key_K:
forward = false;
V_FALLTHROUGH;
case Qt::Key_J:
if (m_lastEntry && VUtils::isControlModifierForVim(modifiers)) {
m_lastEntry->m_entry->selectNextItem(m_lastEntry->m_id, forward);
return;
}
break;
case Qt::Key_Enter:
// Fall through.
V_FALLTHROUGH;
case Qt::Key_Return:
{
if (m_lastEntry) {
m_lastEntry->m_entry->activate(m_lastEntry->m_id);
return;
}
break;
}
case Qt::Key_E:
if (VUtils::isControlModifierForVim(modifiers)) {
// Ctrl+E to eliminate input except the command key.
QString cmd = m_cmdEdit->getEvaluatedText();
if (!cmd.isEmpty()) {
m_cmdEdit->setText(cmd.left(1));
m_cmdTimer->stop();
processCommand();
return;
}
}
break;
default:
break;
}
QWidget::keyPressEvent(p_event);
}
void VUniversalEntry::paintEvent(QPaintEvent *p_event)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
QWidget::paintEvent(p_event);
}
void VUniversalEntry::updateState(IUniversalEntry::State p_state)
{
QString fg;
switch (p_state) {
case IUniversalEntry::Busy:
fg = g_palette->color("ue_cmd_busy_border");
break;
case IUniversalEntry::Fail:
fg = g_palette->color("ue_cmd_fail_border");
break;
default:
break;
}
if (fg.isEmpty()) {
m_cmdEdit->setStyleSheet(m_cmdStyleSheet);
} else {
m_cmdEdit->setStyleSheet(QString("border-color: %1;").arg(fg));
}
}

View File

@ -3,11 +3,41 @@
#include <QWidget>
#include <QRect>
#include <QHash>
#include <QAtomicInt>
class VLineEdit;
#include "iuniversalentry.h"
class VMetaWordLineEdit;
class QVBoxLayout;
class QHideEvent;
class QShowEvent;
class QPaintEvent;
class QKeyEvent;
class QTimer;
class VListWidget;
class VUniversalEntryContainer : public QWidget
{
Q_OBJECT
public:
VUniversalEntryContainer(QWidget *p_parent = nullptr);
void clear();
void setWidget(QWidget *p_widget);
void adjustSizeByWidget();
protected:
QSize sizeHint() const Q_DECL_OVERRIDE;
private:
QWidget *m_widget;
QVBoxLayout *m_layout;
};
class VUniversalEntry : public QWidget
{
@ -18,6 +48,11 @@ public:
// Set the availabel width and height.
void setAvailableRect(const QRect &p_rect);
// Register an entry at @p_key.
// Different entries may use the same @p_entry, in which case they can use
// @p_id to distinguish.
void registerEntry(QChar p_key, IUniversalEntry *p_entry, int p_id = 0);
signals:
// Exit Universal Entry.
void exited();
@ -27,17 +62,62 @@ protected:
void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
void paintEvent(QPaintEvent *p_event) Q_DECL_OVERRIDE;
private:
struct Entry
{
Entry()
: m_entry(NULL), m_id(-1)
{
}
Entry(IUniversalEntry *p_entry, int p_id)
: m_entry(p_entry), m_id(p_id)
{
}
IUniversalEntry *m_entry;
int m_id;
};
void setupUI();
VLineEdit *m_cmdEdit;
void clear();
QVBoxLayout *m_layout;
void processCommand();
void processCommand(const QString &p_cmd);
void updateState(IUniversalEntry::State p_state);
VMetaWordLineEdit *m_cmdEdit;
VUniversalEntryContainer *m_container;
// Rect availabel for the UE to use.
QRect m_availableRect;
int m_minimumWidth;
QHash<QChar, Entry> m_entries;
// Widget to list all entries.
VListWidget *m_infoWidget;
QTimer *m_cmdTimer;
// Last used Entry.
const Entry *m_lastEntry;
// The CMD edit's original style sheet.
QString m_cmdStyleSheet;
QAtomicInt m_inQueue;
bool m_pendingCommand;
};
#endif // VUNIVERSALENTRY_H