New upstream version 0.21.0
authorDennis Braun <d_braun@kabelmail.de>
Wed, 4 May 2022 15:29:26 +0000 (17:29 +0200)
committerDennis Braun <d_braun@kabelmail.de>
Wed, 4 May 2022 15:29:26 +0000 (17:29 +0200)
108 files changed:
CMakeLists.txt
src/core/actions/actionRecorder.cpp
src/core/actions/actionRecorder.h
src/core/channels/midiLighter.cpp
src/core/channels/midiLighter.h
src/core/channels/sampleReactor.h
src/core/conf.cpp
src/core/conf.h
src/core/const.h
src/core/kernelAudio.cpp
src/core/kernelAudio.h
src/core/midiDispatcher.cpp
src/core/midiDispatcher.h
src/core/midiLearnParam.cpp
src/core/midiLearnParam.h
src/core/midiMapper.cpp
src/core/model/model.cpp
src/core/model/storage.cpp
src/core/plugins/plugin.cpp
src/core/plugins/plugin.h
src/core/plugins/pluginHost.cpp
src/core/plugins/pluginHost.h
src/core/plugins/pluginManager.cpp
src/core/plugins/pluginState.cpp
src/core/plugins/pluginState.h
src/core/queue.h
src/core/ringBuffer.h
src/glue/config.cpp
src/glue/events.cpp
src/glue/events.h
src/glue/io.cpp
src/glue/io.h
src/glue/layout.cpp
src/glue/layout.h
src/glue/main.cpp
src/glue/storage.cpp
src/gui/dialogs/actionEditor/sampleActionEditor.cpp
src/gui/dialogs/config.cpp
src/gui/dialogs/config.h
src/gui/dialogs/keyGrabber.cpp
src/gui/dialogs/keyGrabber.h
src/gui/dialogs/mainWindow.cpp
src/gui/dialogs/midiIO/midiInputChannel.cpp
src/gui/dialogs/midiIO/midiInputChannel.h
src/gui/dialogs/midiIO/midiInputMaster.cpp
src/gui/dialogs/midiIO/midiInputMaster.h
src/gui/dialogs/midiIO/midiOutputMidiCh.cpp
src/gui/dialogs/midiIO/midiOutputMidiCh.h
src/gui/dialogs/pluginChooser.cpp
src/gui/dialogs/pluginChooser.h
src/gui/dialogs/pluginWindow.h
src/gui/dialogs/pluginWindowGUI.cpp
src/gui/dialogs/sampleEditor.cpp
src/gui/dialogs/sampleEditor.h
src/gui/dialogs/window.cpp
src/gui/dispatcher.cpp
src/gui/dispatcher.h
src/gui/drawing.cpp
src/gui/drawing.h
src/gui/elems/actionEditor/gridTool.cpp
src/gui/elems/actionEditor/gridTool.h
src/gui/elems/basics/box.cpp
src/gui/elems/basics/box.h
src/gui/elems/basics/choice.cpp
src/gui/elems/basics/choice.h
src/gui/elems/basics/flex.cpp
src/gui/elems/basics/group.cpp
src/gui/elems/basics/group.h
src/gui/elems/basics/liquidScroll.cpp
src/gui/elems/basics/liquidScroll.h
src/gui/elems/basics/resizerBar.cpp
src/gui/elems/basics/scrollPack.cpp
src/gui/elems/basics/scrollPack.h
src/gui/elems/basics/tabs.cpp [new file with mode: 0644]
src/gui/elems/basics/tabs.h [new file with mode: 0644]
src/gui/elems/config/tabAudio.cpp
src/gui/elems/config/tabAudio.h
src/gui/elems/config/tabBehaviors.cpp
src/gui/elems/config/tabBehaviors.h
src/gui/elems/config/tabBindings.cpp [new file with mode: 0644]
src/gui/elems/config/tabBindings.h [new file with mode: 0644]
src/gui/elems/config/tabMidi.cpp
src/gui/elems/config/tabMidi.h
src/gui/elems/config/tabMisc.cpp
src/gui/elems/config/tabMisc.h
src/gui/elems/config/tabPlugins.cpp
src/gui/elems/config/tabPlugins.h
src/gui/elems/keyBinder.cpp [new file with mode: 0644]
src/gui/elems/keyBinder.h [new file with mode: 0644]
src/gui/elems/mainWindow/keyboard/midiChannel.cpp
src/gui/elems/mainWindow/keyboard/sampleChannel.cpp
src/gui/elems/mainWindow/mainTimer.cpp
src/gui/elems/midiIO/midiLearner.cpp
src/gui/elems/midiIO/midiLearner.h
src/gui/elems/midiIO/midiLearnerPack.cpp
src/gui/elems/plugin/pluginElement.cpp
src/gui/elems/plugin/pluginElement.h
src/gui/elems/plugin/pluginParameter.h
src/gui/elems/sampleEditor/boostTool.cpp
src/gui/elems/sampleEditor/boostTool.h
src/gui/ui.cpp
src/gui/ui.h
src/main.cpp
src/utils/fs.cpp
src/utils/gui.cpp
src/utils/gui.h
src/utils/string.cpp
src/utils/vector.h

index 4aeb5e3b90e2b6553f2f8c6b3ac2acf56af65972..5ec69d1a8bb3865fa58214c39c06911b73ccce3c 100644 (file)
@@ -132,6 +132,7 @@ list(APPEND SOURCES
        src/gui/elems/midiIO/midiLearnerPack.cpp
     src/gui/elems/fileBrowser.cpp
        src/gui/elems/soundMeter.cpp
+       src/gui/elems/keyBinder.cpp
        src/gui/elems/plugin/pluginBrowser.cpp
        src/gui/elems/plugin/pluginParameter.cpp
        src/gui/elems/plugin/pluginElement.cpp
@@ -175,6 +176,7 @@ list(APPEND SOURCES
        src/gui/elems/config/tabAudio.cpp
        src/gui/elems/config/tabBehaviors.cpp
        src/gui/elems/config/tabPlugins.cpp
+       src/gui/elems/config/tabBindings.cpp
        src/gui/elems/basics/scroll.cpp
        src/gui/elems/basics/pack.cpp
        src/gui/elems/basics/group.cpp
@@ -194,6 +196,7 @@ list(APPEND SOURCES
        src/gui/elems/basics/split.cpp
        src/gui/elems/basics/browser.cpp
        src/gui/elems/basics/flex.cpp
+       src/gui/elems/basics/tabs.cpp
        src/utils/log.cpp
        src/utils/time.cpp
        src/utils/math.cpp
index 9043034e3629457967faddb250ffd95825fd6a82..12cdaa45e6abcddca145842b970a767420901c3f 100644 (file)
@@ -35,6 +35,7 @@
 #include <algorithm>
 #include <cassert>
 #include <cmath>
+#include <cstddef>
 #include <unordered_map>
 
 namespace giada::m
@@ -351,4 +352,4 @@ void ActionRecorder::updateEvent(ID id, MidiEvent e)
 {
        m_actions.updateEvent(id, e);
 }
-} // namespace giada::m
\ No newline at end of file
+} // namespace giada::m
index ebbe27bc55075a4ff809a1b42ebdbb5966495307..157dc05db979b9f1964cac2ba9888348852e93b9 100644 (file)
@@ -30,6 +30,7 @@
 #include "core/actions/actions.h"
 #include "core/midiEvent.h"
 #include "core/types.h"
+#include <cstddef>
 #include <unordered_set>
 
 namespace giada::m::patch
index 45056e8fbb973c4e30440554c4e93cf8ac95bde4..d21415fef7593856025d39ee30447f4fc784b238 100644 (file)
@@ -121,8 +121,8 @@ void MidiLighter<KernelMidiI>::send(uint32_t learnt, const MidiMap::Message& msg
 
 /* -------------------------------------------------------------------------- */
 
-template struct MidiLighter<KernelMidi>;
+template class MidiLighter<KernelMidi>;
 #ifdef WITH_TESTS
-template struct MidiLighter<KernelMidiMock>;
+template class MidiLighter<KernelMidiMock>;
 #endif
 } // namespace giada::m
\ No newline at end of file
index a58b39f51df364374058c8b8968edc018546f94c..8d540da293b73c097e5737277d388ba3d580c6a3 100644 (file)
@@ -68,9 +68,9 @@ private:
        MidiMapper<KernelMidiI>* m_midiMapper;
 };
 
-extern template struct MidiLighter<KernelMidi>;
+extern template class MidiLighter<KernelMidi>;
 #ifdef WITH_TESTS
-extern template struct MidiLighter<KernelMidiMock>;
+extern template class MidiLighter<KernelMidiMock>;
 #endif
 } // namespace giada::m
 
