#include "ConfigurePopover.hpp" namespace { constexpr int TEXT_HEIGHT {16}; juce::Colour stateToLabelColour(bool state) { if (state) { return UIUtils::highlightColour; } return UIUtils::highlightColour.withAlpha(0.5f); } int getHeightForLabelText(juce::String text) { const int numLines = juce::StringArray::fromTokens(text, "\n").size(); return TEXT_HEIGHT * numLines; } void removePathFromSearchPaths(juce::FileSearchPath& searchPaths, juce::String path) { for (int pathNumber {0}; pathNumber < searchPaths.getNumPaths(); pathNumber++) { if (searchPaths[pathNumber].getFullPathName() == path) { searchPaths.remove(pathNumber); return; } } } } CustomPathsList::CustomPathsList(juce::FileSearchPath& customPaths) : _customPaths(customPaths) { rebuild(); } CustomPathsList::~CustomPathsList() { } void CustomPathsList::resized() { juce::Rectangle availableArea = getLocalBounds(); for (auto& label : _customPathsLabels) { label->setBounds(availableArea.removeFromTop(TEXT_HEIGHT)); } } void CustomPathsList::mouseDown(const juce::MouseEvent& event) { if (!event.mods.isRightButtonDown()) { return; } if (auto label = dynamic_cast(event.eventComponent); label != nullptr) { removePathFromSearchPaths(_customPaths, label->getText()); rebuild(); resized(); } } int CustomPathsList::getRequiredHeight() const { return TEXT_HEIGHT * _customPathsLabels.size(); } void CustomPathsList::rebuild() { _customPathsLabels.clear(); for (int pathNumber {0}; pathNumber < _customPaths.getNumPaths(); pathNumber++) { auto thisLabel = std::make_unique( "Path " + juce::String(pathNumber) + " Label", _customPaths[pathNumber].getFullPathName() ); addAndMakeVisible(thisLabel.get()); thisLabel->setFont(juce::Font(15.00f, juce::Font::plain).withTypefaceStyle("Regular")); thisLabel->setJustificationType(juce::Justification::verticallyCentred | juce::Justification::horizontallyCentred); thisLabel->setEditable(false, false, false); thisLabel->setColour(juce::Label::textColourId, UIUtils::highlightColour); thisLabel->addMouseListener(this, false); thisLabel->setTooltip("Right click to remove"); _customPathsLabels.push_back(std::move(thisLabel)); } } FormatConfigureComponent::FormatConfigureComponent( juce::AudioPluginFormat* format, bool& defaultPathsEnabled, juce::FileSearchPath& customPaths) { const juce::String formatName = format->getName(); _defaultPathsListLabel.reset(new juce::Label(formatName + " Default Paths List Label", format->getDefaultLocationsToSearch().toStringWithSeparator("\n"))); addAndMakeVisible(_defaultPathsListLabel.get()); _defaultPathsListLabel->setFont(juce::Font(15.00f, juce::Font::plain).withTypefaceStyle("Regular")); _defaultPathsListLabel->setJustificationType(juce::Justification::verticallyCentred | juce::Justification::horizontallyCentred); _defaultPathsListLabel->setEditable(false, false, false); _defaultPathsListLabel->setColour(juce::Label::textColourId, stateToLabelColour(defaultPathsEnabled)); _defaultPathsButton.reset(new juce::TextButton(formatName + " Default Paths Button")); addAndMakeVisible(_defaultPathsButton.get()); _defaultPathsButton->setButtonText(TRANS("Include " + formatName + " Default Paths")); _defaultPathsButton->setLookAndFeel(&_toggleButtonLookAndFeel); _defaultPathsButton->setColour(UIUtils::ToggleButtonLookAndFeel::backgroundColour, UIUtils::slotBackgroundColour); _defaultPathsButton->setColour(UIUtils::ToggleButtonLookAndFeel::highlightColour, UIUtils::highlightColour); _defaultPathsButton->setColour(UIUtils::ToggleButtonLookAndFeel::disabledColour, UIUtils::deactivatedColour); _defaultPathsButton->setToggleState(defaultPathsEnabled, juce::NotificationType::dontSendNotification); _defaultPathsButton->onClick = [&]() { defaultPathsEnabled = !defaultPathsEnabled; _defaultPathsButton->setToggleState(defaultPathsEnabled, juce::NotificationType::dontSendNotification); _defaultPathsListLabel->setColour(juce::Label::textColourId, stateToLabelColour(defaultPathsEnabled)); }; _customPathsListComponent.reset(new CustomPathsList(customPaths)); addAndMakeVisible(_customPathsListComponent.get()); _customPathsButton.reset(new juce::TextButton(formatName + " Custom Paths Button")); addAndMakeVisible(_customPathsButton.get()); _customPathsButton->setButtonText(TRANS("Add Custom " + formatName + " Paths")); _customPathsButton->setLookAndFeel(&_staticButtonLookAndFeel); _customPathsButton->setColour(UIUtils::StaticButtonLookAndFeel::backgroundColour, UIUtils::slotBackgroundColour); _customPathsButton->setColour(UIUtils::StaticButtonLookAndFeel::highlightColour, UIUtils::highlightColour); _customPathsButton->setColour(UIUtils::StaticButtonLookAndFeel::disabledColour, UIUtils::deactivatedColour); _customPathsButton->onClick = [&, formatName]() { const int flags {juce::FileBrowserComponent::canSelectDirectories | juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectMultipleItems}; _fileChooser.reset(new juce::FileChooser("Add " + formatName + " Directories")); #if _WIN32 // Workaround on windows to stop the file chooser from appearing behind the plugin selector window getTopLevelComponent()->setAlwaysOnTop(false); #endif _fileChooser->launchAsync(flags, [&](const juce::FileChooser& chooser) { for (juce::File selectedDirectory : chooser.getResults()) { customPaths.addIfNotAlreadyThere(selectedDirectory); } customPaths.removeNonExistentPaths(); _customPathsListComponent->rebuild(); resized(); #if _WIN32 // Other half of the workaround // Restore the always on top state of the plugin selector window after the file chooser has closed getTopLevelComponent()->setAlwaysOnTop(true); #endif }); }; } FormatConfigureComponent::~FormatConfigureComponent() { _defaultPathsButton->setLookAndFeel(nullptr); _defaultPathsListLabel->setLookAndFeel(nullptr); _customPathsButton->setLookAndFeel(nullptr); } void FormatConfigureComponent::resized() { constexpr int BUTTON_WIDTH {180}; constexpr int BUTTON_HEIGHT {24}; constexpr int MARGIN {5}; juce::Rectangle availableArea = getLocalBounds(); juce::Rectangle defaultArea = availableArea.removeFromLeft(availableArea.getWidth() / 2).reduced(MARGIN); _defaultPathsButton->setBounds(defaultArea.removeFromTop(BUTTON_HEIGHT).withSizeKeepingCentre(BUTTON_WIDTH, BUTTON_HEIGHT)); const int defaultPathsHeight {getHeightForLabelText(_defaultPathsListLabel->getText())}; defaultArea.removeFromTop(TEXT_HEIGHT); _defaultPathsListLabel->setBounds(defaultArea.removeFromTop(defaultPathsHeight)); availableArea.reduced(MARGIN); _customPathsButton->setBounds(availableArea.removeFromTop(BUTTON_HEIGHT).withSizeKeepingCentre(BUTTON_WIDTH, BUTTON_HEIGHT)); const int customPathsHeight {_customPathsListComponent->getRequiredHeight()}; availableArea.removeFromTop(TEXT_HEIGHT); _customPathsListComponent->setBounds(availableArea.removeFromTop(customPathsHeight)); } ConfigurePopover::ConfigurePopover(ScanConfiguration& config, std::function onCloseCallback) : _onCloseCallback(onCloseCallback) { _vstConfigure.reset(new FormatConfigureComponent( &config.vstFormat, config.VSTDefaultPaths, config.customVSTPaths)); addAndMakeVisible(_vstConfigure.get()); _vst3Configure.reset(new FormatConfigureComponent( &config.vst3Format, config.VST3DefaultPaths, config.customVST3Paths)); addAndMakeVisible(_vst3Configure.get()); _okButton.reset(new juce::TextButton("OK button")); addAndMakeVisible(_okButton.get()); _okButton->setButtonText(TRANS("OK")); _okButton->setLookAndFeel(&_buttonLookAndFeel); _okButton->setColour(UIUtils::StaticButtonLookAndFeel::backgroundColour, UIUtils::slotBackgroundColour); _okButton->setColour(UIUtils::StaticButtonLookAndFeel::highlightColour, UIUtils::highlightColour); _okButton->setColour(UIUtils::StaticButtonLookAndFeel::disabledColour, UIUtils::deactivatedColour); _okButton->onClick = [&]() { _onCloseCallback(); }; _tooltipLabel.reset(new juce::Label("Tooltip Label", "")); addAndMakeVisible(_tooltipLabel.get()); _tooltipLabel->setFont(juce::Font(15.00f, juce::Font::plain).withTypefaceStyle("Regular")); _tooltipLabel->setJustificationType(juce::Justification::centred); _tooltipLabel->setEditable(false, false, false); _tooltipLabel->setColour(juce::Label::textColourId, UIUtils::tooltipColour); // Start tooltip label _vstConfigure->addMouseListener(this, true); _vst3Configure->addMouseListener(this, true); } ConfigurePopover::~ConfigurePopover() { _okButton->setLookAndFeel(nullptr); } void ConfigurePopover::resized() { constexpr int WINDOW_PADDING {20}; constexpr int AREA_PADDING {5}; juce::Rectangle availableArea = getLocalBounds().reduced(WINDOW_PADDING); _tooltipLabel->setBounds(availableArea.removeFromBottom(16)); juce::Rectangle okButtonArea = availableArea.removeFromBottom(availableArea.getHeight() / 4); _okButton->setBounds(okButtonArea.withSizeKeepingCentre(60, 40)); _vstConfigure->setBounds(availableArea.removeFromTop(availableArea.getHeight() / 2).reduced(AREA_PADDING)); _vst3Configure->setBounds(availableArea.reduced(AREA_PADDING)); } void ConfigurePopover::paint(juce::Graphics& g) { g.fillAll(juce::Colours::black.withAlpha(0.8f)); } void ConfigurePopover::mouseEnter(const juce::MouseEvent& event) { juce::TooltipClient* tooltipClient = dynamic_cast(event.eventComponent); if (tooltipClient != nullptr) { const juce::String displayString = tooltipClient->getTooltip(); _tooltipLabel->setText(displayString, juce::dontSendNotification); } } void ConfigurePopover::mouseExit(const juce::MouseEvent& /*event*/) { if (_tooltipLabel != nullptr) { _tooltipLabel->setText("", juce::dontSendNotification); } }