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.

221 lines
8.1KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #pragma once
  20. class FilePathPropertyComponent : public PropertyComponent
  21. {
  22. public:
  23. /** A Property Component for selecting files or folders.
  24. The user may drag files over the property box, enter the path
  25. manually and/or click the '...' button to open a file selection
  26. dialog box
  27. */
  28. FilePathPropertyComponent (Value valueToControl,
  29. const String& propertyDescription,
  30. bool isDirectory,
  31. const String& wildcards = "*",
  32. const File& rootToUseForRelativePaths = File(),
  33. const bool supportsMultiplePaths = false)
  34. : PropertyComponent (propertyDescription),
  35. innerComp (valueToControl, isDirectory, wildcards, rootToUseForRelativePaths, supportsMultiplePaths)
  36. {
  37. addAndMakeVisible (innerComp);
  38. }
  39. void refresh() override {} // N/A
  40. private:
  41. struct InnerComponent : public Component,
  42. public FileDragAndDropTarget,
  43. private Button::Listener,
  44. private TextEditor::Listener
  45. {
  46. InnerComponent (Value v, bool isDir, const String& wc, const File& rt, const bool multiplePaths)
  47. : value (v),
  48. isDirectory (isDir),
  49. highlightForDragAndDrop (false),
  50. wildcards (wc),
  51. root (rt),
  52. button ("..."),
  53. supportsMultiplePaths (multiplePaths)
  54. {
  55. addAndMakeVisible (textbox);
  56. textbox.getTextValue().referTo (value);
  57. textbox.addListener (this);
  58. addAndMakeVisible (button);
  59. button.addListener (this);
  60. lookAndFeelChanged();
  61. }
  62. void paintOverChildren (Graphics& g) override
  63. {
  64. if (highlightForDragAndDrop)
  65. {
  66. g.setColour (findColour (defaultHighlightColourId).withAlpha (0.5f));
  67. g.fillRect (textbox.getBounds());
  68. }
  69. }
  70. void resized() override
  71. {
  72. juce::Rectangle<int> r (getLocalBounds());
  73. button.setBounds (r.removeFromRight (30));
  74. textbox.setBounds (r);
  75. }
  76. bool isInterestedInFileDrag (const StringArray&) override { return true; }
  77. void fileDragEnter (const StringArray&, int, int) override { highlightForDragAndDrop = true; repaint(); }
  78. void fileDragExit (const StringArray&) override { highlightForDragAndDrop = false; repaint(); }
  79. void filesDropped (const StringArray& files, int, int) override
  80. {
  81. const File firstFile (files[0]);
  82. if (isDirectory)
  83. setTo (firstFile.isDirectory() ? firstFile
  84. : firstFile.getParentDirectory());
  85. else
  86. setTo (firstFile);
  87. highlightForDragAndDrop = false;
  88. repaint();
  89. }
  90. void buttonClicked (Button*) override
  91. {
  92. const File currentFile (root.getChildFile (value.toString()));
  93. if (isDirectory)
  94. {
  95. FileChooser chooser ("Select directory", currentFile);
  96. if (chooser.browseForDirectory())
  97. setTo (chooser.getResult());
  98. }
  99. else
  100. {
  101. FileChooser chooser ("Select file", currentFile, wildcards);
  102. if (chooser.browseForFileToOpen())
  103. setTo (chooser.getResult());
  104. }
  105. }
  106. void textEditorReturnKeyPressed (TextEditor& editor) override { updateEditorColour (editor); }
  107. void textEditorFocusLost (TextEditor& editor) override { updateEditorColour (editor); }
  108. void updateEditorColour (TextEditor& editor)
  109. {
  110. if (supportsMultiplePaths)
  111. {
  112. auto paths = StringArray::fromTokens (editor.getTextValue().toString(), ";", {});
  113. editor.clear();
  114. AttributedString str;
  115. for (auto p : paths)
  116. {
  117. if (root.getChildFile (p.trim()).exists()) editor.setColour (TextEditor::textColourId, findColour (widgetTextColourId));
  118. else editor.setColour (TextEditor::textColourId, Colours::red);
  119. editor.insertTextAtCaret (p);
  120. if (paths.indexOf (p) < paths.size() - 1)
  121. {
  122. editor.setColour (TextEditor::textColourId, findColour (widgetTextColourId));
  123. editor.insertTextAtCaret (";");
  124. }
  125. }
  126. editor.setColour (TextEditor::textColourId, findColour (widgetTextColourId));
  127. }
  128. else
  129. {
  130. auto pathToCheck = editor.getTextValue().toString();
  131. //android SDK/NDK paths
  132. if (pathToCheck.contains ("${user.home}"))
  133. pathToCheck = pathToCheck.replace ("${user.home}", File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  134. #if JUCE_WINDOWS
  135. if (pathToCheck.startsWith ("~"))
  136. pathToCheck = pathToCheck.replace ("~", File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  137. #endif
  138. const auto currentFile = root.getChildFile (pathToCheck);
  139. if (currentFile.exists())
  140. editor.applyColourToAllText (findColour (widgetTextColourId));
  141. else
  142. editor.applyColourToAllText (Colours::red);
  143. }
  144. }
  145. void setTo (const File& f)
  146. {
  147. auto pathName = (root == File()) ? f.getFullPathName()
  148. : f.getRelativePathFrom (root);
  149. if (supportsMultiplePaths && value.toString().isNotEmpty())
  150. value = value.toString().trimCharactersAtEnd (" ;") + "; " + pathName;
  151. else
  152. value = pathName;
  153. updateEditorColour (textbox);
  154. }
  155. void lookAndFeelChanged() override
  156. {
  157. textbox.setColour (TextEditor::backgroundColourId, findColour (widgetBackgroundColourId));
  158. textbox.setColour (TextEditor::outlineColourId, Colours::transparentBlack);
  159. updateEditorColour (textbox);
  160. button.setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId));
  161. button.setColour (TextButton::textColourOffId, Colours::white);
  162. }
  163. Value value;
  164. bool isDirectory, highlightForDragAndDrop;
  165. String wildcards;
  166. File root;
  167. TextEditor textbox;
  168. TextButton button;
  169. bool supportsMultiplePaths;
  170. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InnerComponent)
  171. };
  172. InnerComponent innerComp; // Used so that the PropertyComponent auto first-child positioning works
  173. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePathPropertyComponent)
  174. };