/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2022 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 7 End-User License Agreement and JUCE Privacy Policy. End User License Agreement: www.juce.com/juce-7-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #pragma once //============================================================================== /** A PropertyComponent for selecting files or folders. The user may drag files over the property box, enter the path manually and/or click the '...' button to open a file selection dialog box. */ class FilePathPropertyComponent : public PropertyComponent, public FileDragAndDropTarget, protected Value::Listener { public: FilePathPropertyComponent (Value valueToControl, const String& propertyName, bool isDir, bool thisOS = true, const String& wildcardsToUse = "*", const File& relativeRoot = File()) : PropertyComponent (propertyName), text (valueToControl, propertyName, 1024, false), isDirectory (isDir), isThisOS (thisOS), wildcards (wildcardsToUse), root (relativeRoot) { textValue.referTo (valueToControl); init(); } /** Displays a default value when no value is specified by the user. */ FilePathPropertyComponent (ValueTreePropertyWithDefault valueToControl, const String& propertyName, bool isDir, bool thisOS = true, const String& wildcardsToUse = "*", const File& relativeRoot = File()) : PropertyComponent (propertyName), text (valueToControl, propertyName, 1024, false), isDirectory (isDir), isThisOS (thisOS), wildcards (wildcardsToUse), root (relativeRoot) { textValue = valueToControl.getPropertyAsValue(); init(); } //============================================================================== void refresh() override {} void resized() override { auto bounds = getLocalBounds(); text.setBounds (bounds.removeFromLeft (jmax (400, bounds.getWidth() - 55))); bounds.removeFromLeft (5); browseButton.setBounds (bounds); } void paintOverChildren (Graphics& g) override { if (highlightForDragAndDrop) { g.setColour (findColour (defaultHighlightColourId).withAlpha (0.5f)); g.fillRect (getLookAndFeel().getPropertyComponentContentPosition (text)); } } //============================================================================== bool isInterestedInFileDrag (const StringArray&) override { return true; } void fileDragEnter (const StringArray&, int, int) override { highlightForDragAndDrop = true; repaint(); } void fileDragExit (const StringArray&) override { highlightForDragAndDrop = false; repaint(); } void filesDropped (const StringArray& selectedFiles, int, int) override { setTo (selectedFiles[0]); highlightForDragAndDrop = false; repaint(); } protected: void valueChanged (Value&) override { updateEditorColour(); } private: //============================================================================== void init() { textValue.addListener (this); text.setInterestedInFileDrag (false); addAndMakeVisible (text); browseButton.onClick = [this] { browse(); }; addAndMakeVisible (browseButton); updateLookAndFeel(); } void setTo (File f) { if (isDirectory && ! f.isDirectory()) f = f.getParentDirectory(); auto pathName = (root == File()) ? f.getFullPathName() : f.getRelativePathFrom (root); text.setText (pathName); updateEditorColour(); } void browse() { File currentFile = {}; if (text.getText().isNotEmpty()) currentFile = root.getChildFile (text.getText()); if (isDirectory) { chooser = std::make_unique ("Select directory", currentFile); auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc) { if (fc.getResult() == File{}) return; setTo (fc.getResult()); }); } else { chooser = std::make_unique ("Select file", currentFile, wildcards); auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles; chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc) { if (fc.getResult() == File{}) return; setTo (fc.getResult()); }); } } void updateEditorColour() { if (isThisOS) { text.setColour (TextPropertyComponent::textColourId, findColour (widgetTextColourId)); auto pathToCheck = text.getText(); if (pathToCheck.isNotEmpty()) { pathToCheck.replace ("${user.home}", "~"); #if JUCE_WINDOWS if (pathToCheck.startsWith ("~")) pathToCheck = pathToCheck.replace ("~", File::getSpecialLocation (File::userHomeDirectory).getFullPathName()); #endif if (! root.getChildFile (pathToCheck).exists()) text.setColour (TextPropertyComponent::textColourId, Colours::red); } } } void updateLookAndFeel() { browseButton.setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId)); browseButton.setColour (TextButton::textColourOffId, Colours::white); updateEditorColour(); } void lookAndFeelChanged() override { updateLookAndFeel(); } //============================================================================== Value textValue; TextPropertyComponent text; TextButton browseButton { "..." }; bool isDirectory, isThisOS, highlightForDragAndDrop = false; String wildcards; File root; std::unique_ptr chooser; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePathPropertyComponent) }; //============================================================================== class FilePathPropertyComponentWithEnablement final : public FilePathPropertyComponent { public: FilePathPropertyComponentWithEnablement (const ValueTreePropertyWithDefault& valueToControl, ValueTreePropertyWithDefault valueToListenTo, const String& propertyName, bool isDir, bool thisOS = true, const String& wildcardsToUse = "*", const File& relativeRoot = File()) : FilePathPropertyComponent (valueToControl, propertyName, isDir, thisOS, wildcardsToUse, relativeRoot), propertyWithDefault (valueToListenTo), value (valueToListenTo.getPropertyAsValue()) { value.addListener (this); handleValueChanged (value); } ~FilePathPropertyComponentWithEnablement() override { value.removeListener (this); } private: void handleValueChanged (Value& v) { FilePathPropertyComponent::valueChanged (v); setEnabled (propertyWithDefault.get()); } void valueChanged (Value& v) override { handleValueChanged (v); } ValueTreePropertyWithDefault propertyWithDefault; Value value; };