index 8761f2efc1228aa5f561a0622ecaca9d7c06b20a..0915779776d1bca06e610674f477197f3ade81f9 100644 (file)
@@ -39,7 +39,7 @@ class Model;
 namespace giada::m
 {
 class Channel;
-class ChannelShared;
+struct ChannelShared;
 class Sequencer;
 
 /* SampleReactor
index cf8437dd8e6df14ec00b09d1da5b0420d12ffe9f..ed38bf84649ed533af7b5b2b6ecb9b38f7f5b745 100644 (file)
@@ -142,6 +142,13 @@ bool Conf::read()
        data.midiInVolumeOut            = j.value(CONF_KEY_MIDI_IN_VOLUME_OUT, data.midiInVolumeOut);
        data.midiInBeatDouble           = j.value(CONF_KEY_MIDI_IN_BEAT_DOUBLE, data.midiInBeatDouble);
        data.midiInBeatHalf             = j.value(CONF_KEY_MIDI_IN_BEAT_HALF, data.midiInBeatHalf);
+
+       data.keyBindings[KEY_BIND_PLAY]           = j.value(CONF_KEY_BIND_PLAY, 0);
+       data.keyBindings[KEY_BIND_REWIND]         = j.value(CONF_KEY_BIND_REWIND, 0);
+       data.keyBindings[KEY_BIND_RECORD_ACTIONS] = j.value(CONF_KEY_BIND_RECORD_ACTIONS, 0);
+       data.keyBindings[KEY_BIND_RECORD_INPUT]   = j.value(CONF_KEY_BIND_RECORD_INPUT, 0);
+       data.keyBindings[KEY_BIND_EXIT]           = j.value(CONF_KEY_BIND_EXIT, 0);
+
 #ifdef WITH_VST
        data.pluginChooserX   = j.value(CONF_KEY_PLUGIN_CHOOSER_X, data.pluginChooserX);
        data.pluginChooserY   = j.value(CONF_KEY_PLUGIN_CHOOSER_Y, data.pluginChooserY);
@@ -238,6 +245,13 @@ bool Conf::write() const
        j[CONF_KEY_REC_TRIGGER_MODE]              = static_cast<int>(data.recTriggerMode);
        j[CONF_KEY_REC_TRIGGER_LEVEL]             = data.recTriggerLevel;
        j[CONF_KEY_INPUT_REC_MODE]                = static_cast<int>(data.inputRecMode);
+
+       j[CONF_KEY_BIND_PLAY]           = data.keyBindings.find(KEY_BIND_PLAY)->second;
+       j[CONF_KEY_BIND_REWIND]         = data.keyBindings.find(KEY_BIND_REWIND)->second;
+       j[CONF_KEY_BIND_RECORD_ACTIONS] = data.keyBindings.find(KEY_BIND_RECORD_ACTIONS)->second;
+       j[CONF_KEY_BIND_RECORD_INPUT]   = data.keyBindings.find(KEY_BIND_RECORD_INPUT)->second;
+       j[CONF_KEY_BIND_EXIT]           = data.keyBindings.find(KEY_BIND_EXIT)->second;
+
 #ifdef WITH_VST
        j[CONF_KEY_PLUGIN_CHOOSER_X]   = data.pluginChooserX;
        j[CONF_KEY_PLUGIN_CHOOSER_Y]   = data.pluginChooserY;
index 20e032ddf1c807e15ad6f00b1f22e27096cd87b8..85418789d6fcc734f84e13b10bcba7052c5178dc 100644 (file)
 #include "core/types.h"
 #include "utils/gui.h"
 #include <string>
+#include <unordered_map>
 
 namespace giada::m
 {
 class Conf final
 {
 public:
+       using KeyBindings = std::unordered_map<int, int>;
+
+       static constexpr int KEY_BIND_PLAY           = 1;
+       static constexpr int KEY_BIND_REWIND         = 2;
+       static constexpr int KEY_BIND_RECORD_ACTIONS = 3;
+       static constexpr int KEY_BIND_RECORD_INPUT   = 4;
+       static constexpr int KEY_BIND_EXIT           = 5;
+
        struct Data
        {
                int  logMode          = LOG_MODE_MUTE;
@@ -133,6 +142,13 @@ public:
                int pluginSortMethod = 0;
 
 #endif
+
+               KeyBindings keyBindings = {
+                   {KEY_BIND_PLAY, ' '},
+                   {KEY_BIND_REWIND, FL_BackSpace},
+                   {KEY_BIND_RECORD_ACTIONS, FL_Enter},
+                   {KEY_BIND_RECORD_INPUT, FL_End},
+                   {KEY_BIND_EXIT, FL_Escape}};
        };
 
        Conf();
index e77db0c8a205609522ba563227d886e02f0a1137..db9bc5bab52d4e2e70962b8835e3452291276b60 100644 (file)
 
 /* -- version --------------------------------------------------------------- */
 constexpr auto G_APP_NAME      = "Giada";
-constexpr auto G_VERSION_STR   = "0.20.1";
+constexpr auto G_VERSION_STR   = "0.21.0";
 constexpr int  G_VERSION_MAJOR = 0;
-constexpr int  G_VERSION_MINOR = 20;
-constexpr int  G_VERSION_PATCH = 1;
+constexpr int  G_VERSION_MINOR = 21;
+constexpr int  G_VERSION_PATCH = 0;
 
 constexpr auto CONF_FILENAME = "giada.conf";
 
@@ -444,6 +444,11 @@ constexpr auto CONF_KEY_PLUGIN_SORT_METHOD            = "plugin_sort_method";
 constexpr auto CONF_KEY_REC_TRIGGER_MODE              = "rec_trigger_mode";
 constexpr auto CONF_KEY_REC_TRIGGER_LEVEL             = "rec_trigger_level";
 constexpr auto CONF_KEY_INPUT_REC_MODE                = "input_rec_mode";
+constexpr auto CONF_KEY_BIND_PLAY                     = "key_bind_play";
+constexpr auto CONF_KEY_BIND_REWIND                   = "key_bind_rewind";
+constexpr auto CONF_KEY_BIND_RECORD_ACTIONS           = "key_bind_record_actions";
+constexpr auto CONF_KEY_BIND_RECORD_INPUT             = "key_bind_record_input";
+constexpr auto CONF_KEY_BIND_EXIT                     = "key_bind_record_exit";
 
 /* JSON midimaps keys */
 
index 8c10d8e0abdb614c7ffe7cf5396e533419b08be9..4fa059f0d0e4976e993dd1f9c581bb8087a9597d 100644 (file)
@@ -32,6 +32,7 @@
 #include "utils/log.h"
 #include "utils/vector.h"
 #include <cassert>
+#include <cstddef>
 
 namespace giada::m
 {
index ce3c57bdf86e0742c81d810b3ec3d6e905bf96c9..e509e919f626d105efaa4a9971aacd014e27c300 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "core/conf.h"
 #include "deps/rtaudio/RtAudio.h"
+#include <cstddef>
 #include <functional>
 #include <memory>
 #include <string>
index 0d065214a8cafe973c8cea8bb58f7ee83d8aa857..2cff6997c259b549c5bc847ed20983548fcd2d9f 100644 (file)
@@ -38,6 +38,7 @@
 #include "utils/log.h"
 #include "utils/math.h"
 #include <cassert>
+#include <cstddef>
 #include <vector>
 
 namespace giada::m
index 2a52c0d7d91a1d71e3921d9d3fc0f0fb04a484f5..8bef6a42046022d414515cecc55b58c2e1ef0fd5 100644 (file)
@@ -31,6 +31,7 @@
 #include "core/midiEvent.h"
 #include "core/model/model.h"
 #include "core/types.h"
+#include <cstddef>
 #include <cstdint>
 #include <functional>
 
index 83c820a28a0cdf39e3a3e5dccdb4d1b6f990e814..8166aee967ccef8758f8447636ae3060e9ed0ca1 100644 (file)
@@ -25,6 +25,7 @@
  * -------------------------------------------------------------------------- */
 
 #include "midiLearnParam.h"
+#include <cstddef>
 
 namespace giada::m
 {
@@ -56,4 +57,4 @@ std::size_t MidiLearnParam::getIndex() const
 {
        return m_index;
 }
-} // namespace giada::m
\ No newline at end of file
+} // namespace giada::m
index f2e7738805623c67238042f05e2e62bd321373e0..bf51f803bdac33bf451b958fff3f500c822ccd60 100644 (file)
@@ -28,6 +28,7 @@
 #define G_MIDI_LEARN_PARAM_H
 
 #include "core/weakAtomic.h"
+#include <cstddef>
 #include <atomic>
 
 namespace giada::m
index bee78b87ce40bee50b5c4cd06889ad3295c0f32b..839d69d12ac2163008e57aed04a626c941e66fbc 100644 (file)
@@ -31,6 +31,7 @@
 #include "utils/fs.h"
 #include "utils/log.h"
 #include "utils/string.h"
+#include <cstddef>
 #include <cstring>
 #include <filesystem>
 #include <fstream>
@@ -275,4 +276,4 @@ template class MidiMapper<KernelMidi>;
 #ifdef WITH_TESTS
 template class MidiMapper<KernelMidiMock>;
 #endif
-} // namespace giada::m
\ No newline at end of file
+} // namespace giada::m
index ca27e4cb484491f671ffb784e473661af1ea2bb6..f7b7daa734e15fedd8e237605546fd8e865fd332 100644 (file)
@@ -111,6 +111,7 @@ Model::Model()
 
 void Model::reset()
 {
+       get()    = {};
        m_shared = {};
 
        get().sequencer.shared = &m_shared.sequencerShared;
index c0cc3c8dfab8b6c084296943d18597f3f2891cbe..1065b9172ae0001bc6833f3e42f8b8e62050ab01 100644 (file)
@@ -116,7 +116,7 @@ void store(Conf::Data& conf)
 
 LoadState load(const Patch::Data& patch)
 {
-       DataLock lock = g_engine.model.lockData();
+       DataLock lock = g_engine.model.lockData(SwapType::NONE);
 
        /* Clear and re-initialize channels first. */
 
index 23c07cdfc77dc1fa00f63d88606c1cdd2cc52615..23e1ddeac5dbfec5f1b02971bf10239f04dcd710 100644 (file)
@@ -191,11 +191,11 @@ bool Plugin::isSuspended() const
 
 /* -------------------------------------------------------------------------- */
 
-bool Plugin::acceptsMidi() const
+bool Plugin::isInstrument() const
 {
        if (!valid)
                return false;
-       return m_plugin->acceptsMidi();
+       return m_plugin->acceptsMidi() && m_plugin->getTotalNumInputChannels() == 0;
 }
 
 /* -------------------------------------------------------------------------- */
@@ -216,37 +216,15 @@ void Plugin::setBypass(bool b) { m_bypass.store(b); }
 
 /* -------------------------------------------------------------------------- */
 
-void Plugin::process(juce::AudioBuffer<float>& out, juce::MidiBuffer m)
+const Plugin::Buffer& Plugin::process(const Plugin::Buffer& out, juce::MidiBuffer m)
 {
-       /* If this is not an instrument (i.e. doesn't accept MIDI), copy the 
-       incoming buffer data into the temporary one. This way FXes will process
-       existing audio data. Conversely, if the plug-in is an instrument, it 
-       generates its own audio data inside a clean m_buffer and we can play more 
-       than one plug-in instrument in the same stack, driven by the same set of 
-       MIDI events. */
-
-       const bool isInstrument = m_plugin->acceptsMidi();
-
-       if (!isInstrument)
-               m_buffer = out;
-       else
-               m_buffer.clear();
+       /* Copy the incoming buffer data into the temporary one. This way FXes will 
+       process existing audio data on the private buffer. This is needed later on
+       when merging it back into the incoming buffer. */
 
+       m_buffer = out;
        m_plugin->processBlock(m_buffer, m);
-
-       /* The local buffer is now filled. Let's try to fill the 'out' one as well
-       by taking into account the bus layout - many plug-ins might have mono output
-       and we have a stereo buffer to fill. */
-
-       for (int i = 0, j = 0; i < out.getNumChannels(); i++)
-       {
-               if (isInstrument)
-                       out.addFrom(i, 0, m_buffer, j, 0, m_buffer.getNumSamples());
-               else
-                       out.copyFrom(i, 0, m_buffer, j, 0, m_buffer.getNumSamples());
-               if (i < countMainOutChannels() - 1)
-                       j++;
-       }
+       return m_buffer;
 }
 
 /* -------------------------------------------------------------------------- */
index 6f1758df53d27d8ef4fa15f88e0cf03a545f6b79..06093270faf737ee302f6484d487f867204808f1 100644 (file)
@@ -42,6 +42,8 @@ namespace giada::m
 class Plugin : private juce::ComponentListener
 {
 public:
+       using Buffer = juce::AudioBuffer<float>;
+
        /* Plugin (1)
        Constructs an invalid plug-in. */
 
@@ -73,23 +75,28 @@ public:
        std::string                 getParameterLabel(int index) const;
        bool                        isSuspended() const;
        bool                        isBypassed() const;
+       bool                        isInstrument() const;
        int                         getNumPrograms() const;
        int                         getCurrentProgram() const;
        std::string                 getProgramName(int index) const;
        void                        setParameter(int index, float value) const;
        void                        setCurrentProgram(int index) const;
-       bool                        acceptsMidi() const;
        PluginState                 getState() const;
        juce::AudioProcessorEditor* createEditor() const;
 
+       /* countMainOutChannels
+       Returns the current channel layout for the main output bus. */
+
+       int countMainOutChannels() const;
+
        /* process
-       Process the plug-in with audio and MIDI data. The audio buffer is a reference:
-       it has to be altered by the plug-in itself. Conversely, the MIDI buffer must
-       be passed by copy: each plug-in must receive its own copy of the event set, so
-       that any attempt to change/clear the MIDI buffer will only modify the local 
-       copy. */
+       Process the plug-in with audio and MIDI data. The audio buffer is a 
+       reference, while the MIDI buffer must be passed by copy: each plug-in must 
+       receive its own copy of the event set, so that any attempt to change/clear 
+       the MIDI buffer will only modify the local copy. Returns a reference of the
+       local buffer filled with processed data. */
 
-       void process(juce::AudioBuffer<float>& b, juce::MidiBuffer m);
+       const Buffer& process(const Buffer& b, juce::MidiBuffer m);
 
        void setState(PluginState p);
        void setBypass(bool b);
@@ -131,14 +138,9 @@ private:
 
        juce::AudioProcessor::Bus* getMainBus(BusType b) const;
 
-       /* countMainOutChannels
-       Returns the current channel layout for the main output bus. */
-
-       int countMainOutChannels() const;
-
        std::unique_ptr<juce::AudioPluginInstance> m_plugin;
        std::unique_ptr<PluginHost::Info>          m_playHead;
-       juce::AudioBuffer<float>                   m_buffer;
+       Buffer                                     m_buffer;
 
        std::atomic<bool> m_bypass;
 
index 76c58e55e9231c41f3e582278e250fad6dce7ab5..4d8c724fff72b04c90751f3366ceaee8b9659592 100644 (file)
@@ -36,6 +36,7 @@
 #include "utils/log.h"
 #include "utils/vector.h"
 #include <cassert>
+#include <cstddef>
 #include <memory>
 
 namespace giada::m
@@ -89,22 +90,16 @@ void PluginHost::processStack(mcl::AudioBuffer& outBuf, const std::vector<Plugin
 {
        assert(outBuf.countFrames() == m_audioBuffer.getNumSamples());
 
-       /* If events are null: Audio stack processing (master in, master out or
-       sample channels. No need for MIDI events. 
-       If events are not null: MIDI stack (MIDI channels). MIDI channels must not 
-       process the current buffer: give them an empty and clean one. */
+       giadaToJuceTempBuf(outBuf);
 
        if (events == nullptr)
        {
-               giadaToJuceTempBuf(outBuf);
                juce::MidiBuffer dummyEvents; // empty
                processPlugins(plugins, dummyEvents);
        }
        else
-       {
-               m_audioBuffer.clear();
                processPlugins(plugins, *events);
-       }
+
        juceToGiadaOutBuf(outBuf);
 }
 
@@ -203,10 +198,37 @@ void PluginHost::processPlugins(const std::vector<Plugin*>& plugins, juce::MidiB
        {
                if (!p->valid || p->isSuspended() || p->isBypassed())
                        continue;
-               p->process(m_audioBuffer, events);
+               processPlugin(p, events);
        }
        events.clear();
 }
+
+/* -------------------------------------------------------------------------- */
+
+void PluginHost::processPlugin(Plugin* p, const juce::MidiBuffer& events)
+{
+       const Plugin::Buffer& pluginBuffer = p->process(m_audioBuffer, events);
+       const bool            isInstrument = p->isInstrument();
+
+       /* Merge the plugin buffer back into the local one. Special care is needed
+       if audio channels mismatch. */
+
+       for (int i = 0, j = 0; i < m_audioBuffer.getNumChannels(); i++)
+       {
+               /* If instrument (i.e. a plug-in that accepts MIDI and produces audio 
+               out of it), SUM the local working buffer to the main one. This allows
+               multiple plug-in instruments to play simultaneously on a given set of
+               MIDI events. If it's a normal FX instead (!isInstrument), the local
+               working buffer is simply copied over the main one. */
+
+               if (isInstrument)
+                       m_audioBuffer.addFrom(i, 0, pluginBuffer, j, 0, pluginBuffer.getNumSamples());
+               else
+                       m_audioBuffer.copyFrom(i, 0, pluginBuffer, j, 0, pluginBuffer.getNumSamples());
+               if (i < p->countMainOutChannels() - 1)
+                       j++;
+       }
+}
 } // namespace giada::m
 
 #endif // #ifdef WITH_VST
index 9e198170e2dfdd85695524ada3f1e042e8f3f868..2663690d7b15024572940d21135d3c3bebea3440 100644 (file)
@@ -112,15 +112,20 @@ public:
        void toggleBypass(ID pluginId);
 
 private:
+       /* giadaToJuceTempBuf
+       Copies the Giada buffer 'outBuf' to the private JUCE buffer for local
+       processing. */
+
        void giadaToJuceTempBuf(const mcl::AudioBuffer& outBuf);
 
        /* juceToGiadaOutBuf
-       Converts buffer from Juce to Giada. A note for the future: if we overwrite (=) 
-       (as we do now) it's SEND, if we add (+) it's INSERT. */
+       Copies the private JUCE buffer to Giada buffer 'outBuf'. */
 
        void juceToGiadaOutBuf(mcl::AudioBuffer& outBuf) const;
 
-       void processPlugins(const std::vector<Plugin*>& plugins, juce::MidiBuffer& events);
+       void processPlugins(const std::vector<Plugin*>&, juce::MidiBuffer& events);
+
+       void processPlugin(Plugin*, const juce::MidiBuffer& events);
 
        model::Model& m_model;
 
index e58c406ef376b189bd856b99ea05be56475288d6..8ffbf6e3b7b882cda4c2441f613730a015737cb4 100644 (file)
@@ -35,6 +35,7 @@
 #include "utils/log.h"
 #include "utils/string.h"
 #include <cassert>
+#include <cstddef>
 #include <memory>
 
 namespace giada::m
index 14a54033246354d498d19484967bddfd3c0a3cf5..17cb8f1097270eb7145eeacefa6abd7a870757e5 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "pluginState.h"
 #include "core/const.h"
+#include <cstddef>
 
 namespace giada::m
 {
index 7a5d4402c4fbb703bc8ff7cb4f2e1f2dc339a574..753e66d4e8753f01d12fd666e8c6ecb5540f8381 100644 (file)
@@ -30,6 +30,7 @@
 #define G_PLUGIN_STATE_H
 
 #include "deps/juce-config.h"
+#include <cstddef>
 #include <string>
 
 namespace giada::m
@@ -52,4 +53,4 @@ private:
 
 #endif
 
-#endif // #ifdef WITH_VST
\ No newline at end of file
+#endif // #ifdef WITH_VST
index 7b07395a6b156b9a8be0fe2e6839d64e35a80111..811a50df17a70ec4dd19a4acc7f5a0e58bcff2d8 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef G_QUEUE_H
 #define G_QUEUE_H
 
+#include <cstddef>
 #include <array>
 #include <atomic>
 
index 9e7032c945aa1c5dda9f10447802aa88c845c301..04690a5d43e3150dd74899ab462f5d74c9940a3d 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef G_RING_BUFFER_H
 #define G_RING_BUFFER_H
 
+#include <cstddef>
 #include <array>
 
 namespace giada
index 8a962ead32cc8a95614f8a9816484bd3df7a2c64..db2c926d66e48169dd0c8e45681783f8638d77c9 100644 (file)
@@ -41,6 +41,7 @@
 #include "utils/fs.h"
 #include "utils/vector.h"
 #include <FL/Fl_Tooltip.H>
+#include <cstddef>
 
 extern giada::v::Ui     g_ui;
 extern giada::m::Engine g_engine;
@@ -328,4 +329,4 @@ void setPluginPathCb(void* data)
        configWin->tabPlugins->rebuild();
 }
 #endif
-} // namespace giada::c::config
\ No newline at end of file
+} // namespace giada::c::config
index 2d8b4ca2e7500fe4873523da72d87cdd23864b79..baa91d1df9be4c0f47a8d22e2d07259f5223bba0 100644 (file)
@@ -251,6 +251,14 @@ void rewindSequencer(Thread t)
 
 /* -------------------------------------------------------------------------- */
 
+void stopActionRecording()
+{
+       if (g_engine.kernelAudio.isReady() && g_engine.recorder.isRecordingAction())
+               g_engine.recorder.stopActionRec(g_engine.actionRecorder);
+}
+
+/* -------------------------------------------------------------------------- */
+
 void toggleActionRecording()
 {
        if (!g_engine.kernelAudio.isReady())
@@ -261,6 +269,16 @@ void toggleActionRecording()
                g_engine.recorder.prepareActionRec(g_engine.conf.data.recTriggerMode);
 }
 
+/* -------------------------------------------------------------------------- */
+
+void stopInputRecording()
+{
+       if (g_engine.kernelAudio.isReady() && g_engine.recorder.isRecordingInput())
+               g_engine.recorder.stopInputRec(g_engine.conf.data.inputRecMode, g_engine.kernelAudio.getSampleRate());
+}
+
+/* -------------------------------------------------------------------------- */
+
 void toggleInputRecording()
 {
        if (!g_engine.kernelAudio.isReady() || !g_engine.kernelAudio.isInputEnabled() || !g_engine.mixerHandler.hasInputRecordableChannels())
index f1ca0d2196996113a3e6c13605f48a57a4e3f51c..6897d981dac8fdcc303cc8c1811bb7b481f1e00c 100644 (file)
@@ -69,7 +69,9 @@ void startSequencer(Thread t);
 void stopSequencer(Thread t);
 void toggleSequencer(Thread t);
 void rewindSequencer(Thread t);
+void stopActionRecording();
 void toggleActionRecording();
+void stopInputRecording();
 void toggleInputRecording();
 
 /* Plug-ins. */
index eb3184d76ee33219694c064a971c9852f7d3423f..30e2d4fbef87b9d66cf124240c5655207fef02ad 100644 (file)
@@ -65,6 +65,18 @@ void rebuildMidiWindows_()
        g_ui.rebuildSubWindow(WID_MIDI_INPUT);
        g_ui.rebuildSubWindow(WID_MIDI_OUTPUT);
 }
+
+/* -------------------------------------------------------------------------- */
+
+bool isValidKey_(int key)
+{
+       if (strlen(Fl::event_text()) == 0)
+               return false;
+       for (const auto& [_, val] : g_engine.conf.data.keyBindings)
+               if (key == val)
+                       return false;
+       return true;
+}
 } // namespace
 
 /* -------------------------------------------------------------------------- */
@@ -212,10 +224,13 @@ void channel_setMidiOutputFilter(ID channelId, int ch)
 
 /* -------------------------------------------------------------------------- */
 
-void channel_setKey(ID channelId, int k)
+bool channel_setKey(ID channelId, int k)
 {
+       if (!isValidKey_(k))
+               return false;
        g_engine.model.get().getChannel(channelId).key = k;
        g_engine.model.swap(m::model::SwapType::HARD);
+       return true;
 }
 
 /* -------------------------------------------------------------------------- */
index 74eca916c487bfe6d244526375f9486c8bc20333..3a0cf0b8e6afae3267ddcd878083fb9e71f2eb8d 100644 (file)
@@ -131,9 +131,10 @@ void channel_setMidiInputFilter(ID channelId, int c);
 void channel_setMidiOutputFilter(ID channelId, int c);
 
 /* channel_setKey
-Set key 'k' to Sample Channel 'channelId'. Used for keyboard bindings. */
+Set key 'k' to Sample Channel 'channelId'. Used for keyboard bindings. Returns
+false if the key is not valid (because used for global bindings). */
 
-void channel_setKey(ID channelId, int k);
+bool channel_setKey(ID channelId, int k);
 
 /* MIDI Learning functions. */
 
index f3b9fc19fdce9d60c1f8299fac556bc2cbb31051..76e73578fd5b7df35fd2db4b5c932ee4bc61b32a 100644 (file)
@@ -29,7 +29,9 @@
 #include "core/engine.h"
 #include "core/patch.h"
 #include "core/sequencer.h"
+#include "glue/channel.h"
 #include "glue/config.h"
+#include "glue/io.h"
 #include "glue/storage.h"
 #include "gui/dialogs/about.h"
 #include "gui/dialogs/actionEditor/midiActionEditor.h"
@@ -101,9 +103,13 @@ void openAboutWindow()
 
 /* -------------------------------------------------------------------------- */
 
-void openKeyGrabberWindow(const c::channel::Data& data)
+void openKeyGrabberWindow(int key, std::function<bool(int)> f)
 {
-       g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdKeyGrabber(data), WID_KEY_GRABBER);
+       v::gdKeyGrabber* keyGrabber = new v::gdKeyGrabber(key);
+
+       keyGrabber->onSetKey = f;
+
+       g_ui.openSubWindow(*g_ui.mainWindow.get(), keyGrabber, WID_KEY_GRABBER);
 }
 
 /* -------------------------------------------------------------------------- */
index 8526350af37057307ec2df0363987910125183b4..cdc9f5ac5f3cebeccca9f750707d853a2611f2c6 100644 (file)
@@ -28,6 +28,7 @@
 #define G_GLUE_LAYOUT_H
 
 #include "core/types.h"
+#include <functional>
 #include <string>
 
 namespace giada::m
@@ -52,7 +53,7 @@ void openBrowserForProjectSave();
 void openBrowserForSampleLoad(ID channelId);
 void openBrowserForSampleSave(ID channelId);
 void openAboutWindow();
-void openKeyGrabberWindow(const c::channel::Data&);
+void openKeyGrabberWindow(int key, std::function<bool(int)>);
 void openBpmWindow(std::string bpmValue);
 void openBeatsWindow(int beats, int bars);
 void openConfigWindow();
index fd981bc9b56e774bbee9445e2f5cf0d6e6240e2f..dca6339e53d7293c156027eaa53626671d4aa263 100644 (file)
@@ -279,8 +279,8 @@ void closeProject()
        if (!v::gdConfirmWin("Warning", "Close project: are you sure?"))
                return;
        g_engine.mixer.disable();
-       g_engine.reset();
        g_ui.reset();
+       g_engine.reset();
        g_engine.mixer.enable();
 }
 
index 1f18190db7cdd8dde6a58208e27b81b22a648050..8a7ab66b38f513033e82ed8e7b70a37b348ddd59 100644 (file)
@@ -74,6 +74,11 @@ void loadProject(void* data)
                p.setProgress(v);
        };
 
+       /* Close all sub-windows first, in case there are VST editors visible. VST
+       editors must be closed before deleting their plug-in processors. */
+
+       g_ui.closeAllSubwindows();
+
        m::LoadState state = g_engine.load(projectPath, patchPath, progressCb);
 
        if (state.patch != G_PATCH_OK)
index 0f013d69ccbff4061a50f5077dd7881f117e2fb2..944d4c4dc2e107fdea96ef2f62009bfee6290f36 100644 (file)
@@ -52,10 +52,10 @@ gdSampleActionEditor::gdSampleActionEditor(ID channelId, m::Conf::Data& conf)
        m_barTop.add(&zoomOutBtn);
        m_barTop.resizable(m_barPadding);
 
-       m_actionType.add("Key press");
-       m_actionType.add("Key release");
-       m_actionType.add("Stop sample");
-       m_actionType.value(0);
+       m_actionType.addItem("Key press");
+       m_actionType.addItem("Key release");
+       m_actionType.addItem("Stop sample");
+       m_actionType.showItem(0);
        m_actionType.copy_tooltip("Action type to add");
        if (!canChangeActionType())
                m_actionType.deactivate();
