The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

298 lines
9.4KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #pragma once
  14. #include "../Plugins/IOConfigurationWindow.h"
  15. class PluginGraph;
  16. /**
  17. A window that shows a log of parameter change messages sent by the plugin.
  18. */
  19. class PluginDebugWindow : public AudioProcessorEditor,
  20. public AudioProcessorParameter::Listener,
  21. public ListBoxModel,
  22. public AsyncUpdater
  23. {
  24. public:
  25. PluginDebugWindow (AudioProcessor& proc)
  26. : AudioProcessorEditor (proc), audioProc (proc)
  27. {
  28. setSize (500, 200);
  29. addAndMakeVisible (list);
  30. for (auto* p : audioProc.getParameters())
  31. p->addListener (this);
  32. log.add ("Parameter debug log started");
  33. }
  34. void parameterValueChanged (int parameterIndex, float newValue) override
  35. {
  36. auto* param = audioProc.getParameters()[parameterIndex];
  37. auto value = param->getCurrentValueAsText().quoted() + " (" + String (newValue, 4) + ")";
  38. appendToLog ("parameter change", *param, value);
  39. }
  40. void parameterGestureChanged (int parameterIndex, bool gestureIsStarting) override
  41. {
  42. auto* param = audioProc.getParameters()[parameterIndex];
  43. appendToLog ("gesture", *param, gestureIsStarting ? "start" : "end");
  44. }
  45. private:
  46. void appendToLog (StringRef action, AudioProcessorParameter& param, StringRef value)
  47. {
  48. String entry (action + " " + param.getName (30).quoted() + " [" + String (param.getParameterIndex()) + "]: " + value);
  49. {
  50. ScopedLock lock (pendingLogLock);
  51. pendingLogEntries.add (entry);
  52. }
  53. triggerAsyncUpdate();
  54. }
  55. void resized() override
  56. {
  57. list.setBounds(getLocalBounds());
  58. }
  59. int getNumRows() override
  60. {
  61. return log.size();
  62. }
  63. void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool) override
  64. {
  65. g.setColour (getLookAndFeel().findColour (TextEditor::textColourId));
  66. if (isPositiveAndBelow (rowNumber, log.size()))
  67. g.drawText (log[rowNumber], Rectangle<int> { 0, 0, width, height }, Justification::left, true);
  68. }
  69. void handleAsyncUpdate() override
  70. {
  71. if (log.size() > logSizeTrimThreshold)
  72. log.removeRange (0, log.size() - maxLogSize);
  73. {
  74. ScopedLock lock (pendingLogLock);
  75. log.addArray (pendingLogEntries);
  76. pendingLogEntries.clear();
  77. }
  78. list.updateContent();
  79. list.scrollToEnsureRowIsOnscreen (log.size() - 1);
  80. }
  81. constexpr static const int maxLogSize = 300;
  82. constexpr static const int logSizeTrimThreshold = 400;
  83. ListBox list { "Log", this };
  84. StringArray log;
  85. StringArray pendingLogEntries;
  86. CriticalSection pendingLogLock;
  87. AudioProcessor& audioProc;
  88. };
  89. //==============================================================================
  90. /**
  91. A desktop window containing a plugin's GUI.
  92. */
  93. class PluginWindow : public DocumentWindow
  94. {
  95. public:
  96. enum class Type
  97. {
  98. normal = 0,
  99. generic,
  100. programs,
  101. audioIO,
  102. debug,
  103. numTypes
  104. };
  105. PluginWindow (AudioProcessorGraph::Node* n, Type t, OwnedArray<PluginWindow>& windowList)
  106. : DocumentWindow (n->getProcessor()->getName(),
  107. LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId),
  108. DocumentWindow::minimiseButton | DocumentWindow::closeButton),
  109. activeWindowList (windowList),
  110. node (n), type (t)
  111. {
  112. setSize (400, 300);
  113. if (auto* ui = createProcessorEditor (*node->getProcessor(), type))
  114. setContentOwned (ui, true);
  115. #if JUCE_IOS || JUCE_ANDROID
  116. auto screenBounds = Desktop::getInstance().getDisplays().getTotalBounds (true).toFloat();
  117. auto scaleFactor = jmin ((screenBounds.getWidth() - 50) / getWidth(), (screenBounds.getHeight() - 50) / getHeight());
  118. if (scaleFactor < 1.0f)
  119. setSize ((int) (getWidth() * scaleFactor), (int) (getHeight() * scaleFactor));
  120. setTopLeftPosition (20, 20);
  121. #else
  122. setTopLeftPosition (node->properties.getWithDefault (getLastXProp (type), Random::getSystemRandom().nextInt (500)),
  123. node->properties.getWithDefault (getLastYProp (type), Random::getSystemRandom().nextInt (500)));
  124. #endif
  125. node->properties.set (getOpenProp (type), true);
  126. setVisible (true);
  127. }
  128. ~PluginWindow() override
  129. {
  130. clearContentComponent();
  131. }
  132. void moved() override
  133. {
  134. node->properties.set (getLastXProp (type), getX());
  135. node->properties.set (getLastYProp (type), getY());
  136. }
  137. void closeButtonPressed() override
  138. {
  139. node->properties.set (getOpenProp (type), false);
  140. activeWindowList.removeObject (this);
  141. }
  142. static String getLastXProp (Type type) { return "uiLastX_" + getTypeName (type); }
  143. static String getLastYProp (Type type) { return "uiLastY_" + getTypeName (type); }
  144. static String getOpenProp (Type type) { return "uiopen_" + getTypeName (type); }
  145. OwnedArray<PluginWindow>& activeWindowList;
  146. const AudioProcessorGraph::Node::Ptr node;
  147. const Type type;
  148. private:
  149. float getDesktopScaleFactor() const override { return 1.0f; }
  150. static AudioProcessorEditor* createProcessorEditor (AudioProcessor& processor,
  151. PluginWindow::Type type)
  152. {
  153. if (type == PluginWindow::Type::normal)
  154. {
  155. if (auto* ui = processor.createEditorIfNeeded())
  156. return ui;
  157. type = PluginWindow::Type::generic;
  158. }
  159. if (type == PluginWindow::Type::generic) return new GenericAudioProcessorEditor (processor);
  160. if (type == PluginWindow::Type::programs) return new ProgramAudioProcessorEditor (processor);
  161. if (type == PluginWindow::Type::audioIO) return new IOConfigurationWindow (processor);
  162. if (type == PluginWindow::Type::debug) return new PluginDebugWindow (processor);
  163. jassertfalse;
  164. return {};
  165. }
  166. static String getTypeName (Type type)
  167. {
  168. switch (type)
  169. {
  170. case Type::normal: return "Normal";
  171. case Type::generic: return "Generic";
  172. case Type::programs: return "Programs";
  173. case Type::audioIO: return "IO";
  174. case Type::debug: return "Debug";
  175. case Type::numTypes:
  176. default: return {};
  177. }
  178. }
  179. //==============================================================================
  180. struct ProgramAudioProcessorEditor : public AudioProcessorEditor
  181. {
  182. ProgramAudioProcessorEditor (AudioProcessor& p) : AudioProcessorEditor (p)
  183. {
  184. setOpaque (true);
  185. addAndMakeVisible (panel);
  186. Array<PropertyComponent*> programs;
  187. auto numPrograms = p.getNumPrograms();
  188. int totalHeight = 0;
  189. for (int i = 0; i < numPrograms; ++i)
  190. {
  191. auto name = p.getProgramName (i).trim();
  192. if (name.isEmpty())
  193. name = "Unnamed";
  194. auto pc = new PropertyComp (name, p);
  195. programs.add (pc);
  196. totalHeight += pc->getPreferredHeight();
  197. }
  198. panel.addProperties (programs);
  199. setSize (400, jlimit (25, 400, totalHeight));
  200. }
  201. void paint (Graphics& g) override
  202. {
  203. g.fillAll (Colours::grey);
  204. }
  205. void resized() override
  206. {
  207. panel.setBounds (getLocalBounds());
  208. }
  209. private:
  210. struct PropertyComp : public PropertyComponent,
  211. private AudioProcessorListener
  212. {
  213. PropertyComp (const String& name, AudioProcessor& p) : PropertyComponent (name), owner (p)
  214. {
  215. owner.addListener (this);
  216. }
  217. ~PropertyComp() override
  218. {
  219. owner.removeListener (this);
  220. }
  221. void refresh() override {}
  222. void audioProcessorChanged (AudioProcessor*) override {}
  223. void audioProcessorParameterChanged (AudioProcessor*, int, float) override {}
  224. AudioProcessor& owner;
  225. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertyComp)
  226. };
  227. PropertyPanel panel;
  228. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramAudioProcessorEditor)
  229. };
  230. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginWindow)
  231. };