JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_FileBrowserComponent.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - 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 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-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
26namespace juce
27{
28
33 : FileFilter ({}),
34 fileFilter (fileFilter_),
35 flags (flags_),
36 previewComp (previewComp_),
37 currentPathBox ("path"),
38 fileLabel ("f", TRANS ("file:")),
39 thread ("JUCE FileBrowser"),
40 wasProcessActive (true)
41{
42 // You need to specify one or other of the open/save flags..
43 jassert ((flags & (saveMode | openMode)) != 0);
44 jassert ((flags & (saveMode | openMode)) != (saveMode | openMode));
45
46 // You need to specify at least one of these flags..
47 jassert ((flags & (canSelectFiles | canSelectDirectories)) != 0);
48
49 String filename;
50
52 {
54 }
55 else if (initialFileOrDirectory.isDirectory())
56 {
57 currentRoot = initialFileOrDirectory;
58 }
59 else
60 {
61 chosenFiles.add (initialFileOrDirectory);
62 currentRoot = initialFileOrDirectory.getParentDirectory();
63 filename = initialFileOrDirectory.getFileName();
64 }
65
66 // The thread must be started before the DirectoryContentsList attempts to scan any file
67 thread.startThread (Thread::Priority::low);
68
69 fileList.reset (new DirectoryContentsList (this, thread));
70 fileList->setDirectory (currentRoot, true, true);
71
72 if ((flags & useTreeView) != 0)
73 {
74 auto tree = new FileTreeComponent (*fileList);
75 fileListComponent.reset (tree);
76
77 if ((flags & canSelectMultipleItems) != 0)
78 tree->setMultiSelectEnabled (true);
79
80 addAndMakeVisible (tree);
81 }
82 else
83 {
84 auto list = new FileListComponent (*fileList);
85 fileListComponent.reset (list);
86 list->setOutlineThickness (1);
87
88 if ((flags & canSelectMultipleItems) != 0)
89 list->setMultipleSelectionEnabled (true);
90
91 addAndMakeVisible (list);
92 }
93
94 fileListComponent->addListener (this);
95
96 addAndMakeVisible (currentPathBox);
97 currentPathBox.setEditableText (true);
98 resetRecentPaths();
99 currentPathBox.onChange = [this] { updateSelectedPath(); };
100
101 addAndMakeVisible (filenameBox);
102 filenameBox.setMultiLine (false);
103 filenameBox.setSelectAllWhenFocused (true);
104 filenameBox.setText (filename, false);
105 filenameBox.onTextChange = [this] { sendListenerChangeMessage(); };
106 filenameBox.onReturnKey = [this] { changeFilename(); };
107 filenameBox.onFocusLost = [this]
108 {
109 if (! isSaveMode())
110 selectionChanged();
111 };
112
113 filenameBox.setReadOnly ((flags & (filenameBoxIsReadOnly | canSelectMultipleItems)) != 0);
114
115 addAndMakeVisible (fileLabel);
116 fileLabel.attachToComponent (&filenameBox, true);
117
118 if (previewComp != nullptr)
119 addAndMakeVisible (previewComp);
120
121 lookAndFeelChanged();
122
123 setRoot (currentRoot);
124
125 if (filename.isNotEmpty())
126 setFileName (filename);
127
128 startTimer (2000);
129}
130
132{
133 fileListComponent.reset();
134 fileList.reset();
135 thread.stopThread (10000);
136}
137
138//==============================================================================
143
145{
146 listeners.remove (listener);
147}
148
149//==============================================================================
151{
152 return (flags & saveMode) != 0;
153}
154
156{
157 if (chosenFiles.isEmpty() && currentFileIsValid())
158 return 1;
159
160 return chosenFiles.size();
161}
162
164{
165 if ((flags & canSelectDirectories) != 0 && filenameBox.getText().isEmpty())
166 return currentRoot;
167
168 if (! filenameBox.isReadOnly())
169 return currentRoot.getChildFile (filenameBox.getText());
170
171 return chosenFiles[index];
172}
173
175{
176 auto f = getSelectedFile (0);
177
178 if ((flags & canSelectDirectories) == 0 && f.isDirectory())
179 return false;
180
181 return isSaveMode() || f.exists();
182}
183
185{
186 return fileListComponent->getSelectedFile (0);
187}
188
190{
191 fileListComponent->deselectAllFiles();
192}
193
194//==============================================================================
196{
197 return (flags & canSelectFiles) != 0
198 && (fileFilter == nullptr || fileFilter->isFileSuitable (file));
199}
200
202{
203 return true;
204}
205
206bool FileBrowserComponent::isFileOrDirSuitable (const File& f) const
207{
208 if (f.isDirectory())
209 return (flags & canSelectDirectories) != 0
210 && (fileFilter == nullptr || fileFilter->isDirectorySuitable (f));
211
212 return (flags & canSelectFiles) != 0 && f.exists()
213 && (fileFilter == nullptr || fileFilter->isFileSuitable (f));
214}
215
216//==============================================================================
218{
219 return currentRoot;
220}
221
223{
224 bool callListeners = false;
225
226 if (currentRoot != newRootDirectory)
227 {
228 callListeners = true;
229 fileListComponent->scrollToTop();
230
231 String path (newRootDirectory.getFullPathName());
232
233 if (path.isEmpty())
235
238
239 if (! rootPaths.contains (path, true))
240 {
241 bool alreadyListed = false;
242
243 for (int i = currentPathBox.getNumItems(); --i >= 0;)
244 {
245 if (currentPathBox.getItemText (i).equalsIgnoreCase (path))
246 {
247 alreadyListed = true;
248 break;
249 }
250 }
251
252 if (! alreadyListed)
253 currentPathBox.addItem (path, currentPathBox.getNumItems() + 2);
254 }
255 }
256
257 currentRoot = newRootDirectory;
258 fileList->setDirectory (currentRoot, true, true);
259
260 if (auto* tree = dynamic_cast<FileTreeComponent*> (fileListComponent.get()))
261 tree->refresh();
262
263 auto currentRootName = currentRoot.getFullPathName();
264
265 if (currentRootName.isEmpty())
267
269
270 goUpButton->setEnabled (currentRoot.getParentDirectory().isDirectory()
271 && currentRoot.getParentDirectory() != currentRoot);
272
273 if (callListeners)
274 {
276 listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.browserRootChanged (currentRoot); });
277 }
278}
279
281{
282 filenameBox.setText (newName, true);
283
284 fileListComponent->setSelectedFile (currentRoot.getChildFile (newName));
285}
286
288{
289 currentPathBox.clear();
290
293
294 for (int i = 0; i < rootNames.size(); ++i)
295 {
296 if (rootNames[i].isEmpty())
297 currentPathBox.addSeparator();
298 else
299 currentPathBox.addItem (rootNames[i], i + 1);
300 }
301
302 currentPathBox.addSeparator();
303}
304
306{
307 setRoot (getRoot().getParentDirectory());
308}
309
311{
312 fileList->refresh();
313}
314
316{
317 if (fileFilter != newFileFilter)
318 {
319 fileFilter = newFileFilter;
320 refresh();
321 }
322}
323
325{
326 return isSaveMode() ? ((flags & canSelectDirectories) != 0 ? TRANS ("Choose")
327 : TRANS ("Save"))
328 : TRANS ("Open");
329}
330
332{
333 fileLabel.setText (name, dontSendNotification);
334}
335
336FilePreviewComponent* FileBrowserComponent::getPreviewComponent() const noexcept
337{
338 return previewComp;
339}
340
341DirectoryContentsDisplayComponent* FileBrowserComponent::getDisplayComponent() const noexcept
342{
343 return fileListComponent.get();
344}
345
346//==============================================================================
348{
350 .layoutFileBrowserComponent (*this, fileListComponent.get(), previewComp,
351 &currentPathBox, &filenameBox, goUpButton.get());
352}
353
354//==============================================================================
356{
357 goUpButton.reset (getLookAndFeel().createFileBrowserGoUpButton());
358
359 if (auto* buttonPtr = goUpButton.get())
360 {
362 buttonPtr->onClick = [this] { goUp(); };
363 buttonPtr->setTooltip (TRANS ("Go up to parent directory"));
364 }
365
369
372
373 resized();
374}
375
376//==============================================================================
377void FileBrowserComponent::sendListenerChangeMessage()
378{
380
381 if (previewComp != nullptr)
382 previewComp->selectedFileChanged (getSelectedFile (0));
383
384 // You shouldn't delete the browser when the file gets changed!
385 jassert (! checker.shouldBailOut());
386
387 listeners.callChecked (checker, [] (FileBrowserListener& l) { l.selectionChanged(); });
388}
389
391{
393 bool resetChosenFiles = true;
394
395 for (int i = 0; i < fileListComponent->getNumSelectedFiles(); ++i)
396 {
397 const File f (fileListComponent->getSelectedFile (i));
398
399 if (isFileOrDirSuitable (f))
400 {
402 {
403 chosenFiles.clear();
404 resetChosenFiles = false;
405 }
406
407 chosenFiles.add (f);
409 }
410 }
411
412 if (newFilenames.size() > 0)
413 filenameBox.setText (newFilenames.joinIntoString (", "), false);
414
415 sendListenerChangeMessage();
416}
417
419{
421 listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileClicked (f, e); });
422}
423
425{
426 if (f.isDirectory())
427 {
428 setRoot (f);
429
430 if ((flags & canSelectDirectories) != 0 && (flags & doNotClearFileNameOnRootChange) == 0)
431 filenameBox.setText ({});
432 }
433 else
434 {
436 listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileDoubleClicked (f); });
437 }
438}
439
441
443{
444 #if JUCE_LINUX || JUCE_BSD || JUCE_WINDOWS
445 if (key.getModifiers().isCommandDown()
446 && (key.getKeyCode() == 'H' || key.getKeyCode() == 'h'))
447 {
448 fileList->setIgnoresHiddenFiles (! fileList->ignoresHiddenFiles());
449 fileList->refresh();
450 return true;
451 }
452 #endif
453
454 return false;
455}
456
457//==============================================================================
458void FileBrowserComponent::changeFilename()
459{
460 if (filenameBox.getText().containsChar (File::getSeparatorChar()))
461 {
462 auto f = currentRoot.getChildFile (filenameBox.getText());
463
464 if (f.isDirectory())
465 {
466 setRoot (f);
467 chosenFiles.clear();
468
469 if ((flags & doNotClearFileNameOnRootChange) == 0)
470 filenameBox.setText ({});
471 }
472 else
473 {
475 chosenFiles.clear();
476 chosenFiles.add (f);
477 filenameBox.setText (f.getFileName());
478 }
479 }
480 else
481 {
483 }
484}
485
486//==============================================================================
487void FileBrowserComponent::updateSelectedPath()
488{
489 auto newText = currentPathBox.getText().trim().unquoted();
490
491 if (newText.isNotEmpty())
492 {
493 auto index = currentPathBox.getSelectedId() - 1;
494
495 StringArray rootNames, rootPaths;
497
498 if (rootPaths[index].isNotEmpty())
499 {
500 setRoot (File (rootPaths[index]));
501 }
502 else
503 {
504 File f (newText);
505
506 for (;;)
507 {
508 if (f.isDirectory())
509 {
510 setRoot (f);
511 break;
512 }
513
514 if (f.getParentDirectory() == f)
515 break;
516
517 f = f.getParentDirectory();
518 }
519 }
520 }
521}
522
524{
525 #if JUCE_WINDOWS
528 rootPaths.clear();
529
530 for (int i = 0; i < roots.size(); ++i)
531 {
532 const File& drive = roots.getReference (i);
533
534 String name (drive.getFullPathName());
535 rootPaths.add (name);
536
537 if (drive.isOnHardDisk())
538 {
539 String volume (drive.getVolumeLabel());
540
541 if (volume.isEmpty())
542 volume = TRANS ("Hard Drive");
543
544 name << " [" << volume << ']';
545 }
546 else if (drive.isOnCDRomDrive())
547 {
548 name << " [" << TRANS ("CD/DVD drive") << ']';
549 }
550
551 rootNames.add (name);
552 }
553
554 rootPaths.add ({});
555 rootNames.add ({});
556
558 rootNames.add (TRANS ("Documents"));
560 rootNames.add (TRANS ("Music"));
562 rootNames.add (TRANS ("Pictures"));
564 rootNames.add (TRANS ("Desktop"));
565
566 #elif JUCE_MAC
568 rootNames.add (TRANS ("Home folder"));
570 rootNames.add (TRANS ("Documents"));
572 rootNames.add (TRANS ("Music"));
574 rootNames.add (TRANS ("Pictures"));
576 rootNames.add (TRANS ("Desktop"));
577
578 rootPaths.add ({});
579 rootNames.add ({});
580
581 for (auto& volume : File ("/Volumes").findChildFiles (File::findDirectories, false))
582 {
583 if (volume.isDirectory() && ! volume.getFileName().startsWithChar ('.'))
584 {
585 rootPaths.add (volume.getFullPathName());
586 rootNames.add (volume.getFileName());
587 }
588 }
589
590 #else
591 rootPaths.add ("/");
592 rootNames.add ("/");
594 rootNames.add (TRANS ("Home folder"));
596 rootNames.add (TRANS ("Desktop"));
597 #endif
598}
599
604
605void FileBrowserComponent::timerCallback()
606{
607 const auto isProcessActive = detail::WindowingHelpers::isForegroundOrEmbeddedProcess (this);
608
609 if (wasProcessActive != isProcessActive)
610 {
611 wasProcessActive = isProcessActive;
612
613 if (isProcessActive && fileList != nullptr)
614 refresh();
615 }
616}
617
618//==============================================================================
620{
621 return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
622}
623
624} // namespace juce
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:56
@ arrowColourId
The colour for the arrow shape that pops up the menu.
@ textColourId
The colour for the text in the box.
@ backgroundColourId
The background colour to fill the box with.
void setText(const String &newText, NotificationType notification=sendNotificationAsync)
Sets the contents of the combo-box's text field.
void clear(NotificationType notification=sendNotificationAsync)
Removes all the items from the drop-down list.
String getItemText(int index) const
Returns the text for one of the items in the list.
void addSeparator()
Adds a separator line to the drop-down list.
int getSelectedId() const noexcept
Returns the ID of the item that's currently shown in the box.
String getText() const
Returns the text that is currently shown in the combo-box's text field.
void addItem(const String &newItemText, int newItemId)
Adds an item to be shown in the drop-down list.
int getNumItems() const noexcept
Returns the number of items that have been added to the list.
A class to keep an eye on a component and check for it being deleted.
void addAndMakeVisible(Component *child, int zOrder=-1)
Adds a child component to this one, and also makes the child visible if it isn't already.
void setColour(int colourID, Colour newColour)
Registers a colour to be used for a particular purpose.
Colour findColour(int colourID, bool inheritFromParent=false) const
Looks for a colour that has been registered with the given colour ID number.
LookAndFeel & getLookAndFeel() const noexcept
Finds the appropriate look-and-feel to use for this component.
void refresh()
Refreshes the directory that's currently being listed.
void resized() override
Called when this component's size has been changed.
static void getDefaultRoots(StringArray &rootNames, StringArray &rootPaths)
Returns a platform-specific list of names and paths for some suggested places the user might want to ...
File getSelectedFile(int index) const noexcept
Returns one of the files that the user has chosen.
@ saveMode
specifies that the component should allow the user to specify the name of a file that will be used to...
@ canSelectFiles
specifies that the user can select files (can be used in conjunction with canSelectDirectories).
@ doNotClearFileNameOnRootChange
specifies that the file name should not be cleared upon root change.
@ canSelectDirectories
specifies that the user can select directories (can be used in conjunction with canSelectFiles).
void selectionChanged() override
Callback when the user selects a different file in the browser.
FileBrowserComponent(int flags, const File &initialFileOrDirectory, const FileFilter *fileFilter, FilePreviewComponent *previewComp)
Creates a FileBrowserComponent.
virtual String getActionVerb() const
Returns a verb to describe what should happen when the file is accepted.
virtual void getRoots(StringArray &rootNames, StringArray &rootPaths)
Returns a list of names and paths for the default places the user might want to look.
void browserRootChanged(const File &) override
Callback when the browser's root folder changes.
void setFileFilter(const FileFilter *newFileFilter)
Changes the filter that's being used to sift the files.
void goUp()
Equivalent to pressing the "up" button to browse the parent directory.
void fileClicked(const File &, const MouseEvent &) override
Callback when the user clicks on a file in the browser.
void setFilenameBoxLabel(const String &name)
Sets the label that will be displayed next to the filename entry box.
void setRoot(const File &newRootDirectory)
Changes the directory that's being shown in the listbox.
void deselectAllFiles()
Deselects any files that are currently selected.
void removeListener(FileBrowserListener *listener)
Removes a listener.
void lookAndFeelChanged() override
Called to let the component react to a change in the look-and-feel setting.
void setFileName(const String &newName)
Changes the name that is currently shown in the filename box.
bool isDirectorySuitable(const File &) const override
Should return true if this directory is suitable for inclusion in whatever context the object is bein...
File getHighlightedFile() const noexcept
This returns the last item in the view that the user has highlighted.
bool isFileSuitable(const File &) const override
Should return true if this file is suitable for inclusion in whatever context the object is being use...
bool isSaveMode() const noexcept
Returns true if the saveMode flag was set when this component was created.
int getNumSelectedFiles() const noexcept
Returns the number of files that the user has got selected.
std::unique_ptr< AccessibilityHandler > createAccessibilityHandler() override
Override this method to return a custom AccessibilityHandler for this component.
const File & getRoot() const
Returns the directory whose contents are currently being shown in the listbox.
bool currentFileIsValid() const
Returns true if the currently selected file(s) are usable.
@ currentPathBoxTextColourId
The colour to use for the text of the current path ComboBox.
@ filenameBoxBackgroundColourId
The colour to use to fill the background of the filename TextEditor.
@ currentPathBoxBackgroundColourId
The colour to use to fill the background of the current path ComboBox.
@ filenameBoxTextColourId
The colour to use for the text of the filename TextEditor.
@ currentPathBoxArrowColourId
The colour to use to draw the arrow of the current path ComboBox.
void addListener(FileBrowserListener *listener)
Adds a listener to be told when the user selects and clicks on files.
bool keyPressed(const KeyPress &) override
Called when a key is pressed.
void fileDoubleClicked(const File &) override
Callback when the user double-clicks on a file in the browser.
void resetRecentPaths()
Updates the items in the dropdown list of recent paths with the values from getRoots().
A listener for user selection events in a file browser.
Interface for deciding which files are suitable for something.
virtual bool isFileSuitable(const File &file) const =0
Should return true if this file is suitable for inclusion in whatever context the object is being use...
virtual bool isDirectorySuitable(const File &file) const =0
Should return true if this directory is suitable for inclusion in whatever context the object is bein...
Base class for components that live inside a file chooser dialog box and show previews of the files t...
virtual void selectedFileChanged(const File &newSelectedFile)=0
Called to indicate that the user's currently selected file has changed.
A component that displays the files in a directory as a treeview.
Represents a local file or directory.
Definition juce_File.h:45
bool isOnHardDisk() const
Returns true if this file is on a hard disk.
bool isDirectory() const
Checks whether the file is a directory that exists.
Array< File > findChildFiles(int whatToLookFor, bool searchRecursively, const String &wildCardPattern="*", FollowSymlinks followSymlinks=FollowSymlinks::yes) const
Searches this directory for files matching a wildcard pattern.
bool isOnCDRomDrive() const
Returns true if this file is on a CD or DVD drive.
static StringRef getSeparatorString()
The system-specific file separator character, as a string.
const String & getFullPathName() const noexcept
Returns the complete, absolute path of this file.
Definition juce_File.h:153
String getFileName() const
Returns the last section of the pathname.
File getChildFile(StringRef relativeOrAbsolutePath) const
Returns a file that represents a relative (or absolute) sub-path of the current one.
static File getCurrentWorkingDirectory()
Returns the current working directory.
@ userMusicDirectory
The most likely place where a user might store their music files.
Definition juce_File.h:881
@ userDocumentsDirectory
The user's default documents folder.
Definition juce_File.h:875
@ userPicturesDirectory
The most likely place where a user might store their picture files.
Definition juce_File.h:887
@ userDesktopDirectory
The folder that contains the user's desktop objects.
Definition juce_File.h:878
@ userHomeDirectory
The user's home folder.
Definition juce_File.h:869
static void findFileSystemRoots(Array< File > &results)
Creates a set of files to represent each file root.
String getRelativePathFrom(const File &directoryToBeRelativeTo) const
Creates a relative path that refers to a file relatively to a given directory.
@ findDirectories
Use this flag to indicate that you want to find directories.
Definition juce_File.h:565
static File JUCE_CALLTYPE getSpecialLocation(const SpecialLocationType type)
Finds the location of a special type of file or directory, such as a home folder or documents folder.
File getParentDirectory() const
Returns the directory that contains this file or directory.
String getVolumeLabel() const
Finds the name of the drive on which this file lives.
static juce_wchar getSeparatorChar()
The system-specific file separator character.
bool exists() const
Checks whether the file actually exists.
Represents a key press, including any modifier keys that are needed.
void setText(const String &newText, NotificationType notification)
Changes the label text.
Contains position and status information about a mouse event.
A special array for holding a list of strings.
The JUCE String class!
Definition juce_String.h:53
bool equalsIgnoreCase(const String &other) const noexcept
Case-insensitive comparison with another string.
String trim() const
Returns a copy of this string with any whitespace characters removed from the start and end.
bool isEmpty() const noexcept
Returns true if the string contains no characters.
bool containsChar(juce_wchar character) const noexcept
Tests whether the string contains a particular character.
String unquoted() const
Removes quotation marks from around the string, (if there are any).
bool isNotEmpty() const noexcept
Returns true if the string contains at least one character.
@ backgroundColourId
The colour to use for the text component's background - this can be transparent if necessary.
void applyColourToAllText(const Colour &newColour, bool changeCurrentTextColour=true)
Applies a colour to all the text in the editor.
void setText(const String &newText, bool sendTextChangeMessage=true)
Sets the entire content of the editor.
String getText() const
Returns the entire contents of the editor.
bool stopThread(int timeOutMilliseconds)
Attempts to stop the thread running.
@ low
Uses efficiency cores when possible.
#define TRANS(stringLiteral)
Uses the LocalisedStrings class to translate the given string literal.
#define jassert(expression)
Platform-independent assertion macro.
JUCE Namespace.
@ dontSendNotification
No notification message should be sent.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88