diff --git a/Documentation/Keybindings.md b/Documentation/Keybindings.md index 67d460a7..b22d80cd 100644 --- a/Documentation/Keybindings.md +++ b/Documentation/Keybindings.md @@ -1,8 +1,11 @@ # 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+Q | Focus on global group | -| CTRL+L | Open cartridge manager | +| 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 | diff --git a/Source/CartManager.cpp b/Source/CartManager.cpp index f80cf2e6..ae79a76f 100644 --- a/Source/CartManager.cpp +++ b/Source/CartManager.cpp @@ -87,29 +87,47 @@ class CartBrowserFocusTraverser : public KeyboardFocusTraverser { } Component* getNextComponent(Component* current) override { - for (int i=0;i(orders[i]); + if ( label != nullptr && !label->isActive() ) + continue; + break; } } - return root; + + if ( i == orders.size() ) + return orders.front(); + return orders[i]; } Component* getPreviousComponent(Component* current) override { - for (int i=0;i=0;i--) { if ( orders[i] == current ) { - i -= 1; - if ( i < 0 ) - return orders.back(); - else - return orders[i]; + srcFound = true; + continue; + } + if ( srcFound ) { + ProgramLabel *label = dynamic_cast(orders[i]); + if ( label != nullptr && !label->isActive() ) + continue; + break; } } - return root; + if ( i == -1 ) + return orders.back(); + return orders[i]; } std::vector getAllComponents(Component* parentComponent) override { @@ -164,13 +182,18 @@ CartManager::CartManager(DexedAudioProcessorEditor *editor) : Component("CartMan addAndMakeVisible(activeCartName.get()); focusOrder.push_back(cartBrowser.get()); - focusOrder.push_back(activeCart.get()); - focusOrder.push_back(browserCart.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()); - focusOrder.push_back(activeCartName.get()); /* * @@ -192,16 +215,16 @@ CartManager::~CartManager() { cartBrowserList.reset(NULL); } -std::unique_ptr CartManager::createFocusTraverser() { +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); @@ -425,11 +448,6 @@ bool CartManager::keyPressed(const KeyPress& key, Component* originatingComponen activeCart->setCartridge(mainWindow->processor->currentCart); return true; } - if ( key.getKeyCode() == KeyPress::escapeKey ) { - hideCartridgeManager(); - return true; - } - return false; } diff --git a/Source/CartManager.h b/Source/CartManager.h index 38c28699..f90b5728 100644 --- a/Source/CartManager.h +++ b/Source/CartManager.h @@ -111,7 +111,7 @@ class CartManager : public Component, public Button::Listener, public DragAndDr void initialFocus(); void hideCartridgeManager(); - std::unique_ptr createFocusTraverser() override; + std::unique_ptr< ComponentTraverser> createKeyboardFocusTraverser() override; }; #endif // CARTMANAGER_H_INCLUDED diff --git a/Source/DXComponents.h b/Source/DXComponents.h index 4427e14c..c2e6301b 100644 --- a/Source/DXComponents.h +++ b/Source/DXComponents.h @@ -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,9 +120,15 @@ 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); @@ -80,8 +139,33 @@ class ProgramSelector : public ComboBox { 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); + } +}; + +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 41809579..23475b18 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()); diff --git a/Source/GlobalEditor.cpp b/Source/GlobalEditor.cpp index 6209a489..89b1ec85 100644 --- a/Source/GlobalEditor.cpp +++ b/Source/GlobalEditor.cpp @@ -453,7 +453,6 @@ GlobalEditor::GlobalEditor () lfoType->addItem("SINE", 5); lfoType->addItem("S&HOLD", 6); lfoType->setImage(lookAndFeel->imageLFO); - lfoType->setTitle("LFO Waveform"); programs = programSelector.get(); diff --git a/Source/OperatorEditor.cpp b/Source/OperatorEditor.cpp index c1d8610d..7484055b 100644 --- a/Source/OperatorEditor.cpp +++ b/Source/OperatorEditor.cpp @@ -314,9 +314,6 @@ OperatorEditor::OperatorEditor () background = lookAndFeel->imageOperator; opSwitch->setTitle("Operator switch"); - opMode->setTitle("Operator Mode"); - kbdLeftCurve->setTitle("Keyboard Left Curve"); - kbdRightCurve->setTitle("Keyboard Right Curve"); setWantsKeyboardFocus(true); //[/Constructor] diff --git a/Source/ParamDialog.cpp b/Source/ParamDialog.cpp index 9d48222a..5045b62b 100644 --- a/Source/ParamDialog.cpp +++ b/Source/ParamDialog.cpp @@ -363,6 +363,75 @@ ParamDialog::ParamDialog () if ( JUCEApplication::isStandaloneApp() ) { sysexIn->setVisible(false); } + + // ACCESSIBLITY + pitchRangeUp->setTitle("Pitch Bend Range Up"); + pitchRangeDn->setTitle("Pitch Bend Range Down"); + pitchStep->setTitle("Pitch Bend Step"); + sysexIn->setTitle("Sysex Input"); + sysexOut->setTitle("Sysex Output"); + sysexChl->setTitle("Sysex Channel"); + engineReso->setTitle("Engine Resolution"); + showKeyboard->setTitle("Show Keyboard"); + whlRange->setTitle("Wheel Range"); + ftRange->setTitle("Foot Range"); + brRange->setTitle("Breath Range"); + atRange->setTitle("After Touch Range"); + whlEg->setTitle("Wheel EG"); + ftEg->setTitle("Foot EG"); + brEg->setTitle("Breath EG"); + atEg->setTitle("After Touch EG"); + whlAmp->setTitle("Wheel Amp"); + ftAmp->setTitle("Foot Amp"); + brAmp->setTitle("Breath Amp"); + atAmp->setTitle("After Touch Amp"); + whlPitch->setTitle("Wheel Pitch"); + ftPitch->setTitle("Foot Pitch"); + brPitch->setTitle("Breath Pitch"); + atPitch->setTitle("After Touch Pitch"); + sclButton->setTitle("Scale Button"); + kbmButton->setTitle("Keyboard Mapping Button"); + showTunButton->setTitle("Show Tuning Button"); + resetTuningButton->setTitle("Reset Tuning Button"); + transposeScale->setTitle("Transpose Scale"); + mpePBRange->setTitle("MPE Pitch Bend Range"); + mpeEnabled->setTitle("MPE Enabled"); + transposeHelp->setTitle("Transpose Help"); + scalingFactor->setTitle("Scaling Factor"); + + pitchRangeUp->setWantsKeyboardFocus(true); + pitchRangeDn->setWantsKeyboardFocus(true); + pitchStep->setWantsKeyboardFocus(true); + sysexIn->setWantsKeyboardFocus(true); + sysexOut->setWantsKeyboardFocus(true); + sysexChl->setWantsKeyboardFocus(true); + engineReso->setWantsKeyboardFocus(true); + showKeyboard->setWantsKeyboardFocus(true); + whlRange->setWantsKeyboardFocus(true); + ftRange->setWantsKeyboardFocus(true); + brRange->setWantsKeyboardFocus(true); + atRange->setWantsKeyboardFocus(true); + whlEg->setWantsKeyboardFocus(true); + ftEg->setWantsKeyboardFocus(true); + brEg->setWantsKeyboardFocus(true); + atEg->setWantsKeyboardFocus(true); + whlAmp->setWantsKeyboardFocus(true); + ftAmp->setWantsKeyboardFocus(true); + brAmp->setWantsKeyboardFocus(true); + atAmp->setWantsKeyboardFocus(true); + whlPitch->setWantsKeyboardFocus(true); + ftPitch->setWantsKeyboardFocus(true); + brPitch->setWantsKeyboardFocus(true); + atPitch->setWantsKeyboardFocus(true); + sclButton->setWantsKeyboardFocus(true); + kbmButton->setWantsKeyboardFocus(true); + showTunButton->setWantsKeyboardFocus(true); + resetTuningButton->setWantsKeyboardFocus(true); + transposeScale->setWantsKeyboardFocus(true); + mpePBRange->setWantsKeyboardFocus(true); + mpeEnabled->setWantsKeyboardFocus(true); + transposeHelp->setWantsKeyboardFocus(true); + scalingFactor->setWantsKeyboardFocus(true); //[/Constructor] } diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index a3e6cdbf..47ec4ccf 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -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); @@ -522,7 +522,9 @@ bool DexedAudioProcessorEditor::keyPressed(const KeyPress& key, Component* origi int keycode = key.getKeyCode(); ModifierKeys mods = key.getModifiers(); - TRACE("key pressed: %d\n", keycode); + #ifdef DEXED_EVENT_DEBUG + TRACE("key pressed: %d\n", keycode); + #endif if ( (keycode >= '1' && keycode <= '6') && mods.isCtrlDown() ) { int op = keycode - '1'; @@ -546,5 +548,15 @@ bool DexedAudioProcessorEditor::keyPressed(const KeyPress& key, Component* origi 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 4fd1726b..144b21be 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -41,7 +41,9 @@ class DexedAudioProcessorEditor : public AudioProcessorEditor, public ComboBox: Component cartManagerCover; SharedResourcePointer lookAndFeel; - + #ifdef DEXED_EVENT_DEBUG + FocusLogger focusLogger; + #endif public: DexedAudioProcessor *processor; GlobalEditor global; diff --git a/Source/PluginParam.cpp b/Source/PluginParam.cpp index b242122d..e4f7f83b 100644 --- a/Source/PluginParam.cpp +++ b/Source/PluginParam.cpp @@ -63,6 +63,7 @@ void Ctrl::bind(Slider *s) { void Ctrl::bind(Button *b) { button = b; updateComponent(); + b->setTitle(label); b->addListener(this); b->addMouseListener(this, true); } @@ -70,6 +71,7 @@ void Ctrl::bind(Button *b) { void Ctrl::bind(ComboBox *c) { comboBox = c; updateComponent(); + c->setTitle(label); c->addListener(this); c->addMouseListener(this, true); } diff --git a/Source/ProgramListBox.cpp b/Source/ProgramListBox.cpp index 20e43ad0..fb196c04 100644 --- a/Source/ProgramListBox.cpp +++ b/Source/ProgramListBox.cpp @@ -23,150 +23,6 @@ #include "DXLookNFeel.h" #include "Dexed.h" -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.setColour(Colours::black); - g.fillRect(0,0,getWidth(), getHeight()); - 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.03f); - g.fillAll(alternateColour); - } else { - g.fillAll(DXLookNFeel::lightBackground); - } - } - - 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 mouseDown(const MouseEvent &event) override { - if ( ! pgmListBox->hasContent ) - return; - - if ( event.mods.isPopupMenu()) { - pgmListBox->listener->programRightClicked(pgmListBox, idx); - return; - } - if ( event.getNumberOfClicks() == 2 ) - pgmListBox->listener->programSelected(pgmListBox, idx); - } - - void mouseDrag(const MouseEvent &event) override { - if ( ! pgmListBox->hasContent ) - return; - if ( event.getDistanceFromDragStart() < 7 ) - return; - - if (DragAndDropContainer* const dragContainer = DragAndDropContainer::findParentDragContainerFor(this)) { - Image snapshot (Image::ARGB, getWidth(), getHeight(), true); - Graphics g(snapshot); - paint(g); - 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, dest->idx, (char *)block->getData()); - - repaint(); - } - - struct ProgramLabelAH : public juce::AccessibilityHandler { - explicit ProgramLabelAH(ProgramLabel *s): program(s), juce::AccessibilityHandler(*s, juce::AccessibilityRole::listItem) { - } - - virtual String getTitle() const override { - return String(program->idx + 1) + " " + program->getProgramName(); - } - - ProgramLabel *program; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ProgramLabelAH); - }; - - std::unique_ptr< AccessibilityHandler> createAccessibilityHandler() override { - return std::make_unique(this); - } -}; - ProgramListBox::ProgramListBox(const String name, int numCols) : Component(name) { cols = numCols; rows = 32 / numCols; @@ -187,13 +43,13 @@ ProgramListBox::ProgramListBox(const String name, int numCols) : Component(name) } void ProgramListBox::resized() { - cellWidth = getWidth() / cols; - cellHeight = getHeight() / rows; + cellWidth = (float) getWidth() / cols; + cellHeight = (float) getHeight() / rows; for(int i=0;i<32;i++) { int targetCols = i / rows; int targetRow = i % rows; - labels[i].get()->setBounds(targetCols*cellWidth, targetRow*cellHeight, cellWidth, cellHeight); + labels[i].get()->setBounds(cellWidth*targetCols+1, cellHeight*targetRow+1, cellWidth-2, cellHeight-2); } } @@ -258,98 +114,6 @@ bool ProgramListBox::keyPressed(const KeyPress &key, Component *originatingCompo return true; } -/* - 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;i(comp) == 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(); -} - -void ProgramListBox::itemDragEnter(const SourceDetails &dragSourceDetails) { - dragCandidate = programPosition(dragSourceDetails.localPosition.x, dragSourceDetails.localPosition.y); - repaint(); -} - -void ProgramListBox::itemDragMove(const SourceDetails &dragSourceDetails) { - dragCandidate = programPosition(dragSourceDetails.localPosition.x, dragSourceDetails.localPosition.y); - repaint(); -} - -void ProgramListBox::itemDragExit(const SourceDetails &dragSourceDetails) { - dragCandidate = -1; - repaint(); + g.fillAll(DXLookNFeel::background); } -*/ diff --git a/Source/ProgramListBox.h b/Source/ProgramListBox.h index 0bd92977..5336d6ca 100644 --- a/Source/ProgramListBox.h +++ b/Source/ProgramListBox.h @@ -23,6 +23,7 @@ #include "JuceHeader.h" #include "PluginData.h" +#include "DXLookNFeel.h" class ProgramListBox; class ProgramListBoxListener { @@ -33,14 +34,15 @@ class ProgramListBoxListener { virtual void programDragged(ProgramListBox *destListBox, int dest, char *packedPgm) = 0; }; +class ProgramLabel; class ProgramListBox : public Component, public KeyListener { ProgramListBoxListener *listener; Cartridge cartContent; - std::unique_ptr labels[32]; + std::unique_ptr labels[32]; bool hasContent; int cols, rows; - int cellWidth, cellHeight; + float cellWidth, cellHeight; int activePgm; friend class ProgramLabel; @@ -51,12 +53,174 @@ class ProgramListBox : public Component, public KeyListener { ProgramListBox(const String name, int numCols); void addListener(ProgramListBoxListener *listener); void resized() override; + void paint(Graphics &g) override; void setActive(int idx); Cartridge &getCurrentCart(); void setCartridge(Cartridge &cart); 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.setColour(Colours::black); + g.fillRect(0,0,getWidth(), getHeight()); + 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)) { + Image snapshot (Image::ARGB, getWidth(), getHeight(), true); + Graphics g(snapshot); + paint(g); + 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, dest->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