mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
462 lines
11 KiB
C++
462 lines
11 KiB
C++
#include "vuniversalentry.h"
|
|
|
|
#include <QVBoxLayout>
|
|
#include <QLabel>
|
|
#include <QDebug>
|
|
#include <QHideEvent>
|
|
#include <QShowEvent>
|
|
#include <QPaintEvent>
|
|
#include <QKeyEvent>
|
|
#include <QListWidgetItem>
|
|
#include <QStyleOption>
|
|
#include <QPainter>
|
|
#include <QTimer>
|
|
#include <QAtomicInt>
|
|
|
|
#include "vmetawordlineedit.h"
|
|
#include "utils/vutils.h"
|
|
#include "vlistwidget.h"
|
|
#include "vpalette.h"
|
|
#include "vlistfolderue.h"
|
|
|
|
#define MINIMUM_WIDTH 200
|
|
|
|
#define CMD_EDIT_INTERVAL 500
|
|
#define CMD_EDIT_IDLE_INTERVAL 100
|
|
|
|
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_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 VMetaWordLineEdit(this);
|
|
m_cmdEdit->setPlaceholderText(tr("Universal Entry, reach anything by typing"));
|
|
m_cmdEdit->setCtrlKEnabled(false);
|
|
m_cmdEdit->setCtrlEEnabled(false);
|
|
connect(m_cmdEdit, &VMetaWordLineEdit::textEdited,
|
|
this, [this](const QString &p_text) {
|
|
m_cmdTimer->stop();
|
|
if (p_text.size() <= 1) {
|
|
m_cmdTimer->start(CMD_EDIT_IDLE_INTERVAL);
|
|
} else {
|
|
m_cmdTimer->start(CMD_EDIT_INTERVAL);
|
|
}
|
|
});
|
|
|
|
m_container = new VUniversalEntryContainer(this);
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout();
|
|
mainLayout->addWidget(m_cmdEdit);
|
|
mainLayout->addWidget(m_container);
|
|
|
|
mainLayout->setContentsMargins(1, 1, 1, 1);
|
|
mainLayout->setSpacing(0);
|
|
|
|
setLayout(mainLayout);
|
|
|
|
setMinimumWidth(m_minimumWidth);
|
|
}
|
|
|
|
void VUniversalEntry::hideEvent(QHideEvent *p_event)
|
|
{
|
|
QWidget::hideEvent(p_event);
|
|
if (m_lastEntry) {
|
|
m_lastEntry->m_entry->entryHidden(m_lastEntry->m_id);
|
|
}
|
|
|
|
emit exited();
|
|
}
|
|
|
|
void VUniversalEntry::showEvent(QShowEvent *p_event)
|
|
{
|
|
QWidget::showEvent(p_event);
|
|
|
|
// Fix Chinese input method issue.
|
|
activateWindow();
|
|
|
|
m_cmdEdit->setFocus();
|
|
m_cmdEdit->selectAll();
|
|
|
|
if (m_lastEntry) {
|
|
m_lastEntry->m_entry->entryShown(m_lastEntry->m_id, getCommandFromEdit());
|
|
}
|
|
}
|
|
|
|
void VUniversalEntry::setAvailableRect(const QRect &p_rect)
|
|
{
|
|
m_availableRect = 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]() {
|
|
if (m_lastEntry) {
|
|
m_container->setWidget(m_lastEntry->m_entry->widget(m_lastEntry->m_id));
|
|
} else {
|
|
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();
|
|
|
|
if (m_listEntryKey.isNull()) {
|
|
if (dynamic_cast<VListFolderUE *>(p_entry)) {
|
|
m_listEntryKey = p_key;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
case Qt::Key_Escape:
|
|
hide();
|
|
return;
|
|
|
|
// 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_cmdTimer->stop();
|
|
m_cmdEdit->setText(cmd.left(1));
|
|
processCommand();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case Qt::Key_F:
|
|
if (VUtils::isControlModifierForVim(modifiers)) {
|
|
// Ctrl+F to select the command key.
|
|
QString cmd = m_cmdEdit->getEvaluatedText();
|
|
if (!cmd.isEmpty()) {
|
|
m_cmdTimer->stop();
|
|
m_cmdEdit->setSelection(0, 1);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case Qt::Key_D:
|
|
if (VUtils::isControlModifierForVim(modifiers)) {
|
|
// Ctrl+D to cancel current command.
|
|
m_pendingCommand = false;
|
|
if (m_lastEntry) {
|
|
m_lastEntry->m_entry->askToStop(m_lastEntry->m_id);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case Qt::Key_L:
|
|
if (VUtils::isControlModifierForVim(modifiers)) {
|
|
// Ctrl+L to go up a level.
|
|
if (m_lastEntry) {
|
|
m_lastEntry->m_entry->selectParentItem(m_lastEntry->m_id);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case Qt::Key_I:
|
|
if (VUtils::isControlModifierForVim(modifiers)) {
|
|
// Ctrl+I to expand or collapse an item.
|
|
if (m_lastEntry) {
|
|
m_lastEntry->m_entry->toggleItemExpanded(m_lastEntry->m_id);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case Qt::Key_S:
|
|
if (VUtils::isControlModifierForVim(modifiers)) {
|
|
// Ctrl+S to sort the items.
|
|
// UE could just alternate among all the sort modes.
|
|
if (m_lastEntry) {
|
|
m_lastEntry->m_entry->sort(m_lastEntry->m_id);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case Qt::Key_M:
|
|
if (VUtils::isControlModifierForVim(modifiers)) {
|
|
// Ctrl+M to browse current item folder or the folder containing current
|
|
// item.
|
|
if (m_lastEntry && !m_listEntryKey.isNull()) {
|
|
QString folderPath = m_lastEntry->m_entry->currentItemFolder(m_lastEntry->m_id);
|
|
if (!folderPath.isEmpty()) {
|
|
m_cmdTimer->stop();
|
|
Q_ASSERT(m_entries.contains(m_listEntryKey));
|
|
const Entry &entry = m_entries[m_listEntryKey];
|
|
static_cast<VListFolderUE *>(entry.m_entry)->setFolderPath(folderPath);
|
|
m_cmdEdit->setText(m_listEntryKey);
|
|
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));
|
|
}
|
|
}
|
|
|
|
QString VUniversalEntry::getCommandFromEdit() const
|
|
{
|
|
QString cmd = m_cmdEdit->getEvaluatedText();
|
|
return cmd.mid(1);
|
|
}
|