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.

569 lines
16KB

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