/* ============================================================================== Copyright 2022 Nicolas Chambert This program is free software : you can redistribute itand /or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program.If not, see < http://www.gnu.org/licenses/>. ============================================================================== */ /* ============================================================================== Gestion des options figure rythmique et fichier son du tick. ============================================================================== */ #include "OptionsPanel.h" #include "Figures.h" void ClickSounds::init() { // on récupère tout les sous dossiers dans App/Clicks juce::File curDir{ juce::File::getSpecialLocation(juce::File::SpecialLocationType::currentExecutableFile).getParentDirectory() }; juce::File clickDir{ curDir.getChildFile("Clicks")}; auto it = clickDir.findChildFiles(juce::File::findDirectories, false); for (int i = 0; i < it.size(); ++i) { juce::File sub = it[i]; bool highFile = !sub.findChildFiles(juce::File::TypesOfFileToFind::findFiles, false, "High*.wav").isEmpty(); bool lowFile = !sub.findChildFiles(juce::File::TypesOfFileToFind::findFiles, false, "Low*.wav").isEmpty(); if (highFile && lowFile) { // On n'ajoute que si on a bien les fichiers High et Low juce::String path = sub.getFullPathName(); std::string ss = path.toStdString(); sounds.push_back(ss); } } } std::string ClickSounds::getSoundName(int i) const { // Valeur affiché dans la combo, on récupère le nom du sous dossier juce::File path{ sounds[i] }; return path.getFileNameWithoutExtension().toStdString(); } std::string ClickSounds::getSoundPathHigh(int i) const { // Chemin vers le fichier High juce::File path{ sounds[i] }; return path.findChildFiles(juce::File::TypesOfFileToFind::findFiles, false, "High*.wav")[0].getFullPathName().toStdString(); } std::string ClickSounds::getSoundPathLow(int i) const { // Chemin vers le fichier Low juce::File path{ sounds[i] }; return path.findChildFiles(juce::File::TypesOfFileToFind::findFiles, false, "Low*.wav")[0].getFullPathName().toStdString(); } int ClickSounds::getIndex(std::string soundName) const { // récupère l'index du fichier à partir de son nom // Utilisé pour restaurer la configuration // Du coup si le son n'est pas trouvé on retourne le premier son for (int i = 0; i < sounds.size(); ++i) { if (getSoundName(i) == soundName) { return i; } } return 0; } void ComboLookAndFeel::drawPopupMenuBackground(juce::Graphics& g, int width, int height) { // Permet de setter le background du PopupMenu g.fillAll(juce::Colours::whitesmoke); juce::ignoreUnused(width, height); #if ! JUCE_MAC g.setColour(juce::Colours::darkgrey.withAlpha(0.6f)); g.drawRect(0, 0, width, height); #endif } void ComboLookAndFeel::drawPopupMenuItem(juce::Graphics& g, const juce::Rectangle& area, const bool isSeparator, const bool isActive, const bool isHighlighted, const bool isTicked, const bool hasSubMenu, const juce::String& text, const juce::String& shortcutKeyText, const juce::Drawable* icon, const juce::Colour* const textColourToUse) { // Permet de setter le background du PopupMenuItem if (isSeparator) { auto r = area.reduced(5, 0); r.removeFromTop(juce::roundToInt(((float)r.getHeight() * 0.5f) - 0.5f)); g.setColour(juce::Colours::darkgrey.withAlpha(0.3f)); g.fillRect(r.removeFromTop(1)); } else { auto textColour = (textColourToUse == nullptr ? juce::Colours::darkgrey : *textColourToUse); auto r = area.reduced(1); if (isHighlighted && isActive) { g.setColour(juce::Colours::lightgrey); g.fillRect(r); g.setColour(juce::Colours::darkgrey); } else { g.setColour(textColour.withMultipliedAlpha(isActive ? 1.0f : 0.5f)); } r.reduce(juce::jmin(5, area.getWidth() / 20), 0); auto font = getPopupMenuFont(); auto maxFontHeight = (float)r.getHeight() / 1.3f; if (font.getHeight() > maxFontHeight) font.setHeight(maxFontHeight); g.setFont(font); auto iconArea = r.removeFromLeft(juce::roundToInt(maxFontHeight)).toFloat(); if (icon != nullptr) { icon->drawWithin(g, iconArea, juce::RectanglePlacement::centred | juce::RectanglePlacement::onlyReduceInSize, 1.0f); r.removeFromLeft(juce::roundToInt(maxFontHeight * 0.5f)); } else if (isTicked) { auto tick = getTickShape(1.0f); g.fillPath(tick, tick.getTransformToScaleToFit(iconArea.reduced(iconArea.getWidth() / 5, 0).toFloat(), true)); } if (hasSubMenu) { auto arrowH = 0.6f * getPopupMenuFont().getAscent(); auto x = static_cast (r.removeFromRight((int)arrowH).getX()); auto halfH = static_cast (r.getCentreY()); juce::Path path; path.startNewSubPath(x, halfH - arrowH * 0.5f); path.lineTo(x + arrowH * 0.6f, halfH); path.lineTo(x, halfH + arrowH * 0.5f); g.strokePath(path, juce::PathStrokeType(2.0f)); } r.removeFromRight(3); g.drawFittedText(text, r, juce::Justification::centredLeft, 1); if (shortcutKeyText.isNotEmpty()) { auto f2 = font; f2.setHeight(f2.getHeight() * 0.75f); f2.setHorizontalScale(0.95f); g.setFont(f2); g.drawText(shortcutKeyText, r, juce::Justification::centredRight, true); } } } OptionsPanel::OptionsPanel(juce::Colour c, Config& configuration) : backgroundColour(c) { // initialisation de l'interface clef.reset(new ComboLookAndFeel()); addAndMakeVisible(figures); auto doc = juce::XmlDocument::parse(SVGFigures::noire); // on charge la noire par defaut, va être re-initialisé par la configuration auto note = juce::Drawable::createFromSVG(*doc); figures.setImages(note.get()); figures.setToggleState(false, juce::NotificationType::dontSendNotification); figures.setColour(juce::TextButton::buttonColourId, juce::Colours::whitesmoke); figures.setColour(juce::TextButton::textColourOffId, juce::Colours::black); figures.onClick = [&] { // génère le dropdown menu des figures juce::PopupMenu menu; int width = 70; int height = 48; menu.addCustomItem(1, std::make_unique(SVGFigures::blanche, width, height, juce::Colours::whitesmoke)); menu.addCustomItem(4, std::make_unique(SVGFigures::triolet, width, height, juce::Colours::whitesmoke)); menu.addCustomItem(2, std::make_unique(SVGFigures::noire, width, height, juce::Colours::whitesmoke)); menu.addCustomItem(5, std::make_unique(SVGFigures::doubles, width, height, juce::Colours::whitesmoke)); menu.addCustomItem(3, std::make_unique(SVGFigures::croches, width, height, juce::Colours::whitesmoke)); menu.addCustomItem(6, std::make_unique(SVGFigures::swing, width, height, juce::Colours::whitesmoke)); menu.showMenuAsync(juce::PopupMenu::Options{}.withTargetComponent(figures) .withMinimumNumColumns(3), [&](int resultId) { if(resultId > 0) setFigure(resultId - 1); }); }; addAndMakeVisible(sounds); sounds.setLookAndFeel(clef.get()); sounds.setColour(juce::ComboBox::backgroundColourId, juce::Colours::whitesmoke); sounds.setColour(juce::ComboBox::outlineColourId, juce::Colours::darkgrey); sounds.setColour(juce::ComboBox::arrowColourId, juce::Colours::darkgrey); sounds.setColour(juce::ComboBox::textColourId, juce::Colours::darkgrey); clicksounds.init(); // charge la liste des sons curSoundID = clicksounds.getIndex(configuration.sound.toStdString()); // récupère le son courant dans la configuration } juce::String OptionsPanel::getSoundName() { // Récupère le nom du son courant pour sauver la config return clicksounds.getSoundName(sounds.getSelectedId() - 1); } void OptionsPanel::setFigure(int figure) { // MàJ de l'icone de la figure courante juce::String newSVG; switch (figure) { case 0: newSVG = SVGFigures::blanche; break; case 2: newSVG = SVGFigures::croches; break; case 3: newSVG = SVGFigures::triolet; break; case 4: newSVG = SVGFigures::doubles; break; case 5: newSVG = SVGFigures::swing; break; case 1: default: newSVG = SVGFigures::noire; break; } auto doc = juce::XmlDocument::parse(newSVG); auto note = juce::Drawable::createFromSVG(*doc); figures.setImages(note.get()); setMetroFigure(figure); } OptionsPanel::~OptionsPanel() { // evite une fuite mémoire à la fermeture due au L&F sounds.setLookAndFeel(nullptr); figures.setLookAndFeel(nullptr); } void OptionsPanel::paint(juce::Graphics& g) { g.fillAll(backgroundColour); } void OptionsPanel::resized() { // On utilise un layout grille en 2 colonnes int margin = 3; juce::Grid grid; using Track = juce::Grid::TrackInfo; using Fr = juce::Grid::Fr; grid.templateRows = { Track(Fr(1)) }; grid.templateColumns = { Track(Fr(1)), Track(Fr(1)) }; grid.items = { juce::GridItem(figures).withMargin(juce::GridItem::Margin(margin)), juce::GridItem(sounds).withMargin(juce::GridItem::Margin(margin)), }; grid.performLayout(getLocalBounds()); } void OptionsPanel::init(Metronome& metro) { // On établi les liaisons entre le Metronome et les composants graphique for (int i = 0; i < clicksounds.getSoundCount(); ++i) { sounds.addItem(clicksounds.getSoundName(i), i + 1); // La combo n'autorise pas 0 comme Id, d'ou le + 1 } sounds.setSelectedId(curSoundID + 1); // toujours ce + 1 selectSound(metro); // charge le son courant dans le metronome sounds.onChange = [&, this]() { selectSound(metro); }; setMetroFigure = [&](int figure) { metro.setFigure(figure); }; setFigure((int)metro.getFigure()); } void OptionsPanel::selectSound(Metronome& metro) { // charge le son courant dans le metronome std::string high = clicksounds.getSoundPathHigh(sounds.getSelectedId() - 1); std::string low = clicksounds.getSoundPathLow(sounds.getSelectedId() - 1); metro.loadSounds(high, low); }