Audio plugin host https://kx.studio/carla
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.

614 lines
18KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. FileBrowserComponent::FileBrowserComponent (int flags_,
  22. const File& initialFileOrDirectory,
  23. const FileFilter* fileFilter_,
  24. FilePreviewComponent* previewComp_)
  25. : FileFilter (String()),
  26. fileFilter (fileFilter_),
  27. flags (flags_),
  28. previewComp (previewComp_),
  29. currentPathBox ("path"),
  30. fileLabel ("f", TRANS ("file:")),
  31. thread ("Juce FileBrowser"),
  32. wasProcessActive (true)
  33. {
  34. // You need to specify one or other of the open/save flags..
  35. jassert ((flags & (saveMode | openMode)) != 0);
  36. jassert ((flags & (saveMode | openMode)) != (saveMode | openMode));
  37. // You need to specify at least one of these flags..
  38. jassert ((flags & (canSelectFiles | canSelectDirectories)) != 0);
  39. String filename;
  40. if (initialFileOrDirectory == File())
  41. {
  42. currentRoot = File::getCurrentWorkingDirectory();
  43. }
  44. else if (initialFileOrDirectory.isDirectory())
  45. {
  46. currentRoot = initialFileOrDirectory;
  47. }
  48. else
  49. {
  50. chosenFiles.add (initialFileOrDirectory);
  51. currentRoot = initialFileOrDirectory.getParentDirectory();
  52. filename = initialFileOrDirectory.getFileName();
  53. }
  54. fileList = new DirectoryContentsList (this, thread);
  55. if ((flags & useTreeView) != 0)
  56. {
  57. FileTreeComponent* const tree = new FileTreeComponent (*fileList);
  58. fileListComponent = tree;
  59. if ((flags & canSelectMultipleItems) != 0)
  60. tree->setMultiSelectEnabled (true);
  61. addAndMakeVisible (tree);
  62. }
  63. else
  64. {
  65. FileListComponent* const list = new FileListComponent (*fileList);
  66. fileListComponent = list;
  67. list->setOutlineThickness (1);
  68. if ((flags & canSelectMultipleItems) != 0)
  69. list->setMultipleSelectionEnabled (true);
  70. addAndMakeVisible (list);
  71. }
  72. fileListComponent->addListener (this);
  73. addAndMakeVisible (currentPathBox);
  74. currentPathBox.setEditableText (true);
  75. resetRecentPaths();
  76. currentPathBox.addListener (this);
  77. addAndMakeVisible (filenameBox);
  78. filenameBox.setMultiLine (false);
  79. filenameBox.setSelectAllWhenFocused (true);
  80. filenameBox.setText (filename, false);
  81. filenameBox.addListener (this);
  82. filenameBox.setReadOnly ((flags & (filenameBoxIsReadOnly | canSelectMultipleItems)) != 0);
  83. addAndMakeVisible (fileLabel);
  84. fileLabel.attachToComponent (&filenameBox, true);
  85. addAndMakeVisible (goUpButton = getLookAndFeel().createFileBrowserGoUpButton());
  86. goUpButton->addListener (this);
  87. goUpButton->setTooltip (TRANS ("Go up to parent directory"));
  88. if (previewComp != nullptr)
  89. addAndMakeVisible (previewComp);
  90. setRoot (currentRoot);
  91. thread.startThread (4);
  92. startTimer (2000);
  93. }
  94. FileBrowserComponent::~FileBrowserComponent()
  95. {
  96. fileListComponent = nullptr;
  97. fileList = nullptr;
  98. thread.stopThread (10000);
  99. }
  100. //==============================================================================
  101. void FileBrowserComponent::addListener (FileBrowserListener* const newListener)
  102. {
  103. listeners.add (newListener);
  104. }
  105. void FileBrowserComponent::removeListener (FileBrowserListener* const listener)
  106. {
  107. listeners.remove (listener);
  108. }
  109. //==============================================================================
  110. bool FileBrowserComponent::isSaveMode() const noexcept
  111. {
  112. return (flags & saveMode) != 0;
  113. }
  114. int FileBrowserComponent::getNumSelectedFiles() const noexcept
  115. {
  116. if (chosenFiles.size() == 0 && currentFileIsValid())
  117. return 1;
  118. return chosenFiles.size();
  119. }
  120. File FileBrowserComponent::getSelectedFile (int index) const noexcept
  121. {
  122. if ((flags & canSelectDirectories) != 0 && filenameBox.getText().isEmpty())
  123. return currentRoot;
  124. if (! filenameBox.isReadOnly())
  125. return currentRoot.getChildFile (filenameBox.getText());
  126. return chosenFiles[index];
  127. }
  128. bool FileBrowserComponent::currentFileIsValid() const
  129. {
  130. const File f (getSelectedFile (0));
  131. if (isSaveMode())
  132. return (flags & canSelectDirectories) != 0 || ! f.isDirectory();
  133. return f.exists();
  134. }
  135. File FileBrowserComponent::getHighlightedFile() const noexcept
  136. {
  137. return fileListComponent->getSelectedFile (0);
  138. }
  139. void FileBrowserComponent::deselectAllFiles()
  140. {
  141. fileListComponent->deselectAllFiles();
  142. }
  143. //==============================================================================
  144. bool FileBrowserComponent::isFileSuitable (const File& file) const
  145. {
  146. return (flags & canSelectFiles) != 0
  147. && (fileFilter == nullptr || fileFilter->isFileSuitable (file));
  148. }
  149. bool FileBrowserComponent::isDirectorySuitable (const File&) const
  150. {
  151. return true;
  152. }
  153. bool FileBrowserComponent::isFileOrDirSuitable (const File& f) const
  154. {
  155. if (f.isDirectory())
  156. return (flags & canSelectDirectories) != 0
  157. && (fileFilter == nullptr || fileFilter->isDirectorySuitable (f));
  158. return (flags & canSelectFiles) != 0 && f.exists()
  159. && (fileFilter == nullptr || fileFilter->isFileSuitable (f));
  160. }
  161. //==============================================================================
  162. const File& FileBrowserComponent::getRoot() const
  163. {
  164. return currentRoot;
  165. }
  166. void FileBrowserComponent::setRoot (const File& newRootDirectory)
  167. {
  168. bool callListeners = false;
  169. if (currentRoot != newRootDirectory)
  170. {
  171. callListeners = true;
  172. fileListComponent->scrollToTop();
  173. String path (newRootDirectory.getFullPathName());
  174. if (path.isEmpty())
  175. path = File::separatorString;
  176. StringArray rootNames, rootPaths;
  177. getRoots (rootNames, rootPaths);
  178. if (! rootPaths.contains (path, true))
  179. {
  180. bool alreadyListed = false;
  181. for (int i = currentPathBox.getNumItems(); --i >= 0;)
  182. {
  183. if (currentPathBox.getItemText (i).equalsIgnoreCase (path))
  184. {
  185. alreadyListed = true;
  186. break;
  187. }
  188. }
  189. if (! alreadyListed)
  190. currentPathBox.addItem (path, currentPathBox.getNumItems() + 2);
  191. }
  192. }
  193. currentRoot = newRootDirectory;
  194. fileList->setDirectory (currentRoot, true, true);
  195. if (FileTreeComponent* tree = dynamic_cast<FileTreeComponent*> (fileListComponent.get()))
  196. tree->refresh();
  197. String currentRootName (currentRoot.getFullPathName());
  198. if (currentRootName.isEmpty())
  199. currentRootName = File::separatorString;
  200. currentPathBox.setText (currentRootName, dontSendNotification);
  201. goUpButton->setEnabled (currentRoot.getParentDirectory().isDirectory()
  202. && currentRoot.getParentDirectory() != currentRoot);
  203. if (callListeners)
  204. {
  205. Component::BailOutChecker checker (this);
  206. listeners.callChecked (checker, &FileBrowserListener::browserRootChanged, currentRoot);
  207. }
  208. }
  209. void FileBrowserComponent::setFileName (const String& newName)
  210. {
  211. filenameBox.setText (newName, true);
  212. fileListComponent->setSelectedFile (currentRoot.getChildFile (newName));
  213. }
  214. void FileBrowserComponent::resetRecentPaths()
  215. {
  216. currentPathBox.clear();
  217. StringArray rootNames, rootPaths;
  218. getRoots (rootNames, rootPaths);
  219. for (int i = 0; i < rootNames.size(); ++i)
  220. {
  221. if (rootNames[i].isEmpty())
  222. currentPathBox.addSeparator();
  223. else
  224. currentPathBox.addItem (rootNames[i], i + 1);
  225. }
  226. currentPathBox.addSeparator();
  227. }
  228. void FileBrowserComponent::goUp()
  229. {
  230. setRoot (getRoot().getParentDirectory());
  231. }
  232. void FileBrowserComponent::refresh()
  233. {
  234. fileList->refresh();
  235. }
  236. void FileBrowserComponent::setFileFilter (const FileFilter* const newFileFilter)
  237. {
  238. if (fileFilter != newFileFilter)
  239. {
  240. fileFilter = newFileFilter;
  241. refresh();
  242. }
  243. }
  244. String FileBrowserComponent::getActionVerb() const
  245. {
  246. return isSaveMode() ? ((flags & canSelectDirectories) != 0 ? TRANS("Choose")
  247. : TRANS("Save"))
  248. : TRANS("Open");
  249. }
  250. void FileBrowserComponent::setFilenameBoxLabel (const String& name)
  251. {
  252. fileLabel.setText (name, dontSendNotification);
  253. }
  254. FilePreviewComponent* FileBrowserComponent::getPreviewComponent() const noexcept
  255. {
  256. return previewComp;
  257. }
  258. DirectoryContentsDisplayComponent* FileBrowserComponent::getDisplayComponent() const noexcept
  259. {
  260. return fileListComponent;
  261. }
  262. //==============================================================================
  263. void FileBrowserComponent::resized()
  264. {
  265. getLookAndFeel()
  266. .layoutFileBrowserComponent (*this, fileListComponent, previewComp,
  267. &currentPathBox, &filenameBox, goUpButton);
  268. }
  269. //==============================================================================
  270. void FileBrowserComponent::sendListenerChangeMessage()
  271. {
  272. Component::BailOutChecker checker (this);
  273. if (previewComp != nullptr)
  274. previewComp->selectedFileChanged (getSelectedFile (0));
  275. // You shouldn't delete the browser when the file gets changed!
  276. jassert (! checker.shouldBailOut());
  277. listeners.callChecked (checker, &FileBrowserListener::selectionChanged);
  278. }
  279. void FileBrowserComponent::selectionChanged()
  280. {
  281. StringArray newFilenames;
  282. bool resetChosenFiles = true;
  283. for (int i = 0; i < fileListComponent->getNumSelectedFiles(); ++i)
  284. {
  285. const File f (fileListComponent->getSelectedFile (i));
  286. if (isFileOrDirSuitable (f))
  287. {
  288. if (resetChosenFiles)
  289. {
  290. chosenFiles.clear();
  291. resetChosenFiles = false;
  292. }
  293. chosenFiles.add (f);
  294. newFilenames.add (f.getRelativePathFrom (getRoot()));
  295. }
  296. }
  297. if (newFilenames.size() > 0)
  298. filenameBox.setText (newFilenames.joinIntoString (", "), false);
  299. sendListenerChangeMessage();
  300. }
  301. void FileBrowserComponent::fileClicked (const File& f, const MouseEvent& e)
  302. {
  303. Component::BailOutChecker checker (this);
  304. listeners.callChecked (checker, &FileBrowserListener::fileClicked, f, e);
  305. }
  306. void FileBrowserComponent::fileDoubleClicked (const File& f)
  307. {
  308. if (f.isDirectory())
  309. {
  310. setRoot (f);
  311. if ((flags & canSelectDirectories) != 0 && (flags & doNotClearFileNameOnRootChange) == 0)
  312. filenameBox.setText (String());
  313. }
  314. else
  315. {
  316. Component::BailOutChecker checker (this);
  317. listeners.callChecked (checker, &FileBrowserListener::fileDoubleClicked, f);
  318. }
  319. }
  320. void FileBrowserComponent::browserRootChanged (const File&) {}
  321. bool FileBrowserComponent::keyPressed (const KeyPress& key)
  322. {
  323. #if JUCE_LINUX || JUCE_WINDOWS
  324. if (key.getModifiers().isCommandDown()
  325. && (key.getKeyCode() == 'H' || key.getKeyCode() == 'h'))
  326. {
  327. fileList->setIgnoresHiddenFiles (! fileList->ignoresHiddenFiles());
  328. fileList->refresh();
  329. return true;
  330. }
  331. #endif
  332. ignoreUnused (key);
  333. return false;
  334. }
  335. //==============================================================================
  336. void FileBrowserComponent::textEditorTextChanged (TextEditor&)
  337. {
  338. sendListenerChangeMessage();
  339. }
  340. void FileBrowserComponent::textEditorReturnKeyPressed (TextEditor&)
  341. {
  342. if (filenameBox.getText().containsChar (File::separator))
  343. {
  344. const File f (currentRoot.getChildFile (filenameBox.getText()));
  345. if (f.isDirectory())
  346. {
  347. setRoot (f);
  348. chosenFiles.clear();
  349. if ((flags & doNotClearFileNameOnRootChange) == 0)
  350. filenameBox.setText (String());
  351. }
  352. else
  353. {
  354. setRoot (f.getParentDirectory());
  355. chosenFiles.clear();
  356. chosenFiles.add (f);
  357. filenameBox.setText (f.getFileName());
  358. }
  359. }
  360. else
  361. {
  362. fileDoubleClicked (getSelectedFile (0));
  363. }
  364. }
  365. void FileBrowserComponent::textEditorEscapeKeyPressed (TextEditor&)
  366. {
  367. }
  368. void FileBrowserComponent::textEditorFocusLost (TextEditor&)
  369. {
  370. if (! isSaveMode())
  371. selectionChanged();
  372. }
  373. //==============================================================================
  374. void FileBrowserComponent::buttonClicked (Button*)
  375. {
  376. goUp();
  377. }
  378. void FileBrowserComponent::comboBoxChanged (ComboBox*)
  379. {
  380. const String newText (currentPathBox.getText().trim().unquoted());
  381. if (newText.isNotEmpty())
  382. {
  383. const int index = currentPathBox.getSelectedId() - 1;
  384. StringArray rootNames, rootPaths;
  385. getRoots (rootNames, rootPaths);
  386. if (rootPaths [index].isNotEmpty())
  387. {
  388. setRoot (File (rootPaths [index]));
  389. }
  390. else
  391. {
  392. File f (newText);
  393. for (;;)
  394. {
  395. if (f.isDirectory())
  396. {
  397. setRoot (f);
  398. break;
  399. }
  400. if (f.getParentDirectory() == f)
  401. break;
  402. f = f.getParentDirectory();
  403. }
  404. }
  405. }
  406. }
  407. void FileBrowserComponent::getDefaultRoots (StringArray& rootNames, StringArray& rootPaths)
  408. {
  409. #if JUCE_WINDOWS
  410. Array<File> roots;
  411. File::findFileSystemRoots (roots);
  412. rootPaths.clear();
  413. for (int i = 0; i < roots.size(); ++i)
  414. {
  415. const File& drive = roots.getReference(i);
  416. String name (drive.getFullPathName());
  417. rootPaths.add (name);
  418. if (drive.isOnHardDisk())
  419. {
  420. String volume (drive.getVolumeLabel());
  421. if (volume.isEmpty())
  422. volume = TRANS("Hard Drive");
  423. name << " [" << volume << ']';
  424. }
  425. else if (drive.isOnCDRomDrive())
  426. {
  427. name << " [" << TRANS("CD/DVD drive") << ']';
  428. }
  429. rootNames.add (name);
  430. }
  431. rootPaths.add (String());
  432. rootNames.add (String());
  433. rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName());
  434. rootNames.add (TRANS("Documents"));
  435. rootPaths.add (File::getSpecialLocation (File::userMusicDirectory).getFullPathName());
  436. rootNames.add (TRANS("Music"));
  437. rootPaths.add (File::getSpecialLocation (File::userPicturesDirectory).getFullPathName());
  438. rootNames.add (TRANS("Pictures"));
  439. rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
  440. rootNames.add (TRANS("Desktop"));
  441. #elif JUCE_MAC
  442. rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  443. rootNames.add (TRANS("Home folder"));
  444. rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName());
  445. rootNames.add (TRANS("Documents"));
  446. rootPaths.add (File::getSpecialLocation (File::userMusicDirectory).getFullPathName());
  447. rootNames.add (TRANS("Music"));
  448. rootPaths.add (File::getSpecialLocation (File::userPicturesDirectory).getFullPathName());
  449. rootNames.add (TRANS("Pictures"));
  450. rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
  451. rootNames.add (TRANS("Desktop"));
  452. rootPaths.add (String());
  453. rootNames.add (String());
  454. Array<File> volumes;
  455. File vol ("/Volumes");
  456. vol.findChildFiles (volumes, File::findDirectories, false);
  457. for (int i = 0; i < volumes.size(); ++i)
  458. {
  459. const File& volume = volumes.getReference(i);
  460. if (volume.isDirectory() && ! volume.getFileName().startsWithChar ('.'))
  461. {
  462. rootPaths.add (volume.getFullPathName());
  463. rootNames.add (volume.getFileName());
  464. }
  465. }
  466. #else
  467. rootPaths.add ("/");
  468. rootNames.add ("/");
  469. rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  470. rootNames.add (TRANS("Home folder"));
  471. rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
  472. rootNames.add (TRANS("Desktop"));
  473. #endif
  474. }
  475. void FileBrowserComponent::getRoots (StringArray& rootNames, StringArray& rootPaths)
  476. {
  477. getDefaultRoots (rootNames, rootPaths);
  478. }
  479. void FileBrowserComponent::timerCallback()
  480. {
  481. const bool isProcessActive = Process::isForegroundProcess();
  482. if (wasProcessActive != isProcessActive)
  483. {
  484. wasProcessActive = isProcessActive;
  485. if (isProcessActive && fileList != nullptr)
  486. refresh();
  487. }
  488. }
  489. } // namespace juce