@@ -103,11 +103,11 @@ void gdSampleActionEditor::rebuild()
 
 int gdSampleActionEditor::getActionType() const
 {
-       if (m_actionType.value() == 0)
+       if (m_actionType.getSelectedId() == 0)
                return m::MidiEvent::NOTE_ON;
-       else if (m_actionType.value() == 1)
+       else if (m_actionType.getSelectedId() == 1)
                return m::MidiEvent::NOTE_OFF;
-       else if (m_actionType.value() == 2)
+       else if (m_actionType.getSelectedId() == 2)
                return m::MidiEvent::NOTE_KILL;
 
        assert(false);
index 3ee0661fb5b7f34a7c0dea5083c5bec3b93a76c4..29048a885da31f602b0bba55df1b9b5eb58de142 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "config.h"
+#include "gui/dialogs/config.h"
 #include "core/conf.h"
 #include "core/const.h"
+#include "gui/elems/basics/box.h"
 #include "gui/elems/basics/boxtypes.h"
 #include "gui/elems/basics/button.h"
+#include "gui/elems/basics/flex.h"
+#include "gui/elems/basics/tabs.h"
 #include "gui/elems/config/tabAudio.h"
 #include "gui/elems/config/tabBehaviors.h"
+#include "gui/elems/config/tabBindings.h"
 #include "gui/elems/config/tabMidi.h"
 #include "gui/elems/config/tabMisc.h"
 #include "gui/elems/config/tabPlugins.h"
 #include "utils/gui.h"
-#include <FL/Fl_Tabs.H>
 
 namespace giada::v
 {
 gdConfig::gdConfig(int w, int h, m::Conf::Data& conf)
-: gdWindow(u::gui::centerWindowX(w), u::gui::centerWindowY(h), w, h, "Configuration")
+: gdWindow(u::gui::getCenterWinBounds(w, h), "Configuration")
 {
-       begin();
+       const geompp::Rect<int> bounds = getContentBounds().reduced(G_GUI_OUTER_MARGIN);
 
-       Fl_Tabs* tabs = new Fl_Tabs(8, 8, w - 16, h - 44);
-       tabs->box(G_CUSTOM_BORDER_BOX);
-       tabs->labelcolor(G_COLOR_LIGHT_2);
-       tabs->begin();
-
-       tabAudio     = new geTabAudio(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40);
-       tabMidi      = new geTabMidi(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40);
-       tabBehaviors = new geTabBehaviors(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40, conf);
-       tabMisc      = new geTabMisc(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20);
+       geFlex* container = new geFlex(bounds, Direction::VERTICAL, G_GUI_OUTER_MARGIN);
+       {
+               geTabs* tabs = new geTabs(bounds);
+               {
+                       tabAudio     = new geTabAudio(bounds);
+                       tabMidi      = new geTabMidi(bounds);
+                       tabBehaviors = new geTabBehaviors(bounds, conf);
+                       tabMisc      = new geTabMisc(bounds);
+                       tabBindings  = new geTabBindings(bounds, conf);
+#ifdef WITH_VST
+                       tabPlugins = new geTabPlugins(bounds);
+#endif
+                       tabs->add(tabAudio);
+                       tabs->add(tabMidi);
+                       tabs->add(tabBehaviors);
 #ifdef WITH_VST
-       tabPlugins = new geTabPlugins(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40);
+                       tabs->add(tabPlugins);
 #endif
+                       tabs->add(tabBindings);
+                       tabs->add(tabMisc);
+               }
 
-       tabs->end();
+               geFlex* footer = new geFlex(Direction::HORIZONTAL, G_GUI_OUTER_MARGIN);
+               {
+                       geButton* save   = new geButton("Save");
+                       geButton* cancel = new geButton("Cancel");
+                       save->onClick    = [this]() { saveConfig(); };
+                       cancel->onClick  = [this]() { do_callback(); };
 
-       save   = new geButton(w - 88, h - 28, 80, 20, "Save");
-       cancel = new geButton(w - 176, h - 28, 80, 20, "Cancel");
+                       footer->add(new geBox()); // Spacer
+                       footer->add(cancel, 80);
+                       footer->add(save, 80);
+                       footer->end();
+               }
 
-       end();
+               container->add(tabs);
+               container->add(footer, G_GUI_UNIT);
+               container->end();
+       }
 
-       save->callback(cb_save_config, (void*)this);
-       cancel->callback(cb_cancel, (void*)this);
+       add(container);
+       resizable(container);
+       size_range(w, h);
 
        u::gui::setFavicon(this);
        setId(WID_CONFIG);
@@ -74,12 +98,7 @@ gdConfig::gdConfig(int w, int h, m::Conf::Data& conf)
 
 /* -------------------------------------------------------------------------- */
 
-void gdConfig::cb_save_config(Fl_Widget* /*w*/, void* p) { ((gdConfig*)p)->cb_save_config(); }
-void gdConfig::cb_cancel(Fl_Widget* /*w*/, void* p) { ((gdConfig*)p)->cb_cancel(); }
-
-/* -------------------------------------------------------------------------- */
-
-void gdConfig::cb_save_config()
+void gdConfig::saveConfig()
 {
        tabAudio->save();
        tabBehaviors->save();
@@ -90,11 +109,4 @@ void gdConfig::cb_save_config()
 #endif
        do_callback();
 }
-
-/* -------------------------------------------------------------------------- */
-
-void gdConfig::cb_cancel()
-{
-       do_callback();
-}
 } // namespace giada::v
index 23dabf8275f002327b7d197f70ab768fc74d6cd4..9c4e5c21dc2932988d099b67e5384745f4a89373 100644 (file)
 #include "core/conf.h"
 #include "window.h"
 
-class geCheck;
-class geInput;
-class geBox;
-
 namespace giada::v
 {
 class geButton;
-class geChoice;
 class geTabAudio;
 class geTabBehaviors;
 class geTabMidi;
 class geTabMisc;
+class geTabBindings;
 #ifdef WITH_VST
 class geTabPlugins;
 #endif
@@ -54,17 +50,13 @@ public:
        geTabBehaviors* tabBehaviors;
        geTabMidi*      tabMidi;
        geTabMisc*      tabMisc;
+       geTabBindings*  tabBindings;
 #ifdef WITH_VST
        geTabPlugins* tabPlugins;
 #endif
-       geButton* save;
-       geButton* cancel;
 
 private:
-       static void cb_save_config(Fl_Widget* /*w*/, void* p);
-       static void cb_cancel(Fl_Widget* /*w*/, void* p);
-       void        cb_save_config();
-       void        cb_cancel();
+       void saveConfig();
 };
 } // namespace giada::v
 
index bfdc43345b85f156d3a7895524b3e8e5d942d03d..51fae3cf8c29b48f46ebecd002ff16a233b2800f 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "keyGrabber.h"
-#include "config.h"
+#include "gui/dialogs/keyGrabber.h"
 #include "core/conf.h"
-#include "core/model/model.h"
+#include "glue/channel.h"
 #include "glue/io.h"
 #include "gui/elems/basics/box.h"
-#include "gui/elems/mainWindow/keyboard/channel.h"
-#include "gui/elems/mainWindow/keyboard/channelButton.h"
-#include "gui/elems/mainWindow/keyboard/keyboard.h"
-#include "mainWindow.h"
+#include "gui/elems/basics/button.h"
+#include "gui/elems/basics/flex.h"
 #include "utils/gui.h"
 #include "utils/log.h"
 #include "utils/string.h"
 #include <cassert>
 
-extern giada::v::gdMainWindow* mainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
 {
-gdKeyGrabber::gdKeyGrabber(const c::channel::Data& d)
+gdKeyGrabber::gdKeyGrabber(int key)
 : gdWindow(300, 126, "Key configuration")
-, m_data(d)
+, onSetKey(nullptr)
+, m_key(key)
 {
-       begin();
-       m_text   = new geBox(8, 8, 284, 80, "");
-       m_clear  = new geButton(w() - 88, m_text->y() + m_text->h() + 8, 80, 20, "Clear");
-       m_cancel = new geButton(m_clear->x() - 88, m_clear->y(), 80, 20, "Close");
-       end();
+       geFlex* container = new geFlex(getContentBounds().reduced({G_GUI_OUTER_MARGIN}), Direction::VERTICAL, G_GUI_OUTER_MARGIN);
+       {
+               m_text = new geBox();
 
-       m_clear->callback(cb_clear, (void*)this);
-       m_cancel->callback(cb_cancel, (void*)this);
+               geFlex* footer = new geFlex(Direction::HORIZONTAL, G_GUI_OUTER_MARGIN);
+               {
+                       m_clear  = new geButton("Clear");
+                       m_cancel = new geButton("Close");
 
-       updateText(m_data.key);
+                       footer->add(new geBox()); // Spacer
+                       footer->add(m_clear, 80);
+                       footer->add(m_cancel, 80);
+                       footer->end();
+               }
 
-       u::gui::setFavicon(this);
-       set_modal();
-       show();
-}
+               container->add(m_text);
+               container->add(footer, G_GUI_UNIT);
+               container->end();
+       }
 
-/* -------------------------------------------------------------------------- */
+       add(container);
 
-void gdKeyGrabber::rebuild()
-{
-       updateText(m_data.key);
-}
+       m_clear->onClick = [this]() {
+               assert(onSetKey != nullptr);
 
-/* -------------------------------------------------------------------------- */
+               m_key = 0;
+               onSetKey(m_key);
+               rebuild();
+       };
 
-void gdKeyGrabber::cb_clear(Fl_Widget* /*w*/, void* p) { ((gdKeyGrabber*)p)->cb_clear(); }
-void gdKeyGrabber::cb_cancel(Fl_Widget* /*w*/, void* p) { ((gdKeyGrabber*)p)->cb_cancel(); }
+       m_cancel->onClick = [this]() {
+               do_callback();
+       };
 
-/* -------------------------------------------------------------------------- */
+       rebuild();
 
-void gdKeyGrabber::cb_cancel()
-{
-       do_callback();
+       u::gui::setFavicon(this);
+       set_modal();
+       show();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void gdKeyGrabber::cb_clear()
+void gdKeyGrabber::rebuild()
 {
-       updateText(0);
-       setButtonLabel(0);
+       std::string tmp = "Press a key.\n\nCurrent binding: " + u::gui::keyToString(m_key);
+       m_text->copy_label(tmp.c_str());
 }
 
 /* -------------------------------------------------------------------------- */
 
-void gdKeyGrabber::setButtonLabel(int key)
+int gdKeyGrabber::handle(int e)
 {
-       c::io::channel_setKey(m_data.id, key);
-}
-
-/* -------------------------------------------------------------------------- */
+       if (e != FL_KEYUP)
+               return Fl_Group::handle(e);
 
-void gdKeyGrabber::updateText(int key)
-{
-       std::string tmp = "Press a key.\n\nCurrent binding: ";
-       if (key != 0)
-               tmp += static_cast<wchar_t>(key);
-       else
-               tmp += "[none]";
-       m_text->copy_label(tmp.c_str());
-}
+       assert(onSetKey != nullptr);
 
-/* -------------------------------------------------------------------------- */
+       const int newKey = Fl::event_key();
 
-int gdKeyGrabber::handle(int e)
-{
-       int ret = Fl_Group::handle(e);
-       switch (e)
-       {
-       case FL_KEYUP:
+       if (!onSetKey(newKey))
        {
-               int x = Fl::event_key();
-               if (strlen(Fl::event_text()) != 0 && x != FL_BackSpace && x != FL_Enter && x != FL_Delete && x != FL_Tab && x != FL_End && x != ' ')
-               {
-                       u::log::print("set key '%c' (%d) for channel ID=%d\n", x, x, m_data.id);
-                       setButtonLabel(x);
-                       updateText(x);
-                       break;
-               }
-               else
-                       u::log::print("invalid key\n");
+               u::log::print("Invalid key\n");
+               return 1;
        }
-       }
-       return (ret);
-}
 
-} // namespace v
-} // namespace giada
+       m_key = newKey;
+       rebuild();
+
+       u::log::print("Set key '%c' (%d)\n", m_key, m_key);
+
+       return 1;
+}
+} // namespace giada::v
index 789e170c7ac14346f8d403ed54e4c6f19ef9166b..6c23efcd07523cc8eb566008e868076e39e0ec10 100644 (file)
 #ifndef GD_KEYGRABBER_H
 #define GD_KEYGRABBER_H
 
+#include "core/conf.h"
 #include "window.h"
 #include <FL/Fl.H>
-
-class geBox;
+#include <functional>
 
 namespace giada::c::channel
 {
@@ -39,25 +39,24 @@ struct Data;
 
 namespace giada::v
 {
+class geBox;
 class geButton;
 class gdKeyGrabber : public gdWindow
 {
 public:
-       gdKeyGrabber(const c::channel::Data& d);
+       gdKeyGrabber(int key);
 
        int  handle(int e) override;
        void rebuild() override;
 
-private:
-       static void cb_clear(Fl_Widget* /*w*/, void* p);
-       static void cb_cancel(Fl_Widget* /*w*/, void* p);
-       void        cb_clear();
-       void        cb_cancel();
+       /* onSetKey
+       Callback fired when this widget has grabbed an event. Returns a boolean value
+       to inform the widget if the key is valid. */
 
-       void setButtonLabel(int key);
-       void updateText(int key);
+       std::function<bool(int key)> onSetKey;
 
-       const c::channel::Data& m_data;
+private:
+       int m_key;
 
        geBox*    m_text;
        geButton* m_clear;
index da46a8619a27e3bcbd87d142c85124457817746a..c0c6582bdba122599c8a52a6fe6086dd3559bc35 100644 (file)
@@ -126,7 +126,10 @@ gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** arg
        add(zone2);
        add(zone3);
        add(keyboard);
+
        callback([](Fl_Widget* /*w*/, void* /*v*/) {
+               if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape)
+                       return; // ignore Escape
                m::init::closeMainWindow();
        });
        u::gui::setFavicon(this);
index 214a9570d6c62a5b07a40f9f7918bd460e92d6ff..b41afffbfa85680dce06ae8434245be83b4eaaab 100644 (file)
@@ -30,6 +30,7 @@
 #include "utils/log.h"
 #include <FL/Fl_Pack.H>
 #include <cassert>
+#include <cstddef>
 #ifdef WITH_VST
 #include "core/plugins/plugin.h"
 #endif
@@ -155,24 +156,26 @@ gdMidiInputChannel::gdMidiInputChannel(ID channelId, m::Conf::Data& c)
        m_ok->callback(cb_close, (void*)this);
        m_enable->callback(cb_enable, (void*)this);
 
-       m_channel->add("Channel (any)");
-       m_channel->add("Channel 1");
-       m_channel->add("Channel 2");
-       m_channel->add("Channel 3");
-       m_channel->add("Channel 4");
-       m_channel->add("Channel 5");
-       m_channel->add("Channel 6");
-       m_channel->add("Channel 7");
-       m_channel->add("Channel 8");
-       m_channel->add("Channel 9");
-       m_channel->add("Channel 10");
-       m_channel->add("Channel 11");
-       m_channel->add("Channel 12");
-       m_channel->add("Channel 13");
-       m_channel->add("Channel 14");
-       m_channel->add("Channel 15");
-       m_channel->add("Channel 16");
-       m_channel->callback(cb_setChannel, (void*)this);
+       m_channel->addItem("Channel (any)");
+       m_channel->addItem("Channel 1");
+       m_channel->addItem("Channel 2");
+       m_channel->addItem("Channel 3");
+       m_channel->addItem("Channel 4");
+       m_channel->addItem("Channel 5");
+       m_channel->addItem("Channel 6");
+       m_channel->addItem("Channel 7");
+       m_channel->addItem("Channel 8");
+       m_channel->addItem("Channel 9");
+       m_channel->addItem("Channel 10");
+       m_channel->addItem("Channel 11");
+       m_channel->addItem("Channel 12");
+       m_channel->addItem("Channel 13");
+       m_channel->addItem("Channel 14");
+       m_channel->addItem("Channel 15");
+       m_channel->addItem("Channel 16");
+       m_channel->onChange = [this](ID id) {
+               c::io::channel_setMidiInputFilter(m_data.channelId, id == 0 ? -1 : id - 1);
+       };
 
        m_veloAsVol->callback(cb_veloAsVol, (void*)this);
 
@@ -210,7 +213,7 @@ void gdMidiInputChannel::rebuild()
                static_cast<gePluginLearnerPack*>(m_container->getChild(i++))->update(plugin, m_data.enabled);
 #endif
 
-       m_channel->value(m_data.filter == -1 ? 0 : m_data.filter + 1);
+       m_channel->showItem(m_data.filter == -1 ? 0 : m_data.filter + 1);
 
        if (m_data.enabled)
        {
@@ -228,7 +231,6 @@ void gdMidiInputChannel::rebuild()
 /* -------------------------------------------------------------------------- */
 
 void gdMidiInputChannel::cb_enable(Fl_Widget* /*w*/, void* p) { ((gdMidiInputChannel*)p)->cb_enable(); }
-void gdMidiInputChannel::cb_setChannel(Fl_Widget* /*w*/, void* p) { ((gdMidiInputChannel*)p)->cb_setChannel(); }
 void gdMidiInputChannel::cb_veloAsVol(Fl_Widget* /*w*/, void* p) { ((gdMidiInputChannel*)p)->cb_veloAsVol(); }
 
 /* -------------------------------------------------------------------------- */
@@ -244,12 +246,4 @@ void gdMidiInputChannel::cb_veloAsVol()
 {
        c::io::channel_enableVelocityAsVol(m_data.channelId, m_veloAsVol->value());
 }
-
-/* -------------------------------------------------------------------------- */
-
-void gdMidiInputChannel::cb_setChannel()
-{
-       c::io::channel_setMidiInputFilter(m_data.channelId,
-           m_channel->value() == 0 ? -1 : m_channel->value() - 1);
-}
-} // namespace giada::v
\ No newline at end of file
+} // namespace giada::v
index a9e4e3fdd859e20e49c9bd846605faaf39bf7e43..142ebe311ddcbfc8eeef4296f5ba2e06c490661a 100644 (file)
@@ -73,10 +73,8 @@ public:
 
 private:
        static void cb_enable(Fl_Widget* /*w*/, void* p);
-       static void cb_setChannel(Fl_Widget* /*w*/, void* p);
        static void cb_veloAsVol(Fl_Widget* /*w*/, void* p);
        void        cb_enable();
-       void        cb_setChannel();
        void        cb_veloAsVol();
 
        ID m_channelId;
index 8360e1ff9a1ba54732a57f7302b858673eb13ac7..2a79e12170318abd5871c6f7d24c91d24b920d36 100644 (file)
@@ -97,24 +97,26 @@ gdMidiInputMaster::gdMidiInputMaster(m::Conf::Data& c)
        m_ok->callback(cb_close, (void*)this);
        m_enable->callback(cb_enable, (void*)this);
 
-       m_channel->add("Channel (any)");
-       m_channel->add("Channel 1");
-       m_channel->add("Channel 2");
-       m_channel->add("Channel 3");
-       m_channel->add("Channel 4");
-       m_channel->add("Channel 5");
-       m_channel->add("Channel 6");
-       m_channel->add("Channel 7");
-       m_channel->add("Channel 8");
-       m_channel->add("Channel 9");
-       m_channel->add("Channel 10");
-       m_channel->add("Channel 11");
-       m_channel->add("Channel 12");
-       m_channel->add("Channel 13");
-       m_channel->add("Channel 14");
-       m_channel->add("Channel 15");
-       m_channel->add("Channel 16");
-       m_channel->callback(cb_setChannel, (void*)this);
+       m_channel->addItem("Channel (any)");
+       m_channel->addItem("Channel 1");
+       m_channel->addItem("Channel 2");
+       m_channel->addItem("Channel 3");
+       m_channel->addItem("Channel 4");
+       m_channel->addItem("Channel 5");
+       m_channel->addItem("Channel 6");
+       m_channel->addItem("Channel 7");
+       m_channel->addItem("Channel 8");
+       m_channel->addItem("Channel 9");
+       m_channel->addItem("Channel 10");
+       m_channel->addItem("Channel 11");
+       m_channel->addItem("Channel 12");
+       m_channel->addItem("Channel 13");
+       m_channel->addItem("Channel 14");
+       m_channel->addItem("Channel 15");
+       m_channel->addItem("Channel 16");
+       m_channel->onChange = [](ID id) {
+               c::io::master_setMidiFilter(id == 0 ? -1 : id - 1);
+       };
 
        u::gui::setFavicon(this);
 
@@ -130,7 +132,7 @@ void gdMidiInputMaster::rebuild()
        m_data = c::io::master_getInputData();
 
        m_enable->value(m_data.enabled);
-       m_channel->value(m_data.filter - 1 ? 0 : m_data.filter + 1);
+       m_channel->showItem(m_data.filter - 1 ? 0 : m_data.filter + 1);
        m_learners->update(m_data);
 
        m_data.enabled ? m_channel->activate() : m_channel->deactivate();
@@ -139,7 +141,6 @@ void gdMidiInputMaster::rebuild()
 /* -------------------------------------------------------------------------- */
 
 void gdMidiInputMaster::cb_enable(Fl_Widget* /*w*/, void* p) { ((gdMidiInputMaster*)p)->cb_enable(); }
-void gdMidiInputMaster::cb_setChannel(Fl_Widget* /*w*/, void* p) { ((gdMidiInputMaster*)p)->cb_setChannel(); }
 
 /* -------------------------------------------------------------------------- */
 
@@ -147,11 +148,4 @@ void gdMidiInputMaster::cb_enable()
 {
        c::io::master_enableMidiLearn(m_enable->value());
 }
-
-/* -------------------------------------------------------------------------- */
-
-void gdMidiInputMaster::cb_setChannel()
-{
-       c::io::master_setMidiFilter(m_channel->value() == 0 ? -1 : m_channel->value() - 1);
-}
 } // namespace giada::v
