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.

246 lines
8.6KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - 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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-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 (ValueWithDefault& valueToControl, const String& propertyName, bool isDir, bool thisOS = true,
  40. const String& wildcardsToUse = "*", const File& relativeRoot = File())
  41. : PropertyComponent (propertyName),
  42. text (valueToControl, propertyName, 1024, false),
  43. isDirectory (isDir), isThisOS (thisOS), wildcards (wildcardsToUse), root (relativeRoot)
  44. {
  45. textValue = valueToControl.getPropertyAsValue();
  46. init();
  47. }
  48. //==============================================================================
  49. void refresh() override {}
  50. void resized() override
  51. {
  52. auto bounds = getLocalBounds();
  53. text.setBounds (bounds.removeFromLeft (jmax (400, bounds.getWidth() - 55)));
  54. bounds.removeFromLeft (5);
  55. browseButton.setBounds (bounds);
  56. }
  57. void paintOverChildren (Graphics& g) override
  58. {
  59. if (highlightForDragAndDrop)
  60. {
  61. g.setColour (findColour (defaultHighlightColourId).withAlpha (0.5f));
  62. g.fillRect (getLookAndFeel().getPropertyComponentContentPosition (text));
  63. }
  64. }
  65. //==============================================================================
  66. bool isInterestedInFileDrag (const StringArray&) override { return true; }
  67. void fileDragEnter (const StringArray&, int, int) override { highlightForDragAndDrop = true; repaint(); }
  68. void fileDragExit (const StringArray&) override { highlightForDragAndDrop = false; repaint(); }
  69. void filesDropped (const StringArray& selectedFiles, int, int) override
  70. {
  71. setTo (selectedFiles[0]);
  72. highlightForDragAndDrop = false;
  73. repaint();
  74. }
  75. protected:
  76. void valueChanged (Value&) override
  77. {
  78. updateEditorColour();
  79. }
  80. private:
  81. //==============================================================================
  82. void init()
  83. {
  84. textValue.addListener (this);
  85. text.setInterestedInFileDrag (false);
  86. addAndMakeVisible (text);
  87. browseButton.onClick = [this] { browse(); };
  88. addAndMakeVisible (browseButton);
  89. lookAndFeelChanged();
  90. }
  91. void setTo (File f)
  92. {
  93. if (isDirectory && ! f.isDirectory())
  94. f = f.getParentDirectory();
  95. auto pathName = (root == File()) ? f.getFullPathName()
  96. : f.getRelativePathFrom (root);
  97. text.setText (pathName);
  98. updateEditorColour();
  99. }
  100. void browse()
  101. {
  102. File currentFile = {};
  103. if (text.getText().isNotEmpty())
  104. currentFile = root.getChildFile (text.getText());
  105. if (isDirectory)
  106. {
  107. chooser = std::make_unique<FileChooser> ("Select directory", currentFile);
  108. auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
  109. chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
  110. {
  111. if (fc.getResult() == File{})
  112. return;
  113. setTo (fc.getResult());
  114. });
  115. }
  116. else
  117. {
  118. chooser = std::make_unique<FileChooser> ("Select file", currentFile, wildcards);
  119. auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles;
  120. chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
  121. {
  122. if (fc.getResult() == File{})
  123. return;
  124. setTo (fc.getResult());
  125. });
  126. }
  127. }
  128. void updateEditorColour()
  129. {
  130. if (isThisOS)
  131. {
  132. text.setColour (TextPropertyComponent::textColourId, findColour (widgetTextColourId));
  133. auto pathToCheck = text.getText();
  134. if (pathToCheck.isNotEmpty())
  135. {
  136. pathToCheck.replace ("${user.home}", "~");
  137. #if JUCE_WINDOWS
  138. if (pathToCheck.startsWith ("~"))
  139. pathToCheck = pathToCheck.replace ("~", File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  140. #endif
  141. if (! root.getChildFile (pathToCheck).exists())
  142. text.setColour (TextPropertyComponent::textColourId, Colours::red);
  143. }
  144. }
  145. }
  146. void lookAndFeelChanged() override
  147. {
  148. browseButton.setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId));
  149. browseButton.setColour (TextButton::textColourOffId, Colours::white);
  150. updateEditorColour();
  151. }
  152. //==============================================================================
  153. Value textValue;
  154. TextPropertyComponent text;
  155. TextButton browseButton { "..." };
  156. bool isDirectory, isThisOS, highlightForDragAndDrop = false;
  157. String wildcards;
  158. File root;
  159. std::unique_ptr<FileChooser> chooser;
  160. //==============================================================================
  161. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePathPropertyComponent)
  162. };
  163. //==============================================================================
  164. class FilePathPropertyComponentWithEnablement : public FilePathPropertyComponent
  165. {
  166. public:
  167. FilePathPropertyComponentWithEnablement (ValueWithDefault& valueToControl,
  168. ValueWithDefault valueToListenTo,
  169. const String& propertyName,
  170. bool isDir,
  171. bool thisOS = true,
  172. const String& wildcardsToUse = "*",
  173. const File& relativeRoot = File())
  174. : FilePathPropertyComponent (valueToControl,
  175. propertyName,
  176. isDir,
  177. thisOS,
  178. wildcardsToUse,
  179. relativeRoot),
  180. valueWithDefault (valueToListenTo),
  181. value (valueToListenTo.getPropertyAsValue())
  182. {
  183. value.addListener (this);
  184. valueChanged (value);
  185. }
  186. ~FilePathPropertyComponentWithEnablement() override { value.removeListener (this); }
  187. private:
  188. void valueChanged (Value& v) override
  189. {
  190. FilePathPropertyComponent::valueChanged (v);
  191. setEnabled (valueWithDefault.get());
  192. }
  193. ValueWithDefault valueWithDefault;
  194. Value value;
  195. };