The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

548 lines
16KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. BEGIN_JUCE_NAMESPACE
  19. //==============================================================================
  20. FileBrowserComponent::FileBrowserComponent (int flags_,
  21. const File& initialFileOrDirectory,
  22. const FileFilter* fileFilter_,
  23. FilePreviewComponent* previewComp_)
  24. : FileFilter (String::empty),
  25. fileFilter (fileFilter_),
  26. flags (flags_),
  27. previewComp (previewComp_),
  28. currentPathBox ("path"),
  29. fileLabel ("f", TRANS ("file:")),
  30. thread ("Juce FileBrowser")
  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::nonexistent)
  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. }
  91. FileBrowserComponent::~FileBrowserComponent()
  92. {
  93. fileListComponent = nullptr;
  94. fileList = nullptr;
  95. thread.stopThread (10000);
  96. }
  97. //==============================================================================
  98. void FileBrowserComponent::addListener (FileBrowserListener* const newListener)
  99. {
  100. listeners.add (newListener);
  101. }
  102. void FileBrowserComponent::removeListener (FileBrowserListener* const listener)
  103. {
  104. listeners.remove (listener);
  105. }
  106. //==============================================================================
  107. bool FileBrowserComponent::isSaveMode() const noexcept
  108. {
  109. return (flags & saveMode) != 0;
  110. }
  111. int FileBrowserComponent::getNumSelectedFiles() const noexcept
  112. {
  113. if (chosenFiles.size() == 0 && currentFileIsValid())
  114. return 1;
  115. return chosenFiles.size();
  116. }
  117. File FileBrowserComponent::getSelectedFile (int index) const noexcept
  118. {
  119. if ((flags & canSelectDirectories) != 0 && filenameBox.getText().isEmpty())
  120. return currentRoot;
  121. if (! filenameBox.isReadOnly())
  122. return currentRoot.getChildFile (filenameBox.getText());
  123. return chosenFiles[index];
  124. }
  125. bool FileBrowserComponent::currentFileIsValid() const
  126. {
  127. if (isSaveMode())
  128. return ! getSelectedFile (0).isDirectory();
  129. else
  130. return getSelectedFile (0).exists();
  131. }
  132. File FileBrowserComponent::getHighlightedFile() const noexcept
  133. {
  134. return fileListComponent->getSelectedFile (0);
  135. }
  136. void FileBrowserComponent::deselectAllFiles()
  137. {
  138. fileListComponent->deselectAllFiles();
  139. }
  140. //==============================================================================
  141. bool FileBrowserComponent::isFileSuitable (const File& file) const
  142. {
  143. return (flags & canSelectFiles) != 0 && (fileFilter == nullptr || fileFilter->isFileSuitable (file));
  144. }
  145. bool FileBrowserComponent::isDirectorySuitable (const File&) const
  146. {
  147. return true;
  148. }
  149. bool FileBrowserComponent::isFileOrDirSuitable (const File& f) const
  150. {
  151. if (f.isDirectory())
  152. return (flags & canSelectDirectories) != 0
  153. && (fileFilter == nullptr || fileFilter->isDirectorySuitable (f));
  154. return (flags & canSelectFiles) != 0 && f.exists()
  155. && (fileFilter == nullptr || fileFilter->isFileSuitable (f));
  156. }
  157. //==============================================================================
  158. const File& FileBrowserComponent::getRoot() const
  159. {
  160. return currentRoot;
  161. }
  162. void FileBrowserComponent::setRoot (const File& newRootDirectory)
  163. {
  164. if (currentRoot != newRootDirectory)
  165. {
  166. fileListComponent->scrollToTop();
  167. String path (newRootDirectory.getFullPathName());
  168. if (path.isEmpty())
  169. path = File::separatorString;
  170. StringArray rootNames, rootPaths;
  171. getRoots (rootNames, rootPaths);
  172. if (! rootPaths.contains (path, true))
  173. {
  174. bool alreadyListed = false;
  175. for (int i = currentPathBox.getNumItems(); --i >= 0;)
  176. {
  177. if (currentPathBox.getItemText (i).equalsIgnoreCase (path))
  178. {
  179. alreadyListed = true;
  180. break;
  181. }
  182. }
  183. if (! alreadyListed)
  184. currentPathBox.addItem (path, currentPathBox.getNumItems() + 2);
  185. }
  186. }
  187. currentRoot = newRootDirectory;
  188. fileList->setDirectory (currentRoot, true, true);
  189. String currentRootName (currentRoot.getFullPathName());
  190. if (currentRootName.isEmpty())
  191. currentRootName = File::separatorString;
  192. currentPathBox.setText (currentRootName, true);
  193. goUpButton->setEnabled (currentRoot.getParentDirectory().isDirectory()
  194. && currentRoot.getParentDirectory() != currentRoot);
  195. }
  196. void FileBrowserComponent::resetRecentPaths()
  197. {
  198. currentPathBox.clear();
  199. StringArray rootNames, rootPaths;
  200. getRoots (rootNames, rootPaths);
  201. for (int i = 0; i < rootNames.size(); ++i)
  202. {
  203. if (rootNames[i].isEmpty())
  204. currentPathBox.addSeparator();
  205. else
  206. currentPathBox.addItem (rootNames[i], i + 1);
  207. }
  208. currentPathBox.addSeparator();
  209. }
  210. void FileBrowserComponent::goUp()
  211. {
  212. setRoot (getRoot().getParentDirectory());
  213. }
  214. void FileBrowserComponent::refresh()
  215. {
  216. fileList->refresh();
  217. }
  218. void FileBrowserComponent::setFileFilter (const FileFilter* const newFileFilter)
  219. {
  220. if (fileFilter != newFileFilter)
  221. {
  222. fileFilter = newFileFilter;
  223. refresh();
  224. }
  225. }
  226. const String FileBrowserComponent::getActionVerb() const
  227. {
  228. return isSaveMode() ? TRANS("Save") : TRANS("Open");
  229. }
  230. FilePreviewComponent* FileBrowserComponent::getPreviewComponent() const noexcept
  231. {
  232. return previewComp;
  233. }
  234. //==============================================================================
  235. void FileBrowserComponent::resized()
  236. {
  237. getLookAndFeel()
  238. .layoutFileBrowserComponent (*this, fileListComponent, previewComp,
  239. &currentPathBox, &filenameBox, goUpButton);
  240. }
  241. //==============================================================================
  242. void FileBrowserComponent::sendListenerChangeMessage()
  243. {
  244. Component::BailOutChecker checker (this);
  245. if (previewComp != nullptr)
  246. previewComp->selectedFileChanged (getSelectedFile (0));
  247. // You shouldn't delete the browser when the file gets changed!
  248. jassert (! checker.shouldBailOut());
  249. listeners.callChecked (checker, &FileBrowserListener::selectionChanged);
  250. }
  251. void FileBrowserComponent::selectionChanged()
  252. {
  253. StringArray newFilenames;
  254. bool resetChosenFiles = true;
  255. for (int i = 0; i < fileListComponent->getNumSelectedFiles(); ++i)
  256. {
  257. const File f (fileListComponent->getSelectedFile (i));
  258. if (isFileOrDirSuitable (f))
  259. {
  260. if (resetChosenFiles)
  261. {
  262. chosenFiles.clear();
  263. resetChosenFiles = false;
  264. }
  265. chosenFiles.add (f);
  266. newFilenames.add (f.getRelativePathFrom (getRoot()));
  267. }
  268. }
  269. if (newFilenames.size() > 0)
  270. filenameBox.setText (newFilenames.joinIntoString (", "), false);
  271. sendListenerChangeMessage();
  272. }
  273. void FileBrowserComponent::fileClicked (const File& f, const MouseEvent& e)
  274. {
  275. Component::BailOutChecker checker (this);
  276. listeners.callChecked (checker, &FileBrowserListener::fileClicked, f, e);
  277. }
  278. void FileBrowserComponent::fileDoubleClicked (const File& f)
  279. {
  280. if (f.isDirectory())
  281. {
  282. setRoot (f);
  283. if ((flags & canSelectDirectories) != 0)
  284. filenameBox.setText (String::empty);
  285. }
  286. else
  287. {
  288. Component::BailOutChecker checker (this);
  289. listeners.callChecked (checker, &FileBrowserListener::fileDoubleClicked, f);
  290. }
  291. }
  292. bool FileBrowserComponent::keyPressed (const KeyPress& key)
  293. {
  294. (void) key;
  295. #if JUCE_LINUX || JUCE_WINDOWS
  296. if (key.getModifiers().isCommandDown()
  297. && (key.getKeyCode() == 'H' || key.getKeyCode() == 'h'))
  298. {
  299. fileList->setIgnoresHiddenFiles (! fileList->ignoresHiddenFiles());
  300. fileList->refresh();
  301. return true;
  302. }
  303. #endif
  304. return false;
  305. }
  306. //==============================================================================
  307. void FileBrowserComponent::textEditorTextChanged (TextEditor&)
  308. {
  309. sendListenerChangeMessage();
  310. }
  311. void FileBrowserComponent::textEditorReturnKeyPressed (TextEditor&)
  312. {
  313. if (filenameBox.getText().containsChar (File::separator))
  314. {
  315. const File f (currentRoot.getChildFile (filenameBox.getText()));
  316. if (f.isDirectory())
  317. {
  318. setRoot (f);
  319. chosenFiles.clear();
  320. filenameBox.setText (String::empty);
  321. }
  322. else
  323. {
  324. setRoot (f.getParentDirectory());
  325. chosenFiles.clear();
  326. chosenFiles.add (f);
  327. filenameBox.setText (f.getFileName());
  328. }
  329. }
  330. else
  331. {
  332. fileDoubleClicked (getSelectedFile (0));
  333. }
  334. }
  335. void FileBrowserComponent::textEditorEscapeKeyPressed (TextEditor&)
  336. {
  337. }
  338. void FileBrowserComponent::textEditorFocusLost (TextEditor&)
  339. {
  340. if (! isSaveMode())
  341. selectionChanged();
  342. }
  343. //==============================================================================
  344. void FileBrowserComponent::buttonClicked (Button*)
  345. {
  346. goUp();
  347. }
  348. void FileBrowserComponent::comboBoxChanged (ComboBox*)
  349. {
  350. const String newText (currentPathBox.getText().trim().unquoted());
  351. if (newText.isNotEmpty())
  352. {
  353. const int index = currentPathBox.getSelectedId() - 1;
  354. StringArray rootNames, rootPaths;
  355. getRoots (rootNames, rootPaths);
  356. if (rootPaths [index].isNotEmpty())
  357. {
  358. setRoot (File (rootPaths [index]));
  359. }
  360. else
  361. {
  362. File f (newText);
  363. for (;;)
  364. {
  365. if (f.isDirectory())
  366. {
  367. setRoot (f);
  368. break;
  369. }
  370. if (f.getParentDirectory() == f)
  371. break;
  372. f = f.getParentDirectory();
  373. }
  374. }
  375. }
  376. }
  377. void FileBrowserComponent::getRoots (StringArray& rootNames, StringArray& rootPaths)
  378. {
  379. #if JUCE_WINDOWS
  380. Array<File> roots;
  381. File::findFileSystemRoots (roots);
  382. rootPaths.clear();
  383. for (int i = 0; i < roots.size(); ++i)
  384. {
  385. const File& drive = roots.getReference(i);
  386. String name (drive.getFullPathName());
  387. rootPaths.add (name);
  388. if (drive.isOnHardDisk())
  389. {
  390. String volume (drive.getVolumeLabel());
  391. if (volume.isEmpty())
  392. volume = TRANS("Hard Drive");
  393. name << " [" << volume << ']';
  394. }
  395. else if (drive.isOnCDRomDrive())
  396. {
  397. name << TRANS(" [CD/DVD drive]");
  398. }
  399. rootNames.add (name);
  400. }
  401. rootPaths.add (String::empty);
  402. rootNames.add (String::empty);
  403. rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName());
  404. rootNames.add ("Documents");
  405. rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
  406. rootNames.add ("Desktop");
  407. #elif JUCE_MAC
  408. rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  409. rootNames.add ("Home folder");
  410. rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName());
  411. rootNames.add ("Documents");
  412. rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
  413. rootNames.add ("Desktop");
  414. rootPaths.add (String::empty);
  415. rootNames.add (String::empty);
  416. Array <File> volumes;
  417. File vol ("/Volumes");
  418. vol.findChildFiles (volumes, File::findDirectories, false);
  419. for (int i = 0; i < volumes.size(); ++i)
  420. {
  421. const File& volume = volumes.getReference(i);
  422. if (volume.isDirectory() && ! volume.getFileName().startsWithChar ('.'))
  423. {
  424. rootPaths.add (volume.getFullPathName());
  425. rootNames.add (volume.getFileName());
  426. }
  427. }
  428. #else
  429. rootPaths.add ("/");
  430. rootNames.add ("/");
  431. rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  432. rootNames.add ("Home folder");
  433. rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
  434. rootNames.add ("Desktop");
  435. #endif
  436. }
  437. END_JUCE_NAMESPACE