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