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.

539 lines
16KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-7 by Raw Material Software ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the
  7. GNU General Public License, as published by the Free Software Foundation;
  8. either version 2 of the License, or (at your option) any later version.
  9. JUCE is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with JUCE; if not, visit www.gnu.org/licenses or write to the
  15. Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  16. Boston, MA 02111-1307 USA
  17. ------------------------------------------------------------------------------
  18. If you'd like to release a closed-source product which uses JUCE, commercial
  19. licenses are also available: visit www.rawmaterialsoftware.com/juce for
  20. more information.
  21. ==============================================================================
  22. */
  23. #include "../../../../juce_core/basics/juce_StandardHeader.h"
  24. BEGIN_JUCE_NAMESPACE
  25. #include "juce_FileBrowserComponent.h"
  26. #include "../lookandfeel/juce_LookAndFeel.h"
  27. #include "../../graphics/drawables/juce_DrawablePath.h"
  28. #include "../../../../juce_core/text/juce_LocalisedStrings.h"
  29. #include "../../../../juce_core/basics/juce_SystemStats.h"
  30. #include "juce_FileListComponent.h"
  31. #include "juce_FileTreeComponent.h"
  32. //==============================================================================
  33. class DirectoriesOnlyFilter : public FileFilter
  34. {
  35. public:
  36. DirectoriesOnlyFilter() : FileFilter (String::empty) {}
  37. bool isFileSuitable (const File&) const { return false; }
  38. bool isDirectorySuitable (const File&) const { return true; }
  39. };
  40. //==============================================================================
  41. FileBrowserComponent::FileBrowserComponent (FileChooserMode mode_,
  42. const File& initialFileOrDirectory,
  43. const FileFilter* fileFilter,
  44. FilePreviewComponent* previewComp_,
  45. const bool useTreeView)
  46. : directoriesOnlyFilter (0),
  47. mode (mode_),
  48. listeners (2),
  49. previewComp (previewComp_),
  50. thread ("Juce FileBrowser")
  51. {
  52. String filename;
  53. if (initialFileOrDirectory == File::nonexistent)
  54. {
  55. currentRoot = File::getCurrentWorkingDirectory();
  56. }
  57. else if (initialFileOrDirectory.isDirectory())
  58. {
  59. currentRoot = initialFileOrDirectory;
  60. }
  61. else
  62. {
  63. currentRoot = initialFileOrDirectory.getParentDirectory();
  64. filename = initialFileOrDirectory.getFileName();
  65. }
  66. if (mode_ == chooseDirectoryMode)
  67. fileFilter = directoriesOnlyFilter = new DirectoriesOnlyFilter();
  68. fileList = new DirectoryContentsList (fileFilter, thread);
  69. if (useTreeView)
  70. {
  71. FileTreeComponent* const tree = new FileTreeComponent (*fileList);
  72. addAndMakeVisible (tree);
  73. fileListComponent = tree;
  74. }
  75. else
  76. {
  77. FileListComponent* const list = new FileListComponent (*fileList);
  78. list->setOutlineThickness (1);
  79. addAndMakeVisible (list);
  80. fileListComponent = list;
  81. }
  82. fileListComponent->addListener (this);
  83. addAndMakeVisible (currentPathBox = new ComboBox (T("path")));
  84. currentPathBox->setEditableText (true);
  85. StringArray rootNames, rootPaths;
  86. const BitArray separators (getRoots (rootNames, rootPaths));
  87. for (int i = 0; i < rootNames.size(); ++i)
  88. {
  89. if (separators [i])
  90. currentPathBox->addSeparator();
  91. currentPathBox->addItem (rootNames[i], i + 1);
  92. }
  93. currentPathBox->addSeparator();
  94. currentPathBox->addListener (this);
  95. addAndMakeVisible (filenameBox = new TextEditor());
  96. filenameBox->setMultiLine (false);
  97. filenameBox->setSelectAllWhenFocused (true);
  98. filenameBox->setText (filename, false);
  99. filenameBox->addListener (this);
  100. Label* label = new Label (T("f"), (mode == chooseDirectoryMode) ? TRANS("folder:")
  101. : TRANS("file:"));
  102. addAndMakeVisible (label);
  103. label->attachToComponent (filenameBox, true);
  104. addAndMakeVisible (goUpButton = new DrawableButton (T("up"), DrawableButton::ImageOnButtonBackground));
  105. Path arrowPath;
  106. arrowPath.addArrow (50.0f, 100.0f, 50.0f, 0.0, 40.0f, 100.0f, 50.0f);
  107. DrawablePath arrowImage;
  108. arrowImage.setSolidFill (Colours::black.withAlpha (0.4f));
  109. arrowImage.setPath (arrowPath);
  110. goUpButton->setImages (&arrowImage);
  111. goUpButton->addButtonListener (this);
  112. goUpButton->setTooltip (TRANS ("go up to parent directory"));
  113. if (previewComp != 0)
  114. addAndMakeVisible (previewComp);
  115. setRoot (currentRoot);
  116. thread.startThread (4);
  117. }
  118. FileBrowserComponent::~FileBrowserComponent()
  119. {
  120. if (previewComp != 0)
  121. removeChildComponent (previewComp);
  122. deleteAllChildren();
  123. deleteAndZero (fileList);
  124. delete directoriesOnlyFilter;
  125. thread.stopThread (10000);
  126. }
  127. //==============================================================================
  128. void FileBrowserComponent::addListener (FileBrowserListener* const newListener) throw()
  129. {
  130. jassert (newListener != 0)
  131. if (newListener != 0)
  132. listeners.add (newListener);
  133. }
  134. void FileBrowserComponent::removeListener (FileBrowserListener* const listener) throw()
  135. {
  136. listeners.removeValue (listener);
  137. }
  138. //==============================================================================
  139. const File FileBrowserComponent::getCurrentFile() const throw()
  140. {
  141. return currentRoot.getChildFile (filenameBox->getText());
  142. }
  143. bool FileBrowserComponent::currentFileIsValid() const
  144. {
  145. if (mode == saveFileMode)
  146. return ! getCurrentFile().isDirectory();
  147. else if (mode == loadFileMode)
  148. return getCurrentFile().existsAsFile();
  149. else if (mode == chooseDirectoryMode)
  150. return getCurrentFile().isDirectory();
  151. jassertfalse
  152. return false;
  153. }
  154. //==============================================================================
  155. const File FileBrowserComponent::getRoot() const
  156. {
  157. return currentRoot;
  158. }
  159. void FileBrowserComponent::setRoot (const File& newRootDirectory)
  160. {
  161. if (currentRoot != newRootDirectory)
  162. {
  163. fileListComponent->scrollToTop();
  164. if (mode == chooseDirectoryMode)
  165. filenameBox->setText (String::empty, false);
  166. String path (newRootDirectory.getFullPathName());
  167. if (path.isEmpty())
  168. path += File::separator;
  169. StringArray rootNames, rootPaths;
  170. getRoots (rootNames, rootPaths);
  171. if (! rootPaths.contains (path, true))
  172. {
  173. bool alreadyListed = false;
  174. for (int i = currentPathBox->getNumItems(); --i >= 0;)
  175. {
  176. if (currentPathBox->getItemText (i).equalsIgnoreCase (path))
  177. {
  178. alreadyListed = true;
  179. break;
  180. }
  181. }
  182. if (! alreadyListed)
  183. currentPathBox->addItem (path, currentPathBox->getNumItems() + 2);
  184. }
  185. }
  186. currentRoot = newRootDirectory;
  187. fileList->setDirectory (currentRoot, true, true);
  188. String currentRootName (currentRoot.getFullPathName());
  189. if (currentRootName.isEmpty())
  190. currentRootName += File::separator;
  191. currentPathBox->setText (currentRootName, true);
  192. goUpButton->setEnabled (currentRoot.getParentDirectory().isDirectory()
  193. && currentRoot.getParentDirectory() != currentRoot);
  194. }
  195. void FileBrowserComponent::goUp()
  196. {
  197. setRoot (getRoot().getParentDirectory());
  198. }
  199. void FileBrowserComponent::refresh()
  200. {
  201. fileList->refresh();
  202. }
  203. const String FileBrowserComponent::getActionVerb() const
  204. {
  205. return (mode == chooseDirectoryMode) ? TRANS("Choose")
  206. : ((mode == saveFileMode) ? TRANS("Save") : TRANS("Open"));
  207. }
  208. FilePreviewComponent* FileBrowserComponent::getPreviewComponent() const throw()
  209. {
  210. return previewComp;
  211. }
  212. //==============================================================================
  213. void FileBrowserComponent::resized()
  214. {
  215. const int x = 8;
  216. int w = getWidth() - x - x;
  217. if (previewComp != 0)
  218. {
  219. const int previewWidth = w / 3;
  220. previewComp->setBounds (x + w - previewWidth, 0, previewWidth, getHeight());
  221. w -= previewWidth + 4;
  222. }
  223. int y = 4;
  224. const int controlsHeight = 22;
  225. const int bottomSectionHeight = controlsHeight + 8;
  226. const int upButtonWidth = 50;
  227. currentPathBox->setBounds (x, y, w - upButtonWidth - 6, controlsHeight);
  228. goUpButton->setBounds (x + w - upButtonWidth, y, upButtonWidth, controlsHeight);
  229. y += controlsHeight + 4;
  230. Component* const listAsComp = dynamic_cast <Component*> (fileListComponent);
  231. listAsComp->setBounds (x, y, w, getHeight() - y - bottomSectionHeight);
  232. y = listAsComp->getBottom() + 4;
  233. filenameBox->setBounds (x + 50, y, w - 50, controlsHeight);
  234. }
  235. //==============================================================================
  236. void FileBrowserComponent::sendListenerChangeMessage()
  237. {
  238. ComponentDeletionWatcher deletionWatcher (this);
  239. if (previewComp != 0)
  240. previewComp->selectedFileChanged (getCurrentFile());
  241. jassert (! deletionWatcher.hasBeenDeleted());
  242. for (int i = listeners.size(); --i >= 0;)
  243. {
  244. ((FileBrowserListener*) listeners.getUnchecked (i))->selectionChanged();
  245. if (deletionWatcher.hasBeenDeleted())
  246. return;
  247. i = jmin (i, listeners.size() - 1);
  248. }
  249. }
  250. void FileBrowserComponent::selectionChanged()
  251. {
  252. const File selected (fileListComponent->getSelectedFile());
  253. if ((mode == chooseDirectoryMode && selected.isDirectory())
  254. || selected.existsAsFile())
  255. {
  256. filenameBox->setText (selected.getRelativePathFrom (getRoot()), false);
  257. }
  258. sendListenerChangeMessage();
  259. }
  260. void FileBrowserComponent::fileClicked (const File&, const MouseEvent&)
  261. {
  262. ComponentDeletionWatcher deletionWatcher (this);
  263. for (int i = listeners.size(); --i >= 0;)
  264. {
  265. ((FileBrowserListener*) listeners.getUnchecked (i))->fileClicked (file, e);
  266. if (deletionWatcher.hasBeenDeleted())
  267. return;
  268. i = jmin (i, listeners.size() - 1);
  269. }
  270. }
  271. void FileBrowserComponent::fileDoubleClicked (const File& f)
  272. {
  273. if (f.isDirectory())
  274. {
  275. setRoot (f);
  276. }
  277. else
  278. {
  279. ComponentDeletionWatcher deletionWatcher (this);
  280. for (int i = listeners.size(); --i >= 0;)
  281. {
  282. ((FileBrowserListener*) listeners.getUnchecked (i))->fileDoubleClicked (f);
  283. if (deletionWatcher.hasBeenDeleted())
  284. return;
  285. i = jmin (i, listeners.size() - 1);
  286. }
  287. }
  288. }
  289. //==============================================================================
  290. void FileBrowserComponent::textEditorTextChanged (TextEditor&)
  291. {
  292. sendListenerChangeMessage();
  293. }
  294. void FileBrowserComponent::textEditorReturnKeyPressed (TextEditor&)
  295. {
  296. if (filenameBox->getText().containsChar (File::separator))
  297. {
  298. const File f (currentRoot.getChildFile (filenameBox->getText()));
  299. if (f.isDirectory())
  300. {
  301. setRoot (f);
  302. filenameBox->setText (String::empty);
  303. }
  304. else
  305. {
  306. setRoot (f.getParentDirectory());
  307. filenameBox->setText (f.getFileName());
  308. }
  309. }
  310. else
  311. {
  312. fileDoubleClicked (getCurrentFile());
  313. }
  314. }
  315. void FileBrowserComponent::textEditorEscapeKeyPressed (TextEditor&)
  316. {
  317. }
  318. void FileBrowserComponent::textEditorFocusLost (TextEditor&)
  319. {
  320. if (mode != saveFileMode)
  321. selectionChanged();
  322. }
  323. //==============================================================================
  324. void FileBrowserComponent::buttonClicked (Button*)
  325. {
  326. goUp();
  327. }
  328. void FileBrowserComponent::comboBoxChanged (ComboBox*)
  329. {
  330. const String newText (currentPathBox->getText().trim().unquoted());
  331. if (newText.isNotEmpty())
  332. {
  333. const int index = currentPathBox->getSelectedId() - 1;
  334. StringArray rootNames, rootPaths;
  335. getRoots (rootNames, rootPaths);
  336. if (rootPaths [index].isNotEmpty())
  337. {
  338. setRoot (File (rootPaths [index]));
  339. }
  340. else
  341. {
  342. File f (newText);
  343. for (;;)
  344. {
  345. if (f.isDirectory())
  346. {
  347. setRoot (f);
  348. break;
  349. }
  350. if (f.getParentDirectory() == f)
  351. break;
  352. f = f.getParentDirectory();
  353. }
  354. }
  355. }
  356. }
  357. const BitArray FileBrowserComponent::getRoots (StringArray& rootNames, StringArray& rootPaths)
  358. {
  359. BitArray separators;
  360. #if JUCE_WIN32
  361. OwnedArray<File> roots;
  362. File::findFileSystemRoots (roots);
  363. rootPaths.clear();
  364. for (int i = 0; i < roots.size(); ++i)
  365. {
  366. const File* const drive = roots.getUnchecked(i);
  367. String name (drive->getFullPathName());
  368. rootPaths.add (name);
  369. if (drive->isOnHardDisk())
  370. {
  371. String volume (drive->getVolumeLabel());
  372. if (volume.isEmpty())
  373. volume = TRANS("Hard Drive");
  374. name << " [" << drive->getVolumeLabel() << ']';
  375. }
  376. else if (drive->isOnCDRomDrive())
  377. {
  378. name << TRANS(" [CD/DVD drive]");
  379. }
  380. rootNames.add (name);
  381. }
  382. separators.setBit (rootPaths.size());
  383. rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName());
  384. rootNames.add ("Documents");
  385. rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
  386. rootNames.add ("Desktop");
  387. #endif
  388. #if JUCE_MAC
  389. rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  390. rootNames.add ("Home folder");
  391. rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName());
  392. rootNames.add ("Documents");
  393. rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
  394. rootNames.add ("Desktop");
  395. separators.setBit (rootPaths.size());
  396. OwnedArray <File> volumes;
  397. File vol ("/Volumes");
  398. vol.findChildFiles (volumes, File::findDirectories, false);
  399. for (int i = 0; i < volumes.size(); ++i)
  400. {
  401. const File* const volume = volumes.getUnchecked(i);
  402. if (volume->isDirectory() && ! volume->getFileName().startsWithChar (T('.')))
  403. {
  404. rootPaths.add (volume->getFullPathName());
  405. rootNames.add (volume->getFileName());
  406. }
  407. }
  408. #endif
  409. #if JUCE_LINUX
  410. rootPaths.add ("/");
  411. rootNames.add ("/");
  412. rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  413. rootNames.add ("Home folder");
  414. rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
  415. rootNames.add ("Desktop");
  416. #endif
  417. return separators;
  418. }
  419. END_JUCE_NAMESPACE