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.

260 lines
8.9KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #pragma once
  19. //==============================================================================
  20. /** A PropertyComponent for selecting files or folders.
  21. The user may drag files over the property box, enter the path manually and/or click
  22. the '...' button to open a file selection dialog box.
  23. */
  24. class FilePathPropertyComponent : public PropertyComponent,
  25. public FileDragAndDropTarget,
  26. protected Value::Listener
  27. {
  28. public:
  29. FilePathPropertyComponent (Value valueToControl, const String& propertyName, bool isDir, bool thisOS = true,
  30. const String& wildcardsToUse = "*", const File& relativeRoot = File())
  31. : PropertyComponent (propertyName),
  32. text (valueToControl, propertyName, 1024, false),
  33. isDirectory (isDir), isThisOS (thisOS), wildcards (wildcardsToUse), root (relativeRoot)
  34. {
  35. textValue.referTo (valueToControl);
  36. init();
  37. }
  38. /** Displays a default value when no value is specified by the user. */
  39. FilePathPropertyComponent (ValueTreePropertyWithDefault valueToControl,
  40. const String& propertyName,
  41. bool isDir,
  42. bool thisOS = true,
  43. const String& wildcardsToUse = "*",
  44. const File& relativeRoot = File())
  45. : PropertyComponent (propertyName),
  46. text (valueToControl, propertyName, 1024, false),
  47. isDirectory (isDir), isThisOS (thisOS), wildcards (wildcardsToUse), root (relativeRoot)
  48. {
  49. textValue = valueToControl.getPropertyAsValue();
  50. init();
  51. }
  52. //==============================================================================
  53. void refresh() override {}
  54. void resized() override
  55. {
  56. auto bounds = getLocalBounds();
  57. text.setBounds (bounds.removeFromLeft (jmax (400, bounds.getWidth() - 55)));
  58. bounds.removeFromLeft (5);
  59. browseButton.setBounds (bounds);
  60. }
  61. void paintOverChildren (Graphics& g) override
  62. {
  63. if (highlightForDragAndDrop)
  64. {
  65. g.setColour (findColour (defaultHighlightColourId).withAlpha (0.5f));
  66. g.fillRect (getLookAndFeel().getPropertyComponentContentPosition (text));
  67. }
  68. }
  69. //==============================================================================
  70. bool isInterestedInFileDrag (const StringArray&) override { return true; }
  71. void fileDragEnter (const StringArray&, int, int) override { highlightForDragAndDrop = true; repaint(); }
  72. void fileDragExit (const StringArray&) override { highlightForDragAndDrop = false; repaint(); }
  73. void filesDropped (const StringArray& selectedFiles, int, int) override
  74. {
  75. setTo (selectedFiles[0]);
  76. highlightForDragAndDrop = false;
  77. repaint();
  78. }
  79. protected:
  80. void valueChanged (Value&) override
  81. {
  82. updateEditorColour();
  83. }
  84. private:
  85. //==============================================================================
  86. void init()
  87. {
  88. textValue.addListener (this);
  89. text.setInterestedInFileDrag (false);
  90. addAndMakeVisible (text);
  91. browseButton.onClick = [this] { browse(); };
  92. addAndMakeVisible (browseButton);
  93. updateLookAndFeel();
  94. }
  95. void setTo (File f)
  96. {
  97. if (isDirectory && ! f.isDirectory())
  98. f = f.getParentDirectory();
  99. auto pathName = (root == File()) ? f.getFullPathName()
  100. : f.getRelativePathFrom (root);
  101. text.setText (pathName);
  102. updateEditorColour();
  103. }
  104. void browse()
  105. {
  106. File currentFile = {};
  107. if (text.getText().isNotEmpty())
  108. currentFile = root.getChildFile (text.getText());
  109. if (isDirectory)
  110. {
  111. chooser = std::make_unique<FileChooser> ("Select directory", currentFile);
  112. auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
  113. chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
  114. {
  115. if (fc.getResult() == File{})
  116. return;
  117. setTo (fc.getResult());
  118. });
  119. }
  120. else
  121. {
  122. chooser = std::make_unique<FileChooser> ("Select file", currentFile, wildcards);
  123. auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles;
  124. chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
  125. {
  126. if (fc.getResult() == File{})
  127. return;
  128. setTo (fc.getResult());
  129. });
  130. }
  131. }
  132. void updateEditorColour()
  133. {
  134. if (isThisOS)
  135. {
  136. text.setColour (TextPropertyComponent::textColourId, findColour (widgetTextColourId));
  137. auto pathToCheck = text.getText();
  138. if (pathToCheck.isNotEmpty())
  139. {
  140. pathToCheck.replace ("${user.home}", "~");
  141. #if JUCE_WINDOWS
  142. if (pathToCheck.startsWith ("~"))
  143. pathToCheck = pathToCheck.replace ("~", File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  144. #endif
  145. if (! root.getChildFile (pathToCheck).exists())
  146. text.setColour (TextPropertyComponent::textColourId, Colours::red);
  147. }
  148. }
  149. }
  150. void updateLookAndFeel()
  151. {
  152. browseButton.setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId));
  153. browseButton.setColour (TextButton::textColourOffId, Colours::white);
  154. updateEditorColour();
  155. }
  156. void lookAndFeelChanged() override
  157. {
  158. updateLookAndFeel();
  159. }
  160. //==============================================================================
  161. Value textValue;
  162. TextPropertyComponent text;
  163. TextButton browseButton { "..." };
  164. bool isDirectory, isThisOS, highlightForDragAndDrop = false;
  165. String wildcards;
  166. File root;
  167. std::unique_ptr<FileChooser> chooser;
  168. //==============================================================================
  169. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePathPropertyComponent)
  170. };
  171. //==============================================================================
  172. class FilePathPropertyComponentWithEnablement final : public FilePathPropertyComponent
  173. {
  174. public:
  175. FilePathPropertyComponentWithEnablement (const ValueTreePropertyWithDefault& valueToControl,
  176. ValueTreePropertyWithDefault valueToListenTo,
  177. const String& propertyName,
  178. bool isDir,
  179. bool thisOS = true,
  180. const String& wildcardsToUse = "*",
  181. const File& relativeRoot = File())
  182. : FilePathPropertyComponent (valueToControl,
  183. propertyName,
  184. isDir,
  185. thisOS,
  186. wildcardsToUse,
  187. relativeRoot),
  188. propertyWithDefault (valueToListenTo),
  189. value (valueToListenTo.getPropertyAsValue())
  190. {
  191. value.addListener (this);
  192. handleValueChanged (value);
  193. }
  194. ~FilePathPropertyComponentWithEnablement() override { value.removeListener (this); }
  195. private:
  196. void handleValueChanged (Value& v)
  197. {
  198. FilePathPropertyComponent::valueChanged (v);
  199. setEnabled (propertyWithDefault.get());
  200. }
  201. void valueChanged (Value& v) override
  202. {
  203. handleValueChanged (v);
  204. }
  205. ValueTreePropertyWithDefault propertyWithDefault;
  206. Value value;
  207. };