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.

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