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.

219 lines
8.0KB

  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. //==============================================================================
  21. class FilePathPropertyComponent : public PropertyComponent
  22. {
  23. public:
  24. /** A Property Component for selecting files or folders.
  25. The user may drag files over the property box, enter the path
  26. manually and/or click the '...' button to open a file selection
  27. dialog box
  28. */
  29. FilePathPropertyComponent (Value valueToControl,
  30. const String& propertyDescription,
  31. bool isDirectory,
  32. const String& wildcards = "*",
  33. const File& rootToUseForRelativePaths = File(),
  34. const bool supportsMultiplePaths = false)
  35. : PropertyComponent (propertyDescription),
  36. innerComp (valueToControl, isDirectory, wildcards, rootToUseForRelativePaths, supportsMultiplePaths)
  37. {
  38. addAndMakeVisible (innerComp);
  39. }
  40. void refresh() override {} // N/A
  41. private:
  42. struct InnerComponent : public Component,
  43. public FileDragAndDropTarget
  44. {
  45. InnerComponent (Value v, bool isDir, const String& wc, const File& rt, const bool multiplePaths)
  46. : value (v),
  47. isDirectory (isDir),
  48. highlightForDragAndDrop (false),
  49. wildcards (wc),
  50. root (rt),
  51. button ("..."),
  52. supportsMultiplePaths (multiplePaths)
  53. {
  54. addAndMakeVisible (textbox);
  55. textbox.getTextValue().referTo (value);
  56. textbox.onReturnKey = [this] { updateEditorColour (textbox); };
  57. textbox.onFocusLost = [this] { updateEditorColour (textbox); };
  58. textbox.onTextChange = [this] { updateEditorColour (textbox); };
  59. addAndMakeVisible (button);
  60. button.onClick = [this] { browse(); };
  61. lookAndFeelChanged();
  62. }
  63. void paintOverChildren (Graphics& g) override
  64. {
  65. if (highlightForDragAndDrop)
  66. {
  67. g.setColour (findColour (defaultHighlightColourId).withAlpha (0.5f));
  68. g.fillRect (textbox.getBounds());
  69. }
  70. }
  71. void resized() override
  72. {
  73. juce::Rectangle<int> r (getLocalBounds());
  74. button.setBounds (r.removeFromRight (30));
  75. textbox.setBounds (r);
  76. }
  77. bool isInterestedInFileDrag (const StringArray&) override { return true; }
  78. void fileDragEnter (const StringArray&, int, int) override { highlightForDragAndDrop = true; repaint(); }
  79. void fileDragExit (const StringArray&) override { highlightForDragAndDrop = false; repaint(); }
  80. void filesDropped (const StringArray& files, int, int) override
  81. {
  82. const File firstFile (files[0]);
  83. if (isDirectory)
  84. setTo (firstFile.isDirectory() ? firstFile
  85. : firstFile.getParentDirectory());
  86. else
  87. setTo (firstFile);
  88. highlightForDragAndDrop = false;
  89. repaint();
  90. }
  91. void browse()
  92. {
  93. auto currentFile = root.getChildFile (value.toString());
  94. if (isDirectory)
  95. {
  96. FileChooser chooser ("Select directory", currentFile);
  97. if (chooser.browseForDirectory())
  98. setTo (chooser.getResult());
  99. }
  100. else
  101. {
  102. FileChooser chooser ("Select file", currentFile, wildcards);
  103. if (chooser.browseForFileToOpen())
  104. setTo (chooser.getResult());
  105. }
  106. }
  107. void updateEditorColour (TextEditor& editor)
  108. {
  109. if (supportsMultiplePaths)
  110. {
  111. auto paths = StringArray::fromTokens (editor.getTextValue().toString(), ";", {});
  112. editor.clear();
  113. AttributedString str;
  114. for (auto p : paths)
  115. {
  116. if (root.getChildFile (p.trim()).exists()) editor.setColour (TextEditor::textColourId, findColour (widgetTextColourId));
  117. else editor.setColour (TextEditor::textColourId, Colours::red);
  118. editor.insertTextAtCaret (p);
  119. if (paths.indexOf (p) < paths.size() - 1)
  120. {
  121. editor.setColour (TextEditor::textColourId, findColour (widgetTextColourId));
  122. editor.insertTextAtCaret (";");
  123. }
  124. }
  125. editor.setColour (TextEditor::textColourId, findColour (widgetTextColourId));
  126. }
  127. else
  128. {
  129. auto pathToCheck = editor.getTextValue().toString();
  130. //android SDK/NDK paths
  131. if (pathToCheck.contains ("${user.home}"))
  132. pathToCheck = pathToCheck.replace ("${user.home}", File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  133. #if JUCE_WINDOWS
  134. if (pathToCheck.startsWith ("~"))
  135. pathToCheck = pathToCheck.replace ("~", File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  136. #endif
  137. const auto currentFile = root.getChildFile (pathToCheck);
  138. if (currentFile.exists())
  139. editor.applyColourToAllText (findColour (widgetTextColourId));
  140. else
  141. editor.applyColourToAllText (Colours::red);
  142. }
  143. }
  144. void setTo (const File& f)
  145. {
  146. auto pathName = (root == File()) ? f.getFullPathName()
  147. : f.getRelativePathFrom (root);
  148. if (supportsMultiplePaths && value.toString().isNotEmpty())
  149. value = value.toString().trimCharactersAtEnd (" ;") + "; " + pathName;
  150. else
  151. value = pathName;
  152. updateEditorColour (textbox);
  153. }
  154. void lookAndFeelChanged() override
  155. {
  156. textbox.setColour (TextEditor::backgroundColourId, findColour (widgetBackgroundColourId));
  157. textbox.setColour (TextEditor::outlineColourId, Colours::transparentBlack);
  158. updateEditorColour (textbox);
  159. button.setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId));
  160. button.setColour (TextButton::textColourOffId, Colours::white);
  161. }
  162. Value value;
  163. bool isDirectory, highlightForDragAndDrop;
  164. String wildcards;
  165. File root;
  166. TextEditor textbox;
  167. TextButton button;
  168. bool supportsMultiplePaths;
  169. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InnerComponent)
  170. };
  171. InnerComponent innerComp; // Used so that the PropertyComponent auto first-child positioning works
  172. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePathPropertyComponent)
  173. };