#include "completerwindow.h" #include "ui_completerwindow.h" #include "completermodel.h" #include "common/unused.h" #include "sqleditor.h" #include "common/utils_sql.h" #include "services/codesnippetmanager.h" #include "sqlitestudio.h" #include #include #include #include #include #include CompleterWindow::CompleterWindow(SqlEditor *parent) : QDialog(parent, Qt::FramelessWindowHint), ui(new Ui::CompleterWindow), sqlEditor(parent) { init(); } CompleterWindow::~CompleterWindow() { delete ui; } void CompleterWindow::init() { ui->setupUi(this); modeChangeShortcut = new QShortcut(GET_SHORTCUTS_CATEGORY(SqlEditor).COMPLETE.get(), this); snippetSignalMapper = new QSignalMapper(this); model = new CompleterModel(this); ui->list->setModel(model); model->setCompleterView(ui->list); setFocusProxy(ui->list); connect(ui->list, SIGNAL(focusOut()), this, SLOT(focusOut())); connect(ui->list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(doubleClicked(QModelIndex))); connect(ui->list->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), this, SLOT(currentRowChanged(QModelIndex,QModelIndex))); connect(ui->list, SIGNAL(textTyped(QString)), this, SIGNAL(textTyped(QString))); connect(ui->list, SIGNAL(backspace()), this, SIGNAL(backspacePressed())); connect(ui->list, SIGNAL(left()), this, SIGNAL(leftPressed())); connect(ui->list, SIGNAL(right()), this, SIGNAL(rightPressed())); connect(modeChangeShortcut, SIGNAL(activated()), this, SLOT(modeChangeRequested())); connect(ui->snippets, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(snippetDoubleClicked(QListWidgetItem*))); #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) connect(snippetSignalMapper, SIGNAL(mappedInt(int)), this, SLOT(snippetHotkeyPressed(int))); #else connect(snippetSignalMapper, SIGNAL(mapped(int)), this, SLOT(snippetHotkeyPressed(int))); #endif reset(); } void CompleterWindow::refreshSnippets() { ui->snippets->clear(); for (QShortcut*& sc : snippetShortcuts) delete sc; snippetShortcuts.clear(); int i = 0; for (CodeSnippetManager::CodeSnippet* snip : CODESNIPPETS->getSnippets()) { ui->snippets->addItem(snip->name); if (!snip->hotkey.isEmpty()) { QShortcut* shortcut = new QShortcut(snip->hotkey, ui->snippets); snippetShortcuts << shortcut; snippetSignalMapper->setMapping(shortcut, i); connect(shortcut, SIGNAL(activated()), snippetSignalMapper, SLOT(map())); } i++; } if (ui->snippets->count() > 0) ui->snippets->setCurrentRow(0); } void CompleterWindow::reset() { model->clear(); ui->status->showMessage(QString()); } void CompleterWindow::setData(const CompletionHelper::Results& completionResults) { ui->status->showMessage(QString()); model->setData(completionResults.expectedTokens); filter = completionResults.partialToken; wrappedFilter = completionResults.wrappedToken; updateFilter(); } void CompleterWindow::setDb(Db* db) { this->db = db; } void CompleterWindow::updateFilter() { model->setFilter(filter); ui->list->selectFirstVisible(); if (!ui->list->hasVisibleItem()) reject(); } void CompleterWindow::shringFilterBy(int chars) { if (filter.size() < chars) { if (wrappedFilter && chars == 1) { wrappedFilter = false; updateFilter(); return; } reject(); return; } filter.truncate(filter.length() - chars); updateFilter(); } void CompleterWindow::extendFilterBy(const QString& text) { if (filter.isEmpty() && text.size() == 1 && isWrapperChar(text[0])) { wrappedFilter = true; updateFilter(); return; } filter.append(text); updateFilter(); } bool CompleterWindow::immediateResolution() { if (ui->list->countVisibleItem() == 1) { accept(); return true; } return false; } CompleterWindow::Mode CompleterWindow::getMode() const { return static_cast(ui->modeStack->currentIndex()); } QString CompleterWindow::getSnippetName() const { return ui->snippets->currentItem()->text(); } ExpectedTokenPtr CompleterWindow::getSelected() const { QModelIndex current = ui->list->currentIndex(); if (!current.isValid()) return ExpectedTokenPtr(); return model->getToken(current.row()); } int CompleterWindow::getNumberOfCharsToRemove() { return filter.size() + (wrappedFilter ? 1 : 0); } void CompleterWindow::changeEvent(QEvent *e) { QDialog::changeEvent(e); switch (e->type()) { case QEvent::LanguageChange: ui->retranslateUi(this); break; default: break; } } void CompleterWindow::keyPressEvent(QKeyEvent* e) { if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { accept(); return; } QDialog::keyPressEvent(e); } QString CompleterWindow::getStatusMsg(const QModelIndex& index) { ExpectedToken::Type type = (ExpectedToken::Type)index.data(CompleterModel::TYPE).toInt(); QString value = index.data(CompleterModel::VALUE).toString(); QString label = index.data(CompleterModel::LABEL).toString(); switch (type) { case ExpectedToken::COLUMN: return tr("Column: %1", "completer statusbar").arg(value); case ExpectedToken::TABLE: return tr("Table: %1", "completer statusbar").arg(value); case ExpectedToken::INDEX: return tr("Index: %1", "completer statusbar").arg(value); case ExpectedToken::TRIGGER: return tr("Trigger: %1", "completer statusbar").arg(value); case ExpectedToken::VIEW: return tr("View: %1", "completer statusbar").arg(value); case ExpectedToken::DATABASE: return tr("Database: %1", "completer statusbar").arg(value); case ExpectedToken::OTHER: { if (!label.isEmpty()) return label; if (!value.isEmpty()) return value; return ""; } case ExpectedToken::KEYWORD: return tr("Keyword: %1", "completer statusbar").arg(value); case ExpectedToken::FUNCTION: return tr("Function: %1", "completer statusbar").arg(value); case ExpectedToken::OPERATOR: return tr("Operator: %1", "completer statusbar").arg(value); case ExpectedToken::STRING: return tr("String", "completer statusbar"); case ExpectedToken::NUMBER: return tr("Number", "completer statusbar").arg(value); case ExpectedToken::BLOB: return tr("Binary data", "completer statusbar").arg(value); case ExpectedToken::COLLATION: return tr("Collation: %1", "completer statusbar").arg(value); case ExpectedToken::PRAGMA: return tr("Pragma function: %1", "completer statusbar").arg(value); case ExpectedToken::NO_VALUE: { if (!label.isEmpty()) return label; if (!value.isEmpty()) return value; return ""; } } return ""; } void CompleterWindow::focusOut() { QWidget* focused = QApplication::focusWidget(); if (!focused || focused == this || isAncestorOf(focused)) return; reject(); } void CompleterWindow::doubleClicked(const QModelIndex& index) { UNUSED(index); accept(); } void CompleterWindow::currentRowChanged(const QModelIndex& current, const QModelIndex& previous) { UNUSED(previous); ui->status->showMessage(getStatusMsg(current)); } void CompleterWindow::modeChangeRequested() { ui->modeStack->setCurrentIndex((ui->modeStack->currentIndex() + 1) % ui->modeStack->count()); switch (ui->modeStack->currentIndex()) { case SNIPPETS: ui->status->showMessage(tr("Insert a code snippet")); refreshSnippets(); break; case CODE: ui->status->showMessage(getStatusMsg(ui->list->currentIndex())); break; } } void CompleterWindow::snippetHotkeyPressed(int index) { ui->snippets->setCurrentRow(index); accept(); } void CompleterWindow::snippetDoubleClicked(QListWidgetItem* item) { UNUSED(item); accept(); } void CompleterWindow::showEvent(QShowEvent*e) { ui->modeStack->setCurrentIndex(0); QDialog::showEvent(e); // A hack for Gnome3 to give this widget a focus. Harmless for others. ui->list->activateWindow(); // Refresh hotkey if changed modeChangeShortcut->setKey(GET_SHORTCUTS_CATEGORY(SqlEditor).COMPLETE.get()); }