From e4d6a1568244afea4c102adae6bb1d6097e6d4d7 Mon Sep 17 00:00:00 2001 From: mrbesen Date: Wed, 21 Sep 2022 00:33:41 +0200 Subject: [PATCH] simple button search --- include/buttonsearch.h | 39 ++++++++++ include/config.h | 4 + include/mainwindow.h | 3 + include/soundbutton.h | 7 +- soundboard.pro | 3 + src/buttonsearch.cpp | 163 +++++++++++++++++++++++++++++++++++++++++ src/config.cpp | 25 +++++++ src/config_json.cpp | 1 + src/mainwindow.cpp | 35 ++++++++- src/soundbutton.cpp | 22 ++++-- ui/buttonsearch.ui | 109 +++++++++++++++++++++++++++ ui/mainwindow.ui | 8 ++ 12 files changed, 406 insertions(+), 13 deletions(-) create mode 100644 include/buttonsearch.h create mode 100644 src/buttonsearch.cpp create mode 100644 ui/buttonsearch.ui diff --git a/include/buttonsearch.h b/include/buttonsearch.h new file mode 100644 index 0000000..1084927 --- /dev/null +++ b/include/buttonsearch.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include "qxtglobalshortcut.h" + +#include "config.h" + +namespace Ui { + class ButtonSearch; +} + +class ButtonSearch : public QDialog { + Q_OBJECT + +public: + explicit ButtonSearch(const Config& config, QWidget *parent = nullptr); + ButtonSearch(const ButtonSearch&) = delete; // no copy + ~ButtonSearch(); + +protected: + void buttonPressed(int i); + +private slots: + void closeSearch(); + + void selectionDown(); + void selectionUp(); + +private: + const Config& config; + Ui::ButtonSearch *ui; + + static const uint8_t NUMCOUNT = 46; + QxtGlobalShortcut* esc; + QxtGlobalShortcut* enter; + QxtGlobalShortcut* up; + QxtGlobalShortcut* down; + QxtGlobalShortcut* nums[NUMCOUNT]; +}; diff --git a/include/config.h b/include/config.h index c5b27c9..e8e3b20 100644 --- a/include/config.h +++ b/include/config.h @@ -11,6 +11,7 @@ public: void load(); void save(); + void assignIDs(); struct AudioConfig { std::vector devices; @@ -29,6 +30,7 @@ public: struct ButtonConfig { std::string name; std::string key; + uint32_t id; uint8_t width = DEFAULTWIDTH; // default is 6 std::vector samples; @@ -43,6 +45,7 @@ public: std::string left; std::string right; std::string play; + std::string search; std::string stop; }; @@ -54,6 +57,7 @@ public: ShortcutConfig shortcuts; } rootConfig; + const ButtonConfig* getButtonByID(uint32_t btnid) const; private: std::string binaryPath; const std::string file = "soundboard.json"; diff --git a/include/mainwindow.h b/include/mainwindow.h index 45b7314..71c7195 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -39,6 +39,7 @@ private slots: void saveConfig(); void openButtonManager(); void openSettings(); + void openSearch(); signals: void newStatusMessage(const QString&); @@ -48,6 +49,7 @@ private: void loadShortcuts(); void removeAllButtons(); void loadButtonsFromConfig(); + void playButtonID(uint32_t buttonid); void reselectNext(std::function stepf); QLayoutItem* findNextButton(std::function stepf); @@ -63,6 +65,7 @@ private: QxtGlobalShortcut* left = nullptr; QxtGlobalShortcut* right = nullptr; QxtGlobalShortcut* play = nullptr; + QxtGlobalShortcut* search = nullptr; // "real" coordinates uint8_t xCoord = 0; diff --git a/include/soundbutton.h b/include/soundbutton.h index 76c84d5..b6c1d62 100644 --- a/include/soundbutton.h +++ b/include/soundbutton.h @@ -18,7 +18,7 @@ public: float volume = 1.f; }; - explicit SoundButton(const std::string& name_ = "", const std::string& keycombo = "", QWidget* parent = nullptr); + explicit SoundButton(uint32_t id, const std::string& name_ = "", const std::string& keycombo = "", QWidget* parent = nullptr); ~SoundButton(); const std::string& getName() const; @@ -35,11 +35,10 @@ public slots: private: void setDisabled(); - QString getInfo() const; + QString getInfo(bool withid = false) const; - static uint64_t nextid; - uint64_t id; + uint32_t id; std::string name; std::string keycombo; diff --git a/soundboard.pro b/soundboard.pro index f2a0a8c..433e4df 100644 --- a/soundboard.pro +++ b/soundboard.pro @@ -19,6 +19,7 @@ DEFINES += QT_DEPRECATED_WARNINGS #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ + src/buttonsearch.cpp \ src/main.cpp \ src/mainwindow.cpp \ src/sound.cpp \ @@ -38,6 +39,7 @@ SOURCES += \ Log/Log.cpp HEADERS += \ + include/buttonsearch.h \ include/mainwindow.h \ include/sound.h \ include/sounddevice.h \ @@ -63,6 +65,7 @@ OBJECTS_DIR = obj/ FORMS += \ ui/addnewwhat.ui \ ui/buttonmanager.ui \ + ui/buttonsearch.ui \ ui/editbutton.ui \ ui/editsample.ui \ ui/mainwindow.ui \ diff --git a/src/buttonsearch.cpp b/src/buttonsearch.cpp new file mode 100644 index 0000000..8cae03b --- /dev/null +++ b/src/buttonsearch.cpp @@ -0,0 +1,163 @@ +#include "buttonsearch.h" +#include "ui_buttonsearch.h" + +#include + +#include + +static char getSymbol(uint8_t i) { + return (i < 10) ? ('0' + i) : ('a' + i-10); +} + +static bool startsWith(const std::string& a, const std::string& b) { + // not very performant but working + return QString().fromStdString(a).toLower().startsWith(QString().fromStdString(b).toLower()); +} + +ButtonSearch::ButtonSearch(const Config& config, QWidget *parent) : QDialog(parent), config(config), ui(new Ui::ButtonSearch) { + ui->setupUi(this); + + Qt::WindowFlags eFlags = windowFlags(); + setWindowFlags(eFlags | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); + + esc = new QxtGlobalShortcut(QKeySequence("Esc")); + QObject::connect(esc, &QxtGlobalShortcut::activated, [this](){ buttonPressed(-1); }); + enter = new QxtGlobalShortcut(QKeySequence("Enter")); + QObject::connect(enter, &QxtGlobalShortcut::activated, [this](){ buttonPressed(-2); }); + + up = new QxtGlobalShortcut(QKeySequence("Up")); + QObject::connect(up, &QxtGlobalShortcut::activated, std::bind(&ButtonSearch::selectionUp, this)); + down = new QxtGlobalShortcut(QKeySequence("Down")); + QObject::connect(down, &QxtGlobalShortcut::activated, std::bind(&ButtonSearch::selectionDown, this)); + + for(uint8_t i = 0; i < NUMCOUNT-10; ++i) { + nums[i] = new QxtGlobalShortcut(QKeySequence(QString() + getSymbol(i))); + QObject::connect(nums[i], &QxtGlobalShortcut::activated, [this, i](){ buttonPressed(i); }); + } + + // numpad numbers + for(uint8_t i = 0; i < 10; ++i) { + nums[NUMCOUNT-10 +i] = new QxtGlobalShortcut(QKeySequence(QString("Num+") + getSymbol(i))); + QObject::connect(nums[NUMCOUNT-10 +i], &QxtGlobalShortcut::activated, [this, i](){ buttonPressed(i); }); + } +} + +void ButtonSearch::buttonPressed(int i) { + if(i == -1) { + // esc + Log::info << "esc pressed"; + done(-1); + } else if(i == -2) { + // enter + Log::info << "enter pressed"; + closeSearch(); + } else { + char sym = getSymbol(i); + Log::info << "button pressed: " << i << " sym: " << getSymbol(i); + + ui->searchText->setText(ui->searchText->toPlainText() + sym); + ui->searchResults->clear(); + + // execute search + const std::string searchStr = ui->searchText->toPlainText().toStdString(); + for(const auto& row : config.rootConfig.buttons) { + for(const auto& btn : row) { + if(startsWith(btn.name, searchStr)) { + QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(btn.name)); + item->setData(Qt::UserRole, QVariant(btn.id)); + ui->searchResults->addItem(item); + } else if(startsWith(std::to_string(btn.id), searchStr)) { + QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(btn.name)); + item->setData(Qt::UserRole, QVariant(btn.id)); + ui->searchResults->addItem(item); + } + } + } + } +} + +void ButtonSearch::closeSearch() { + QItemSelectionModel* selmod = ui->searchResults->selectionModel(); + auto idx = selmod->selectedRows(); + if(idx.isEmpty()) { + if(ui->searchResults->count() == 1) { + Log::info << "only one selected"; + QVariant var = ui->searchResults->item(0)->data(Qt::UserRole); + assert(var.type() == QVariant::Type::UInt); + done((int) var.toUInt()); + return; + } + + done(-1); // nothing selected + } else { + QVariant var = idx.at(0).data(Qt::UserRole); + assert(var.type() == QVariant::Type::UInt); + done((int) var.toUInt()); + } +} + +void ButtonSearch::selectionDown() { + Log::info << "down"; + + if(ui->searchResults->count() == 0) { + Log::warn << "empty search results"; + return; // avoid empty list + } + + QItemSelectionModel* selmod = ui->searchResults->selectionModel(); + QAbstractItemModel* itemmod = ui->searchResults->model(); + auto idx = selmod->selectedRows(); + if(idx.isEmpty()) { + // none selected - select first + Log::debug << "select first"; + selmod->setCurrentIndex(itemmod->index(0, 0), QItemSelectionModel::SelectionFlag::ClearAndSelect); + } else { + // select next + int selectedRow = idx.at(0).row(); + Log::debug << "idx: " << selectedRow; + if(ui->searchResults->count() > selectedRow+1) { + selmod->setCurrentIndex(itemmod->index(selectedRow+1, 0), QItemSelectionModel::SelectionFlag::ClearAndSelect); + } else { + // wrap around + selmod->setCurrentIndex(itemmod->index(0, 0), QItemSelectionModel::SelectionFlag::ClearAndSelect); + } + } +} + +void ButtonSearch::selectionUp() { + Log::info << "up"; + + if(ui->searchResults->count() == 0) { + Log::warn << "empty search results"; + return; // avoid empty list + } + + QItemSelectionModel* selmod = ui->searchResults->selectionModel(); + QAbstractItemModel* itemmod = ui->searchResults->model(); + auto idx = selmod->selectedRows(); + if(idx.isEmpty()) { + // none selected - select last + Log::debug << "select last"; + selmod->setCurrentIndex(itemmod->index(itemmod->rowCount()-1, 0), QItemSelectionModel::SelectionFlag::ClearAndSelect); + } else { + // select next + int selectedRow = idx.at(0).row(); + Log::debug << "idx: " << selectedRow; + if(selectedRow-1 >= 0) { + selmod->setCurrentIndex(itemmod->index(selectedRow-1, 0), QItemSelectionModel::SelectionFlag::ClearAndSelect); + } else { + // wrap around + selmod->setCurrentIndex(itemmod->index(itemmod->rowCount()-1, 0), QItemSelectionModel::SelectionFlag::ClearAndSelect); + } + } +} + +ButtonSearch::~ButtonSearch() { + delete ui; + + delete esc; + delete enter; + for(uint8_t i = 0; i < NUMCOUNT; ++i) { + delete nums[i]; + } +} diff --git a/src/config.cpp b/src/config.cpp index 2690867..15b69b8 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -56,6 +56,7 @@ void Config::load() { try { stream >> json; rootConfig = json; + assignIDs(); } catch(nlohmann::detail::parse_error& pe) { std::cout << "json error: " << pe.what() << std::endl; return; @@ -72,7 +73,31 @@ void Config::save() { if(stream) { json j = rootConfig; stream << j.dump(1, '\t'); + assignIDs(); } else { Log::error << "could not write configfile"; } } + +void Config::assignIDs() { + for(uint32_t row = 0; row < rootConfig.buttons.size(); ++row) { + auto& r = rootConfig.buttons.at(row); + for(uint32_t col = 0; col < r.size(); ++col) { + auto& btn = r.at(col); + btn.id = ((row+1) * 100) + (col+1); // TODO: problems when more than 99 colums in one row but should be fine for now + } + } +} + +const Config::ButtonConfig* Config::getButtonByID(uint32_t btnid) const { + uint32_t row = (btnid / 100) -1; + uint32_t col = (btnid % 100) -1; + + if(row >= rootConfig.buttons.size()) return nullptr; + + const auto& rowref = rootConfig.buttons.at(row); + + if(col >= rowref.size()) return nullptr; + + return &rowref.at(col); +} diff --git a/src/config_json.cpp b/src/config_json.cpp index 75e0120..99a938d 100644 --- a/src/config_json.cpp +++ b/src/config_json.cpp @@ -57,6 +57,7 @@ void from_json(const json& j, Config::ShortcutConfig& sc) { sc.left = j.value("left", ""); sc.right = j.value("right", ""); sc.play = j.value("play", ""); + sc.search = j.value("search", ""); sc.stop = j.value("stop", ""); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e92b7e3..7b211bf 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4,6 +4,7 @@ #include "buttonmanager.h" #include "editsample.h" #include "settings.h" +#include "buttonsearch.h" #include #include @@ -36,6 +37,7 @@ MainWindow::~MainWindow() { delete left; delete right; delete play; + delete search; Sound::deinitInstance(); } @@ -147,6 +149,19 @@ void MainWindow::openSettings() { } } +void MainWindow::openSearch() { + Log::info << "openSearch"; + + ButtonSearch* bs = new ButtonSearch(config); + int res = bs->exec(); + + if(res >= 0) { + playButtonID(res); + } + + delete bs; +} + void MainWindow::loadSoundFromConfig() { Sound& sound = Sound::instance(); // init sound sound.reset(); @@ -176,6 +191,7 @@ void MainWindow::loadShortcuts() { delete left; delete right; delete play; + delete search; delete stopGlobal; const Config::ShortcutConfig& sc = config.rootConfig.shortcuts; @@ -184,6 +200,7 @@ void MainWindow::loadShortcuts() { left = loadShortcut(sc.left); right = loadShortcut(sc.right); play = loadShortcut(sc.play); + search = loadShortcut(sc.search); stopGlobal = loadShortcut(sc.stop); QObject::connect(up, SIGNAL( activated() ), this, SLOT( moveUp() )); @@ -191,6 +208,7 @@ void MainWindow::loadShortcuts() { QObject::connect(left, SIGNAL( activated() ), this, SLOT( moveLeft() )); QObject::connect(right, SIGNAL( activated() ), this, SLOT( moveRight() )); QObject::connect(play, SIGNAL( activated() ), this, SLOT( playCurrent() )); + QObject::connect(search, SIGNAL( activated() ), this, SLOT( openSearch() )); QObject::connect(stopGlobal, SIGNAL( activated() ), this, SLOT( stop() )); } @@ -213,7 +231,7 @@ void MainWindow::loadButtonsFromConfig() { for (const Config::ButtonConfig& bc : line) { if (!bc.isValid()) continue; - SoundButton* sb = new SoundButton(bc.name, bc.key); + SoundButton* sb = new SoundButton(bc.id, bc.name, bc.key); for(const Config::SampleConfig& sc : bc.samples) { if(!sc.isValid()) continue; @@ -245,6 +263,21 @@ void MainWindow::loadButtonsFromConfig() { // ui->gridLayout->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)); } +void MainWindow::playButtonID(uint32_t buttonid) { + const Config::ButtonConfig* btnconf = config.getButtonByID(buttonid); + if(!btnconf) { + Log::error << "invalid button id triggered: " << buttonid; + return; + } + + SoundButton* sb = findChild(QString::fromStdString("soundButton" + std::to_string(buttonid))); + if(sb == nullptr) { + Log::error << "button not found: " << buttonid; + return; + } + sb->click(); +} + void MainWindow::reselectNext(std::function stepf) { QLayoutItem* item = ui->gridLayout->itemAtPosition(yCoord, xCoord); QLayoutItem* newitem = findNextButton(stepf); diff --git a/src/soundbutton.cpp b/src/soundbutton.cpp index 890ceed..c282c58 100644 --- a/src/soundbutton.cpp +++ b/src/soundbutton.cpp @@ -6,14 +6,12 @@ #include -uint64_t SoundButton::nextid = 0; -SoundButton::SoundButton(const std::string& name_, const std::string& keycombo, QWidget* parent) : QPushButton(parent), id(nextid++), name(name_), keycombo(keycombo) { - QString info = getInfo(); - setText(info); +SoundButton::SoundButton(uint32_t id, const std::string& name_, const std::string& keycombo, QWidget* parent) : QPushButton(parent), id(id), name(name_), keycombo(keycombo) { + setText(getInfo(false)); setObjectName(QString::fromStdString("soundButton" + std::to_string(id))); setMinimumSize(QSize(80, 20)); - setToolTip(info); + setToolTip(getInfo(true)); setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)); @@ -50,6 +48,7 @@ void SoundButton::paintEvent(QPaintEvent* event) { QPushButton::paintEvent(event); QPainter painter(this); + // highlight if(highlighted) { QSize s = size(); QRect rect(HIGHLIGHTBORDEROFFSET, HIGHLIGHTBORDEROFFSET, s.width() - HIGHLIGHTBORDEROFFSET*2, s.height() - HIGHLIGHTBORDEROFFSET*2); @@ -57,14 +56,21 @@ void SoundButton::paintEvent(QPaintEvent* event) { painter.drawRect(rect); } + // sample count if(samples.size() > 1) { - QString text = QString::fromStdString(std::to_string(currentSample+1) + "/" + std::to_string(samples.size())); + const QString text = QString::fromStdString(std::to_string(currentSample+1) + "/" + std::to_string(samples.size())); painter.setPen(QPen(Qt::white)); QPoint textPos(0, painter.fontMetrics().height()); textPos.rx() = width() - painter.fontMetrics().horizontalAdvance(text) - 6; painter.drawText(textPos, text); } + + // button id + const QString idtext = QString::fromStdString(std::to_string(id)); + painter.setPen(QPen(Qt::gray)); + QPoint textPos(6, painter.fontMetrics().height()); + painter.drawText(textPos, idtext); } void SoundButton::play() { @@ -95,6 +101,6 @@ void SoundButton::setDisabled() { setEnabled(false); } -QString SoundButton::getInfo() const { - return QString::fromStdString(name + (keycombo.empty() ? "" : "\n" + keycombo)); +QString SoundButton::getInfo(bool withid) const { + return QString::fromStdString(name + (keycombo.empty() ? "" : "\n" + keycombo) + (withid ? "\nID: " + std::to_string(id) : "")); } \ No newline at end of file diff --git a/ui/buttonsearch.ui b/ui/buttonsearch.ui new file mode 100644 index 0000000..221cc4b --- /dev/null +++ b/ui/buttonsearch.ui @@ -0,0 +1,109 @@ + + + ButtonSearch + + + + 0 + 0 + 400 + 300 + + + + ButtonSearch + + + + + + Close + + + + + + + + 16777215 + 40 + + + + + + + + + + + + + closeButton + clicked() + ButtonSearch + closeSearch() + + + 169 + 29 + + + 398 + 48 + + + + + searchResults + itemDoubleClicked(QListWidgetItem*) + ButtonSearch + closeSearch() + + + 263 + 204 + + + 398 + 198 + + + + + searchResults + itemClicked(QListWidgetItem*) + ButtonSearch + closeSearch() + + + 339 + 227 + + + 397 + 247 + + + + + searchResults + itemActivated(QListWidgetItem*) + ButtonSearch + closeSearch() + + + 358 + 281 + + + 395 + 279 + + + + + + closeSearch() + + diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 645abb5..a8d1a3a 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -125,6 +125,14 @@ Ctrl+R + + + TEST + + + Ctrl+T + +