1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
FilenameComponent(const String & name,const File & currentFile,bool canEditFilename,bool isDirectory,bool isForSaving,const String & fileBrowserWildcard,const String & suffix,const String & textWhenNothingSelected)29 FilenameComponent::FilenameComponent (const String& name,
30                                       const File& currentFile,
31                                       bool canEditFilename,
32                                       bool isDirectory,
33                                       bool isForSaving,
34                                       const String& fileBrowserWildcard,
35                                       const String& suffix,
36                                       const String& textWhenNothingSelected)
37     : Component (name),
38       isDir (isDirectory),
39       isSaving (isForSaving),
40       wildcard (fileBrowserWildcard),
41       enforcedSuffix (suffix)
42 {
43     addAndMakeVisible (filenameBox);
44     filenameBox.setEditableText (canEditFilename);
45     filenameBox.setTextWhenNothingSelected (textWhenNothingSelected);
46     filenameBox.setTextWhenNoChoicesAvailable (TRANS ("(no recently selected files)"));
47     filenameBox.onChange = [this] { setCurrentFile (getCurrentFile(), true); };
48 
49     setBrowseButtonText ("...");
50 
51     setCurrentFile (currentFile, true, dontSendNotification);
52 }
53 
~FilenameComponent()54 FilenameComponent::~FilenameComponent()
55 {
56 }
57 
58 //==============================================================================
paintOverChildren(Graphics & g)59 void FilenameComponent::paintOverChildren (Graphics& g)
60 {
61     if (isFileDragOver)
62     {
63         g.setColour (Colours::red.withAlpha (0.2f));
64         g.drawRect (getLocalBounds(), 3);
65     }
66 }
67 
resized()68 void FilenameComponent::resized()
69 {
70     getLookAndFeel().layoutFilenameComponent (*this, &filenameBox, browseButton.get());
71 }
72 
createFocusTraverser()73 KeyboardFocusTraverser* FilenameComponent::createFocusTraverser()
74 {
75     // This prevents the sub-components from grabbing focus if the
76     // FilenameComponent has been set to refuse focus.
77     return getWantsKeyboardFocus() ? Component::createFocusTraverser() : nullptr;
78 }
79 
setBrowseButtonText(const String & newBrowseButtonText)80 void FilenameComponent::setBrowseButtonText (const String& newBrowseButtonText)
81 {
82     browseButtonText = newBrowseButtonText;
83     lookAndFeelChanged();
84 }
85 
lookAndFeelChanged()86 void FilenameComponent::lookAndFeelChanged()
87 {
88     browseButton.reset();
89     browseButton.reset (getLookAndFeel().createFilenameComponentBrowseButton (browseButtonText));
90     addAndMakeVisible (browseButton.get());
91     browseButton->setConnectedEdges (Button::ConnectedOnLeft);
92     browseButton->onClick = [this] { showChooser(); };
93     resized();
94 }
95 
setTooltip(const String & newTooltip)96 void FilenameComponent::setTooltip (const String& newTooltip)
97 {
98     SettableTooltipClient::setTooltip (newTooltip);
99     filenameBox.setTooltip (newTooltip);
100 }
101 
setDefaultBrowseTarget(const File & newDefaultDirectory)102 void FilenameComponent::setDefaultBrowseTarget (const File& newDefaultDirectory)
103 {
104     defaultBrowseFile = newDefaultDirectory;
105 }
106 
getLocationToBrowse()107 File FilenameComponent::getLocationToBrowse()
108 {
109     if (lastFilename.isEmpty() && defaultBrowseFile != File())
110         return defaultBrowseFile;
111 
112     return getCurrentFile();
113 }
114 
showChooser()115 void FilenameComponent::showChooser()
116 {
117    #if JUCE_MODAL_LOOPS_PERMITTED
118     FileChooser fc (isDir ? TRANS ("Choose a new directory")
119                           : TRANS ("Choose a new file"),
120                     getLocationToBrowse(),
121                     wildcard);
122 
123     if (isDir ? fc.browseForDirectory()
124               : (isSaving ? fc.browseForFileToSave (false)
125                           : fc.browseForFileToOpen()))
126     {
127         setCurrentFile (fc.getResult(), true);
128     }
129    #else
130     ignoreUnused (isSaving);
131     jassertfalse; // needs rewriting to deal with non-modal environments
132    #endif
133 }
134 
isInterestedInFileDrag(const StringArray &)135 bool FilenameComponent::isInterestedInFileDrag (const StringArray&)
136 {
137     return true;
138 }
139 
filesDropped(const StringArray & filenames,int,int)140 void FilenameComponent::filesDropped (const StringArray& filenames, int, int)
141 {
142     isFileDragOver = false;
143     repaint();
144 
145     const File f (filenames[0]);
146 
147     if (f.exists() && (f.isDirectory() == isDir))
148         setCurrentFile (f, true);
149 }
150 
fileDragEnter(const StringArray &,int,int)151 void FilenameComponent::fileDragEnter (const StringArray&, int, int)
152 {
153     isFileDragOver = true;
154     repaint();
155 }
156 
fileDragExit(const StringArray &)157 void FilenameComponent::fileDragExit (const StringArray&)
158 {
159     isFileDragOver = false;
160     repaint();
161 }
162 
163 //==============================================================================
getCurrentFileText() const164 String FilenameComponent::getCurrentFileText() const
165 {
166     return filenameBox.getText();
167 }
168 
getCurrentFile() const169 File FilenameComponent::getCurrentFile() const
170 {
171     auto f = File::getCurrentWorkingDirectory().getChildFile (getCurrentFileText());
172 
173     if (enforcedSuffix.isNotEmpty())
174         f = f.withFileExtension (enforcedSuffix);
175 
176     return f;
177 }
178 
setCurrentFile(File newFile,const bool addToRecentlyUsedList,NotificationType notification)179 void FilenameComponent::setCurrentFile (File newFile,
180                                         const bool addToRecentlyUsedList,
181                                         NotificationType notification)
182 {
183     if (enforcedSuffix.isNotEmpty())
184         newFile = newFile.withFileExtension (enforcedSuffix);
185 
186     if (newFile.getFullPathName() != lastFilename)
187     {
188         lastFilename = newFile.getFullPathName();
189 
190         if (addToRecentlyUsedList)
191             addRecentlyUsedFile (newFile);
192 
193         filenameBox.setText (lastFilename, dontSendNotification);
194 
195         if (notification != dontSendNotification)
196         {
197             triggerAsyncUpdate();
198 
199             if (notification == sendNotificationSync)
200                 handleUpdateNowIfNeeded();
201         }
202     }
203 }
204 
setFilenameIsEditable(const bool shouldBeEditable)205 void FilenameComponent::setFilenameIsEditable (const bool shouldBeEditable)
206 {
207     filenameBox.setEditableText (shouldBeEditable);
208 }
209 
getRecentlyUsedFilenames() const210 StringArray FilenameComponent::getRecentlyUsedFilenames() const
211 {
212     StringArray names;
213 
214     for (int i = 0; i < filenameBox.getNumItems(); ++i)
215         names.add (filenameBox.getItemText (i));
216 
217     return names;
218 }
219 
setRecentlyUsedFilenames(const StringArray & filenames)220 void FilenameComponent::setRecentlyUsedFilenames (const StringArray& filenames)
221 {
222     if (filenames != getRecentlyUsedFilenames())
223     {
224         filenameBox.clear();
225 
226         for (int i = 0; i < jmin (filenames.size(), maxRecentFiles); ++i)
227             filenameBox.addItem (filenames[i], i + 1);
228     }
229 }
230 
setMaxNumberOfRecentFiles(const int newMaximum)231 void FilenameComponent::setMaxNumberOfRecentFiles (const int newMaximum)
232 {
233     maxRecentFiles = jmax (1, newMaximum);
234 
235     setRecentlyUsedFilenames (getRecentlyUsedFilenames());
236 }
237 
addRecentlyUsedFile(const File & file)238 void FilenameComponent::addRecentlyUsedFile (const File& file)
239 {
240     auto files = getRecentlyUsedFilenames();
241 
242     if (file.getFullPathName().isNotEmpty())
243     {
244         files.removeString (file.getFullPathName(), true);
245         files.insert (0, file.getFullPathName());
246 
247         setRecentlyUsedFilenames (files);
248     }
249 }
250 
251 //==============================================================================
addListener(FilenameComponentListener * const listener)252 void FilenameComponent::addListener (FilenameComponentListener* const listener)
253 {
254     listeners.add (listener);
255 }
256 
removeListener(FilenameComponentListener * const listener)257 void FilenameComponent::removeListener (FilenameComponentListener* const listener)
258 {
259     listeners.remove (listener);
260 }
261 
handleAsyncUpdate()262 void FilenameComponent::handleAsyncUpdate()
263 {
264     Component::BailOutChecker checker (this);
265     listeners.callChecked (checker, [this] (FilenameComponentListener& l) { l.filenameComponentChanged (this); });
266 }
267 
268 } // namespace juce
269