\ No newline at end of file
index 512f8ef5a8b1c16e8d5d17ef99d700993d52bcb8..865236fe750d1518de9d02e49bcab218a3c410dc 100644 (file)
@@ -56,9 +56,7 @@ public:
 
 private:
        static void cb_enable(Fl_Widget* /*w*/, void* p);
-       static void cb_setChannel(Fl_Widget* /*w*/, void* p);
        void        cb_enable();
-       void        cb_setChannel();
 
        c::io::Master_InputData m_data;
 
index 997cbbeed1a96375e5356b01acc0234b117c9622..ca318904e31c6f2d115597c972cbdcb662fa1099 100644 (file)
@@ -33,9 +33,7 @@
 #include "utils/gui.h"
 #include <FL/Fl_Pack.H>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 gdMidiOutputMidiCh::gdMidiOutputMidiCh(ID channelId)
 : gdMidiOutputBase(300, 168, channelId)
@@ -60,25 +58,27 @@ gdMidiOutputMidiCh::gdMidiOutputMidiCh(ID channelId)
        add(m_learners);
        add(m_close);
 
-       m_chanListOut->add("Channel 1");
-       m_chanListOut->add("Channel 2");
-       m_chanListOut->add("Channel 3");
-       m_chanListOut->add("Channel 4");
-       m_chanListOut->add("Channel 5");
-       m_chanListOut->add("Channel 6");
-       m_chanListOut->add("Channel 7");
-       m_chanListOut->add("Channel 8");
-       m_chanListOut->add("Channel 9");
-       m_chanListOut->add("Channel 10");
-       m_chanListOut->add("Channel 11");
-       m_chanListOut->add("Channel 12");
-       m_chanListOut->add("Channel 13");
-       m_chanListOut->add("Channel 14");
-       m_chanListOut->add("Channel 15");
-       m_chanListOut->add("Channel 16");
-       m_chanListOut->value(0);
-
-       m_chanListOut->callback(cb_setChannel, (void*)this);
+       m_chanListOut->addItem("Channel 1");
+       m_chanListOut->addItem("Channel 2");
+       m_chanListOut->addItem("Channel 3");
+       m_chanListOut->addItem("Channel 4");
+       m_chanListOut->addItem("Channel 5");
+       m_chanListOut->addItem("Channel 6");
+       m_chanListOut->addItem("Channel 7");
+       m_chanListOut->addItem("Channel 8");
+       m_chanListOut->addItem("Channel 9");
+       m_chanListOut->addItem("Channel 10");
+       m_chanListOut->addItem("Channel 11");
+       m_chanListOut->addItem("Channel 12");
+       m_chanListOut->addItem("Channel 13");
+       m_chanListOut->addItem("Channel 14");
+       m_chanListOut->addItem("Channel 15");
+       m_chanListOut->addItem("Channel 16");
+       m_chanListOut->showItem(0);
+       m_chanListOut->onChange = [this](ID id) {
+               c::io::channel_setMidiOutputFilter(m_channelId, id);
+       };
+
        m_enableOut->callback(cb_enableOut, (void*)this);
        m_enableLightning->callback(cb_enableLightning, (void*)this);
        m_close->callback(cb_close, (void*)this);
@@ -99,7 +99,7 @@ void gdMidiOutputMidiCh::rebuild()
        assert(m_data.output.has_value());
 
        m_learners->update(m_data);
-       m_chanListOut->value(m_data.output->filter);
+       m_chanListOut->showItem(m_data.output->filter);
        m_enableOut->value(m_data.output->enabled);
 
        m_data.output->enabled ? m_chanListOut->activate() : m_chanListOut->deactivate();
@@ -108,7 +108,6 @@ void gdMidiOutputMidiCh::rebuild()
 /* -------------------------------------------------------------------------- */
 
 void gdMidiOutputMidiCh::cb_enableOut(Fl_Widget* /*w*/, void* p) { ((gdMidiOutputMidiCh*)p)->cb_enableOut(); }
-void gdMidiOutputMidiCh::cb_setChannel(Fl_Widget* /*w*/, void* p) { ((gdMidiOutputMidiCh*)p)->cb_setChannel(); }
 
 /* -------------------------------------------------------------------------- */
 
@@ -116,12 +115,4 @@ void gdMidiOutputMidiCh::cb_enableOut()
 {
        c::io::channel_enableMidiOutput(m_channelId, m_enableOut->value());
 }
-
-/* -------------------------------------------------------------------------- */
-
-void gdMidiOutputMidiCh::cb_setChannel()
-{
-       c::io::channel_setMidiOutputFilter(m_channelId, m_chanListOut->value());
-}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
index 53f8a95a765efc1c1756ba9312e21a46227bc50f..1abc715347a00c2304afe4e5f04b6758abd58c18 100644 (file)
@@ -31,9 +31,7 @@
 
 #include "midiOutputBase.h"
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class geChoice;
 class gdMidiOutputMidiCh : public gdMidiOutputBase
@@ -43,16 +41,13 @@ public:
 
        void rebuild() override;
 
-  private:
+private:
        static void cb_enableOut(Fl_Widget* /*w*/, void* p);
-       static void cb_setChannel(Fl_Widget* /*w*/, void* p);
        void        cb_enableOut();
-       void        cb_setChannel();
 
        geCheck*  m_enableOut;
        geChoice* m_chanListOut;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index b170aa7f8c50c48d1f4d30aa7cf9d4fb69dbe916..1f06e17c0cd9138195d34522e08d07431c307357 100644 (file)
@@ -46,7 +46,7 @@ gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId, m::Co
 
        /* top area */
        Fl_Group* group_top = new Fl_Group(8, 8, w() - 16, 20);
-       sortMethod          = new geChoice(group_top->x() + 45, group_top->y(), 100, 20, "Sort by");
+       sortMethod          = new geChoice(group_top->x(), group_top->y(), 180, 20, "Sort by", 0);
        geBox* b1           = new geBox(sortMethod->x() + sortMethod->w(), group_top->y(), 100, 20); // spacer window border <-> menu
        group_top->resizable(b1);
        group_top->end();
@@ -64,12 +64,15 @@ gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId, m::Co
 
        end();
 
-       sortMethod->add("Name");
-       sortMethod->add("Category");
-       sortMethod->add("Manufacturer");
-       sortMethod->add("Format");
-       sortMethod->callback(cb_sort, (void*)this);
-       sortMethod->value(m_conf.pluginSortMethod);
+       sortMethod->addItem("Name");
+       sortMethod->addItem("Category");
+       sortMethod->addItem("Manufacturer");
+       sortMethod->addItem("Format");
+       sortMethod->showItem(m_conf.pluginSortMethod);
+       sortMethod->onChange = [this](ID id) {
+               c::plugin::sortPlugins(static_cast<m::PluginManager::SortMethod>(id));
+               browser->refresh();
+       };
 
        addBtn->callback(cb_add, (void*)this);
        addBtn->shortcut(FL_Enter);
@@ -88,14 +91,13 @@ gdPluginChooser::~gdPluginChooser()
        m_conf.pluginChooserY   = y();
        m_conf.pluginChooserW   = w();
        m_conf.pluginChooserH   = h();
-       m_conf.pluginSortMethod = sortMethod->value();
+       m_conf.pluginSortMethod = sortMethod->getSelectedId();
 }
 
 /* -------------------------------------------------------------------------- */
 
 void gdPluginChooser::cb_close(Fl_Widget* /*w*/, void* p) { ((gdPluginChooser*)p)->cb_close(); }
 void gdPluginChooser::cb_add(Fl_Widget* /*w*/, void* p) { ((gdPluginChooser*)p)->cb_add(); }
-void gdPluginChooser::cb_sort(Fl_Widget* /*w*/, void* p) { ((gdPluginChooser*)p)->cb_sort(); }
 
 /* -------------------------------------------------------------------------- */
 
@@ -106,14 +108,6 @@ void gdPluginChooser::cb_close()
 
 /* -------------------------------------------------------------------------- */
 
