319 lines
10 KiB
C++
319 lines
10 KiB
C++
/*
|
|
==============================================================================
|
|
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<int>& 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<float> (r.removeFromRight((int)arrowH).getX());
|
|
auto halfH = static_cast<float> (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<FigureButton>(SVGFigures::blanche, width, height, juce::Colours::whitesmoke));
|
|
menu.addCustomItem(4, std::make_unique<FigureButton>(SVGFigures::triolet, width, height, juce::Colours::whitesmoke));
|
|
menu.addCustomItem(2, std::make_unique<FigureButton>(SVGFigures::noire, width, height, juce::Colours::whitesmoke));
|
|
menu.addCustomItem(5, std::make_unique<FigureButton>(SVGFigures::doubles, width, height, juce::Colours::whitesmoke));
|
|
menu.addCustomItem(3, std::make_unique<FigureButton>(SVGFigures::croches, width, height, juce::Colours::whitesmoke));
|
|
menu.addCustomItem(6, std::make_unique<FigureButton>(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);
|
|
} |