diff --git a/src/common/PatchDB.cpp b/src/common/PatchDB.cpp index 8f3c2ccd9c2..de05cf5e65d 100644 --- a/src/common/PatchDB.cpp +++ b/src/common/PatchDB.cpp @@ -227,9 +227,8 @@ struct TxnGuard struct PatchDB::WriterWorker { - static constexpr const char *schema_version = "11"; // I will rebuild if this is not my version + static constexpr const char *schema_version = "14"; // I will rebuild if this is not my version - // language=SQL static constexpr const char *setup_sql = R"SQL( DROP TABLE IF EXISTS "Patches"; DROP TABLE IF EXISTS "PatchFeature"; @@ -244,6 +243,7 @@ CREATE TABLE "Patches" ( id integer primary key, path varchar(2048), name varchar(256), + search_over varchar(1024), category varchar(2048), category_type int, last_write_time big int @@ -562,6 +562,17 @@ CREATE TABLE IF NOT EXISTS Favorites ( { res.emplace_back("AUTHOR", STRING, 0, meta->Attribute("author")); } + + auto tags = TINYXML_SAFE_TO_ELEMENT(meta->FirstChild("tags")); + if (tags) + { + auto tag = tags->FirstChildElement(); + while (tag) + { + res.emplace_back("TAG", STRING, 0, tag->Attribute("tag")); + tag = tag->NextSiblingElement(); + } + } } auto parameters = TINYXML_SAFE_TO_ELEMENT(patch->FirstChild("parameters")); @@ -801,6 +812,9 @@ CREATE TABLE IF NOT EXISTS Favorites ( return; } + std::ostringstream searchName; + searchName << p.name << " "; + std::ifstream stream(p.path, std::ios::in | std::ios::binary); std::vector contents((std::istreambuf_iterator(stream)), std::istreambuf_iterator()); @@ -862,6 +876,7 @@ CREATE TABLE IF NOT EXISTS Favorites ( auto feat = extractFeaturesFromXML(xml); for (auto f : feat) { + auto ftype = std::get<0>(f); ins.bindi64(1, patchid); ins.bind(2, std::get<0>(f)); ins.bind(3, (int)std::get<1>(f)); @@ -872,6 +887,10 @@ CREATE TABLE IF NOT EXISTS Favorites ( ins.clearBindings(); ins.reset(); + if (ftype == "TAG") + { + searchName << " " << std::get<3>(f); + } } ins.finalize(); @@ -881,6 +900,22 @@ CREATE TABLE IF NOT EXISTS Favorites ( storage->reportError(e.what(), "PatchDB - FXP Features"); return; } + + auto sns = searchName.str(); + try + { + auto ins = SQL::Statement(dbh, "UPDATE PATCHES SET search_over=?1 WHERE id=?2"); + ins.bind(1, sns); + ins.bind(2, patchid); + + ins.step(); + ins.finalize(); + } + catch (const SQL::Exception &e) + { + storage->reportError(e.what(), "PatchDB - FXP Features"); + return; + } } void setFavorite(const std::string &p, bool v) @@ -1420,7 +1455,7 @@ std::string PatchDB::sqlWhereClauseFor(const std::unique_ptrcontent) << "%' )"; + oss << "( p.search_over LIKE '%" << protect(t->content) << "%' )"; break; case PatchDBQueryParser::AND: case PatchDBQueryParser::OR: @@ -1448,7 +1483,7 @@ PatchDB::queryFromQueryString(const std::unique_ptr & // FIXME - cache this by pushing it to the worker std::string query = "select p.id, p.path, p.category as category, p.name, pf.feature_svalue as " - "author from Patches " + "author, p.search_over from Patches " "as p, PatchFeature as pf where pf.patch_id == p.id and pf.feature LIKE " "'AUTHOR' and " + sqlWhereClauseFor(t) + " ORDER BY p.category_type, p.category, p.name"; diff --git a/src/surge-testrunner/UnitTestsQUERY.cpp b/src/surge-testrunner/UnitTestsQUERY.cpp index 14f45bbcbc6..62a68049ed1 100644 --- a/src/surge-testrunner/UnitTestsQUERY.cpp +++ b/src/surge-testrunner/UnitTestsQUERY.cpp @@ -135,27 +135,28 @@ TEST_CASE("SQL Generation", "[query]") { auto t = Surge::PatchStorage::PatchDBQueryParser::parseQuery("init"); auto s = Surge::PatchStorage::PatchDB::sqlWhereClauseFor(t); - REQUIRE(s == "( p.name LIKE '%init%' )"); + REQUIRE(s == "( p.search_over LIKE '%init%' )"); } SECTION("Duple") { auto t = Surge::PatchStorage::PatchDBQueryParser::parseQuery("init sine"); auto s = Surge::PatchStorage::PatchDB::sqlWhereClauseFor(t); - REQUIRE(s == "( ( p.name LIKE '%init%' ) AND ( p.name LIKE '%sine%' ) )"); + REQUIRE(s == "( ( p.search_over LIKE '%init%' ) AND ( p.search_over LIKE '%sine%' ) )"); } SECTION("Single Quote") { auto t = Surge::PatchStorage::PatchDBQueryParser::parseQuery("init 'sine"); auto s = Surge::PatchStorage::PatchDB::sqlWhereClauseFor(t); - REQUIRE(s == "( ( p.name LIKE '%init%' ) AND ( p.name LIKE '%''sine%' ) )"); + REQUIRE(s == "( ( p.search_over LIKE '%init%' ) AND ( p.search_over LIKE '%''sine%' ) )"); } SECTION("Lots of Single Quotes Quote") { auto t = Surge::PatchStorage::PatchDBQueryParser::parseQuery("in'it' ''sine"); auto s = Surge::PatchStorage::PatchDB::sqlWhereClauseFor(t); - REQUIRE(s == "( ( p.name LIKE '%in''it''%' ) AND ( p.name LIKE '%''''sine%' ) )"); + REQUIRE(s == + "( ( p.search_over LIKE '%in''it''%' ) AND ( p.search_over LIKE '%''''sine%' ) )"); } } \ No newline at end of file diff --git a/src/surge-xt/gui/SurgeGUIEditorOverlays.cpp b/src/surge-xt/gui/SurgeGUIEditorOverlays.cpp index 49975675df1..760ef6d72fd 100644 --- a/src/surge-xt/gui/SurgeGUIEditorOverlays.cpp +++ b/src/surge-xt/gui/SurgeGUIEditorOverlays.cpp @@ -97,6 +97,7 @@ std::unique_ptr SurgeGUIEditor::makeStorePatc // since it is now modal center in the window auto posRect = skinCtrl->getRect().withCentre(frame->getBounds().getCentre()); + pb->setEnclosingParentTitle("Save Patch"); pb->setEnclosingParentPosition(posRect); pb->setHasIndependentClose(false); @@ -405,6 +406,7 @@ std::unique_ptr SurgeGUIEditor::createOverlay return nullptr; } + void SurgeGUIEditor::showOverlay(OverlayTags olt, std::function setup) { @@ -416,13 +418,13 @@ void SurgeGUIEditor::showOverlay(OverlayTags olt, return; } + setup(ol.get()); + // copy these before the std::move below auto t = ol->getEnclosingParentTitle(); auto r = ol->getEnclosingParentPosition(); auto c = ol->getHasIndependentClose(); - setup(ol.get()); - std::function onClose = []() {}; bool isModal = false; @@ -463,6 +465,7 @@ void SurgeGUIEditor::showOverlay(OverlayTags olt, default: break; } + addJuceEditorOverlay(std::move(ol), t, olt, r, c, onClose, isModal); switch (olt) @@ -492,6 +495,7 @@ void SurgeGUIEditor::showOverlay(OverlayTags olt, getOverlayIfOpen(olt)->grabKeyboardFocus(); } + void SurgeGUIEditor::closeOverlay(OverlayTags olt) { auto olw = getOverlayWrapperIfOpen(olt); diff --git a/src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp b/src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp index 7f49f0cda6e..6fe38b39aa3 100644 --- a/src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp +++ b/src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp @@ -36,11 +36,12 @@ #include "widgets/Switch.h" #include "widgets/PatchSelector.h" #include "widgets/ParameterInfowindow.h" +#include "widgets/WaveShaperSelector.h" #include "widgets/XMLConfiguredMenus.h" -#include "overlays/TypeinParamEditor.h" -#include "widgets/WaveShaperSelector.h" #include "overlays/ModulationEditor.h" +#include "overlays/PatchStoreDialog.h" +#include "overlays/TypeinParamEditor.h" std::string decodeControllerID(int id) { @@ -3478,7 +3479,20 @@ void SurgeGUIEditor::valueChanged(Surge::GUI::IComponentTagValue *control) break; case tag_store: { - showOverlay(SAVE_PATCH); + bool showTags = juce::ModifierKeys::currentModifiers.isShiftDown() || + juce::ModifierKeys::currentModifiers.isAltDown(); + + showOverlay(SurgeGUIEditor::SAVE_PATCH, + [this, showTags](Surge::Overlays::OverlayComponent *co) { + auto psd = dynamic_cast(co); + + if (!psd) + { + return; + } + + psd->setShowTagsField(showTags); + }); } break; diff --git a/src/surge-xt/gui/overlays/PatchStoreDialog.cpp b/src/surge-xt/gui/overlays/PatchStoreDialog.cpp index ad4a520ba5e..12eb229b09e 100644 --- a/src/surge-xt/gui/overlays/PatchStoreDialog.cpp +++ b/src/surge-xt/gui/overlays/PatchStoreDialog.cpp @@ -38,9 +38,11 @@ struct PatchStoreDialogCategoryProvider : public Surge::Widgets::TypeAheadDataPr { std::vector res; std::map alreadySeen; + if (storage) { int idx = 0; + for (auto &c : storage->patch_category) { if (!c.isFactory || (idx < storage->firstThirdPartyCategory && @@ -49,6 +51,7 @@ struct PatchStoreDialogCategoryProvider : public Surge::Widgets::TypeAheadDataPr auto it = std::search( c.name.begin(), c.name.end(), s.begin(), s.end(), [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }); + if (it != c.name.end()) { // De-duplicate if factory and user are both there. @@ -70,6 +73,7 @@ struct PatchStoreDialogCategoryProvider : public Surge::Widgets::TypeAheadDataPr } } } + res.push_back(idx); alreadySeen[c.name] = idx; } @@ -77,6 +81,7 @@ struct PatchStoreDialogCategoryProvider : public Surge::Widgets::TypeAheadDataPr idx++; } } + // Now sort that res std::sort(res.begin(), res.end(), [this](const auto &a, const auto &b) { const auto pa = storage->patch_category[a]; @@ -92,11 +97,13 @@ struct PatchStoreDialogCategoryProvider : public Surge::Widgets::TypeAheadDataPr return pb.isFactory; } }); + return res; } juce::Font font{12}; juce::Colour hl, hlbg, txt, bg; + void paintDataItem(int searchIndex, juce::Graphics &g, int width, int height, bool rowIsSelected) override { @@ -110,6 +117,7 @@ struct PatchStoreDialogCategoryProvider : public Surge::Widgets::TypeAheadDataPr g.fillAll(bg); g.setColour(txt); } + g.setFont(font); g.drawText(textBoxValueForIndex(searchIndex), 4, 0, width - 4, height, juce::Justification::centredLeft); @@ -121,6 +129,7 @@ struct PatchStoreDialogCategoryProvider : public Surge::Widgets::TypeAheadDataPr { return storage->patch_category[idx].name; } + return ""; } }; @@ -145,16 +154,13 @@ PatchStoreDialog::PatchStoreDialog() commentEd = makeEd("patch comment"); commentEd->setMultiLine(true, true); commentEd->setReturnKeyStartsNewLine(true); - commentEd->setTitle("patch comment"); commentEd->setJustification(juce::Justification::topLeft); - Surge::Widgets::fixupJuceTextEditorAccessibility(*commentEd); - -#if HAS_TAGS_FIELD tagEd = makeEd("patch tags"); -#endif + tagEd->setVisible(showTagsField); categoryProvider = std::make_unique(); auto ta = std::make_unique("patch category", categoryProvider.get()); + ta->setJustification(juce::Justification::centredLeft); ta->setSelectAllWhenFocused(true); ta->setToElementZeroOnReturn = true; @@ -168,11 +174,10 @@ PatchStoreDialog::PatchStoreDialog() addAndMakeVisible(*lb); return std::move(lb); }; + nameEdL = makeL("Name"); authorEdL = makeL("Author"); -#if HAS_TAGS_FIELD tagEdL = makeL("Tags"); -#endif catEdL = makeL("Category"); commentEdL = makeL("Comment"); @@ -226,6 +231,7 @@ void PatchStoreDialog::paint(juce::Graphics &g) void PatchStoreDialog::setSurgeGUIEditor(SurgeGUIEditor *e) { editor = e; + if (!editor || !editor->synth->storage.datapathOverriden) { okOverButton->setVisible(false); @@ -236,7 +242,9 @@ void PatchStoreDialog::setSurgeGUIEditor(SurgeGUIEditor *e) void PatchStoreDialog::shownInParent() { if (nameEd->isShowing() && isVisible()) + { nameEd->grabKeyboardFocus(); + } } void PatchStoreDialog::onSkinChanged() @@ -265,19 +273,16 @@ void PatchStoreDialog::onSkinChanged() resetColors(nameEd); resetColors(authorEd); resetColors(catEd); + resetColors(tagEd); resetColors(commentEd); resetLabel(nameEdL); resetLabel(authorEdL); resetLabel(catEdL); + resetLabel(tagEdL); resetLabel(commentEdL); resetLabel(storeTuningLabel); -#if HAS_TAGS_FIELD - resetLabel(tagEdL); - resetColors(tagEd); -#endif - storeTuningButton->setColour(juce::ToggleButton::tickDisabledColourId, skin->getColor(Colors::Dialog::Checkbox::Border)); storeTuningButton->setColour(juce::ToggleButton::tickColourId, @@ -304,7 +309,7 @@ void PatchStoreDialog::setIsRename(bool b) { isRename = b; } void PatchStoreDialog::resized() { auto h = 25; - auto commH = getHeight() - 5 * h + 8; + auto commH = getHeight() - (5 + showTagsField) * h + 8; auto xSplit = 70; auto buttonWidth = 50; auto margin = 4; @@ -329,11 +334,12 @@ void PatchStoreDialog::resized() nameEd->grabKeyboardFocus(); } -#if HAS_TAGS_FIELD - ce = ce.translated(0, h); - tagEd->setBounds(ce); - tagEd->setIndents(4, (tagEd->getHeight() - tagEd->getTextHeight()) / 2); -#endif + if (showTagsField) + { + ce = ce.translated(0, h); + tagEd->setBounds(ce); + tagEd->setIndents(4, (tagEd->getHeight() - tagEd->getTextHeight()) / 2); + } ce = ce.translated(0, h); @@ -359,10 +365,11 @@ void PatchStoreDialog::resized() cl = cl.translated(0, h); authorEdL->setBounds(cl); -#if HAS_TAGS_FIELD - cl = cl.translated(0, h); - tagEdL->setBounds(cl); -#endif + if (showTagsField) + { + cl = cl.translated(0, h); + tagEdL->setBounds(cl); + } cl = cl.translated(0, h); commentEdL->setBounds(cl); @@ -401,21 +408,25 @@ void PatchStoreDialog::buttonClicked(juce::Button *button) synth->storage.getPatch().category = catEd->getText().toStdString(); synth->storage.getPatch().comment = commentEd->getText().toStdString(); -#if HAS_TAGS_FIELD auto tagString = tagEd->getText(); std::vector tags; + while (tagString.contains(",")) { auto ltag = tagString.upToFirstOccurrenceOf(",", false, true); + tagString = tagString.fromFirstOccurrenceOf(",", false, true); tags.emplace_back(ltag.trim().toStdString()); } + tagString = tagString.trim(); + if (tagString.length()) + { tags.emplace_back(tagString.toStdString()); + } synth->storage.getPatch().tags = tags; -#endif synth->storage.getPatch().patchTuning.tuningStoredInPatch = storeTuningButton->isVisible() && storeTuningButton->getToggleState(); @@ -464,10 +475,9 @@ void PatchStoreDialog::buttonClicked(juce::Button *button) void PatchStoreDialog::setTags(const std::vector &itags) { -#if HAS_TAGS_FIELD - std::string pfx = ""; std::ostringstream oss; + for (const auto &t : itags) { oss << pfx << t.tag; @@ -475,7 +485,6 @@ void PatchStoreDialog::setTags(const std::vector &itags) } tagEd->setText(oss.str(), juce::NotificationType::dontSendNotification); -#endif } } // namespace Overlays diff --git a/src/surge-xt/gui/overlays/PatchStoreDialog.h b/src/surge-xt/gui/overlays/PatchStoreDialog.h index dd9d0c51b02..06530693436 100644 --- a/src/surge-xt/gui/overlays/PatchStoreDialog.h +++ b/src/surge-xt/gui/overlays/PatchStoreDialog.h @@ -51,6 +51,25 @@ struct PatchStoreDialog : public OverlayComponent, SurgeStorage *storage{nullptr}; void setStorage(SurgeStorage *s); + bool showTagsField{false}; + void setShowTagsField(bool s) + { + if (s && !showTagsField) + { + auto pos = getEnclosingParentPosition(); + pos = pos.withHeight(pos.getHeight() + 25); + setEnclosingParentPosition(pos); + } + if (!s && showTagsField) + { + auto pos = getEnclosingParentPosition(); + pos = pos.withHeight(pos.getHeight() - 25); + setEnclosingParentPosition(pos); + } + showTagsField = s; + tagEd->setVisible(s); + }; + void setName(const std::string &n) { nameEd->setText(n, juce::dontSendNotification); } void setAuthor(const std::string &a) { authorEd->setText(a, juce::dontSendNotification); } void setCategory(const std::string &c) { catEd->setText(c, juce::dontSendNotification); } diff --git a/src/surge-xt/gui/widgets/PatchSelector.cpp b/src/surge-xt/gui/widgets/PatchSelector.cpp index 325a72445aa..9d85d23a2b4 100644 --- a/src/surge-xt/gui/widgets/PatchSelector.cpp +++ b/src/surge-xt/gui/widgets/PatchSelector.cpp @@ -1374,7 +1374,6 @@ void PatchSelectorCommentTooltip::paint(juce::Graphics &g) g.fillRect(getLocalBounds().reduced(1)); g.setColour(skin->getColor(clr::Text)); g.setFont(skin->fontManager->getLatoAtSize(9)); - printf("Popup width: %d\n", getWidth()); g.drawMultiLineText(comment, 5, g.getCurrentFont().getHeight() + 2, getWidth(), juce::Justification::left); }