-void gdPluginChooser::cb_sort()
-{
-       c::plugin::sortPlugins(static_cast<m::PluginManager::SortMethod>(sortMethod->value()));
-       browser->refresh();
-}
-
-/* -------------------------------------------------------------------------- */
-
 void gdPluginChooser::cb_add()
 {
        int pluginIndex = browser->value() - 3; // subtract header lines
index e3d2f849d0e8f2718f4a47cbacf640b7c6416243..80d4389c308da09019ee61cae7046e07c7ff6463 100644 (file)
@@ -50,10 +50,8 @@ public:
 private:
        static void cb_close(Fl_Widget* /*w*/, void* p);
        static void cb_add(Fl_Widget* /*w*/, void* p);
-       static void cb_sort(Fl_Widget* /*w*/, void* p);
        void        cb_close();
        void        cb_add();
-       void        cb_sort();
 
        m::Conf::Data& m_conf;
 
index 0b825484ef5bcd5f9e1102dbcef34397b451a840..875d821df542d25b0563194165ef853c480d55f3 100644 (file)
 
 #include "window.h"
 
-class geBox;
 class geSlider;
 
 namespace giada::c::plugin
 {
 struct Plugin;
 }
+
 namespace giada::m
 {
 class Plugin;
 }
+
 namespace giada::v
 {
+class geBox;
 class geLiquidScroll;
 class gdPluginWindow : public gdWindow
 {
index f06126d7e201106a4f10884de3c9673599e7eda4..5568c57847b888514cc5c587e9845efc53f66a59 100644 (file)
@@ -107,6 +107,7 @@ void gdPluginWindowGUI::openEditor()
 
 void gdPluginWindowGUI::closeEditor()
 {
+       m_plugin.setResizeCallback(nullptr);
        m_editor.reset();
 }
 } // namespace giada::v
index 9a80c58d1ef94e7c728336f945864437badb56c2..194748f9fa7961c6667661137cff6f551d6bf965 100644 (file)
@@ -103,7 +103,7 @@ gdSampleEditor::~gdSampleEditor()
        m_conf.sampleEditorY       = y();
        m_conf.sampleEditorW       = w();
        m_conf.sampleEditorH       = h();
-       m_conf.sampleEditorGridVal = atoi(grid->text());
+       m_conf.sampleEditorGridVal = grid->getSelectedId();
        m_conf.sampleEditorGridOn  = snap->value();
 
        c::sampleEditor::stopPreview();
@@ -152,22 +152,20 @@ gePack* gdSampleEditor::createUpperBar()
 
        reload->callback(cb_reload, (void*)this);
 
-       grid->add("(off)");
-       grid->add("2");
-       grid->add("3");
-       grid->add("4");
-       grid->add("6");
-       grid->add("8");
-       grid->add("16");
-       grid->add("32");
-       grid->add("64");
+       grid->addItem("(off)");
+       grid->addItem("2");
+       grid->addItem("3");
+       grid->addItem("4");
+       grid->addItem("6");
+       grid->addItem("8");
+       grid->addItem("16");
+       grid->addItem("32");
+       grid->addItem("64");
        grid->copy_tooltip("Grid frequency");
-
-       if (m_conf.sampleEditorGridVal == 0)
-               grid->value(0);
-       else
-               grid->value(grid->find_item(u::string::iToString(m_conf.sampleEditorGridVal).c_str()));
-       grid->callback(cb_changeGrid, (void*)this);
+       grid->showItem(m_conf.sampleEditorGridVal);
+       grid->onChange = [this](ID) {
+               waveTools->waveform->setGridLevel(std::stoi(grid->getSelectedLabel()));
+       };
 
        snap->value(m_conf.sampleEditorGridOn);
        snap->copy_tooltip("Snap to grid");
@@ -262,7 +260,6 @@ gePack* gdSampleEditor::createBottomBar(int x, int y, int h)
 void gdSampleEditor::cb_reload(Fl_Widget* /*w*/, void* p) { ((gdSampleEditor*)p)->cb_reload(); }
 void gdSampleEditor::cb_zoomIn(Fl_Widget* /*w*/, void* p) { ((gdSampleEditor*)p)->cb_zoomIn(); }
 void gdSampleEditor::cb_zoomOut(Fl_Widget* /*w*/, void* p) { ((gdSampleEditor*)p)->cb_zoomOut(); }
-void gdSampleEditor::cb_changeGrid(Fl_Widget* /*w*/, void* p) { ((gdSampleEditor*)p)->cb_changeGrid(); }
 void gdSampleEditor::cb_enableSnap(Fl_Widget* /*w*/, void* p) { ((gdSampleEditor*)p)->cb_enableSnap(); }
 void gdSampleEditor::cb_togglePreview(Fl_Widget* /*w*/, void* p) { ((gdSampleEditor*)p)->cb_togglePreview(); }
 void gdSampleEditor::cb_rewindPreview(Fl_Widget* /*w*/, void* p) { ((gdSampleEditor*)p)->cb_rewindPreview(); }
@@ -315,13 +312,6 @@ void gdSampleEditor::cb_zoomOut()
 
 /* -------------------------------------------------------------------------- */
 
-void gdSampleEditor::cb_changeGrid()
-{
-       waveTools->waveform->setGridLevel(atoi(grid->text()));
-}
-
-/* -------------------------------------------------------------------------- */
-
 void gdSampleEditor::updateInfo()
 {
        std::string bitDepth = m_data.waveBits != 0 ? u::string::iToString(m_data.waveBits) : "(unknown)";
index 81c9074eb7e18ed990c73c8ab63c596b7a06f31b..fa48b03dfe8b5021357857ce6469fda03b7e9b86 100644 (file)
@@ -33,7 +33,6 @@
 #include "window.h"
 
 class geCheck;
-class geBox;
 class geStatusButton;
 
 namespace giada::m
@@ -43,6 +42,7 @@ class Wave;
 
 namespace giada::v
 {
+class geBox;
 class geButton;
 class geChoice;
 class gePack;
@@ -96,14 +96,12 @@ private:
        static void cb_reload(Fl_Widget* /*w*/, void* p);
        static void cb_zoomIn(Fl_Widget* /*w*/, void* p);
        static void cb_zoomOut(Fl_Widget* /*w*/, void* p);
-       static void cb_changeGrid(Fl_Widget* /*w*/, void* p);
        static void cb_enableSnap(Fl_Widget* /*w*/, void* p);
        static void cb_togglePreview(Fl_Widget* /*w*/, void* p);
        static void cb_rewindPreview(Fl_Widget* /*w*/, void* p);
        void        cb_reload();
        void        cb_zoomIn();
        void        cb_zoomOut();
-       void        cb_changeGrid();
        void        cb_enableSnap();
        void        cb_togglePreview();
        void        cb_rewindPreview();
index 58754ada619cebcf9f022c4e2c2c7760a39b0f79..9b0196fa895d4240fd672e023b5c0418b4c07a1a 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "window.h"
 #include "utils/log.h"
+#include <FL/Fl.H>
 
 namespace giada::v
 {
@@ -72,6 +73,10 @@ gdWindow::~gdWindow()
 
 void gdWindow::cb_closeChild(Fl_Widget* w, void* /*p*/)
 {
+       /* Disable default FLTK behavior where 'escape' closes the window. */
+       if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape)
+               return;
+
        gdWindow* child = (gdWindow*)w;
        if (child->getParent() != nullptr)
                (child->getParent())->delSubWindow(child);
index 75b48c2836addce2bf11ca5dda8047ad7653810d..423f75f15335e0ae375422f91edfacaeb0ee858a 100644 (file)
@@ -38,13 +38,9 @@ extern giada::v::Ui g_ui;
 
 namespace giada::v
 {
-Dispatcher::Dispatcher()
-: m_backspace(false)
-, m_end(false)
-, m_enter(false)
-, m_space(false)
-, m_esc(false)
-, m_key(false)
+Dispatcher::Dispatcher(const m::Conf::KeyBindings& k)
+: m_keyBindings(k)
+, m_keyPressed(-1)
 {
 }
 
@@ -89,55 +85,34 @@ void Dispatcher::dispatchKey(int event)
 
        if (event == FL_KEYDOWN)
        {
-               if (Fl::event_key() == FL_BackSpace && !m_backspace)
-               {
-                       m_backspace = true;
+               if (m_keyPressed == Fl::event_key()) // Avoid key retrig
+                       return;
+
+               m_keyPressed = Fl::event_key();
+
+               if (m_keyPressed == m_keyBindings.at(m::Conf::KEY_BIND_PLAY))
+                       c::events::toggleSequencer(Thread::MAIN);
+               else if (m_keyPressed == m_keyBindings.at(m::Conf::KEY_BIND_REWIND))
                        c::events::rewindSequencer(Thread::MAIN);
-               }
-               else if (Fl::event_key() == FL_End && !m_end)
-               {
-                       m_end = true;
-                       c::events::toggleInputRecording();
-               }
-               else if (Fl::event_key() == FL_Enter && !m_enter)
-               {
-                       m_enter = true;
+               else if (m_keyPressed == m_keyBindings.at(m::Conf::KEY_BIND_RECORD_ACTIONS))
                        c::events::toggleActionRecording();
-               }
-               else if (Fl::event_key() == ' ' && !m_space)
-               {
-                       m_space = true;
-                       c::events::toggleSequencer(Thread::MAIN);
-               }
-               else if (Fl::event_key() == FL_Escape && !m_esc)
+               else if (m_keyPressed == m_keyBindings.at(m::Conf::KEY_BIND_RECORD_INPUT))
+                       c::events::toggleInputRecording();
+               else if (m_keyPressed == m_keyBindings.at(m::Conf::KEY_BIND_EXIT))
                {
-                       m_esc = true;
-                       m::init::closeMainWindow();
+                       c::events::stopActionRecording();
+                       c::events::stopInputRecording();
                }
-               else if (!m_key)
+               else
                {
-                       m_key = true;
                        onEventOccured();
                        dispatchChannels(event);
                }
        }
        else if (event == FL_KEYUP)
        {
-               if (Fl::event_key() == FL_BackSpace)
-                       m_backspace = false;
-               else if (Fl::event_key() == FL_End)
-                       m_end = false;
-               else if (Fl::event_key() == ' ')
-                       m_space = false;
-               else if (Fl::event_key() == FL_Enter)
-                       m_enter = false;
-               else if (Fl::event_key() == FL_Escape)
-                       m_esc = false;
-               else
-               {
-                       m_key = false;
-                       dispatchChannels(event);
-               }
+               m_keyPressed = -1;
+               dispatchChannels(event);
        }
 }
 
index e31d121b59756497dbf2dd1cd8d13c4a27b477fe..81387c6e7737f59a3e0ff31a866b54a1cc8edb67 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef G_V_DISPATCHER_H
 #define G_V_DISPATCHER_H
 
+#include "core/conf.h"
 #include "core/types.h"
 #include <functional>
 
@@ -36,7 +37,7 @@ class geChannel;
 class Dispatcher final
 {
 public:
-       Dispatcher();
+       Dispatcher(const m::Conf::KeyBindings& m_keyBindings);
 
        /* dispatchKey
     Processes a key pressed on the physical keyboard. */
@@ -62,12 +63,9 @@ private:
 
        void dispatchChannels(int event) const;
 
-       bool m_backspace;
-       bool m_end;
-       bool m_enter;
-       bool m_space;
-       bool m_esc;
-       bool m_key;
+       const m::Conf::KeyBindings& m_keyBindings;
+
+       int m_keyPressed;
 };
 } // namespace giada::v
 
index 3bc616bdd09fb0f5f4f0c1364fadb3fac65557ce..15d192f166a183bc5037b8438913d7ba174420f5 100644 (file)
@@ -25,7 +25,9 @@
  * -------------------------------------------------------------------------- */
 
 #include "drawing.h"
+#include "utils/gui.h"
 #include <FL/Fl.H>
+#include <cassert>
 
 namespace giada::v
 {
@@ -48,4 +50,14 @@ void drawLine(geompp::Line<int> l, Fl_Color c)
        fl_color(c);
        fl_line(l.x1, l.y1, l.x2, l.y2);
 }
+
+/* -------------------------------------------------------------------------- */
+
+void drawText(const std::string& s, geompp::Rect<int> b, Fl_Color c, int alignment)
+{
+       assert(!s.empty());
+
+       fl_color(c);
+       fl_draw(u::gui::truncate(s, b.w - 16).c_str(), b.x, b.y, b.w, b.h, alignment);
+}
 } // namespace giada::v
index 6eb3b61eb9622b082d38ab07c71edd64a246a53f..2882a0f8f34ed3f98d44954fb2c4dd43cea468b9 100644 (file)
 
 #include "deps/geompp/src/rect.hpp"
 #include <FL/fl_draw.H>
+#include <string>
 
 namespace giada::v
 {
-void drawRectf(geompp::Rect<int> r, Fl_Color c);
-void drawRect(geompp::Rect<int> r, Fl_Color c);
-void drawLine(geompp::Line<int> l, Fl_Color c);
+void drawRectf(geompp::Rect<int>, Fl_Color);
+void drawRect(geompp::Rect<int>, Fl_Color);
+void drawLine(geompp::Line<int>, Fl_Color);
+void drawText(const std::string&, geompp::Rect<int>, Fl_Color c, int alignment = FL_ALIGN_CENTER);
 } // namespace giada::v
 
 #endif
\ No newline at end of file
index ff30eb51a66e6b4882b0460d605093ae8c52dfb8..97ce87f7a3b89febf614d7d52da35b499da0d471 100644 (file)
@@ -38,20 +38,22 @@ geGridTool::geGridTool(Pixel x, Pixel y, m::Conf::Data& c)
 , m_conf(c)
 {
        gridType = new geChoice(x, y, 40, 20);
-       gridType->add("1");
-       gridType->add("2");
-       gridType->add("3");
-       gridType->add("4");
-       gridType->add("6");
-       gridType->add("8");
-       gridType->add("16");
-       gridType->add("32");
-       gridType->value(0);
-       gridType->callback(cb_changeType, (void*)this);
+       gridType->addItem("1");
+       gridType->addItem("2");
+       gridType->addItem("3");
+       gridType->addItem("4");
+       gridType->addItem("6");
+       gridType->addItem("8");
+       gridType->addItem("16");
+       gridType->addItem("32");
+       gridType->showItem(0);
+       gridType->onChange = [this](ID) {
+               window()->redraw();
+       };
 
        active = new geCheck(gridType->x() + gridType->w() + 4, y, 20, 20);
 
-       gridType->value(m_conf.actionEditorGridVal);
+       gridType->showItem(m_conf.actionEditorGridVal);
        active->value(m_conf.actionEditorGridOn);
 
        end();
@@ -64,23 +66,12 @@ geGridTool::geGridTool(Pixel x, Pixel y, m::Conf::Data& c)
 
 geGridTool::~geGridTool()
 {
-       m_conf.actionEditorGridVal = gridType->value();
+       m_conf.actionEditorGridVal = gridType->getSelectedId();
        m_conf.actionEditorGridOn  = active->value();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void geGridTool::cb_changeType(Fl_Widget* /*w*/, void* p) { ((geGridTool*)p)->cb_changeType(); }
-
-/* -------------------------------------------------------------------------- */
-
-void geGridTool::cb_changeType()
-{
-       window()->redraw();
-}
-
-/* -------------------------------------------------------------------------- */
-
 bool geGridTool::isOn() const
 {
        return active->value();
@@ -90,7 +81,7 @@ bool geGridTool::isOn() const
 
 int geGridTool::getValue() const
 {
-       switch (gridType->value())
+       switch (gridType->getSelectedId())
        {
        case 0:
                return 1;
index 5481045afc612cf827b4c26594e11d8d677f0458..ffa9660c094142d67b291fb482aed46d39132f99 100644 (file)
@@ -57,9 +57,6 @@ private:
 
        geChoice* gridType;
        geCheck*  active;
-
-       static void cb_changeType(Fl_Widget* /*w*/, void* p);
-       void        cb_changeType();
 };
 } // namespace giada::v
 
index 2e92b010386362f222f5ed3ba7514e7891ab045c..cab44a40f35ef749c5575a374e928c3217960220 100644 (file)
@@ -29,6 +29,8 @@
 #include "utils/gui.h"
 #include <FL/fl_draw.H>
 
+namespace giada::v
+{
 geBox::geBox(int x, int y, int w, int h, const char* l, Fl_Align al)
 : Fl_Box(x, y, w, h)
 {
@@ -58,8 +60,9 @@ void geBox::draw()
                draw_label(); // draw_label also paints image, if any
        else if (label() != nullptr)
        {
-               fl_color(G_COLOR_LIGHT_2);
+               fl_color(active() ? G_COLOR_LIGHT_2 : G_COLOR_GREY_4);
                fl_font(FL_HELVETICA, G_GUI_FONT_SIZE_BASE);
-               fl_draw(giada::u::gui::truncate(label(), w() - 8).c_str(), x() + 4, y(), w() - 4, h(), align());
+               fl_draw(giada::u::gui::truncate(label(), w()).c_str(), x(), y(), w(), h(), align());
        }
-}
\ No newline at end of file
+}
+} // namespace giada::v
\ No newline at end of file
index c2ec4ed595d8157ebc8a7ded5bd8617241f6c3fe..6cc6eda7499b2efa98875f40238750f11ed154d0 100644 (file)
@@ -29,6 +29,8 @@
 
 #include <FL/Fl_Box.H>
 
+namespace giada::v
+{
 class geBox : public Fl_Box
 {
 public:
@@ -37,5 +39,6 @@ public:
 
        void draw() override;
 };
+} // namespace giada::v
 
 #endif
index f0065baa255138194a6ca9406755f641f0e0dafa..c2a41e9a135ae19c9f062556baf69d107f795d1d 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "choice.h"
+#include "gui/elems/basics/choice.h"
 #include "core/const.h"
+#include "gui/drawing.h"
 #include "utils/gui.h"
 #include "utils/vector.h"
 #include <FL/fl_draw.H>
 #include <cassert>
-#include <string>
 
 namespace giada::v
 {
-geChoice::geChoice(int x, int y, int w, int h, const char* l, bool ang)
-: Fl_Choice(x, y, w, h, l)
-, m_angle(ang)
+geChoice::geMenu::geMenu(int x, int y, int w, int h)
+: Fl_Choice(x, y, w, h)
 {
        labelsize(G_GUI_FONT_SIZE_BASE);
        labelcolor(G_COLOR_LIGHT_2);
@@ -48,45 +47,67 @@ geChoice::geChoice(int x, int y, int w, int h, const char* l, bool ang)
 
 /* -------------------------------------------------------------------------- */
 
-void geChoice::cb_onChange(Fl_Widget* /*w*/, void* p) { (static_cast<geChoice*>(p))->cb_onChange(); }
+void geChoice::geMenu::draw()
+{
+       geompp::Rect<int> bounds(x(), y(), w(), h());
+
+       drawRectf(bounds, G_COLOR_GREY_2);                       // background
+       drawRect(bounds, static_cast<Fl_Color>(G_COLOR_GREY_4)); // border
+       fl_polygon(x() + w() - 8, y() + h() - 1, x() + w() - 1, y() + h() - 8, x() + w() - 1, y() + h() - 1);
+       if (value() != -1)
+               drawText(text(value()), bounds, active() ? G_COLOR_LIGHT_2 : G_COLOR_GREY_4);
+}
 
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-void geChoice::cb_onChange()
+geChoice::geChoice(int x, int y, int w, int h, const char* l, int labelWidth)
+: geFlex(x, y, w, h, Direction::HORIZONTAL, G_GUI_INNER_MARGIN)
+, m_text(nullptr)
+, m_menu(nullptr)
 {
-       if (onChange != nullptr)
-               onChange(getSelectedId());
+       if (l != nullptr)
+       {
+               m_text = new geBox(l, FL_ALIGN_RIGHT);
+               add(m_text, labelWidth != 0 ? labelWidth : u::gui::getStringRect(l).w);
+       }
+       m_menu = new geMenu(x, y, w, h);
+       add(m_menu);
+       end();
 }
 
 /* -------------------------------------------------------------------------- */
 
-void geChoice::draw()
+geChoice::geChoice(const char* l, int labelWidth)
+: geChoice(0, 0, 0, 0, l, labelWidth)
 {
-       fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_2);                       // bg
-       fl_rect(x(), y(), w(), h(), static_cast<Fl_Color>(G_COLOR_GREY_4)); // border
-       if (m_angle)
-               fl_polygon(x() + w() - 8, y() + h() - 1, x() + w() - 1, y() + h() - 8, x() + w() - 1, y() + h() - 1);
+}
 
-       /* pick up the text() from the selected item (value()) and print it in
-        * the box and avoid overflows */
+/* -------------------------------------------------------------------------- */
 
-       fl_color(!active() ? G_COLOR_GREY_4 : G_COLOR_LIGHT_2);
-       if (value() != -1)
-               fl_draw(u::gui::truncate(text(value()), w() - 16).c_str(), x(), y(), w(), h(), FL_ALIGN_CENTER);
+void geChoice::cb_onChange(Fl_Widget* /*w*/, void* p) { (static_cast<geChoice*>(p))->cb_onChange(); }
+
+/* -------------------------------------------------------------------------- */
+
+void geChoice::cb_onChange()
+{
+       if (onChange != nullptr)
+               onChange(getSelectedId());
 }
 
 /* -------------------------------------------------------------------------- */
 
 ID geChoice::getSelectedId() const
 {
-       return value() == -1 ? -1 : m_ids.at(value());
+       return m_menu->value() == -1 ? -1 : m_ids.at(m_menu->value());
 }
 
 /* -------------------------------------------------------------------------- */
 
 void geChoice::addItem(const std::string& label, ID id)
 {
-       Fl_Choice::add(label.c_str(), 0, cb_onChange, static_cast<void*>(this));
+       m_menu->add(label.c_str(), 0, cb_onChange, static_cast<void*>(this));
 
        if (id != -1)
                m_ids.push_back(id);
@@ -98,19 +119,51 @@ void geChoice::addItem(const std::string& label, ID id)
 
 void geChoice::showItem(const std::string& label)
 {
-       value(find_index(label.c_str()));
+       m_menu->value(m_menu->find_index(label.c_str()));
 }
 
 void geChoice::showItem(ID id)
 {
-       value(u::vector::indexOf(m_ids, id));
+       m_menu->value(u::vector::indexOf(m_ids, id));
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geChoice::activate()
+{
+       geFlex::activate();
+       m_menu->activate();
+       if (m_text != nullptr)
+               m_text->activate();
+}
+
+void geChoice::deactivate()
+{
+       geFlex::deactivate();
+       m_menu->deactivate();
+       if (m_text != nullptr)
+               m_text->deactivate();
+}
+
+/* -------------------------------------------------------------------------- */
+
+std::string geChoice::getSelectedLabel() const
+{
+       return m_menu->text();
+}
+
+/* -------------------------------------------------------------------------- */
+
+std::size_t geChoice::countItems() const
+{
+       return m_ids.size();
 }
 
 /* -------------------------------------------------------------------------- */
 
 void geChoice::clear()
 {
-       Fl_Choice::clear();
+       m_menu->clear();
        m_ids.clear();
 }
 
index 4485e8376b1d3f1310deb33e78a0a8e288fb4558..f96feac98ce7924f7d7595a4abefd25a8a28a10a 100644 (file)
@@ -28,6 +28,8 @@
 #define GE_CHOICE_H
 
 #include "core/types.h"
+#include "gui/elems/basics/box.h"
+#include "gui/elems/basics/flex.h"
 #include <FL/Fl_Choice.H>
 #include <functional>
 #include <string>
 
 namespace giada::v
 {
-class geChoice : public Fl_Choice
+class geChoice : public geFlex
 {
 public:
-       geChoice(int x, int y, int w, int h, const char* l = 0, bool angle = true);
-       void draw() override;
+       /* geChoice 
+       Constructors. If label is != nullptr but labelWidth is not specified, the
+       label width is automatically computed and adjusted accordingly. */
 
-       ID getSelectedId() const;
+       geChoice(int x, int y, int w, int h, const char* l = nullptr, int labelWidth = 0);
+       geChoice(const char* l = nullptr, int labelWidth = 0);
+
+       ID          getSelectedId() const;
+       std::string getSelectedLabel() const;
+       std::size_t countItems() const;
 
        /* addItem
-       Adds a new item with a certain ID. Pass id = -1 to auto-increment it. */
+       Adds a new item with a certain ID. Pass id = -1 to auto-increment it (ID
+       starts from 0). */
 
        void addItem(const std::string& label, ID id = -1);
 
        void showItem(const std::string& label);
-       void showItem(ID id);
+       void showItem(ID);
+       void activate();
+       void deactivate();
+
        void clear();
 
        std::function<void(ID)> onChange = nullptr;
 
 private:
+       class geMenu : public Fl_Choice
+       {
+       public:
+               geMenu(int x, int y, int w, int h);
+               void draw() override;
+       };
+
        static void cb_onChange(Fl_Widget* w, void* p);
        void        cb_onChange();
 
-       bool            m_angle;
+       geBox*          m_text;
+       geMenu*         m_menu;
        std::vector<ID> m_ids;
 };
 } // namespace giada::v
index 4bf142136aeb37c05b4372683c037980c00a93e2..070be45f38c165b52b2d1c5c9b3934622e01892b 100644 (file)
@@ -1,4 +1,31 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2022 Giovanni A. Zuliani | Monocasual Laboratories
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
 #include "flex.h"
+#include <cstddef>
 #include <numeric>
 
 namespace giada::v
@@ -133,4 +160,4 @@ void geFlex::end()
        Fl_Group::end();
        resize(x(), y(), w(), h());
 }
-} // namespace giada::v
\ No newline at end of file
+} // namespace giada::v
index 08fd58eeabdd120105825c1c0ace312304321109..fc50cd2073324b64f035d7788ae7845e2f349917 100644 (file)
@@ -28,6 +28,7 @@
  * -------------------------------------------------------------------------- */
 
 #include "group.h"
+#include <cstddef>
 #include <algorithm>
 
 namespace giada
@@ -86,4 +87,4 @@ Fl_Widget* geGroup::getLastChild()
        return m_widgets.at(m_widgets.size() - 1); // Throws std::out_of_range in case
 }
 } // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada
index 49a32023d01375d35c7ff8b892223c27f63661eb..1460911f729a10a2eb0a64dcbcaebb5e3a844eb5 100644 (file)
@@ -31,6 +31,7 @@
 #define GE_GROUP_H
 
 #include <FL/Fl_Group.H>
+#include <cstddef>
 #include <vector>
 
 namespace giada
index 64f0903a38b2a00712d41c65ebc0e28afce9bc00..2544c0de92762c48a132a597dca67d6ced011743 100644 (file)
@@ -43,6 +43,13 @@ geLiquidScroll::geLiquidScroll(int x, int y, int w, int h, Direction d)
 
 /* -------------------------------------------------------------------------- */
 
+geLiquidScroll::geLiquidScroll(geompp::Rect<int> r, Direction d)
+: geLiquidScroll(r.x, r.y, r.w, r.h, d)
+{
+}
+
+/* -------------------------------------------------------------------------- */
+
 void geLiquidScroll::resize(int X, int Y, int W, int H)
 {
        const int nc = children() - 2; // skip hscrollbar and vscrollbar
index d90a28c2b3b21923bf818cdfdeb4a070be1be297..f56e78b52dae4781393fb2f5a6418b5aa2e08310 100644 (file)
@@ -33,6 +33,7 @@
 #define GE_LIQUID_SCROLL_H
 
 #include "core/const.h"
+#include "deps/geompp/src/rect.hpp"
 #include "gui/types.h"
 #include "scroll.h"
 
@@ -42,6 +43,7 @@ class geLiquidScroll : public geScroll
 {
 public:
        geLiquidScroll(int x, int y, int w, int h, Direction d);
+       geLiquidScroll(geompp::Rect<int>, Direction d);
 
        void resize(int x, int y, int w, int h) override;
 
index f4dacc6abadea3ba37d963447d322e090350fd45..8ec0827e9e842cdbf0ee47cddf3ddbc3b317f310 100644 (file)
@@ -30,6 +30,7 @@
 #include <FL/Fl_Group.H>
 #include <FL/fl_draw.H>
 #include <cassert>
+#include <cstddef>
 #include <vector>
 
 namespace giada::v
@@ -229,4 +230,4 @@ void geResizerBar::moveTo(int p)
        const int        curr = m_direction == Direction::VERTICAL ? wd.h() : wd.w();
        handleDrag(p - curr);
 }
-} // namespace giada::v
\ No newline at end of file
+} // namespace giada::v
index 22a3171d64bba171bd250b0357cb3c28c469f308..0b5892ad2193483a744fffa4c9791a58e7537c73 100644 (file)
@@ -28,6 +28,7 @@
 #include "boxtypes.h"
 #include "core/const.h"
 #include <cassert>
+#include <cstddef>
 
 namespace giada
 {
@@ -78,4 +79,4 @@ Fl_Widget* geScrollPack::getLastChild()
        return m_widgets.at(m_widgets.size() - 1); // Throws std::out_of_range in case
 }
 } // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada
index 95d946e756a88123bf9a7250255d414b5eebfb05..14dfe4d324c43d6fa7894a6b93e1b5a0febeb452 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "gui/elems/basics/pack.h"
 #include "gui/elems/basics/scroll.h"
+#include <cstddef>
 
 namespace giada
 {
diff --git a/src/gui/elems/basics/tabs.cpp b/src/gui/elems/basics/tabs.cpp
new file mode 100644 (file)
index 0000000..b2aeca9
--- /dev/null
@@ -0,0 +1,54 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2022 Giovanni A. Zuliani | Monocasual Laboratories
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "gui/elems/basics/tabs.h"
+#include "core/const.h"
+#include "gui/elems/basics/boxtypes.h"
+
+namespace giada::v
+{
+geTabs::geTabs(geompp::Rect<int> r)
+: Fl_Tabs(r.x, r.y, r.w, r.h)
+{
+       box(G_CUSTOM_BORDER_BOX);
+       labelcolor(G_COLOR_LIGHT_2);
+       end();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geTabs::add(Fl_Widget* wg)
+{
+       constexpr int TAB_HEIGHT = 25;
+
+       wg->resize(x(), y() + TAB_HEIGHT, w(), h() - TAB_HEIGHT);
+       wg->labelsize(G_GUI_FONT_SIZE_BASE);
+       wg->selection_color(G_COLOR_GREY_4);
+
+       Fl_Tabs::add(wg);
+       resizable(wg);//  To keep the tab height constant during resizing
+}
+} // namespace giada::v
diff --git a/src/gui/elems/basics/tabs.h b/src/gui/elems/basics/tabs.h
new file mode 100644 (file)
index 0000000..37229f2
--- /dev/null
@@ -0,0 +1,44 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2022 Giovanni A. Zuliani | Monocasual Laboratories
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef GE_TABS_H
+#define GE_TABS_H
+
+#include "deps/geompp/src/rect.hpp"
+#include <FL/Fl_Tabs.H>
+
+namespace giada::v
+{
+class geTabs : public Fl_Tabs
+{
+public:
+       geTabs(geompp::Rect<int>);
+
+       void add(Fl_Widget*);
+};
+} // namespace giada::v
+
+#endif
\ No newline at end of file
index 7894597ab26a849ada586fa10a24630e9207037b..aaac8de49a7feef85524c60939d634dc44a866a7 100644 (file)
 #include "core/kernelAudio.h"
 #include "deps/rtaudio/RtAudio.h"
 #include "gui/elems/basics/box.h"
-#include "gui/elems/basics/button.h"
 #include "gui/elems/basics/check.h"
+#include "gui/elems/basics/choice.h"
+#include "gui/elems/basics/flex.h"
 #include "gui/elems/basics/input.h"
 #include "utils/string.h"
 #include <string>
 
+constexpr int LABEL_WIDTH = 110;
+
 namespace giada::v
 {
-geTabAudio::geDeviceMenu::geDeviceMenu(int x, int y, int w, int h, const char* l,
-    const std::vector<c::config::AudioDeviceData>& devices)
-: geChoice(x, y, w, h, l)
+geTabAudio::geDeviceMenu::geDeviceMenu(const char* l, const std::vector<c::config::AudioDeviceData>& devices)
+: geChoice(l, LABEL_WIDTH)
 {
        if (devices.size() == 0)
        {
@@ -56,9 +58,8 @@ geTabAudio::geDeviceMenu::geDeviceMenu(int x, int y, int w, int h, const char* l
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-geTabAudio::geChannelMenu::geChannelMenu(int x, int y, int w, int h, const char* l,
-    const c::config::AudioDeviceData& data)
-: geChoice(x, y, w, h, l)
+geTabAudio::geChannelMenu::geChannelMenu(const char* l, const c::config::AudioDeviceData& data)
+: geChoice(l, LABEL_WIDTH)
 , m_data(data)
 {
 }
@@ -111,28 +112,75 @@ void geTabAudio::geChannelMenu::rebuild(const c::config::AudioDeviceData& data)
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-geTabAudio::geTabAudio(int X, int Y, int W, int H)
-: Fl_Group(X, Y, W, H, "Sound System")
+geTabAudio::geTabAudio(geompp::Rect<int> bounds)
+: Fl_Group(bounds.x, bounds.y, bounds.w, bounds.h, "Audio")
 , m_data(c::config::getAudioData())
 , m_initialApi(m_data.api)
 {
-       begin();
-       soundsys        = new geChoice(x() + 114, y() + 9, 250, 20, "System");
-       buffersize      = new geChoice(x() + 114, y() + 37, 55, 20, "Buffer size");
-       samplerate      = new geChoice(x() + 304, y() + 37, 60, 20, "Sample rate");
-       sounddevOut     = new geDeviceMenu(x() + 114, y() + 65, 250, 20, "Output device", m_data.outputDevices);
-       channelsOut     = new geChannelMenu(x() + 114, y() + 93, 55, 20, "Output channels", m_data.outputDevice);
-       limitOutput     = new geCheck(x() + 177, y() + 93, 100, 20, "Limit output");
-       sounddevIn      = new geDeviceMenu(x() + 114, y() + 121, 234, 20, "Input device", m_data.inputDevices);
-       enableIn        = new geCheck(sounddevIn->x() + sounddevIn->w() + 4, sounddevIn->y(), 12, 20);
-       channelsIn      = new geChannelMenu(x() + 114, y() + 149, 55, 20, "Input channels", m_data.inputDevice);
-       recTriggerLevel = new geInput(x() + 309, y() + 149, 55, 20, "Rec threshold (dB)");
-       rsmpQuality     = new geChoice(x() + 114, y() + 177, 250, 20, "Resampling");
-       new geBox(x(), rsmpQuality->y() + rsmpQuality->h() + 8, w(), 92, "Restart Giada for the changes to take effect.");
        end();
 
-       labelsize(G_GUI_FONT_SIZE_BASE);
-       selection_color(G_COLOR_GREY_4);
+       geFlex* body = new geFlex(bounds.reduced(G_GUI_OUTER_MARGIN), Direction::VERTICAL, G_GUI_OUTER_MARGIN);
+       {
+               soundsys = new geChoice("System", LABEL_WIDTH);
+
+               geFlex* line1 = new geFlex(Direction::HORIZONTAL, G_GUI_OUTER_MARGIN);
+               {
+                       buffersize = new geChoice("Buffer size", LABEL_WIDTH);
+                       samplerate = new geChoice("Sample rate", LABEL_WIDTH);
+
+                       line1->add(buffersize, 180);
+                       line1->add(samplerate, 180);
+                       line1->end();
+               }
+
+               sounddevOut = new geDeviceMenu("Output device", m_data.outputDevices);
+
+               geFlex* line2 = new geFlex(Direction::HORIZONTAL, G_GUI_OUTER_MARGIN);
+               {
+                       channelsOut = new geChannelMenu("Output channels", m_data.outputDevice);
+                       limitOutput = new geCheck(x() + 177, y() + 93, 100, 20, "Limit output");
+
+                       line2->add(channelsOut, 180);
+                       line2->add(limitOutput);
+                       line2->end();
+               }
+
+               geFlex* line3 = new geFlex(Direction::HORIZONTAL, G_GUI_OUTER_MARGIN);
+               {
+                       sounddevIn = new geDeviceMenu("Input device", m_data.inputDevices);
+                       enableIn   = new geCheck(0, 0, 0, 0);
+
+                       line3->add(sounddevIn);
+                       line3->add(enableIn, 12);
+                       line3->end();
+               }
+
+               geFlex* line4 = new geFlex(Direction::HORIZONTAL, G_GUI_OUTER_MARGIN);
+               {
+                       channelsIn      = new geChannelMenu("Input channels", m_data.inputDevice);
+                       recTriggerLevel = new geInput(0, 0, 0, 0, "Rec threshold (dB)");
+
+                       line4->add(channelsIn, 180);
+                       line4->add(new geBox(), 132); // TODO - temporary hack for geInput's label
+                       line4->add(recTriggerLevel, 40);
+                       line4->end();
+               }
+
+               rsmpQuality = new geChoice("Resampling", LABEL_WIDTH);
+
+               body->add(soundsys, 20);
+               body->add(line1, 20);
+               body->add(sounddevOut, 20);
+               body->add(line2, 20);
+               body->add(line3, 20);
+               body->add(line4, 20);
+               body->add(rsmpQuality, 20);
+               body->add(new geBox("Restart Giada for the changes to take effect."));
+               body->end();
+       }
+
+       add(body);
+       resizable(body);
 
        for (const auto& [key, value] : m_data.apis)
                soundsys->addItem(value.c_str(), key);
index b2f77bcdbd0956503488dce2407d4ba274129595..52d3deef7a3d5b2e052418a4a0688a2a4c4be3b6 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef GE_TAB_AUDIO_H
 #define GE_TAB_AUDIO_H
 
+#include "deps/geompp/src/rect.hpp"
 #include "glue/config.h"
 #include "gui/elems/basics/choice.h"
 #include <FL/Fl_Group.H>
@@ -36,18 +37,17 @@ class geInput;
 
 namespace giada::v
 {
-class geButton;
 class geTabAudio : public Fl_Group
 {
 public:
        struct geDeviceMenu : public geChoice
        {
-               geDeviceMenu(int x, int y, int w, int h, const char* l, const std::vector<c::config::AudioDeviceData>&);
+               geDeviceMenu(const char* l, const std::vector<c::config::AudioDeviceData>&);
        };
 
        struct geChannelMenu : public geChoice
        {
-               geChannelMenu(int x, int y, int w, int h, const char* l, const c::config::AudioDeviceData&);
+               geChannelMenu(const char* l, const c::config::AudioDeviceData&);
 
                int getChannelsCount() const;
                int getChannelsStart() const;
@@ -60,7 +60,7 @@ public:
                c::config::AudioDeviceData m_data;
        };
 
-       geTabAudio(int x, int y, int w, int h);
+       geTabAudio(geompp::Rect<int>);
 
        void save();
 
index c15c2f7bc992c58bc5effbf1fa7c5181f993610f..bcf24941dce4f8ead5940a39378ad16a06662932 100644 (file)
 #include "core/const.h"
 #include "gui/elems/basics/box.h"
 #include "gui/elems/basics/check.h"
+#include "gui/elems/basics/flex.h"
 #include <FL/Fl_Pack.H>
 
 namespace giada::v
 {
-geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H, m::Conf::Data& c)
-: Fl_Group(X, Y, W, H)
-, m_container(X, Y + G_GUI_OUTER_MARGIN, Direction::VERTICAL, G_GUI_OUTER_MARGIN)
-, m_chansStopOnSeqHalt(0, 0, 280, 30, "Dynamic channels stop immediately when the sequencer\nis halted")
-, m_treatRecsAsLoops(0, 0, 280, 20, "Treat one shot channels with actions as loops")
-, m_inputMonitorDefaultOn(0, 0, 280, 20, "New sample channels have input monitor on by default")
-, m_overdubProtectionDefaultOn(0, 0, 280, 30, "New sample channels have overdub protection on\nby default")
+geTabBehaviors::geTabBehaviors(geompp::Rect<int> bounds, m::Conf::Data& c)
+: Fl_Group(bounds.x, bounds.y, bounds.w, bounds.h, "Behaviors")
 , m_conf(c)
 {
        end();
 
-       label("Behaviors");
-       labelsize(G_GUI_FONT_SIZE_BASE);
-       selection_color(G_COLOR_GREY_4);
+       geFlex* body = new geFlex(bounds.reduced(G_GUI_OUTER_MARGIN), Direction::VERTICAL, G_GUI_OUTER_MARGIN);
+       {
+               m_chansStopOnSeqHalt         = new geCheck(0, 0, 0, 0, "Dynamic channels stop immediately when the sequencer\nis halted");
+               m_treatRecsAsLoops           = new geCheck(0, 0, 0, 0, "Treat one shot channels with actions as loops");
+               m_inputMonitorDefaultOn      = new geCheck(0, 0, 0, 0, "New sample channels have input monitor on by default");
+               m_overdubProtectionDefaultOn = new geCheck(0, 0, 0, 0, "New sample channels have overdub protection on\nby default");
 
-       m_container.add(&m_chansStopOnSeqHalt);
-       m_container.add(&m_treatRecsAsLoops);
-       m_container.add(&m_inputMonitorDefaultOn);
-       m_container.add(&m_overdubProtectionDefaultOn);
+               body->add(m_chansStopOnSeqHalt, 30);
+               body->add(m_treatRecsAsLoops, 20);
+               body->add(m_inputMonitorDefaultOn, 20);
+               body->add(m_overdubProtectionDefaultOn, 30);
+               body->end();
+       };
 
-       add(m_container);
+       add(body);
+       resizable(body);
 
-       m_chansStopOnSeqHalt.value(m_conf.chansStopOnSeqHalt);
-       m_treatRecsAsLoops.value(m_conf.treatRecsAsLoops);
-       m_inputMonitorDefaultOn.value(m_conf.inputMonitorDefaultOn);
-       m_overdubProtectionDefaultOn.value(m_conf.overdubProtectionDefaultOn);
+       m_chansStopOnSeqHalt->value(m_conf.chansStopOnSeqHalt);
+       m_treatRecsAsLoops->value(m_conf.treatRecsAsLoops);
+       m_inputMonitorDefaultOn->value(m_conf.inputMonitorDefaultOn);
+       m_overdubProtectionDefaultOn->value(m_conf.overdubProtectionDefaultOn);
 }
 
 /* -------------------------------------------------------------------------- */
 
 void geTabBehaviors::save()
 {
-       m_conf.chansStopOnSeqHalt         = m_chansStopOnSeqHalt.value();
-       m_conf.treatRecsAsLoops           = m_treatRecsAsLoops.value();
-       m_conf.inputMonitorDefaultOn      = m_inputMonitorDefaultOn.value();
-       m_conf.overdubProtectionDefaultOn = m_overdubProtectionDefaultOn.value();
+       m_conf.chansStopOnSeqHalt         = m_chansStopOnSeqHalt->value();
+       m_conf.treatRecsAsLoops           = m_treatRecsAsLoops->value();
+       m_conf.inputMonitorDefaultOn      = m_inputMonitorDefaultOn->value();
+       m_conf.overdubProtectionDefaultOn = m_overdubProtectionDefaultOn->value();
 }
 } // namespace giada::v
\ No newline at end of file
index f000fb38c2f9468909189ea65f3863e78767a4ab..4e488a7a6bb6dc170f7fc47c812ebf23de871a44 100644 (file)
@@ -28,8 +28,8 @@
 #define GE_TAB_BEHAVIORS_H
 
 #include "core/conf.h"
+#include "deps/geompp/src/rect.hpp"
 #include "gui/elems/basics/check.h"
-#include "gui/elems/basics/pack.h"
 #include <FL/Fl_Group.H>
 
 namespace giada::v
@@ -37,16 +37,15 @@ namespace giada::v
 class geTabBehaviors : public Fl_Group
 {
 public:
-       geTabBehaviors(int x, int y, int w, int h, m::Conf::Data&);
+       geTabBehaviors(geompp::Rect<int>, m::Conf::Data&);
 
        void save();
 
 private:
-       gePack  m_container;
-       geCheck m_chansStopOnSeqHalt;
-       geCheck m_treatRecsAsLoops;
-       geCheck m_inputMonitorDefaultOn;
-       geCheck m_overdubProtectionDefaultOn;
+       geCheck* m_chansStopOnSeqHalt;
+       geCheck* m_treatRecsAsLoops;
+       geCheck* m_inputMonitorDefaultOn;
+       geCheck* m_overdubProtectionDefaultOn;
 
        m::Conf::Data& m_conf;
 };
diff --git a/src/gui/elems/config/tabBindings.cpp b/src/gui/elems/config/tabBindings.cpp
new file mode 100644 (file)
index 0000000..59c07b5
--- /dev/null
@@ -0,0 +1,59 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2022 Giovanni A. Zuliani | Monocasual Laboratories
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "gui/elems/config/tabBindings.h"
+#include "core/const.h"
+#include "gui/elems/basics/liquidScroll.h"
+#include "gui/elems/keyBinder.h"
+#include "utils/gui.h"
+
+namespace giada::v
+{
+geTabBindings::geTabBindings(geompp::Rect<int> bounds, m::Conf::Data& conf)
+: Fl_Group(bounds.x, bounds.y, bounds.w, bounds.h, "Key Bindings")
+{
+       end();
+
+       geFlex* body = new geFlex(bounds.reduced(G_GUI_OUTER_MARGIN), Direction::VERTICAL, G_GUI_INNER_MARGIN);
+       {
+               play          = new geKeyBinder("Play", conf.keyBindings.find(m::Conf::KEY_BIND_PLAY)->second);
+               rewind        = new geKeyBinder("Rewind", conf.keyBindings.find(m::Conf::KEY_BIND_REWIND)->second);
+               recordActions = new geKeyBinder("Record actions", conf.keyBindings.find(m::Conf::KEY_BIND_RECORD_ACTIONS)->second);
+               recordInput   = new geKeyBinder("Record audio", conf.keyBindings.find(m::Conf::KEY_BIND_RECORD_INPUT)->second);
+               exit          = new geKeyBinder("Exit", conf.keyBindings.find(m::Conf::KEY_BIND_EXIT)->second);
+
+               body->add(play, G_GUI_UNIT);
+               body->add(rewind, G_GUI_UNIT);
+               body->add(recordActions, G_GUI_UNIT);
+               body->add(recordInput, G_GUI_UNIT);
+               body->add(exit, G_GUI_UNIT);
+               body->end();
+       }
+
+       add(body);
+       resizable(body);
+}
+} // namespace giada::v
\ No newline at end of file
diff --git a/src/gui/elems/config/tabBindings.h b/src/gui/elems/config/tabBindings.h
new file mode 100644 (file)
index 0000000..fc6aafe
--- /dev/null
@@ -0,0 +1,54 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2022 Giovanni A. Zuliani | Monocasual Laboratories
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef GE_CONFIG_TAB_BINDINGS_H
+#define GE_CONFIG_TAB_BINDINGS_H
+
+#include "core/conf.h"
+#include "deps/geompp/src/rect.hpp"
+#include <FL/Fl_Group.H>
+
+class geCheck;
+class geInput;
+
+namespace giada::v
+{
+class geKeyBinder;
+class geTabBindings : public Fl_Group
+{
+public:
+       geTabBindings(geompp::Rect<int>, m::Conf::Data&);
+
+private:
+       geKeyBinder* play;
+       geKeyBinder* rewind;
+       geKeyBinder* recordActions;
+       geKeyBinder* recordInput;
+       geKeyBinder* exit;
+};
+} // namespace giada::v
+
+#endif
index 3ca5f11984f4c216978e14dd4b79c53ba4a2bb97..c34e62863be2003724b9287d449b988348345c59 100644 (file)
 #include "utils/gui.h"
 #include <string>
 
+constexpr int LABEL_WIDTH = 120;
+
 namespace giada::v
 {
-geTabMidi::geMenu::geMenu(int x, int y, int w, int h, const char* l,
-    const std::vector<std::string>& data, const std::string& msgIfNotFound)
-: geChoice(x, y, w, h, l)
+geTabMidi::geMenu::geMenu(const char* l, const std::vector<std::string>& data,
+    const std::string& msgIfNotFound)
+: geChoice(l, LABEL_WIDTH)
 {
        if (data.size() == 0)
        {
@@ -54,24 +56,51 @@ geTabMidi::geMenu::geMenu(int x, int y, int w, int h, const char* l,
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
-geTabMidi::geTabMidi(int X, int Y, int W, int H)
-: Fl_Group(X, Y, W, H, "MIDI")
+geTabMidi::geTabMidi(geompp::Rect<int> bounds)
+: Fl_Group(bounds.x, bounds.y, bounds.w, bounds.h, "MIDI")
 , m_data(c::config::getMidiData())
 , m_initialApi(m_data.api)
 {
-       begin();
-       system    = new geChoice(x() + w() - 250, y() + 9, 250, 20, "System");
-       portOut   = new geMenu(x() + w() - 250, system->y() + system->h() + 8, 234, 20, "Output port", m_data.outPorts, "-- no ports found --");
-       enableOut = new geCheck(portOut->x() + portOut->w() + 4, portOut->y(), 12, 20);
-       portIn    = new geMenu(x() + w() - 250, portOut->y() + portOut->h() + 8, 234, 20, "Input port", m_data.inPorts, "-- no ports found --");
-       enableIn  = new geCheck(portIn->x() + portIn->w() + 4, portIn->y(), 12, 20);
-       midiMap   = new geMenu(x() + w() - 250, portIn->y() + portIn->h() + 8, 250, 20, "Output Midi Map", m_data.midiMaps, "(no MIDI maps available)");
-       sync      = new geChoice(x() + w() - 250, midiMap->y() + midiMap->h() + 8, 250, 20, "Sync");
-       new geBox(x(), sync->y() + sync->h() + 8, w(), h() - 150, "Restart Giada for the changes to take effect.");
        end();
 
-       labelsize(G_GUI_FONT_SIZE_BASE);
-       selection_color(G_COLOR_GREY_4);
+       geFlex* body = new geFlex(bounds.reduced(G_GUI_OUTER_MARGIN), Direction::VERTICAL, G_GUI_OUTER_MARGIN);
+       {
+               system = new geChoice("System", LABEL_WIDTH);
+
+               geFlex* line1 = new geFlex(Direction::HORIZONTAL, G_GUI_OUTER_MARGIN);
+               {
+                       portOut   = new geMenu("Output port", m_data.outPorts, "-- no ports found --");
+                       enableOut = new geCheck(0, 0, 0, 0);
+
+                       line1->add(portOut);
+                       line1->add(enableOut, 12);
+                       line1->end();
+               }
+
+               geFlex* line2 = new geFlex(Direction::HORIZONTAL, G_GUI_OUTER_MARGIN);
+               {
+                       portIn   = new geMenu("Input port", m_data.inPorts, "-- no ports found --");
+                       enableIn = new geCheck(0, 0, 0, 0);
+
+                       line2->add(portIn);
+                       line2->add(enableIn, 12);
+                       line2->end();
+               }
+
+               midiMap = new geMenu("Output Midi Map", m_data.midiMaps, "(no MIDI maps available)");
+               sync    = new geChoice("Sync", LABEL_WIDTH);
+
+               body->add(system, 20);
+               body->add(line1, 20);
+               body->add(line2, 20);
+               body->add(midiMap, 20);
+               body->add(sync, 20);
+               body->add(new geBox("Restart Giada for the changes to take effect."));
+               body->end();
+       }
+
+       add(body);
+       resizable(body);
 
        for (const auto& [key, value] : m_data.apis)
                system->addItem(value.c_str(), key);
@@ -93,7 +122,7 @@ geTabMidi::geTabMidi(int X, int Y, int W, int H)
        enableOut->onChange = [this](bool b) {
                if (b)
                {
-                       m_data.outPort = portOut->value();
+                       m_data.outPort = portOut->getSelectedId();
                        portOut->activate();
                }
                else
@@ -108,7 +137,7 @@ geTabMidi::geTabMidi(int X, int Y, int W, int H)
        enableIn->onChange = [this](bool b) {
                if (b)
                {
-                       m_data.inPort = portIn->value();
+                       m_data.inPort = portIn->getSelectedId();
                        portIn->activate();
                }
                else
index 0fce166b8eb149bdfef45fa8042fad0bc41a40ec..3add0b5bb540fb23cea302ae20b3c0af3f02d3b5 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef GE_TAB_MIDI_H
 #define GE_TAB_MIDI_H
 
+#include "deps/geompp/src/rect.hpp"
 #include "glue/config.h"
 #include "gui/elems/basics/choice.h"
 #include <FL/Fl_Group.H>
@@ -40,11 +41,10 @@ class geTabMidi : public Fl_Group
 public:
        struct geMenu : public geChoice
        {
-               geMenu(int x, int y, int w, int h, const char* l, const std::vector<std::string>&,
-                   const std::string& msgIfNotFound);
+               geMenu(const char* l, const std::vector<std::string>&, const std::string& msgIfNotFound);
        };
 
-       geTabMidi(int x, int y, int w, int h);
+       geTabMidi(geompp::Rect<int>);
 
        void save() const;
 
index 81f8badc98cb821d271442cc19b83044106820c3..8cf108e82d75a7f3897596c65ecd3ed8fcd7900f 100644 (file)
 
 #include "tabMisc.h"
 #include "core/const.h"
+#include "gui/elems/basics/choice.h"
+
+constexpr int LABEL_WIDTH = 120;
 
 namespace giada::v
 {
-geTabMisc::geTabMisc(int X, int Y, int W)
-: geGroup(X, Y)
+geTabMisc::geTabMisc(geompp::Rect<int> bounds)
+: Fl_Group(bounds.x, bounds.y, bounds.w, bounds.h, "Misc")
 , m_data(c::config::getMiscData())
-, m_debugMsg(W - 230, 9, 230, 20, "Debug messages")
-, m_tooltips(W - 230, 37, 230, 20, "Tooltips")
 {
-       add(&m_debugMsg);
-       add(&m_tooltips);
+       end();
+
+       geFlex* body = new geFlex(bounds.reduced(G_GUI_OUTER_MARGIN), Direction::VERTICAL, G_GUI_OUTER_MARGIN);
+       {
+               m_debugMsg = new geChoice("Debug messages", LABEL_WIDTH);
+               m_tooltips = new geChoice("Tooltips", LABEL_WIDTH);
 
-       m_debugMsg.addItem("Disabled");
-       m_debugMsg.addItem("To standard output");
-       m_debugMsg.addItem("To file");
-       m_debugMsg.onChange = [this](ID id) { m_data.logMode = id; };
+               body->add(m_debugMsg, 20);
+               body->add(m_tooltips, 20);
+               body->end();
+       }
 
-       m_tooltips.addItem("Disabled");
-       m_tooltips.addItem("Enabled");
-       m_tooltips.onChange = [this](ID id) { m_data.showTooltips = id; };
+       add(body);
+       resizable(body);
 
-       m_debugMsg.showItem(m_data.logMode);
-       m_tooltips.showItem(m_data.showTooltips);
+       m_debugMsg->addItem("Disabled");
+       m_debugMsg->addItem("To standard output");
+       m_debugMsg->addItem("To file");
+       m_debugMsg->showItem(m_data.logMode);
+       m_debugMsg->onChange = [this](ID id) { m_data.logMode = id; };
 
-       copy_label("Misc");
-       labelsize(G_GUI_FONT_SIZE_BASE);
-       selection_color(G_COLOR_GREY_4);
+       m_tooltips->addItem("Disabled");
+       m_tooltips->addItem("Enabled");
+       m_tooltips->showItem(m_data.showTooltips);
+       m_tooltips->onChange = [this](ID id) { m_data.showTooltips = id; };
 }
 
 /* -------------------------------------------------------------------------- */
index 727e4b59103253fa455bd26581c4e756c767d90b..f2d3c5a4664b5e3a5ea669581c0e4f225c51fd24 100644 (file)
 #ifndef GE_TAB_MISC_H
 #define GE_TAB_MISC_H
 
+#include "deps/geompp/src/rect.hpp"
 #include "glue/config.h"
-#include "gui/elems/basics/choice.h"
-#include "gui/elems/basics/group.h"
+#include <FL/Fl_Group.H>
 
 namespace giada::v
 {
-class geTabMisc : public geGroup
+class geChoice;
+class geTabMisc : public Fl_Group
 {
 public:
-       geTabMisc(int x, int y, int w);
+       geTabMisc(geompp::Rect<int>);
 
        void save();
 
 private:
        c::config::MiscData m_data;
 
-       geChoice m_debugMsg;
-       geChoice m_tooltips;
+       geChoice* m_debugMsg;
+       geChoice* m_tooltips;
 };
 } // namespace giada::v
 
index 3c4a3f11bbc7b3a656912efd0bf1f13cafa803c5..43f5507dc1171dc3f4f1fa0cb19de986a5674c2f 100644 (file)
@@ -36,6 +36,7 @@
 #include "gui/elems/basics/box.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/check.h"
+#include "gui/elems/basics/flex.h"
 #include "gui/elems/basics/input.h"
 #include "utils/gui.h"
 #include "utils/string.h"
 
 namespace giada::v
 {
-geTabPlugins::geTabPlugins(int X, int Y, int W, int H)
-: Fl_Group(X, Y, W, H, "Plug-ins")
-, m_browse(x() + w() - G_GUI_UNIT, y() + 9, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm)
-, m_folderPath(m_browse.x() - 258, y() + 9, 250, G_GUI_UNIT)
-, m_scanButton(x() + w() - 150, m_folderPath.y() + m_folderPath.h() + 8, 150, G_GUI_UNIT)
-, m_info(x(), m_scanButton.y() + m_scanButton.h() + 8, w(), 240)
+geTabPlugins::geTabPlugins(geompp::Rect<int> bounds)
+: Fl_Group(bounds.x, bounds.y, bounds.w, bounds.h, "Plug-ins")
 {
        end();
 
-       labelsize(G_GUI_FONT_SIZE_BASE);
-       selection_color(G_COLOR_GREY_4);
+       geFlex* body = new geFlex(bounds.reduced(G_GUI_OUTER_MARGIN), Direction::VERTICAL, G_GUI_OUTER_MARGIN);
+       {
+               geFlex* line1 = new geFlex(Direction::HORIZONTAL, G_GUI_OUTER_MARGIN);
+               {
+                       m_folderPath = new geInput(0, 0, 0, 0);
+                       m_browse     = new geButton("", zoomInOff_xpm, zoomInOn_xpm);
 
-       m_info.hide();
+                       line1->add(new geBox(), 80); // TODO - temporary hack for geInput's label
+                       line1->add(m_folderPath);
+                       line1->add(m_browse, 20);
+                       line1->end();
+               }
 
-       m_folderPath.label("Plug-ins folder");
-       m_folderPath.onChange = [this](const std::string& v) {
+               m_scanButton = new geButton();
+               m_info       = new geBox();
+
+               body->add(line1, 20);
+               body->add(m_scanButton, 20);
+               body->add(m_info);
+               body->end();
+       }
+
+       add(body);
+       resizable(body);
+
+       m_info->hide();
+
+       m_folderPath->label("Plug-ins folder");
+       m_folderPath->onChange = [this](const std::string& v) {
                m_data.pluginPath = v;
        };
 
-       m_browse.callback(cb_browse, (void*)this);
-       m_scanButton.callback(cb_scan, (void*)this);
+       m_browse->onClick = [this]() {
+               c::layout::openBrowserForPlugins(*static_cast<v::gdWindow*>(top_window()));
+       };
+
+       m_scanButton->onClick = [this]() {
+               std::function<void(float)> callback = [this](float progress) {
+                       std::string l = "Scan in progress (" + std::to_string((int)(progress * 100)) + "%). Please wait...";
+                       m_info->label(l.c_str());
+                       Fl::wait();
+               };
+
+               m_info->show();
+               c::config::scanPlugins(m_folderPath->value(), callback);
+               m_info->hide();
+               rebuild();
+       };
 
        rebuild();
 }
@@ -76,38 +109,10 @@ void geTabPlugins::rebuild()
        m_data = c::config::getPluginData();
 
        const std::string scanLabel = "Scan (" + std::to_string(m_data.numAvailablePlugins) + " found)";
-       m_scanButton.copy_label(scanLabel.c_str());
-
-       m_folderPath.value(m_data.pluginPath.c_str());
-       m_folderPath.redraw();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void geTabPlugins::cb_scan(Fl_Widget* /*w*/, void* p) { ((geTabPlugins*)p)->cb_scan(); }
-void geTabPlugins::cb_browse(Fl_Widget* /*w*/, void* p) { ((geTabPlugins*)p)->cb_browse(); }
+       m_scanButton->copy_label(scanLabel.c_str());
 
-/* -------------------------------------------------------------------------- */
-
-void geTabPlugins::cb_browse()
-{
-       c::layout::openBrowserForPlugins(*static_cast<v::gdWindow*>(top_window()));
-}
-
-/* -------------------------------------------------------------------------- */
-
-void geTabPlugins::cb_scan()
-{
-       std::function<void(float)> callback = [this](float progress) {
-               std::string l = "Scan in progress (" + std::to_string((int)(progress * 100)) + "%). Please wait...";
-               m_info.label(l.c_str());
-               Fl::wait();
-       };
-
-       m_info.show();
-       c::config::scanPlugins(m_folderPath.value(), callback);
-       m_info.hide();
-       rebuild();
+       m_folderPath->value(m_data.pluginPath.c_str());
+       m_folderPath->redraw();
 }
 
 /* -------------------------------------------------------------------------- */
index 91ad1113f328d0210d8c6464358d9815787c0bbf..2eea680b40aabf4d4927afc4c622edc279b90df9 100644 (file)
 
 #ifdef WITH_VST
 
+#include "deps/geompp/src/rect.hpp"
 #include "glue/config.h"
-#include "gui/elems/basics/box.h"
-#include "gui/elems/basics/button.h"
-#include "gui/elems/basics/input.h"
 #include <FL/Fl_Group.H>
 
+class geInput;
+class geBox;
+
 namespace giada::v
 {
+class geBox;
+class geButton;
 class geTabPlugins : public Fl_Group
 {
 public:
-       geTabPlugins(int x, int y, int w, int h);
+       geTabPlugins(geompp::Rect<int>);
 
        void save();
        void rebuild();
 
 private:
-       static void cb_scan(Fl_Widget* /*w*/, void* p);
-       static void cb_browse(Fl_Widget* /*w*/, void* p);
-       void        cb_scan();
-       void        cb_browse();
-
        c::config::PluginData m_data;
 
-       geButton m_browse;
-       geInput  m_folderPath;
-       geButton m_scanButton;
-       geBox    m_info;
+       geButton* m_browse;
+       geInput*  m_folderPath;
+       geButton* m_scanButton;
+       geBox*    m_info;
 };
 } // namespace giada::v
 
diff --git a/src/gui/elems/keyBinder.cpp b/src/gui/elems/keyBinder.cpp
new file mode 100644 (file)
index 0000000..93d2c3f
--- /dev/null
@@ -0,0 +1,69 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2022 Giovanni A. Zuliani | Monocasual Laboratories
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "gui/elems/keyBinder.h"
+#include "core/const.h"
+#include "glue/layout.h"
+#include "gui/dialogs/keyGrabber.h"
+#include "gui/elems/basics/box.h"
+#include "gui/elems/basics/boxtypes.h"
+#include "gui/elems/basics/button.h"
+#include "utils/gui.h"
+
+namespace giada::v
+{
+geKeyBinder::geKeyBinder(const std::string& l, int& keyRef)
+: geFlex(Direction::HORIZONTAL, G_GUI_INNER_MARGIN)
+{
+       m_labelBox = new geBox(l.c_str());
+       m_keyBox   = new geBox(u::gui::keyToString(keyRef).c_str());
+       m_bindBtn  = new geButton("Bind");
+       m_clearBtn = new geButton("Clear");
+
+       add(m_labelBox);
+       add(m_keyBox, 100);
+       add(m_bindBtn, 50);
+       add(m_clearBtn, 50);
+       end();
+
+       m_labelBox->box(G_CUSTOM_BORDER_BOX);
+       m_keyBox->box(G_CUSTOM_BORDER_BOX);
+
+       m_bindBtn->onClick = [&keyRef, this]() {
+               c::layout::openKeyGrabberWindow(keyRef, [&keyRef, this](int newKey) {
+                       keyRef = newKey;
+                       m_keyBox->copy_label(u::gui::keyToString(keyRef).c_str());
+                       return true;
+               });
+       };
+
+       m_clearBtn->onClick = [&keyRef, this]() {
+               keyRef = 0;
+               m_keyBox->copy_label(u::gui::keyToString(keyRef).c_str());
+       };
+}
+
+} // namespace giada::v
\ No newline at end of file
diff --git a/src/gui/elems/keyBinder.h b/src/gui/elems/keyBinder.h
new file mode 100644 (file)
index 0000000..d91eb75
--- /dev/null
@@ -0,0 +1,50 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2022 Giovanni A. Zuliani | Monocasual Laboratories
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef GE_KEY_BINDER_H
+#define GE_KEY_BINDER_H
+
+#include "gui/elems/basics/flex.h"
+#include <string>
+
+namespace giada::v
+{
+class geBox;
+class geButton;
+class geKeyBinder : public geFlex
+{
+public:
+       geKeyBinder(const std::string& l, int& keyRef);
+
+private:
+       geBox*    m_labelBox;
+       geBox*    m_keyBox;
+       geButton* m_bindBtn;
+       geButton* m_clearBtn;
+};
+} // namespace giada::v
+
+#endif
index 0859e0f210d02b8832da9b8d464bf43be1d0521e..3273c855587c7e44f68b86fee9e86a4ce0a5a374 100644 (file)
@@ -82,7 +82,9 @@ void menuCallback(Fl_Widget* w, void* v)
                c::recorder::clearAllActions(data.id);
                break;
        case Menu::SETUP_KEYBOARD_INPUT:
-               c::layout::openKeyGrabberWindow(data);
+               c::layout::openKeyGrabberWindow(data.key, [channelId = data.id](int key) {
+                       return c::io::channel_setKey(channelId, key);
+               });
                break;
        case Menu::SETUP_MIDI_INPUT:
                c::layout::openChannelMidiInputWindow(data.id);
index d868e61af169ec07285af0fcbb64672ed87c91ef..f2759e46417f1bc6180981a7d361e77ba8a3cb1a 100644 (file)
@@ -102,7 +102,9 @@ void menuCallback(Fl_Widget* w, void* v)
        }
        case Menu::SETUP_KEYBOARD_INPUT:
        {
-               c::layout::openKeyGrabberWindow(data);
+               c::layout::openKeyGrabberWindow(data.key, [channelId = data.id](int key) {
+                       return c::io::channel_setKey(channelId, key);
+               });
                break;
        }
        case Menu::SETUP_MIDI_INPUT:
index 32932858699abe44172f1a2e8f16d977f6b1bb16..737b47541379500eaa01d1ea8db636b3313022c1 100644 (file)
@@ -42,7 +42,7 @@ geMainTimer::geMainTimer(int x, int y)
 {
        m_bpm        = new geButton(0, 0, 60, G_GUI_UNIT);
        m_meter      = new geButton(0, 0, 60, G_GUI_UNIT);
-       m_quantizer  = new geChoice(0, 0, 60, G_GUI_UNIT, "", false);
+       m_quantizer  = new geChoice(0, 0, 60, G_GUI_UNIT);
        m_multiplier = new geButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", multiplyOff_xpm, multiplyOn_xpm);
        m_divider    = new geButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", divideOff_xpm, divideOn_xpm);
        add(m_quantizer);
@@ -144,7 +144,7 @@ void geMainTimer::setLock(bool v)
 
 void geMainTimer::setQuantizer(int q)
 {
-       m_quantizer->value(q);
+       m_quantizer->showItem(q);
 }
 
 /* -------------------------------------------------------------------------- */
index 988da8497c5eaf5fa2b76cf9be0119ac0c5f443e..e730c23d77102231970f1a75561ae1dea170a0ab 100644 (file)
  *
  * -------------------------------------------------------------------------- */
 
-#include "midiLearner.h"
+#include "gui/elems/midiIO/midiLearner.h"
+#include "core/const.h"
 #include "gui/elems/basics/box.h"
 #include "gui/elems/basics/boxtypes.h"
 #include "gui/elems/basics/button.h"
 #include "utils/string.h"
 #include <cassert>
 
-namespace giada
+namespace giada::v
 {
-namespace v
-{
-geMidiLearner::geMidiLearner(int x, int y, std::string l, int param)
-: gePack(x, y, Direction::HORIZONTAL)
+geMidiLearner::geMidiLearner(int x, int y, int w, int h, std::string l, int param)
+: geFlex(x, y, w, h, Direction::HORIZONTAL, G_GUI_INNER_MARGIN)
 , onStartLearn(nullptr)
 , onStopLearn(nullptr)
 , onClearLearn(nullptr)
 , m_param(param)
-, m_text(0, 0, 146, 20, l.c_str())
-, m_valueBtn(0, 0, 80, 20)
-, m_button(0, 0, 50, 20, "learn")
 {
-       add(&m_text);
-       add(&m_valueBtn);
-       add(&m_button);
-
-       m_text.box(G_CUSTOM_BORDER_BOX);
-
-       m_valueBtn.box(G_CUSTOM_BORDER_BOX);
-       m_valueBtn.callback(cb_value, (void*)this);
-       m_valueBtn.when(FL_WHEN_RELEASE);
-
-       m_button.type(FL_TOGGLE_BUTTON);
-       m_button.callback(cb_button, (void*)this);
+       m_text     = new geBox(l.c_str());
+       m_valueBtn = new geButton();
+       m_button   = new geButton("learn");
+
+       add(m_text);
+       add(m_valueBtn, 80);
+       add(m_button, 50);
+       end();
+
+       m_text->box(G_CUSTOM_BORDER_BOX);
+
+       m_valueBtn->box(G_CUSTOM_BORDER_BOX);
+       m_valueBtn->when(FL_WHEN_RELEASE);
+       m_valueBtn->onClick = [this]() {
+               assert(onClearLearn != nullptr);
+
+               if (Fl::event_button() == FL_RIGHT_MOUSE)
+                       onClearLearn(m_param);
+       };
+
+       m_button->type(FL_TOGGLE_BUTTON);
+       m_button->onClick = [this]() {
+               assert(onStartLearn != nullptr);
+               assert(onStopLearn != nullptr);
+
+               if (m_button->value() == 1)
+                       onStartLearn(m_param);
+               else
+                       onStopLearn();
+       };
 }
 
 /* -------------------------------------------------------------------------- */
 
-void geMidiLearner::cb_button(Fl_Widget* /*w*/, void* p) { ((geMidiLearner*)p)->onLearn(); }
-void geMidiLearner::cb_value(Fl_Widget* /*w*/, void* p) { ((geMidiLearner*)p)->onReset(); }
-
-/* -------------------------------------------------------------------------- */
-
 void geMidiLearner::update(uint32_t value)
 {
        std::string tmp = "(not set)";
@@ -77,47 +86,31 @@ void geMidiLearner::update(uint32_t value)
                tmp.pop_back(); // Remove last two digits, useless in MIDI messages
        }
 
-       m_valueBtn.copy_label(tmp.c_str());
-       m_button.value(0);
+       m_valueBtn->copy_label(tmp.c_str());
+       m_button->value(0);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void geMidiLearner::activate()
-{
-       Fl_Group::activate();
-       m_valueBtn.activate();
-       m_button.activate();
-}
-
-void geMidiLearner::deactivate()
+void geMidiLearner::update(const std::string& s)
 {
-       Fl_Group::deactivate();
-       m_valueBtn.deactivate();
-       m_button.deactivate();
+       m_valueBtn->copy_label(s.c_str());
+       m_button->value(0);
 }
 
 /* -------------------------------------------------------------------------- */
 
-void geMidiLearner::onLearn() const
+void geMidiLearner::activate()
 {
-       assert(onStartLearn != nullptr);
-       assert(onStopLearn != nullptr);
-
-       if (m_button.value() == 1)
-               onStartLearn(m_param);
-       else
-               onStopLearn();
+       Fl_Group::activate();
+       m_valueBtn->activate();
+       m_button->activate();
 }
 
-/* -------------------------------------------------------------------------- */
-
-void geMidiLearner::onReset() const
+void geMidiLearner::deactivate()
 {
-       assert(onClearLearn != nullptr);
-
-       if (Fl::event_button() == FL_RIGHT_MOUSE)
-               onClearLearn(m_param);
+       Fl_Group::deactivate();
+       m_valueBtn->deactivate();
+       m_button->deactivate();
 }
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
index 7e97b66a3f247466537a4694d98fce1699be1bda..393c005635b010cc712ab849e6b73c836c154cd2 100644 (file)
 #ifndef GE_MIDI_LEARNER_H
 #define GE_MIDI_LEARNER_H
 
-#include "gui/elems/basics/box.h"
-#include "gui/elems/basics/button.h"
-#include "gui/elems/basics/pack.h"
+#include "gui/elems/basics/flex.h"
 #include <functional>
 #include <string>
 
-class geBox;
-
-namespace giada
-{
-namespace v
+namespace giada::v
 {
+class geBox;
 class geButton;
-class geMidiLearner : public gePack
+class geMidiLearner : public geFlex
 {
 public:
-       geMidiLearner(int x, int y, std::string l, int param);
+       geMidiLearner(int x, int y, int w, int h, std::string l, int param);
 
        /* update
        Updates and repaints the label widget with value 'value'. */
 
        void update(uint32_t value);
 
+       /* update (1)
+       Just sets the label widget with a string value (no parsing done as in (1)). */
+
+       void update(const std::string&);
+
        void activate();
        void deactivate();
 
@@ -63,18 +63,10 @@ protected:
 
        int m_param;
 
-       geBox    m_text;
-       geButton m_valueBtn;
-       geButton m_button;
-
-private:
-       static void cb_button(Fl_Widget* /*w*/, void* p);
-       static void cb_value(Fl_Widget* /*w*/, void* p);
-
-       void onLearn() const;
-       void onReset() const;
+       geBox*    m_text;
+       geButton* m_valueBtn;
+       geButton* m_button;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index 65558a8414d5327b8115c617e8aa53dad082c0cd..c79b1ef2fb344b7ad71856bb78f15b041c4633f2 100644 (file)
@@ -63,7 +63,7 @@ void geMidiLearnerPack::setCallbacks(std::function<void(uint32_t)> s, std::funct
 
 void geMidiLearnerPack::addMidiLearner(std::string label, int param, bool visible)
 {
-       geMidiLearner* l = new geMidiLearner(0, 0, label, param);
+       geMidiLearner* l = new geMidiLearner(0, 0, LEARNER_WIDTH, G_GUI_UNIT, label, param);
 
        l->onStartLearn = m_onStartLearn;
        l->onClearLearn = m_onClearLearn;
index a521fa8a23c4c59aad356fdafb88267b709eb8a0..34eecda1784e2eacbd40a8abce50251f1dca0167 100644 (file)
@@ -42,9 +42,7 @@
 #include <cassert>
 #include <string>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 gePluginElement::gePluginElement(int x, int y, c::plugin::Plugin data)
 : gePack(x, y, Direction::HORIZONTAL)
@@ -80,18 +78,20 @@ gePluginElement::gePluginElement(int x, int y, c::plugin::Plugin data)
        button.copy_label(m_plugin.name.c_str());
        button.callback(cb_openPluginWindow, (void*)this);
 
-       program.callback(cb_setProgram, (void*)this);
+       program.onChange = [pluginId = m_plugin.id](ID id) {
+               c::plugin::setProgram(pluginId, id);
+       };
 
        for (const auto& p : m_plugin.programs)
-               program.add(u::gui::removeFltkChars(p.name).c_str());
+               program.addItem(u::gui::removeFltkChars(p.name));
 
-       if (program.size() == 0)
+       if (program.countItems() == 0)
        {
-               program.add("-- no programs --\0");
+               program.addItem("-- no programs --\0");
                program.deactivate();
        }
        else
-               program.value(m_plugin.currentProgram);
+               program.showItem(m_plugin.currentProgram);
 
        bypass.callback(cb_setBypass, (void*)this);
        bypass.type(FL_TOGGLE_BUTTON);
@@ -120,7 +120,6 @@ void gePluginElement::cb_openPluginWindow(Fl_Widget* /*w*/, void* p) { ((gePlugi
 void gePluginElement::cb_setBypass(Fl_Widget* /*w*/, void* p) { ((gePluginElement*)p)->cb_setBypass(); }
 void gePluginElement::cb_shiftUp(Fl_Widget* /*w*/, void* p) { ((gePluginElement*)p)->cb_shiftUp(); }
 void gePluginElement::cb_shiftDown(Fl_Widget* /*w*/, void* p) { ((gePluginElement*)p)->cb_shiftDown(); }
-void gePluginElement::cb_setProgram(Fl_Widget* /*w*/, void* p) { ((gePluginElement*)p)->cb_setProgram(); }
 
 /* -------------------------------------------------------------------------- */
 
@@ -186,14 +185,6 @@ void gePluginElement::cb_setBypass()
 {
        c::plugin::toggleBypass(m_plugin.id);
 }
-
-/* -------------------------------------------------------------------------- */
-
-void gePluginElement::cb_setProgram()
-{
-       c::plugin::setProgram(m_plugin.id, program.value());
-}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif // #ifdef WITH_VST
index cf667d6e60e343db8b8854f4cc3b732d5c527124..9277e4dc4a09c23b6c72aac5aefb756300506656 100644 (file)
@@ -34,9 +34,7 @@
 #include "gui/elems/basics/choice.h"
 #include "gui/elems/basics/pack.h"
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 class gePluginElement : public gePack
 {
@@ -53,24 +51,21 @@ public:
        geButton shiftDown;
        geButton remove;
 
-  private:
+private:
        static void cb_removePlugin(Fl_Widget* /*w*/, void* p);
        static void cb_openPluginWindow(Fl_Widget* /*w*/, void* p);
        static void cb_setBypass(Fl_Widget* /*w*/, void* p);
        static void cb_shiftUp(Fl_Widget* /*w*/, void* p);
        static void cb_shiftDown(Fl_Widget* /*w*/, void* p);
-       static void cb_setProgram(Fl_Widget* /*w*/, void* p);
        void        cb_removePlugin();
        void        cb_openPluginWindow();
        void        cb_setBypass();
        void        cb_shiftUp();
        void        cb_shiftDown();
-       void        cb_setProgram();
 
        c::plugin::Plugin m_plugin;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
 
index 92e1d3356e9a737ff16ce9b3e323588433104437..1db9f93afc0626aca05416a1c1049fe7df6d6338 100644 (file)
 #include "glue/plugin.h"
 #include <FL/Fl_Group.H>
 
-class geBox;
 class geSlider;
 
 namespace giada::v
 {
+class geBox;
 class gePluginParameter : public Fl_Group
 {
 public:
@@ -45,7 +45,7 @@ public:
 
        void update(const c::plugin::Param& p, bool changeSlider);
 
-  private:
+private:
        static void cb_setValue(Fl_Widget* /*w*/, void* p);
        void        cb_setValue();
 
index bcfe7a8f726233025900109a6995d088d2e430cb..5c8dea079ddf40e09b0433afa7c4fa347334e921 100644 (file)
@@ -39,9 +39,7 @@
 #include "waveTools.h"
 #include <FL/Fl.H>
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
 geBoostTool::geBoostTool(int X, int Y)
 : Fl_Pack(X, Y, 220, G_GUI_UNIT)
@@ -106,6 +104,4 @@ void geBoostTool::cb_setBoostNum()
 void geBoostTool::cb_normalize()
 {
 }
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
index b0ad40bf7a71a4058fc6a7dd10c788e76a998000..a01f9c30cfd3b56cd20e8e05086ff4577b905a79 100644 (file)
 #include <FL/Fl_Pack.H>
 
 class geInput;
-class geBox;
 
-namespace giada
-{
-namespace v
+namespace giada::v
 {
+class geBox;
 class geDial;
 class geButton;
 class geBoostTool : public Fl_Pack
@@ -58,7 +56,6 @@ private:
        geInput*  input;
        geButton* normalize;
 };
-} // namespace v
-} // namespace giada
+} // namespace giada::v
 
 #endif
index acdc0510410baddd4aee45115d357fab125a1138..1b1e73248e2bf9b5cf0610c75ed5a7ea55417b30 100644 (file)
@@ -44,8 +44,9 @@
 
 namespace giada::v
 {
-Ui::Ui(m::Recorder& recorder)
-: m_updater(*this)
+Ui::Ui(m::Recorder& recorder, const m::Conf::Data& conf)
+: dispatcher(conf.keyBindings)
+, m_updater(*this)
 , m_blinker(0)
 {
        dispatcher.onEventOccured = [&recorder]() {
index ca25a1eb2307c16474c3f46512614b48c3048e0f..116eaf05fa3cb7cf78904d4a0641cb1469622124 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef G_V_UI_H
 #define G_V_UI_H
 
+#include "core/conf.h"
 #include "core/patch.h"
 #include "gui/dialogs/mainWindow.h"
 #include "gui/dispatcher.h"
@@ -45,7 +46,7 @@ namespace giada::v
 class Ui final
 {
 public:
-       Ui(m::Recorder&);
+       Ui(m::Recorder&, const m::Conf::Data&);
 
        /* shouldBlink
        Return whether is time to blink something or not. This is used to make 
index ff7829c3f6b8868fc4efe85aebbb08a497d94af2..065002fa68c9e97bf8be4f36cbe4a35dfa030640 100644 (file)
@@ -28,7 +28,7 @@
 #include "gui/ui.h"
 
 giada::m::Engine g_engine;
-giada::v::Ui     g_ui(g_engine.recorder);
+giada::v::Ui     g_ui(g_engine.recorder, g_engine.conf.data);
 
 int main(int argc, char** argv)
 {
index ecec2791bdbadf3ba8a3e2682e3708d7f008b50e..6f1979568657d5ed92a20644a891e08e4f9706df 100644 (file)
@@ -148,11 +148,11 @@ std::string getHomePath()
 
        char buf[PATH_MAX];
        snprintf(buf, PATH_MAX, "%s/.giada", getenv("HOME"));
+       return stdfs::path(buf).string();
 
 #elif defined(G_OS_WINDOWS)
 
-       char buf[MAX_PATH];
-       snprintf(buf, MAX_PATH, ".");
+       return stdfs::current_path().string();
 
 #elif defined(G_OS_MAC)
 
@@ -166,9 +166,9 @@ std::string getHomePath()
        const char* home = pwd->pw_dir;
        snprintf(buf, PATH_MAX, "%s/Library/Application Support/Giada", home);
 
-#endif
-
        return stdfs::path(buf).string();
+
+#endif
 }
 
 /* -------------------------------------------------------------------------- */
index 406279e31d2fe8c0201704ad7bc8634b32180366..da5b7a50695ef04f7c80711fd0791ac86b3b29e0 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <FL/Fl.H>
 #include <FL/fl_draw.H>
+#include <cstddef>
 #include <string>
 #if defined(_WIN32)
 #include "../ext/resource.h"
@@ -147,4 +148,109 @@ int centerWindowY(int h)
 {
        return (Fl::h() / 2) - (h / 2);
 }
+
+/* -------------------------------------------------------------------------- */
+
+std::string keyToString(int key)
+{
+       // https://github.com/fltk/fltk/blob/570a05a33c9dc42a16caa5a1a11cf34d4df1c1f9/FL/Enumerations.H
+       // https://www.fltk.org/doc-1.3/group__fl__events.html#gafa17a5b4d8d9163631c88142e60447ed
+
+       if (key == 0)
+               return "[None]";
+
+       switch (key)
+       {
+       case ' ':
+               return "Space";
+       case FL_BackSpace:
+               return "Backspace";
+       case FL_Tab:
+               return "Tab";
+       case FL_Enter:
+               return "Enter";
+       case FL_Pause:
+               return "Pause";
+       case FL_Scroll_Lock:
+               return "Scroll lock";
+       case FL_Escape:
+               return "Escape";
+       case FL_Home:
+               return "Home";
+       case FL_Left:
+               return "Left";
+       case FL_Up:
+               return "Up";
+       case FL_Right:
+               return "Right";
+       case FL_Down:
+               return "Down";
+       case FL_Page_Up:
+               return "Page up";
+       case FL_Page_Down:
+               return "Page down";
+       case FL_End:
+               return "End";
+       case FL_Print:
+               return "Print";
+       case FL_Insert:
+               return "Insert";
+       case FL_Menu:
+               return "Menu";
+       case FL_Help:
+               return "Help";
+       case FL_Num_Lock:
+               return "Num lock";
+       case FL_KP: // TODO ?
+               return "";
+       case FL_KP_Enter:
+               return "KP Enter";
+       case FL_F + 1:
+               return "F1";
+       case FL_F + 2:
+               return "F2";
+       case FL_F + 3:
+               return "F3";
+       case FL_F + 4:
+               return "F4";
+       case FL_F + 5:
+               return "F5";
+       case FL_F + 6:
+               return "F6";
+       case FL_F + 7:
+               return "F7";
+       case FL_F + 8:
+               return "F8";
+       case FL_F + 9:
+               return "F9";
+       case FL_F + 10:
+               return "F10";
+       case FL_F + 11:
+               return "F11";
+       case FL_F + 12:
+               return "F12";
+       case FL_Shift_L:
+               return "Shift L";
+       case FL_Shift_R:
+               return "Shift R";
+       case FL_Control_L:
+               return "Control L";
+       case FL_Control_R:
+               return "Control R";
+       case FL_Caps_Lock:
+               return "Caps lock";
+       case FL_Meta_L:
+               return "Meta L";
+       case FL_Meta_R:
+               return "Meta R";
+       case FL_Alt_L:
+               return "Alt L";
+       case FL_Alt_R:
+               return "Alt R";
+       case FL_Delete:
+               return "Delete";
+       default:
+               return Fl::event_text();
+       }
+}
 } // namespace giada::u::gui
index 86d4dc4b3250173fd4092d7270287208615cde55..dec672184c7340a4958248bf6d1f5f034a2d5ef7 100644 (file)
@@ -75,6 +75,11 @@ std::string truncate(const std::string& s, Pixel width);
 int centerWindowX(int w);
 int centerWindowY(int h);
 
+/* keyToString
+Translates an FLTK key event into a human-readable string. */
+
+std::string keyToString(int key);
+
 /* makeMenuItem
 Makes a new Fl_Menu_Item at compile time. Used to initialize pop-up menus. */
 
index e5d5ac42f53a19d2605ee2811ab65b6fcffbafa5..f50c858bd4fd1bb051c99318247d6b15c825031c 100644 (file)
@@ -30,6 +30,7 @@
 #include "core/const.h"
 #include <climits>
 #include <cstdarg>
+#include <cstddef>
 #include <iomanip>
 #include <memory>
 
index e3353e7601cdf025559ee540aec98197d5b4ab11..dfbcace82e4dba04095d1b754eb0d24d819cc750 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef G_UTILS_VECTOR_H
 #define G_UTILS_VECTOR_H
 
+#include <cstddef>
 #include <algorithm>
 #include <functional>
 #include <vector>