diff --git a/Documentation/Keybindings.md b/Documentation/Keybindings.md new file mode 100644 index 00000000..6543c6f3 --- /dev/null +++ b/Documentation/Keybindings.md @@ -0,0 +1,12 @@ +# Dexed key bindings + +| Key bindings | Actions | +| ---------------------------- | ------------------------------------------ | +| CTRL+1 to CTRL+6 | Focus on operator 'X' group | +| CTRL+SHIFT+1 TO CTRL+SHIFT+6 | Toggle active operator 'X' | +| CTRL+G | Focus on global group | +| CTRL+L | Open cartridge manager | +| CTRL+P | Open parameter dialog | +| CTRL+C | Copy current context on clipboard as hexa | +| CTRL+V | Paste context from clipboard content | +| SHIFT+UP or SHIFT+DOWN | On sliders, it steps 10 % | \ No newline at end of file diff --git a/README.md b/README.md index 7c950522..a36701a7 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,13 @@ the original machine. Dexed is licensed on the GPL v3. The msfa component (acronym for music synthesizer for android, see msfa in the source folder) stays on the Apache 2.0 license to able to collaborate between projects. +Donation +-------- +As a maintainer of this 10 year old project, donations are welcomed. This also applies for the Apple users +to cover for the notarization of this software in the comming years. Thank you! + +https://www.paypal.com/paypalme/asb2m10 + Dexed Forks ----------- * [MiniDexed](https://github.com/probonopd/MiniDexed) Run a DX7 bare metal from a Raspberry Pi @@ -22,16 +29,21 @@ Dexed Forks Changelog --------- +#### Version 0.9.8 (in development) +* UI Refresh +* Accessibility implementation (including keyboard shortcuts) +* Mono/Poly parameter is now a plugin parameter +* Fix Apple Logic startup issue + #### Version 0.9.7 * [MTS-ESP](https://oddsound.com/index.php) microtuning support * [CLAP](https://github.com/free-audio/clap) plugin support (sadly scaling is not available for now, but we are working on this) * Scalable UI upgrade (better resolution), optimized UI redraw * More accurate VU meter. Thanks @FulopNandor -* Releases are now notarized for mac OS +* Releases are now notarized for macOS * Fix for VST3 automation (again) * For developers: cmake is now the built system - #### Version 0.9.6 * Apple Silicon M1 builds * Fix VST3 automation issues diff --git a/Resources/Installers/Windows/dexed.iss b/Resources/Installers/Windows/dexed.iss index f623b755..3f7c2e53 100644 --- a/Resources/Installers/Windows/dexed.iss +++ b/Resources/Installers/Windows/dexed.iss @@ -3,7 +3,7 @@ AppName=Dexed AppVersion=0.9.7 DefaultDirName={commonpf64}\Dexed DefaultGroupName=Dexed -Compression=lzma2 +Compression=zip SolidCompression=yes OutputDir=.\ OutputBaseFilename=DexedInstaller @@ -23,7 +23,7 @@ Name: "clap"; Description: "64-bit CLAP Plugin"; Types: full custom; [Files] Source: "Dexed.exe"; DestDir: "{app}"; Components:app; Flags: ignoreversion recursesubdirs createallsubdirs -Source: "Dexed.vst3"; DestDir: "{commoncf64}\VST3"; Components:vst3_64; Flags: ignoreversion +Source: "Dexed.vst3"; DestDir: "{commoncf64}\VST3"; Components:vst3_64; Flags: ignoreversion Source: "Dexed.clap"; DestDir: "{commoncf64}\CLAP"; Components:clap; Flags: ignoreversion [Icons] diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index db1ae900..a8313d1a 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -21,7 +21,7 @@ juce_add_plugin("${BaseTargetName}" NEEDS_MIDI_INPUT TRUE NEEDS_MIDI_OUTPUT TRUE IS_MIDI_EFFECT FALSE - EDITOR_WANTS_KEYBOARD_FOCUS FALSE + EDITOR_WANTS_KEYBOARD_FOCUS TRUE PLUGIN_MANUFACTURER_CODE DGSB PLUGIN_CODE Dexd FORMATS ${DEXED_JUCE_FORMATS} diff --git a/Source/CartManager.cpp b/Source/CartManager.cpp index bfd4a5d2..779026e7 100644 --- a/Source/CartManager.cpp +++ b/Source/CartManager.cpp @@ -71,15 +71,79 @@ public : } }; +/** + * This is a hack to override global dexed focus traversal when the cart manager is shown. We could have used a + * modal window but this would have blocked the keys and the user would not have been able to play the current + * selected preset. Anything that is keyboard related should be ordered in the focusOrder vector. + */ +class CartBrowserFocusTraverser : public KeyboardFocusTraverser { + std::vector &orders; + Component *root; +public: + CartBrowserFocusTraverser(Component *root, std::vector &orders) : orders(orders), root(root) {} + + Component* getDefaultComponent(Component* parentComponent) override { + return orders[0]; + } + + Component* getNextComponent(Component* current) override { + bool srcFound = false; + int i; + + for (i=0;i(orders[i]); + if ( label != nullptr && !label->isActive() ) + continue; + break; + } + } + + if ( i == orders.size() ) + return orders.front(); + return orders[i]; + } + + Component* getPreviousComponent(Component* current) override { + bool srcFound = false; + int i=0; + + for(i=orders.size()-1;i>=0;i--) { + if ( orders[i] == current ) { + srcFound = true; + continue; + } + if ( srcFound ) { + ProgramLabel *label = dynamic_cast(orders[i]); + if ( label != nullptr && !label->isActive() ) + continue; + break; + } + } + if ( i == -1 ) + return orders.back(); + return orders[i]; + } + + std::vector getAllComponents(Component* parentComponent) override { + return orders; + } +}; + CartManager::CartManager(DexedAudioProcessorEditor *editor) : Component("CartManager") { mainWindow = editor; cartDir = DexedAudioProcessor::dexedCartDir; - activeCart.reset(new ProgramListBox("activepgm", 8)); + activeCart.reset(new ProgramListBox("Active Programs Selector", 8)); addAndMakeVisible(activeCart.get()); activeCart->addListener(this); - browserCart.reset(new ProgramListBox("browserpgm", 2)); + browserCart.reset(new ProgramListBox("Browser Programs Selector", 2)); addAndMakeVisible(browserCart.get()); browserCart->addListener(this); @@ -94,6 +158,7 @@ CartManager::CartManager(DexedAudioProcessorEditor *editor) : Component("CartMan cartBrowser->addKeyListener(this); addAndMakeVisible(cartBrowser.get()); + cartBrowser->setTitle("Cartridge file browser"); cartBrowser->setDragAndDropDescription("Sysex Browser"); cartBrowser->addListener(this); @@ -116,6 +181,20 @@ CartManager::CartManager(DexedAudioProcessorEditor *editor) : Component("CartMan activeCartName.reset(new CartridgeFileDisplay()); addAndMakeVisible(activeCartName.get()); + focusOrder.push_back(cartBrowser.get()); + //focusOrder.push_back(browserCart.get()); + for(int i=0;i<32;i++) { + focusOrder.push_back(browserCart->getProgramComponent(i)); + } + //focusOrder.push_back(activeCart.get()); + for(int i=0;i<32;i++) { + focusOrder.push_back(activeCart->getProgramComponent(i)); + } + focusOrder.push_back(closeButton.get()); + focusOrder.push_back(loadButton.get()); + focusOrder.push_back(saveButton.get()); + focusOrder.push_back(fileMgrButton.get()); + /* * * I've removed this since it only works on the DX7 II. TBC. @@ -136,12 +215,16 @@ CartManager::~CartManager() { cartBrowserList.reset(NULL); } +std::unique_ptr CartManager::createKeyboardFocusTraverser() { + return std::make_unique(this, focusOrder); +} + void CartManager::resized() { - float activeSize = ((float) getWidth() - 30) / 8; + float activeSize = 100; - activeCart->setBounds(15, 402, activeSize * 8, 96); - browserCart->setBounds(activeSize * 6 + 15, 10, activeSize * 2, 384); - cartBrowser->setBounds(15, 10, activeSize * 6 - 1, 383); + activeCart->setBounds(14, 402, activeSize * 8, 96); + browserCart->setBounds(activeSize * 6 + 15, 10, activeSize * 2, 385); + cartBrowser->setBounds(14, 10, activeSize * 6 - 4, 385); closeButton->setBounds(4, getHeight() - 40, 70, 30); saveButton->setBounds(144, getHeight() - 40, 70, 30); loadButton->setBounds(74, getHeight() - 40, 70, 30); @@ -159,14 +242,14 @@ void CartManager::updateCartFilename() { void CartManager::programSelected(ProgramListBox *source, int pos) { if ( source == activeCart.get() ) { - browserCart->setSelected(-1); + browserCart->setActive(-1); mainWindow->processor->setCurrentProgram(pos); mainWindow->processor->updateHostDisplay(); } else { uint8_t unpackPgm[161]; source->getCurrentCart().unpackProgram(unpackPgm, pos); - activeCart->setSelected(-1); - browserCart->setSelected(pos); + activeCart->setActive(-1); + browserCart->setActive(pos); repaint(); mainWindow->processor->updateProgramFromSysex((uint8_t *) unpackPgm); mainWindow->processor->updateHostDisplay(); @@ -175,8 +258,7 @@ void CartManager::programSelected(ProgramListBox *source, int pos) { void CartManager::buttonClicked(juce::Button *buttonThatWasClicked) { if ( buttonThatWasClicked == closeButton.get() ) { - mainWindow->startTimer(100); - getParentComponent()->setVisible(false); + hideCartridgeManager(); return; } @@ -257,8 +339,8 @@ void CartManager::fileClicked(const File& file, const MouseEvent& e) { void CartManager::setActiveProgram(int idx, String activeName) { if ( activeCart->programNames[idx] == activeName ) { - activeCart->setSelected(idx); - browserCart->setSelected(-1); + activeCart->setActive(idx); + browserCart->setActive(-1); } activeCart->repaint(); } @@ -288,7 +370,7 @@ void CartManager::selectionChanged() { } else { browserCart->readOnly = false; } - browserCart->setSelected(-1); + browserCart->setActive(-1); browserCart->setCartridge(browserSysex); } @@ -322,7 +404,6 @@ void CartManager::programRightClicked(ProgramListBox *source, int pos) { mainWindow->processor->sendCurrentSysexCartridge(); break; } - } void CartManager::programDragged(ProgramListBox *destListBox, int dest, char *packedPgm) { @@ -343,7 +424,7 @@ void CartManager::programDragged(ProgramListBox *destListBox, int dest, char *pa Cartridge cart; cart.load(file); - memcpy(cart.getRawVoice()+(dest*128), packedPgm, 128); + cart.replaceProgram(dest, packedPgm);; cart.saveVoice(file); browserCart->setCartridge(cart); } @@ -353,8 +434,13 @@ void CartManager::initialFocus() { cartBrowser->grabKeyboardFocus(); } +void CartManager::hideCartridgeManager() { + mainWindow->startTimer(100); + getParentComponent()->setVisible(false); +} + bool CartManager::keyPressed(const KeyPress& key, Component* originatingComponent) { - if ( key.getKeyCode() == 13 ) { + if ( key.getKeyCode() == KeyPress::returnKey ) { File file = cartBrowser->getSelectedFile(); if ( file.isDirectory() ) return true; @@ -373,5 +459,3 @@ void CartManager::showSysexConfigMsg() { // unused stuff from FileBrowserListener void CartManager::browserRootChanged (const File& newRoot) {} - - diff --git a/Source/CartManager.h b/Source/CartManager.h index 39861c3e..f90b5728 100644 --- a/Source/CartManager.h +++ b/Source/CartManager.h @@ -1,6 +1,6 @@ /** * - * Copyright (c) 2015 Pascal Gauthier. + * Copyright (c) 2015-2024 Pascal Gauthier. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -83,6 +83,8 @@ class CartManager : public Component, public Button::Listener, public DragAndDr void showSysexConfigMsg(); + std::vector focusOrder; + public: CartManager(DexedAudioProcessorEditor *editor); virtual ~CartManager(); @@ -107,6 +109,9 @@ class CartManager : public Component, public Button::Listener, public DragAndDr virtual bool keyPressed(const KeyPress& key, Component* originatingComponent) override; void initialFocus(); + void hideCartridgeManager(); + + std::unique_ptr< ComponentTraverser> createKeyboardFocusTraverser() override; }; #endif // CARTMANAGER_H_INCLUDED diff --git a/Source/DXComponents.cpp b/Source/DXComponents.cpp index 71f2d411..8fa107a0 100644 --- a/Source/DXComponents.cpp +++ b/Source/DXComponents.cpp @@ -344,7 +344,7 @@ void LcdDisplay::setSystemMsg(String msg) { void LcdDisplay::paint(Graphics &g) { g.setColour (Colours::white); - g.drawText (paramMsg, + g.drawFittedText(paramMsg, 0, 0, 140, 14, Justification::centred, false); } diff --git a/Source/DXComponents.h b/Source/DXComponents.h index b503104c..49ca8e7c 100644 --- a/Source/DXComponents.h +++ b/Source/DXComponents.h @@ -1,6 +1,6 @@ /** * - * Copyright (c) 2014 Pascal Gauthier. + * Copyright (c) 2014-2024 Pascal Gauthier. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,65 @@ #include "../JuceLibraryCode/JuceHeader.h" #include +//============================================================================== +// THIS IS A TEMPORY FIX FOR COMBOBOX ACCESSIBILITY +// SEE: https://forum.juce.com/t/bug-combo-box-accessibility-bug/62501/2 +// WILL BE REMOVED ONCE WE UPDATE TO JUCE 8 +class ComboBoxAccessibilityHandlerFix final : public AccessibilityHandler +{ +public: + explicit ComboBoxAccessibilityHandlerFix (ComboBox& comboBoxToWrap) + : AccessibilityHandler (comboBoxToWrap, + AccessibilityRole::comboBox, + getAccessibilityActions (comboBoxToWrap), + { std::make_unique (comboBoxToWrap) }), + comboBox (comboBoxToWrap) + { + } + + AccessibleState getCurrentState() const override + { + auto state = AccessibilityHandler::getCurrentState().withExpandable(); + + return comboBox.isPopupActive() ? state.withExpanded() : state.withCollapsed(); + } + + String getTitle() const override { return comboBox.getTitle(); } // THE FIX IS RIGHT HERE + String getHelp() const override { return comboBox.getTooltip(); } + +private: + class ComboBoxValueInterface final : public AccessibilityTextValueInterface + { + public: + explicit ComboBoxValueInterface (ComboBox& comboBoxToWrap) + : comboBox (comboBoxToWrap) + { + } + + bool isReadOnly() const override { return true; } + String getCurrentValueAsString() const override { return comboBox.getText(); } + void setValueAsString (const String&) override {} + + private: + ComboBox& comboBox; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBoxValueInterface) + }; + + static AccessibilityActions getAccessibilityActions (ComboBox& comboBox) + { + return AccessibilityActions().addAction (AccessibilityActionType::press, [&comboBox] { comboBox.showPopup(); }) + .addAction (AccessibilityActionType::showMenu, [&comboBox] { comboBox.showPopup(); }); + } + + ComboBox& comboBox; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBoxAccessibilityHandlerFix) +}; +//============================================================================== + class EnvDisplay : public Component { public: EnvDisplay(); @@ -40,13 +99,7 @@ class PitchEnvDisplay : public Component { char vPos; void paint(Graphics &g); }; -/* -class VuMeter: public Component { - void paint(Graphics &g); -public : - float v; -}; -*/ + class LcdDisplay : public Component { public: LcdDisplay(); @@ -67,17 +120,77 @@ class ComboBoxImage : public ComboBox { virtual void showPopup() override; void setImage(Image image); void setImage(Image image, int pos[]); + + std::unique_ptr createAccessibilityHandler() override { + return std::make_unique (*this); + } + }; class ProgramSelector : public ComboBox { + float accum_wheel; public: + ProgramSelector() { + setWantsKeyboardFocus(true); + setTitle("Program selector"); + } virtual void mouseDown(const MouseEvent &event) override; virtual void mouseEnter(const MouseEvent &event) override { accum_wheel = 0; } virtual void mouseWheelMove(const MouseEvent &event, const MouseWheelDetails &wheel) override; virtual void paint(Graphics &g) override; -private: - float accum_wheel; + std::unique_ptr createAccessibilityHandler() override { + return std::make_unique (*this); + } +}; + +// Simple Slider to make 10 % jumps when shift is pressed +class DXSlider : public Slider { +public: + DXSlider(const String& componentName) : Slider(componentName) { + setWantsKeyboardFocus(true); + } + + bool keyPressed(const KeyPress &key) override { + if ( key.getModifiers().isShiftDown() ) { + float len = getRange().getLength() * 0.10; + if (key.getKeyCode() == key.upKey) { + setValue(getValue() + len); + return true; + } + if (key.getKeyCode() == key.downKey) { + setValue(getValue() - len); + return true; + } + } else { + return Slider::keyPressed(key); + } + return false; + } +}; + +class FocusLogger final : public juce::FocusChangeListener +{ +public: + FocusLogger () + { + juce::Desktop::getInstance ().addFocusChangeListener (this); + } + + ~FocusLogger () override + { + juce::Desktop::getInstance ().removeFocusChangeListener (this); + } + + void globalFocusChanged (juce::Component * focusedComponent) override + { + if (focusedComponent == nullptr) + return; + + DBG ("Component title: " << focusedComponent->getTitle ()); + DBG ("Component type: " << typeid (*focusedComponent).name ()); + DBG ("---"); + } }; #endif // DXCOMPONENTS_H_INCLUDED diff --git a/Source/DXLookNFeel.cpp b/Source/DXLookNFeel.cpp index ae2ba73b..ecf7982d 100644 --- a/Source/DXLookNFeel.cpp +++ b/Source/DXLookNFeel.cpp @@ -1,6 +1,6 @@ /** * - * Copyright (c) 2013-2018 Pascal Gauthier. + * Copyright (c) 2013-2024 Pascal Gauthier. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -114,6 +114,8 @@ DXLookNFeel::DXLookNFeel() { REG_COLOUR(TreeView::backgroundColourId, background); REG_COLOUR(DirectoryContentsDisplayComponent::highlightColourId, fillColour); REG_COLOUR(DirectoryContentsDisplayComponent::textColourId, Colours::white); + REG_COLOUR(DialogWindow::backgroundColourId, background); + REG_COLOUR(ListBox::backgroundColourId, ctrlBackground); // Register ``Scrollbar::thumbColourId`` to allow its redefinion in ``DexedTheme.xml``. REG_COLOUR(ScrollBar::thumbColourId, background.darker()); @@ -274,7 +276,7 @@ Typeface::Ptr DXLookNFeel::getTypefaceForFont(const Font &) { void DXLookNFeel::drawRotarySlider( Graphics &g, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, Slider &slider ) { if ( imageKnob.isNull() ) { - LookAndFeel_V3::drawRotarySlider(g, x, y, width, height, sliderPosProportional, rotaryStartAngle, rotaryEndAngle, slider); + LookAndFeel_V4::drawRotarySlider(g, x, y, width, height, sliderPosProportional, rotaryStartAngle, rotaryEndAngle, slider); return; } @@ -294,7 +296,7 @@ void DXLookNFeel::drawRotarySlider( Graphics &g, int x, int y, int width, int he void DXLookNFeel::drawToggleButton(Graphics& g, ToggleButton& button, bool isMouseOverButton, bool isButtonDown) { if ( imageSwitch.isNull() ) { - LookAndFeel_V3::drawToggleButton(g, button, isMouseOverButton, isButtonDown); + LookAndFeel_V4::drawToggleButton(g, button, isMouseOverButton, isButtonDown); return; } @@ -303,7 +305,7 @@ void DXLookNFeel::drawToggleButton(Graphics& g, ToggleButton& button, bool isMou if( lb ) { if( imageSwitchLighted.isNull() ) { - LookAndFeel_V3::drawToggleButton(g, button, isMouseOverButton, isButtonDown); + LookAndFeel_V4::drawToggleButton(g, button, isMouseOverButton, isButtonDown); return; } g.drawImage(imageSwitchLighted, 0, 0, 48, 26, 0, button.getToggleState() ? 0 : 26, 48, 26); @@ -315,36 +317,29 @@ void DXLookNFeel::drawToggleButton(Graphics& g, ToggleButton& button, bool isMou void DXLookNFeel::drawButtonBackground(Graphics &g, Button &button, const Colour& backgroundColour, bool isMouseOverButton, bool isButtonDown) { if ( imageButton.isNull() ) { - LookAndFeel_V3::drawButtonBackground(g, button, backgroundColour, isMouseOverButton, isButtonDown); + LookAndFeel_V4::drawButtonBackground(g, button, backgroundColour, isMouseOverButton, isButtonDown); return; } int w = button.getWidth(); - - // dx, dy, dw, dl, sx, sy, sw, sl - g.drawImage(imageButton, 0, 0, 3, 30, 0, isButtonDown ? 30 : 0, 3, 30); - g.drawImage(imageButton, 3, 0, w-6, 30, 3, isButtonDown ? 30 : 0, 44, 30); - g.drawImage(imageButton, w-3, 0, 3, 30, 47, isButtonDown ? 30 : 0, 47, 30); -} + int l = button.getHeight(); -void DXLookNFeel::drawLinearSliderBackground (Graphics&, int x, int y, int width, int height, - float sliderPos, float minSliderPos, float maxSliderPos, - const Slider::SliderStyle st, Slider& s) { - // NOP + // dx, dy, dw, dl, sx, sy, sw, sl + g.drawImage(imageButton, 0, 0, 3, l, 0, isButtonDown ? 30 : 0, 3, 30); + g.drawImage(imageButton, 3, 0, w-6, l, 3, isButtonDown ? 30 : 0, 44, 30); + g.drawImage(imageButton, w-3, 0, 3, l, 47, isButtonDown ? 30 : 0, 47, 30); } -void DXLookNFeel::drawLinearSliderThumb (Graphics& g, int x, int y, int width, int height, - float sliderPos, float minSliderPos, float maxSliderPos, - const Slider::SliderStyle st, Slider& s) { - // TODO: find out why the V4 LookNFeel doesn't call this - // TRACE("draw slider"); +void DXLookNFeel::drawLinearSlider (Graphics& g, int x, int y, int width, int height, + float sliderPos, float minSliderPos, float maxSliderPos, + const Slider::SliderStyle style, Slider& slider) { if ( imageSlider.isNull() ) { - LookAndFeel_V3::drawLinearSliderThumb(g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, st, s); + LookAndFeel_V4::drawLinearSliderThumb(g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider); return; } int p = sliderPos - minSliderPos; - p -= 6; + p -= 2; g.drawImage(imageSlider, p, 0, 26, 26, 0, 0, 52, 52); } @@ -357,7 +352,7 @@ void DXLookNFeel::positionComboBoxText(ComboBox& box, Label& label) { return; } - LookAndFeel_V3::positionComboBoxText(box, label); + LookAndFeel_V4::positionComboBoxText(box, label); } Colour DXLookNFeel::fillColour = Colour(77,159,151); diff --git a/Source/DXLookNFeel.h b/Source/DXLookNFeel.h index dfb8cbe7..f3d58dec 100644 --- a/Source/DXLookNFeel.h +++ b/Source/DXLookNFeel.h @@ -29,7 +29,7 @@ class LightedToggleButton : public ToggleButton { }; -class DXLookNFeel : public LookAndFeel_V3 { +class DXLookNFeel : public LookAndFeel_V4 { HashMap colourMap; public: @@ -43,14 +43,12 @@ class DXLookNFeel : public LookAndFeel_V3 { virtual void drawRotarySlider(Graphics &g, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, Slider &slider ) override; virtual void drawToggleButton(Graphics& g, ToggleButton& button, bool isMouseOverButton, bool isButtonDown) override; - virtual void drawLinearSliderBackground (Graphics&, int x, int y, int width, int height, - float sliderPos, float minSliderPos, float maxSliderPos, - const Slider::SliderStyle, Slider&) override; - virtual void drawLinearSliderThumb (Graphics&, int x, int y, int width, int height, - float sliderPos, float minSliderPos, float maxSliderPos, - const Slider::SliderStyle, Slider&) override; virtual void drawButtonBackground (Graphics&, Button&, const Colour& backgroundColour, bool isMouseOverButton, bool isButtonDown) override; + virtual void drawLinearSlider (Graphics&, int x, int y, int width, int height, + float sliderPos, float minSliderPos, float maxSliderPos, + const Slider::SliderStyle, Slider&) override; + //virtual Font getTextButtonFont(TextButton&, int buttonHeight) override; virtual Typeface::Ptr getTypefaceForFont(const Font &) override; virtual void positionComboBoxText (ComboBox& box, Label& label) override; diff --git a/Source/GlobalEditor.cpp b/Source/GlobalEditor.cpp index d0ac83a2..ca5c494e 100644 --- a/Source/GlobalEditor.cpp +++ b/Source/GlobalEditor.cpp @@ -7,7 +7,7 @@ the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded and re-saved. - Created with Projucer version: 6.0.7 + Created with Projucer version: 7.0.9 ------------------------------------------------------------------------------ @@ -40,7 +40,7 @@ class MidiMonitor : public Component { int imageHeight2; int imageWidth; - SharedResourcePointer lookAndFeel; + SharedResourcePointer lookAndFeel; public: MidiMonitor(SysexComm *sysexComm) { midi = sysexComm; @@ -95,7 +95,7 @@ class AboutBox : public DialogWindow { surge->setColour(HyperlinkButton::ColourIds::textColourId, Colour(0xFF4ea097)); surge->setJustificationType(Justification::left); surge->setBounds(18, 458, getWidth() - 36, 30); - + // create a new Component to hold ''dexed'' and ''surge'' as subcomponents // and set this holder Component as the content component of the DialogWindow Component* holder = new Component(); @@ -133,8 +133,9 @@ GlobalEditor::GlobalEditor () //[Constructor_pre] You can add your own custom stuff here.. //[/Constructor_pre] - lfoSpeed.reset (new juce::Slider ("lfoSpeed")); + lfoSpeed.reset (new DXSlider ("lfoSpeed")); addAndMakeVisible (lfoSpeed.get()); + lfoSpeed->setExplicitFocusOrder (14); lfoSpeed->setRange (0, 99, 1); lfoSpeed->setSliderStyle (juce::Slider::RotaryVerticalDrag); lfoSpeed->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -142,8 +143,9 @@ GlobalEditor::GlobalEditor () lfoSpeed->setBounds (564, 50, 34, 34); - lfoAmDepth.reset (new juce::Slider ("lfoAmDepth")); + lfoAmDepth.reset (new DXSlider ("lfoAmDepth")); addAndMakeVisible (lfoAmDepth.get()); + lfoAmDepth->setExplicitFocusOrder (19); lfoAmDepth->setRange (0, 99, 1); lfoAmDepth->setSliderStyle (juce::Slider::RotaryVerticalDrag); lfoAmDepth->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -151,8 +153,9 @@ GlobalEditor::GlobalEditor () lfoAmDepth->setBounds (686, 50, 34, 34); - lfoPitchDepth.reset (new juce::Slider ("lfoPitchDepth")); + lfoPitchDepth.reset (new DXSlider ("lfoPitchDepth")); addAndMakeVisible (lfoPitchDepth.get()); + lfoPitchDepth->setExplicitFocusOrder (18); lfoPitchDepth->setRange (0, 99, 1); lfoPitchDepth->setSliderStyle (juce::Slider::RotaryVerticalDrag); lfoPitchDepth->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -160,8 +163,9 @@ GlobalEditor::GlobalEditor () lfoPitchDepth->setBounds (646, 50, 34, 34); - lfoDelay.reset (new juce::Slider ("lfoDelay")); + lfoDelay.reset (new DXSlider ("lfoDelay")); addAndMakeVisible (lfoDelay.get()); + lfoDelay->setExplicitFocusOrder (15); lfoDelay->setRange (0, 99, 1); lfoDelay->setSliderStyle (juce::Slider::RotaryVerticalDrag); lfoDelay->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -169,8 +173,9 @@ GlobalEditor::GlobalEditor () lfoDelay->setBounds (603, 50, 34, 34); - cutoff.reset (new juce::Slider ("cutoff")); + cutoff.reset (new DXSlider ("cutoff")); addAndMakeVisible (cutoff.get()); + cutoff->setExplicitFocusOrder (6); cutoff->setRange (0, 1, 0); cutoff->setSliderStyle (juce::Slider::RotaryVerticalDrag); cutoff->setTextBoxStyle (juce::Slider::NoTextBox, true, 80, 20); @@ -178,8 +183,9 @@ GlobalEditor::GlobalEditor () cutoff->setBounds (234, 9, 34, 34); - reso.reset (new juce::Slider ("reso")); + reso.reset (new DXSlider ("reso")); addAndMakeVisible (reso.get()); + reso->setExplicitFocusOrder (7); reso->setRange (0, 1, 0); reso->setSliderStyle (juce::Slider::RotaryVerticalDrag); reso->setTextBoxStyle (juce::Slider::NoTextBox, true, 80, 20); @@ -187,8 +193,9 @@ GlobalEditor::GlobalEditor () reso->setBounds (278, 9, 34, 34); - pitchRate2.reset (new juce::Slider ("pitchRate2")); + pitchRate2.reset (new DXSlider ("pitchRate2")); addAndMakeVisible (pitchRate2.get()); + pitchRate2->setExplicitFocusOrder (24); pitchRate2->setRange (0, 99, 1); pitchRate2->setSliderStyle (juce::Slider::RotaryVerticalDrag); pitchRate2->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -196,8 +203,9 @@ GlobalEditor::GlobalEditor () pitchRate2->setBounds (767, 96, 34, 34); - pitchRate3.reset (new juce::Slider ("pitchRate3")); + pitchRate3.reset (new DXSlider ("pitchRate3")); addAndMakeVisible (pitchRate3.get()); + pitchRate3->setExplicitFocusOrder (26); pitchRate3->setRange (0, 99, 1); pitchRate3->setSliderStyle (juce::Slider::RotaryVerticalDrag); pitchRate3->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -205,8 +213,9 @@ GlobalEditor::GlobalEditor () pitchRate3->setBounds (795, 96, 35, 34); - pitchRate4.reset (new juce::Slider ("pitchRate4")); + pitchRate4.reset (new DXSlider ("pitchRate4")); addAndMakeVisible (pitchRate4.get()); + pitchRate4->setExplicitFocusOrder (28); pitchRate4->setRange (0, 99, 1); pitchRate4->setSliderStyle (juce::Slider::RotaryVerticalDrag); pitchRate4->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -214,8 +223,9 @@ GlobalEditor::GlobalEditor () pitchRate4->setBounds (823, 96, 34, 34); - pitchRate1.reset (new juce::Slider ("pitchRate1")); + pitchRate1.reset (new DXSlider ("pitchRate1")); addAndMakeVisible (pitchRate1.get()); + pitchRate1->setExplicitFocusOrder (22); pitchRate1->setRange (0, 99, 1); pitchRate1->setSliderStyle (juce::Slider::RotaryVerticalDrag); pitchRate1->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -223,8 +233,9 @@ GlobalEditor::GlobalEditor () pitchRate1->setBounds (739, 96, 34, 34); - pitchLevel2.reset (new juce::Slider ("pitchLevel2")); + pitchLevel2.reset (new DXSlider ("pitchLevel2")); addAndMakeVisible (pitchLevel2.get()); + pitchLevel2->setExplicitFocusOrder (23); pitchLevel2->setRange (0, 99, 1); pitchLevel2->setSliderStyle (juce::Slider::RotaryVerticalDrag); pitchLevel2->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -232,8 +243,9 @@ GlobalEditor::GlobalEditor () pitchLevel2->setBounds (767, 57, 34, 34); - pitchLevel3.reset (new juce::Slider ("pitchLevel3")); + pitchLevel3.reset (new DXSlider ("pitchLevel3")); addAndMakeVisible (pitchLevel3.get()); + pitchLevel3->setExplicitFocusOrder (25); pitchLevel3->setRange (0, 99, 1); pitchLevel3->setSliderStyle (juce::Slider::RotaryVerticalDrag); pitchLevel3->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -241,8 +253,9 @@ GlobalEditor::GlobalEditor () pitchLevel3->setBounds (795, 56, 34, 34); - pitchLevel4.reset (new juce::Slider ("pitchLevel4")); + pitchLevel4.reset (new DXSlider ("pitchLevel4")); addAndMakeVisible (pitchLevel4.get()); + pitchLevel4->setExplicitFocusOrder (27); pitchLevel4->setRange (0, 99, 1); pitchLevel4->setSliderStyle (juce::Slider::RotaryVerticalDrag); pitchLevel4->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -250,8 +263,9 @@ GlobalEditor::GlobalEditor () pitchLevel4->setBounds (823, 56, 34, 34); - pitchLevel1.reset (new juce::Slider ("pitchLevel1")); + pitchLevel1.reset (new DXSlider ("pitchLevel1")); addAndMakeVisible (pitchLevel1.get()); + pitchLevel1->setExplicitFocusOrder (21); pitchLevel1->setRange (0, 99, 1); pitchLevel1->setSliderStyle (juce::Slider::RotaryVerticalDrag); pitchLevel1->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -259,8 +273,9 @@ GlobalEditor::GlobalEditor () pitchLevel1->setBounds (739, 57, 34, 34); - transpose.reset (new juce::Slider ("transpose")); + transpose.reset (new DXSlider ("transpose")); addAndMakeVisible (transpose.get()); + transpose->setExplicitFocusOrder (9); transpose->setRange (0, 48, 1); transpose->setSliderStyle (juce::Slider::RotaryVerticalDrag); transpose->setTextBoxStyle (juce::Slider::NoTextBox, true, 80, 20); @@ -270,13 +285,15 @@ GlobalEditor::GlobalEditor () oscSync.reset (new juce::ToggleButton ("oscSync")); addAndMakeVisible (oscSync.get()); + oscSync->setExplicitFocusOrder (20); oscSync->setButtonText (juce::String()); oscSync->addListener (this); oscSync->setBounds (650, 96, 48, 26); - pitchModSens.reset (new juce::Slider ("pitchModSens")); + pitchModSens.reset (new DXSlider ("pitchModSens")); addAndMakeVisible (pitchModSens.get()); + pitchModSens->setExplicitFocusOrder (17); pitchModSens->setRange (0, 7, 1); pitchModSens->setSliderStyle (juce::Slider::RotaryVerticalDrag); pitchModSens->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -286,6 +303,7 @@ GlobalEditor::GlobalEditor () lfoSync.reset (new juce::ToggleButton ("lfoSync")); addAndMakeVisible (lfoSync.get()); + lfoSync->setExplicitFocusOrder (16); lfoSync->setButtonText (juce::String()); lfoSync->addListener (this); @@ -303,8 +321,9 @@ GlobalEditor::GlobalEditor () algoDisplay->setBounds (335, 30, 152, 91); - feedback.reset (new juce::Slider ("feedback")); + feedback.reset (new DXSlider ("feedback")); addAndMakeVisible (feedback.get()); + feedback->setExplicitFocusOrder (12); feedback->setRange (0, 7, 1); feedback->setSliderStyle (juce::Slider::RotaryVerticalDrag); feedback->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -312,8 +331,9 @@ GlobalEditor::GlobalEditor () feedback->setBounds (501, 81, 34, 34); - algo.reset (new juce::Slider ("algo")); + algo.reset (new DXSlider ("algo")); addAndMakeVisible (algo.get()); + algo->setExplicitFocusOrder (11); algo->setRange (1, 32, 1); algo->setSliderStyle (juce::Slider::RotaryVerticalDrag); algo->setTextBoxStyle (juce::Slider::NoTextBox, true, 80, 20); @@ -327,8 +347,9 @@ GlobalEditor::GlobalEditor () lcdDisplay->setBounds (6, 87, 140, 13); - output.reset (new juce::Slider ("output")); + output.reset (new DXSlider ("output")); addAndMakeVisible (output.get()); + output->setExplicitFocusOrder (8); output->setRange (0, 1, 0); output->setSliderStyle (juce::Slider::RotaryVerticalDrag); output->setTextBoxStyle (juce::Slider::NoTextBox, true, 80, 20); @@ -336,9 +357,7 @@ GlobalEditor::GlobalEditor () output->setBounds (157, 60, 34, 34); - //vuOutput.reset (new VuMeter()); - //vuOutput.reset(new VuMeterMain(6)); - vuOutput.reset(new VuMeterOutput); + vuOutput.reset (new VuMeterOutput ()); addAndMakeVisible (vuOutput.get()); vuOutput->setName ("vuOutput"); @@ -346,34 +365,39 @@ GlobalEditor::GlobalEditor () initButton.reset (new juce::TextButton ("initButton")); addAndMakeVisible (initButton.get()); - initButton->setButtonText (translate("INIT")); + initButton->setExplicitFocusOrder (3); + initButton->setButtonText (TRANS ("INIT")); initButton->addListener (this); initButton->setBounds (100, 111, 50, 30); parmButton.reset (new juce::TextButton ("parmButton")); addAndMakeVisible (parmButton.get()); - parmButton->setButtonText (translate("PARM")); + parmButton->setExplicitFocusOrder (2); + parmButton->setButtonText (TRANS ("PARM")); parmButton->addListener (this); parmButton->setBounds (52, 111, 50, 30); cartButton.reset (new juce::TextButton ("cartButton")); addAndMakeVisible (cartButton.get()); - cartButton->setButtonText (translate("CART")); + cartButton->setExplicitFocusOrder (1); + cartButton->setButtonText (TRANS ("CART")); cartButton->addListener (this); cartButton->setBounds (3, 111, 50, 30); storeButton.reset (new juce::TextButton ("storeButton")); addAndMakeVisible (storeButton.get()); - storeButton->setButtonText (translate("STORE")); + storeButton->setExplicitFocusOrder (4); + storeButton->setButtonText (TRANS ("STORE")); storeButton->addListener (this); storeButton->setBounds (270, 109, 50, 30); monoMode.reset (new juce::ToggleButton ("monoMode")); addAndMakeVisible (monoMode.get()); + monoMode->setExplicitFocusOrder (10); monoMode->setButtonText (juce::String()); monoMode->addListener (this); @@ -381,6 +405,7 @@ GlobalEditor::GlobalEditor () lfoType.reset (new ComboBoxImage()); addAndMakeVisible (lfoType.get()); + lfoType->setExplicitFocusOrder (13); lfoType->setName ("lfoType"); lfoType->setBounds (583, 8, 36, 26); @@ -402,8 +427,9 @@ GlobalEditor::GlobalEditor () juce::Image(), 1.000f, juce::Colour (0x00000000)); aboutButton->setBounds (8, 11, 135, 46); - tune.reset (new juce::Slider ("tune")); + tune.reset (new DXSlider ("tune")); addAndMakeVisible (tune.get()); + tune->setExplicitFocusOrder (5); tune->setRange (0, 1, 0); tune->setSliderStyle (juce::Slider::RotaryVerticalDrag); tune->setTextBoxStyle (juce::Slider::NoTextBox, true, 80, 20); @@ -432,6 +458,10 @@ GlobalEditor::GlobalEditor () background = lookAndFeel->imageGlobal; imageLight = lookAndFeel->imageLight; + setTitle("Global Parameters"); + setFocusContainerType(FocusContainerType::focusContainer); + setWantsKeyboardFocus(true); + aboutButton->setTitle("About DEXED"); //[/Constructor] } @@ -791,86 +821,86 @@ BEGIN_JUCER_METADATA fixedSize="1" initialWidth="864" initialHeight="144"> + explicitFocusOrder="0" pos="6 103 140 8" class="VuMeterOutput" + params=""/> diff --git a/Source/GlobalEditor.h b/Source/GlobalEditor.h index 65b59b43..232ef68c 100644 --- a/Source/GlobalEditor.h +++ b/Source/GlobalEditor.h @@ -7,7 +7,7 @@ the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded and re-saved. - Created with Projucer version: 6.0.7 + Created with Projucer version: 7.0.9 ------------------------------------------------------------------------------ diff --git a/Source/OperatorEditor.cpp b/Source/OperatorEditor.cpp index 9f82e6d9..13a872c1 100644 --- a/Source/OperatorEditor.cpp +++ b/Source/OperatorEditor.cpp @@ -7,7 +7,7 @@ the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded and re-saved. - Created with Projucer version: 6.0.1 + Created with Projucer version: 7.0.9 ------------------------------------------------------------------------------ @@ -19,7 +19,7 @@ //[Headers] You can add your own extra header files here... //[/Headers] -#include "VUMeter.h" + #include "OperatorEditor.h" @@ -50,8 +50,9 @@ OperatorEditor::OperatorEditor () //[Constructor_pre] You can add your own custom stuff here.. //[/Constructor_pre] - s_egl1.reset (new juce::Slider ("egl1")); + s_egl1.reset (new DXSlider ("egl1")); addAndMakeVisible (s_egl1.get()); + s_egl1->setExplicitFocusOrder (4); s_egl1->setRange (0, 99, 1); s_egl1->setSliderStyle (juce::Slider::RotaryVerticalDrag); s_egl1->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -59,17 +60,19 @@ OperatorEditor::OperatorEditor () s_egl1->setBounds (5, 128, 34, 34); - s_egl2.reset (new juce::Slider ("egl2")); + s_egl2.reset (new DXSlider ("egl2")); addAndMakeVisible (s_egl2.get()); + s_egl2->setExplicitFocusOrder (6); s_egl2->setRange (0, 99, 1); s_egl2->setSliderStyle (juce::Slider::RotaryVerticalDrag); s_egl2->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); s_egl2->addListener (this); - s_egl2->setBounds (33, 129, 34, 34); + s_egl2->setBounds (33, 128, 34, 34); - s_egl3.reset (new juce::Slider ("egl3")); + s_egl3.reset (new DXSlider ("egl3")); addAndMakeVisible (s_egl3.get()); + s_egl3->setExplicitFocusOrder (8); s_egl3->setRange (0, 99, 1); s_egl3->setSliderStyle (juce::Slider::RotaryVerticalDrag); s_egl3->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -77,8 +80,9 @@ OperatorEditor::OperatorEditor () s_egl3->setBounds (61, 128, 34, 34); - s_egl4.reset (new juce::Slider ("egl4")); + s_egl4.reset (new DXSlider ("egl4")); addAndMakeVisible (s_egl4.get()); + s_egl4->setExplicitFocusOrder (10); s_egl4->setRange (0, 99, 1); s_egl4->setSliderStyle (juce::Slider::RotaryVerticalDrag); s_egl4->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -86,8 +90,9 @@ OperatorEditor::OperatorEditor () s_egl4->setBounds (89, 128, 34, 34); - s_egv1.reset (new juce::Slider ("egr1")); + s_egv1.reset (new DXSlider ("egr1")); addAndMakeVisible (s_egv1.get()); + s_egv1->setExplicitFocusOrder (5); s_egv1->setRange (0, 99, 1); s_egv1->setSliderStyle (juce::Slider::RotaryVerticalDrag); s_egv1->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -95,8 +100,9 @@ OperatorEditor::OperatorEditor () s_egv1->setBounds (5, 169, 34, 34); - s_egv2.reset (new juce::Slider ("egr3")); + s_egv2.reset (new DXSlider ("egr3")); addAndMakeVisible (s_egv2.get()); + s_egv2->setExplicitFocusOrder (7); s_egv2->setRange (0, 99, 1); s_egv2->setSliderStyle (juce::Slider::RotaryVerticalDrag); s_egv2->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -104,8 +110,9 @@ OperatorEditor::OperatorEditor () s_egv2->setBounds (33, 169, 34, 34); - s_egv3.reset (new juce::Slider ("egr3")); + s_egv3.reset (new DXSlider ("egr3")); addAndMakeVisible (s_egv3.get()); + s_egv3->setExplicitFocusOrder (9); s_egv3->setRange (0, 99, 1); s_egv3->setSliderStyle (juce::Slider::RotaryVerticalDrag); s_egv3->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -113,8 +120,9 @@ OperatorEditor::OperatorEditor () s_egv3->setBounds (61, 169, 34, 34); - s_egv4.reset (new juce::Slider ("egr4")); + s_egv4.reset (new DXSlider ("egr4")); addAndMakeVisible (s_egv4.get()); + s_egv4->setExplicitFocusOrder (11); s_egv4->setRange (0, 99, 1); s_egv4->setSliderStyle (juce::Slider::RotaryVerticalDrag); s_egv4->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -122,8 +130,9 @@ OperatorEditor::OperatorEditor () s_egv4->setBounds (89, 169, 34, 34); - opLevel.reset (new juce::Slider ("opLevel")); + opLevel.reset (new DXSlider ("opLevel")); addAndMakeVisible (opLevel.get()); + opLevel->setExplicitFocusOrder (15); opLevel->setRange (0, 99, 1); opLevel->setSliderStyle (juce::Slider::RotaryVerticalDrag); opLevel->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -131,8 +140,9 @@ OperatorEditor::OperatorEditor () opLevel->setBounds (245, 76, 34, 34); - opFine.reset (new juce::Slider ("opFine")); + opFine.reset (new DXSlider ("opFine")); addAndMakeVisible (opFine.get()); + opFine->setExplicitFocusOrder (3); opFine->setRange (0, 99, 1); opFine->setSliderStyle (juce::Slider::RotaryVerticalDrag); opFine->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -140,8 +150,9 @@ OperatorEditor::OperatorEditor () opFine->setBounds (78, 24, 34, 34); - opCoarse.reset (new juce::Slider ("opCoarse")); + opCoarse.reset (new DXSlider ("opCoarse")); addAndMakeVisible (opCoarse.get()); + opCoarse->setExplicitFocusOrder (2); opCoarse->setRange (0, 31, 1); opCoarse->setSliderStyle (juce::Slider::RotaryVerticalDrag); opCoarse->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -150,7 +161,7 @@ OperatorEditor::OperatorEditor () opCoarse->setBounds (43, 24, 34, 34); khzDisplay.reset (new juce::Label ("khz", - translate("1,000 kHz"))); + TRANS ("1,000 kHz"))); addAndMakeVisible (khzDisplay.get()); khzDisplay->setFont (juce::Font (12.60f, juce::Font::plain).withTypefaceStyle ("Regular")); khzDisplay->setJustificationType (juce::Justification::centred); @@ -163,8 +174,9 @@ OperatorEditor::OperatorEditor () khzDisplay->setBounds (15, 10, 95, 10); - detune.reset (new juce::Slider ("detune")); + detune.reset (new DXSlider ("detune")); addAndMakeVisible (detune.get()); + detune->setExplicitFocusOrder (1); detune->setRange (-7, 7, 1); detune->setSliderStyle (juce::Slider::RotaryVerticalDrag); detune->setTextBoxStyle (juce::Slider::NoTextBox, true, 80, 20); @@ -178,9 +190,10 @@ OperatorEditor::OperatorEditor () envDisplay->setBounds (16, 83, 94, 30); - sclLeftLevel.reset (new juce::Slider ("sclLeftLevel")); + sclLeftLevel.reset (new DXSlider ("sclLeftLevel")); addAndMakeVisible (sclLeftLevel.get()); - sclLeftLevel->setTooltip (translate("Keyboard Scale Level Left Depth ")); + sclLeftLevel->setTooltip (TRANS ("Keyboard Scale Level Left Depth ")); + sclLeftLevel->setExplicitFocusOrder (16); sclLeftLevel->setRange (0, 99, 1); sclLeftLevel->setSliderStyle (juce::Slider::RotaryVerticalDrag); sclLeftLevel->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -188,9 +201,10 @@ OperatorEditor::OperatorEditor () sclLeftLevel->setBounds (131, 115, 34, 34); - sclRightLevel.reset (new juce::Slider ("sclRightLevel")); + sclRightLevel.reset (new DXSlider ("sclRightLevel")); addAndMakeVisible (sclRightLevel.get()); - sclRightLevel->setTooltip (translate("Keyboard Scale Level Right Depth ")); + sclRightLevel->setTooltip (TRANS ("Keyboard Scale Level Right Depth ")); + sclRightLevel->setExplicitFocusOrder (18); sclRightLevel->setRange (0, 99, 1); sclRightLevel->setSliderStyle (juce::Slider::RotaryVerticalDrag); sclRightLevel->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -198,9 +212,10 @@ OperatorEditor::OperatorEditor () sclRightLevel->setBounds (241, 115, 34, 34); - sclLvlBrkPt.reset (new juce::Slider ("sclLvlBrkPt")); + sclLvlBrkPt.reset (new DXSlider ("sclLvlBrkPt")); addAndMakeVisible (sclLvlBrkPt.get()); - sclLvlBrkPt->setTooltip (translate("Scale Level Breakpoint")); + sclLvlBrkPt->setTooltip (TRANS ("Scale Level Breakpoint")); + sclLvlBrkPt->setExplicitFocusOrder (17); sclLvlBrkPt->setRange (0, 99, 1); sclLvlBrkPt->setSliderStyle (juce::Slider::LinearHorizontal); sclLvlBrkPt->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -208,9 +223,10 @@ OperatorEditor::OperatorEditor () sclLvlBrkPt->setBounds (178, 130, 54, 24); - sclRateScaling.reset (new juce::Slider ("sclRateScaling")); + sclRateScaling.reset (new DXSlider ("sclRateScaling")); addAndMakeVisible (sclRateScaling.get()); - sclRateScaling->setTooltip (translate("Keyboard Rate Scaling")); + sclRateScaling->setTooltip (TRANS ("Keyboard Rate Scaling")); + sclRateScaling->setExplicitFocusOrder (20); sclRateScaling->setRange (0, 7, 1); sclRateScaling->setSliderStyle (juce::Slider::RotaryVerticalDrag); sclRateScaling->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -218,8 +234,9 @@ OperatorEditor::OperatorEditor () sclRateScaling->setBounds (186, 179, 34, 34); - keyVelSens.reset (new juce::Slider ("keyVelSens")); + keyVelSens.reset (new DXSlider ("keyVelSens")); addAndMakeVisible (keyVelSens.get()); + keyVelSens->setExplicitFocusOrder (14); keyVelSens->setRange (0, 7, 1); keyVelSens->setSliderStyle (juce::Slider::RotaryVerticalDrag); keyVelSens->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -227,8 +244,9 @@ OperatorEditor::OperatorEditor () keyVelSens->setBounds (204, 76, 34, 34); - ampModSens.reset (new juce::Slider ("ampModSens")); + ampModSens.reset (new DXSlider ("ampModSens")); addAndMakeVisible (ampModSens.get()); + ampModSens->setExplicitFocusOrder (13); ampModSens->setRange (0, 3, 1); ampModSens->setSliderStyle (juce::Slider::RotaryVerticalDrag); ampModSens->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -244,6 +262,7 @@ OperatorEditor::OperatorEditor () opMode.reset (new juce::ToggleButton ("opMode")); addAndMakeVisible (opMode.get()); + opMode->setExplicitFocusOrder (12); opMode->setButtonText (juce::String()); opMode->addListener (this); @@ -251,12 +270,14 @@ OperatorEditor::OperatorEditor () kbdLeftCurve.reset (new ComboBoxImage()); addAndMakeVisible (kbdLeftCurve.get()); + kbdLeftCurve->setExplicitFocusOrder (19); kbdLeftCurve->setName ("kbdLeftCurve"); kbdLeftCurve->setBounds (128, 170, 36, 26); kbdRightCurve.reset (new ComboBoxImage()); addAndMakeVisible (kbdRightCurve.get()); + kbdRightCurve->setExplicitFocusOrder (21); kbdRightCurve->setName ("kbdRightCurve"); kbdRightCurve->setBounds (240, 170, 36, 26); @@ -276,7 +297,7 @@ OperatorEditor::OperatorEditor () light = lookAndFeel->imageLight; Image tmp = lookAndFeel->imageScaling; - + kbdLeftCurve->addItem("-LN", 1); kbdLeftCurve->addItem("-EX", 2); kbdLeftCurve->addItem("+EX", 3); @@ -292,7 +313,9 @@ OperatorEditor::OperatorEditor () kbdRightCurve->setImage(tmp, posRight); background = lookAndFeel->imageOperator; + opSwitch->setTitle("Operator switch"); + setWantsKeyboardFocus(true); //[/Constructor] } @@ -531,6 +554,8 @@ void OperatorEditor::bind(DexedAudioProcessor *parent, int op) { opNum << op + 1; internalOp = 5-op; + setTitle("Operator " + opNum); + setFocusContainerType(FocusContainerType::focusContainer); } void OperatorEditor::updateGain(float v) { @@ -601,6 +626,10 @@ void OperatorEditor::mouseDown(const MouseEvent &event) { } } +void OperatorEditor::toggleOpSwitch() { + opSwitch->setToggleState(!opSwitch->getToggleState(), dontSendNotification); +} + //[/MiscUserCode] @@ -619,47 +648,47 @@ BEGIN_JUCER_METADATA fixedSize="1" initialWidth="287" initialHeight="218"> + virtualName="" explicitFocusOrder="5" pos="236 136 90 24" editable="0" + layout="33" items="100 % 125 % 150 % 200 % 300 % 400 %" + textWhenNonSelected="" textWhenNoItems="(no choices)"/> END_JUCER_METADATA diff --git a/Source/ParamDialog.h b/Source/ParamDialog.h index fe918af9..3fb08d2d 100644 --- a/Source/ParamDialog.h +++ b/Source/ParamDialog.h @@ -7,7 +7,7 @@ the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded and re-saved. - Created with Projucer version: 6.0.7 + Created with Projucer version: 7.0.9 ------------------------------------------------------------------------------ @@ -40,7 +40,8 @@ class ParamDialog : public Component, public juce::Slider::Listener, public juce::ComboBox::Listener, - public juce::Button::Listener + public juce::Button::Listener, + Timer { public: //============================================================================== @@ -63,6 +64,7 @@ class ParamDialog : public Component, void setGeneralCallback(std::function gc ) { general_callback_ = gc; } void setIsStandardTuning(bool s); + void timerCallback() override; //[/UserMethods] void paint (juce::Graphics& g) override; diff --git a/Source/PluginData.h b/Source/PluginData.h index 68e29019..4d0dfa2f 100644 --- a/Source/PluginData.h +++ b/Source/PluginData.h @@ -236,12 +236,21 @@ class Cartridge { dest.add( normalizePgmName(getRawVoice() + ((i * 128) + 118)) ); } + String getProgramName(int idx) { + jassert(idx >= 0 && idx < 32); + return normalizePgmName(getRawVoice() + ((idx * 128) + 118)); + } + Cartridge operator =(const Cartridge other) { memcpy(voiceData, other.voiceData, SYSEX_SIZE); memcpy(perfData, other.perfData, SYSEX_SIZE); return *this; } - + + void replaceProgram(int idx, char *src) { + memcpy(getRawVoice() + (idx * 128), src, 128); + } + void unpackProgram(uint8_t *unpackPgm, int idx); void packProgram(uint8_t *src, int idx, String name, char *opSwitch); }; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 582f83f5..47ec4ccf 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -37,7 +37,7 @@ DexedAudioProcessorEditor::DexedAudioProcessorEditor (DexedAudioProcessor* owner cartManager(this) { setSize(WINDOW_SIZE_X, (ownerFilter->showKeyboard ? WINDOW_SIZE_Y : WINDOW_SIZE_Y - 94)); - + setExplicitFocusOrder(1); processor = ownerFilter; lookAndFeel->setDefaultLookAndFeel(lookAndFeel); @@ -73,8 +73,8 @@ DexedAudioProcessorEditor::DexedAudioProcessorEditor (DexedAudioProcessor* owner // The DX7 is a badass on the bass, keep it that way midiKeyboard.setLowestVisibleKey(24); - midiKeyboard.setBounds(4, 581, getWidth() - 8, 90); + midiKeyboard.setTitle("Keyboard keys"); addAndMakeVisible(&global); global.setBounds(2,436,864,144); @@ -89,6 +89,7 @@ DexedAudioProcessorEditor::DexedAudioProcessorEditor (DexedAudioProcessor* owner cartManagerCover.addChildComponent(&cartManager); cartManager.setVisible(true); + addKeyListener(this); updateUI(); startTimer(100); } @@ -115,6 +116,10 @@ void DexedAudioProcessorEditor::cartShow() { cartManager.initialFocus(); } +std::unique_ptr DexedAudioProcessorEditor::createFocusTraverser() { + return std::make_unique(); +} + void DexedAudioProcessorEditor::loadCart(File file) { Cartridge cart; @@ -512,3 +517,46 @@ void DexedAudioProcessorEditor::filesDropped (const StringArray &files, int x, i "Related to file \'"+fn.toStdString()+"\', an unknown exception occured."); }; } + +bool DexedAudioProcessorEditor::keyPressed(const KeyPress& key, Component* originatingComponent) { + int keycode = key.getKeyCode(); + ModifierKeys mods = key.getModifiers(); + + #ifdef DEXED_EVENT_DEBUG + TRACE("key pressed: %d\n", keycode); + #endif + + if ( (keycode >= '1' && keycode <= '6') && mods.isCtrlDown() ) { + int op = keycode - '1'; + + if ( mods.isShiftDown() ) { + operators[op].toggleOpSwitch(); + return true; + } + + operators[op].grabKeyboardFocus(); + return true; + } + + if ( keycode == 'G' && mods.isCtrlDown() ) { + global.grabKeyboardFocus(); + return true; + } + + if ( keycode == 'L' && mods.isCtrlDown() ) { + cartShow(); + return true; + } + + if ( keycode == 'P' && mods.isCtrlDown() ) { + parmShow(); + return true; + } + + if ( key.getKeyCode() == KeyPress::escapeKey ) { + cartManager.hideCartridgeManager(); + return true; + } + + return false; +} \ No newline at end of file diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 5a269ec0..144b21be 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -32,7 +32,7 @@ /** */ class DexedAudioProcessorEditor : public AudioProcessorEditor, public ComboBox::Listener, public Timer, - public FileDragAndDropTarget { + public FileDragAndDropTarget, public KeyListener { MidiKeyboardComponent midiKeyboard; OperatorEditor operators[6]; Colour background; @@ -41,6 +41,9 @@ class DexedAudioProcessorEditor : public AudioProcessorEditor, public ComboBox: Component cartManagerCover; SharedResourcePointer lookAndFeel; + #ifdef DEXED_EVENT_DEBUG + FocusLogger focusLogger; + #endif public: DexedAudioProcessor *processor; GlobalEditor global; @@ -64,7 +67,10 @@ class DexedAudioProcessorEditor : public AudioProcessorEditor, public ComboBox: virtual bool isInterestedInFileDrag (const StringArray &files) override; virtual void filesDropped (const StringArray &files, int x, int y ) override; - + std::unique_ptr createFocusTraverser() override; + + bool keyPressed(const KeyPress& key, Component* originatingComponent) override; + static const int WINDOW_SIZE_X = 866; static const int WINDOW_SIZE_Y = 674; }; diff --git a/Source/PluginParam.cpp b/Source/PluginParam.cpp index c3461e81..e4f7f83b 100644 --- a/Source/PluginParam.cpp +++ b/Source/PluginParam.cpp @@ -1,6 +1,6 @@ /** * - * Copyright (c) 2013-2017 Pascal Gauthier. + * Copyright (c) 2013-2024 Pascal Gauthier. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,6 +40,129 @@ class CtrlUpdate : public CallbackMessage { } }; +// ************************************************************************ +// +Ctrl::Ctrl(String name) { + label << name; + slider = NULL; + button = NULL; + comboBox = NULL; +} + +void Ctrl::bind(Slider *s) { + slider = s; + updateComponent(); + s->addListener(this); + s->addMouseListener(this, true); + s->setVelocityModeParameters (0.1, 1, 0.05, 1, ModifierKeys::shiftModifier); + s->setTitle(label); + s->textFromValueFunction = [this](double value) { return this->getValueDisplay(); }; + s->setWantsKeyboardFocus(true); +} + +void Ctrl::bind(Button *b) { + button = b; + updateComponent(); + b->setTitle(label); + b->addListener(this); + b->addMouseListener(this, true); +} + +void Ctrl::bind(ComboBox *c) { + comboBox = c; + updateComponent(); + c->setTitle(label); + c->addListener(this); + c->addMouseListener(this, true); +} + +void Ctrl::unbind() { + if (slider != NULL) { + slider->removeListener(this); + slider->removeMouseListener(this); + slider = NULL; + } + + if (button != NULL) { + button->removeListener(this); + button->removeMouseListener(this); + button = NULL; + } + + if (comboBox != NULL) { + comboBox->removeListener(this); + comboBox->removeMouseListener(this); + comboBox = NULL; + } +} + +void Ctrl::publishValueAsync(float value) { + CtrlUpdate *update = new CtrlUpdate(this, value); + update->post(); +} + +void Ctrl::publishValue(float value) { + parent->beginParameterChangeGesture(idx); + parent->setParameterNotifyingHost(idx, value); + parent->endParameterChangeGesture(idx); +} + +void Ctrl::sliderValueChanged(Slider* moved) { + publishValue(moved->getValue()); +} + +void Ctrl::buttonClicked(Button* clicked) { + publishValue(clicked->getToggleState()); +} + +void Ctrl::comboBoxChanged(ComboBox* combo) { + publishValue((combo->getSelectedId() - 1) / combo->getNumItems()); +} + +void Ctrl::mouseEnter(const juce::MouseEvent &event) { + updateDisplayName(); +} + +void Ctrl::mouseDown(const juce::MouseEvent &event) { + if ( event.mods.isPopupMenu()) { + PopupMenu popup; + + if ( parent->mappedMidiCC.containsValue(this) ) { + popup.addItem(3, "Re-Map controller to midi CC for: " + String(label)); + popup.addSeparator(); + popup.addItem(1, "Remove midi CC mapping for this controller"); + } else { + popup.addItem(3, "Map controller to midi CC for: " + String(label)); + popup.addSeparator(); + } + popup.addItem(2, "Clear midi CC mapping"); + + switch(popup.show()) { + case 1: + parent->mappedMidiCC.removeValue(this); + parent->savePreference(); + break; + case 2: + if ( AlertWindow::showYesNoCancelBox(AlertWindow::WarningIcon, "Confirm", "Clear midi mapping for all controller change (CC) messages?", "YES", "NO", "CANCEL") ) { + parent->mappedMidiCC.clear(); + parent->savePreference(); + } + break; + case 3: + AudioProcessorEditor *editor = parent->getActiveEditor(); + if ( editor == NULL ) { + return; + } + DexedAudioProcessorEditor *dexedEditor = (DexedAudioProcessorEditor *) editor; + dexedEditor->discoverMidiCC(this); + break; + } + } +} + +void Ctrl::updateDisplayName() { +} + // ************************************************************************ // Custom displays @@ -228,124 +351,6 @@ class CtrlMonoPoly : public Ctrl { } }; -// ************************************************************************ -// -Ctrl::Ctrl(String name) { - label << name; - slider = NULL; - button = NULL; - comboBox = NULL; -} - -void Ctrl::bind(Slider *s) { - slider = s; - updateComponent(); - s->addListener(this); - s->addMouseListener(this, true); - s->setVelocityModeParameters (0.1, 1, 0.05, 1, ModifierKeys::shiftModifier); -} - -void Ctrl::bind(Button *b) { - button = b; - updateComponent(); - b->addListener(this); - b->addMouseListener(this, true); -} - -void Ctrl::bind(ComboBox *c) { - comboBox = c; - updateComponent(); - c->addListener(this); - c->addMouseListener(this, true); -} - -void Ctrl::unbind() { - if (slider != NULL) { - slider->removeListener(this); - slider->removeMouseListener(this); - slider = NULL; - } - - if (button != NULL) { - button->removeListener(this); - button->removeMouseListener(this); - button = NULL; - } - - if (comboBox != NULL) { - comboBox->removeListener(this); - comboBox->removeMouseListener(this); - comboBox = NULL; - } -} - -void Ctrl::publishValueAsync(float value) { - CtrlUpdate *update = new CtrlUpdate(this, value); - update->post(); -} - -void Ctrl::publishValue(float value) { - parent->beginParameterChangeGesture(idx); - parent->setParameterNotifyingHost(idx, value); - parent->endParameterChangeGesture(idx); -} - -void Ctrl::sliderValueChanged(Slider* moved) { - publishValue(moved->getValue()); -} - -void Ctrl::buttonClicked(Button* clicked) { - publishValue(clicked->getToggleState()); -} - -void Ctrl::comboBoxChanged(ComboBox* combo) { - publishValue((combo->getSelectedId() - 1) / combo->getNumItems()); -} - -void Ctrl::mouseEnter(const juce::MouseEvent &event) { - updateDisplayName(); -} - -void Ctrl::mouseDown(const juce::MouseEvent &event) { - if ( event.mods.isPopupMenu()) { - PopupMenu popup; - - if ( parent->mappedMidiCC.containsValue(this) ) { - popup.addItem(3, "Re-Map controller to midi CC for: " + String(label)); - popup.addSeparator(); - popup.addItem(1, "Remove midi CC mapping for this controller"); - } else { - popup.addItem(3, "Map controller to midi CC for: " + String(label)); - popup.addSeparator(); - } - popup.addItem(2, "Clear midi CC mapping"); - - switch(popup.show()) { - case 1: - parent->mappedMidiCC.removeValue(this); - parent->savePreference(); - break; - case 2: - if ( AlertWindow::showYesNoCancelBox(AlertWindow::WarningIcon, "Confirm", "Clear midi mapping for all controller change (CC) messages?", "YES", "NO", "CANCEL") ) { - parent->mappedMidiCC.clear(); - parent->savePreference(); - } - break; - case 3: - AudioProcessorEditor *editor = parent->getActiveEditor(); - if ( editor == NULL ) { - return; - } - DexedAudioProcessorEditor *dexedEditor = (DexedAudioProcessorEditor *) editor; - dexedEditor->discoverMidiCC(this); - break; - } - } -} - -void Ctrl::updateDisplayName() { -} - // ************************************************************************ // CtrlFloat - control float values CtrlFloat::CtrlFloat(String name, float *storageValue) : Ctrl(name) { diff --git a/Source/PluginParam.h b/Source/PluginParam.h index bc07742e..c814c3ad 100644 --- a/Source/PluginParam.h +++ b/Source/PluginParam.h @@ -23,6 +23,9 @@ #include "../JuceLibraryCode/JuceHeader.h" +#include +#include + class DexedAudioProcessor; class Ctrl : public Slider::Listener, public Button::Listener, public ComboBox::Listener, public MouseListener { diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 57b25c5c..be261e55 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -306,7 +306,6 @@ void DexedAudioProcessor::processBlock(AudioSampleBuffer& buffer, MidiBuffer& mi buffer.copyFrom(1, 0, channelData, numSamples, 1); } - //============================================================================== // This creates new instances of the plugin.. AudioProcessor* JUCE_CALLTYPE createPluginFilter() { @@ -815,6 +814,7 @@ AudioProcessorEditor* DexedAudioProcessor::createEditor() { setDpiScaleFactor(1.0); else setDpiScaleFactor(scaleFactor); + return editor; } diff --git a/Source/ProgramListBox.cpp b/Source/ProgramListBox.cpp index cac09e66..fb196c04 100644 --- a/Source/ProgramListBox.cpp +++ b/Source/ProgramListBox.cpp @@ -1,6 +1,6 @@ /** * - * Copyright (c) 2015 Pascal Gauthier. + * Copyright (c) 2015-2024 Pascal Gauthier. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,64 +26,31 @@ ProgramListBox::ProgramListBox(const String name, int numCols) : Component(name) { cols = numCols; rows = 32 / numCols; - selectedPgm = -1; + activePgm = -1; hasContent = false; - dragCandidate = -1; readOnly = false; programNames.clear(); -} - -void ProgramListBox::paint(Graphics &g) { - int pgm = 0; - - g.setColour(Colour(20,18,18)); - g.fillRect(0,0,getWidth(), getHeight()); - g.setColour(Colour(0,0,0)); - g.drawLine(0,0,getWidth(), 0, 2); - g.setColour(Colour(3,3,1)); - g.drawLine(0,0,0,getHeight(),2); - g.setColour(Colour(34,32,32)); - g.drawLine(getWidth(), 3, getWidth(), getHeight(), 2); - g.setColour(Colour(75,73,73)); - g.drawLine(0,getHeight(),getWidth(),getHeight(), 2); - - const float dashLength[] = { 4, 4 }; - - g.setColour(Colour(83,76,69)); - for(int i=1;i line(cellWidth*i,0,cellWidth*i,getHeight()); - g.drawDashedLine(line, dashLength, 2); - } - for(int i=1;i line(2, cellHeight*i,getWidth(),cellHeight*i); - g.drawDashedLine(line, dashLength, 2); - } - for(int i=0;isetBounds(cellWidth*targetCols+1, cellHeight*targetRow+1, cellWidth-2, cellHeight-2); + } } void ProgramListBox::setCartridge(Cartridge &cart) { @@ -97,94 +64,56 @@ void ProgramListBox::addListener(ProgramListBoxListener *listener) { this->listener = listener; } -int ProgramListBox::programPosition(int x, int y) { - return (y / cellHeight) + ((x / cellWidth) * rows); -} - -void ProgramListBox::mouseDown(const MouseEvent &event) { - if ( ! hasContent ) - return; - - int pos = programPosition(event.getMouseDownX(), event.getMouseDownY()); - - if ( event.mods.isPopupMenu()) { - listener->programRightClicked(this, pos); - return; - } - - listener->programSelected(this, pos); -} - -void ProgramListBox::mouseUp(const MouseEvent &event) { -} - -void ProgramListBox::mouseDrag(const MouseEvent &event) { - if ( ! hasContent ) - return; - if ( dragCandidate != -1 ) - return; - if ( event.getDistanceFromDragStart() < 7 ) - return; - - if (DragAndDropContainer* const dragContainer = DragAndDropContainer::findParentDragContainerFor(this)) { - Image snapshot (Image::ARGB, cellWidth, cellHeight, true); - int position = programPosition(event.getMouseDownX(), event.getMouseDownY()); - Graphics g(snapshot); - g.setColour(DXLookNFeel::lightBackground); - g.fillRect(0,0,cellWidth, cellHeight); - g.setColour(Colours::white); - g.drawFittedText(programNames[position], 0, 0, cellWidth, cellHeight, Justification::centred, true); - void *src = cartContent.getRawVoice() + (position*128); - var description = var(src, 128); - dragContainer->startDragging(description, this, snapshot, false); - } -} - -void ProgramListBox::setSelected(int idx) { - selectedPgm = idx; +void ProgramListBox::setActive(int idx) { + activePgm = idx; + repaint(); } Cartridge &ProgramListBox::getCurrentCart() { return cartContent; } -bool ProgramListBox::isInterestedInDragSource (const SourceDetails& dragSourceDetail) { - if ( readOnly ) - return false; - if ( ! hasContent ) - return false; - - Component *comp = dragSourceDetail.sourceComponent.get(); - if ( comp == nullptr ) - return false; - if ( dynamic_cast(comp) == nullptr ) +bool ProgramListBox::keyPressed(const KeyPress &key, Component *originatingComponent) { + ProgramLabel *programLabel = dynamic_cast(getCurrentlyFocusedComponent()); + if ( programLabel == nullptr ) return false; - - return true; -} -void ProgramListBox::itemDropped(const SourceDetails& dragSourceDetails) { - dragCandidate = programPosition(dragSourceDetails.localPosition.x, dragSourceDetails.localPosition.y); - - MemoryBlock* block = dragSourceDetails.description.getBinaryData(); - if ( listener != nullptr ) - listener->programDragged(this, dragCandidate, (char *)block->getData()); - dragCandidate = -1; - repaint(); -} + if ( key.isKeyCode(KeyPress::returnKey) ) { + activePgm = programLabel->idx; + if ( activePgm != -1 ) { + listener->programSelected(this, activePgm); + } + return true; + } -void ProgramListBox::itemDragEnter(const SourceDetails &dragSourceDetails) { - dragCandidate = programPosition(dragSourceDetails.localPosition.x, dragSourceDetails.localPosition.y); - repaint(); -} + int currentIdx = programLabel->idx; + + if ( key.isKeyCode(KeyPress::upKey) ) { + currentIdx--; + if ( currentIdx < 0 ) + currentIdx += rows; + } else if ( key.isKeyCode(KeyPress::downKey) ) { + currentIdx++; + if ( currentIdx >= 32 ) + currentIdx -= rows; + } else if ( key.isKeyCode(KeyPress::leftKey) ) { + currentIdx -= rows; + if ( currentIdx < 0 ) + currentIdx += 32; + } else if ( key.isKeyCode(KeyPress::rightKey) ) { + currentIdx += rows; + if ( currentIdx >= 32 ) + currentIdx -= 32; + } else { + return false; + } -void ProgramListBox::itemDragMove(const SourceDetails &dragSourceDetails) { - dragCandidate = programPosition(dragSourceDetails.localPosition.x, dragSourceDetails.localPosition.y); - repaint(); -} + labels[currentIdx]->grabKeyboardFocus(); -void ProgramListBox::itemDragExit(const SourceDetails &dragSourceDetails) { - dragCandidate = -1; repaint(); + return true; } +void ProgramListBox::paint(Graphics &g) { + g.fillAll(DXLookNFeel::background); +} diff --git a/Source/ProgramListBox.h b/Source/ProgramListBox.h index a8ee40a2..274da137 100644 --- a/Source/ProgramListBox.h +++ b/Source/ProgramListBox.h @@ -1,6 +1,6 @@ /** * - * Copyright (c) 2015 Pascal Gauthier. + * Copyright (c) 2015-2024 Pascal Gauthier. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,51 +23,201 @@ #include "JuceHeader.h" #include "PluginData.h" +#include "DXLookNFeel.h" class ProgramListBox; class ProgramListBoxListener { public: - virtual ~ProgramListBoxListener() {} + virtual ~ProgramListBoxListener() = default; virtual void programSelected(ProgramListBox *source, int pos) = 0; virtual void programRightClicked(ProgramListBox *source, int pos) = 0; virtual void programDragged(ProgramListBox *destListBox, int dest, char *packedPgm) = 0; }; -class ProgramListBox : public Component, public DragAndDropTarget { - ProgramListBoxListener *listener; +class ProgramLabel; +class ProgramListBox : public Component, public KeyListener { + ProgramListBoxListener *listener; + Cartridge cartContent; + std::unique_ptr labels[32]; + bool hasContent; - bool showPgmNumber; int cols, rows; - int cellWidth, cellHeight; - int programPosition(int x, int y); - int selectedPgm; + float cellWidth, cellHeight; + int activePgm; - Cartridge cartContent; - - int dragCandidate; + friend class ProgramLabel; public: - StringArray programNames; - + StringArray programNames; bool readOnly; ProgramListBox(const String name, int numCols); void addListener(ProgramListBoxListener *listener); - void paint(Graphics &g) override; void resized() override; - void mouseDown(const MouseEvent &event) override; - void mouseDrag(const MouseEvent &event) override; - void mouseUp(const MouseEvent &event) override; - void setSelected(int idx); + void paint(Graphics &g) override; + void setActive(int idx); Cartridge &getCurrentCart(); void setCartridge(Cartridge &cart); - - bool isInterestedInDragSource(const SourceDetails& dragSourceDetails) override; - void itemDragEnter(const SourceDetails &dragSourceDetails) override; - void itemDragMove(const SourceDetails &dragSourceDetails) override; - void itemDragExit(const SourceDetails &dragSourceDetails) override; - void itemDropped(const SourceDetails& dragSourceDetails) override; + bool keyPressed (const KeyPress& key, Component* originatingComponent) override; + + ProgramLabel *getProgramComponent(int idx) { + return labels[idx].get(); + } }; +class ProgramLabel : public Component, public DragAndDropTarget { + ProgramListBox *pgmListBox; + bool inDrag = false; + +public: + int idx; + + ProgramLabel(ProgramListBox *pgmListBox, int idx) { + this->pgmListBox = pgmListBox; + this->idx = idx; + setWantsKeyboardFocus(true); + setExplicitFocusOrder(idx+1); + } + + void paint(Graphics &g) override { + if ( inDrag ) { + g.fillAll(DXLookNFeel::background); + return; + } + + if ( pgmListBox->hasContent == false ) + return; + if ( getCurrentlyFocusedComponent() == this ) + g.fillAll(DXLookNFeel::fillColour); + else { + if ( idx % 2 == 0 ) { + auto alternateColour = DXLookNFeel::lightBackground.interpolatedWith (getLookAndFeel().findColour(ListBox::textColourId), 0.75f); + g.fillAll(alternateColour); + } else { + auto alternateColour = DXLookNFeel::lightBackground.interpolatedWith (getLookAndFeel().findColour(ListBox::textColourId), 0.15f); + g.fillAll(alternateColour); + } + } + + if ( idx == pgmListBox->activePgm ) { + g.setColour(Colours::white); + } else { + g.setColour(Colours::black); + } + + g.drawFittedText(getProgramName(), 0, 0, getWidth(), getHeight(), Justification::centred, true); + } + + void focusGained(FocusChangeType cause) override { + repaint(); + } + + void focusLost(FocusChangeType cause) override { + repaint(); + } + + String getProgramName() { + return pgmListBox->cartContent.getProgramName(idx); + } + + void loadProgram() { + if ( ! pgmListBox->hasContent ) + return; + pgmListBox->listener->programSelected(pgmListBox, idx); + } + + void mouseDown(const MouseEvent &event) override { + if ( ! pgmListBox->hasContent ) + return; + + if ( event.mods.isPopupMenu()) { + pgmListBox->listener->programRightClicked(pgmListBox, idx); + return; + } + if ( event.getNumberOfClicks() == 2 ) + loadProgram(); + } + + void mouseDrag(const MouseEvent &event) override { + if ( ! pgmListBox->hasContent ) + return; + if ( event.getDistanceFromDragStart() < 7 ) + return; + + if (DragAndDropContainer* const dragContainer = DragAndDropContainer::findParentDragContainerFor(this)) { + ScaledImage snapshot; + void *src = pgmListBox->cartContent.getRawVoice() + (idx*128); + var description = var(src, 128); + dragContainer->startDragging(description, this, snapshot, false); + } + } + + bool isInterestedInDragSource(const SourceDetails& dragSourceDetails) override { + if ( pgmListBox->readOnly ) + return false; + if ( ! pgmListBox->hasContent ) + return false; + + Component *comp = dragSourceDetails.sourceComponent.get(); + + if ( comp == this ) + return false; + if ( dynamic_cast(comp) == nullptr ) + return false; + + return true; + } + + void itemDragEnter(const SourceDetails &dragSourceDetails) override { + inDrag = true; + repaint(); + } + + void itemDragMove(const SourceDetails &dragSourceDetails) override { + } + + void itemDragExit(const SourceDetails &dragSourceDetails) override { + inDrag = false; + repaint(); + } + + void itemDropped(const SourceDetails& dragSourceDetails) override { + inDrag = false; + + Component *comp = dragSourceDetails.sourceComponent.get(); + ProgramLabel *dest = dynamic_cast(comp); + jassert(dest); + + MemoryBlock* block = dragSourceDetails.description.getBinaryData(); + if ( pgmListBox->listener != nullptr ) + pgmListBox->listener->programDragged(pgmListBox, idx, (char *)block->getData()); + + repaint(); + } + + bool isActive() { + return pgmListBox->hasContent; + } + + struct ProgramLabelAH : public juce::AccessibilityHandler { + explicit ProgramLabelAH(ProgramLabel *s): program(s), juce::AccessibilityHandler(*s, juce::AccessibilityRole::cell, getAccessibilityActions(s)) { + } + + virtual String getTitle() const override { + return String(program->idx + 1) + " " + program->getProgramName(); + } + + ProgramLabel *program; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ProgramLabelAH); + + static AccessibilityActions getAccessibilityActions (ProgramLabel* label) { + return AccessibilityActions().addAction(AccessibilityActionType::press, [label] { label->loadProgram(); }); + } + }; + + std::unique_ptr< AccessibilityHandler> createAccessibilityHandler() override { + return std::make_unique(this); + } +}; #endif // PROGRAMLISTBOX_H_INCLUDED diff --git a/libs/MTS-ESP b/libs/MTS-ESP index 514ed958..803c3aab 160000 --- a/libs/MTS-ESP +++ b/libs/MTS-ESP @@ -1 +1 @@ -Subproject commit 514ed958b38e5ab501999f902a2427ebe6232a78 +Subproject commit 803c3aab3d43dfaea430a1084ab31b606f5cd72c diff --git a/libs/clap-juce-extensions b/libs/clap-juce-extensions index 4491bc30..2c23b918 160000 --- a/libs/clap-juce-extensions +++ b/libs/clap-juce-extensions @@ -1 +1 @@ -Subproject commit 4491bc30223cecf8ff495de943510863b71a7a81 +Subproject commit 2c23b918828ba5fbc5fcb4c95d3a046fbf7e9285 diff --git a/libs/tuning-library b/libs/tuning-library index 601c0eab..3bbe9514 160000 --- a/libs/tuning-library +++ b/libs/tuning-library @@ -1 +1 @@ -Subproject commit 601c0eabff31fff8a543b49d3e48073f338f413c +Subproject commit 3bbe9514816e1ae674c207b09e9f20eea4df372a diff --git a/libs/vst3sdk b/libs/vst3sdk index e9895dc9..56e4b2a6 160000 --- a/libs/vst3sdk +++ b/libs/vst3sdk @@ -1 +1 @@ -Subproject commit e9895dc9ef20bedd93a0fde5ad664bc8b56d4338 +Subproject commit 56e4b2a644be164c5d324e8bc9de55b964b0f102