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.

1112 lines
43KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 7 technical preview.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For the technical preview this file cannot be licensed commercially.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. namespace juce
  14. {
  15. //==============================================================================
  16. class FileBasedDocument::Pimpl
  17. {
  18. private:
  19. //==============================================================================
  20. class SafeParentPointer
  21. {
  22. public:
  23. SafeParentPointer (Pimpl* parent, bool isAsync)
  24. : ptr (parent), shouldCheck (isAsync)
  25. {}
  26. Pimpl* operator->() const noexcept
  27. {
  28. return ptr.get();
  29. }
  30. bool operator== (Pimpl* object) const noexcept { return ptr.get() == object; }
  31. bool operator!= (Pimpl* object) const noexcept { return ptr.get() != object; }
  32. bool shouldExitAsyncCallback() const noexcept
  33. {
  34. return shouldCheck && ptr == nullptr;
  35. }
  36. private:
  37. WeakReference<Pimpl> ptr;
  38. bool shouldCheck = false;
  39. };
  40. public:
  41. //==============================================================================
  42. Pimpl (FileBasedDocument& parent_,
  43. const String& fileExtension_,
  44. const String& fileWildcard_,
  45. const String& openFileDialogTitle_,
  46. const String& saveFileDialogTitle_)
  47. : document (parent_),
  48. fileExtension (fileExtension_),
  49. fileWildcard (fileWildcard_),
  50. openFileDialogTitle (openFileDialogTitle_),
  51. saveFileDialogTitle (saveFileDialogTitle_)
  52. {
  53. }
  54. //==============================================================================
  55. bool hasChangedSinceSaved() const
  56. {
  57. return changedSinceSave;
  58. }
  59. void setChangedFlag (bool hasChanged)
  60. {
  61. if (changedSinceSave != hasChanged)
  62. {
  63. changedSinceSave = hasChanged;
  64. document.sendChangeMessage();
  65. }
  66. }
  67. void changed()
  68. {
  69. changedSinceSave = true;
  70. document.sendChangeMessage();
  71. }
  72. //==============================================================================
  73. Result loadFrom (const File& newFile, bool showMessageOnFailure, bool showWaitCursor = true)
  74. {
  75. SafeParentPointer parent { this, false };
  76. auto result = Result::ok();
  77. loadFromImpl (parent,
  78. newFile,
  79. showMessageOnFailure,
  80. showWaitCursor,
  81. [this] (const File& file, const auto& callback) { callback (document.loadDocument (file)); },
  82. [&result] (Result r) { result = r; });
  83. return result;
  84. }
  85. void loadFromAsync (const File& newFile,
  86. bool showMessageOnFailure,
  87. std::function<void (Result)> callback)
  88. {
  89. SafeParentPointer parent { this, true };
  90. loadFromImpl (parent,
  91. newFile,
  92. showMessageOnFailure,
  93. false,
  94. [parent] (const File& file, auto cb)
  95. {
  96. if (parent != nullptr)
  97. parent->document.loadDocumentAsync (file, std::move (cb));
  98. },
  99. std::move (callback));
  100. }
  101. //==============================================================================
  102. #if JUCE_MODAL_LOOPS_PERMITTED
  103. Result loadFromUserSpecifiedFile (bool showMessageOnFailure)
  104. {
  105. FileChooser fc (openFileDialogTitle,
  106. document.getLastDocumentOpened(),
  107. fileWildcard);
  108. if (fc.browseForFileToOpen())
  109. return loadFrom (fc.getResult(), showMessageOnFailure);
  110. return Result::fail (TRANS ("User cancelled"));
  111. }
  112. #endif
  113. void loadFromUserSpecifiedFileAsync (const bool showMessageOnFailure, std::function<void (Result)> callback)
  114. {
  115. asyncFc = std::make_unique<FileChooser> (openFileDialogTitle,
  116. document.getLastDocumentOpened(),
  117. fileWildcard);
  118. asyncFc->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
  119. [this, showMessageOnFailure, callback = std::move (callback)] (const FileChooser& fc)
  120. {
  121. auto chosenFile = fc.getResult();
  122. if (chosenFile == File{})
  123. {
  124. if (callback != nullptr)
  125. callback (Result::fail (TRANS ("User cancelled")));
  126. return;
  127. }
  128. WeakReference<Pimpl> parent { this };
  129. loadFromAsync (chosenFile, showMessageOnFailure, [parent, callback] (Result result)
  130. {
  131. if (parent != nullptr && callback != nullptr)
  132. callback (result);
  133. });
  134. asyncFc = nullptr;
  135. });
  136. }
  137. //==============================================================================
  138. #if JUCE_MODAL_LOOPS_PERMITTED
  139. FileBasedDocument::SaveResult save (bool askUserForFileIfNotSpecified,
  140. bool showMessageOnFailure)
  141. {
  142. return saveAs (documentFile,
  143. false,
  144. askUserForFileIfNotSpecified,
  145. showMessageOnFailure);
  146. }
  147. #endif
  148. void saveAsync (bool askUserForFileIfNotSpecified,
  149. bool showMessageOnFailure,
  150. std::function<void (SaveResult)> callback)
  151. {
  152. saveAsAsync (documentFile,
  153. false,
  154. askUserForFileIfNotSpecified,
  155. showMessageOnFailure,
  156. std::move (callback));
  157. }
  158. //==============================================================================
  159. #if JUCE_MODAL_LOOPS_PERMITTED
  160. FileBasedDocument::SaveResult saveIfNeededAndUserAgrees()
  161. {
  162. SafeParentPointer parent { this, false };
  163. SaveResult result;
  164. saveIfNeededAndUserAgreesImpl (parent,
  165. [&result] (SaveResult r) { result = r; },
  166. AskToSaveChangesSync { *this },
  167. SaveSync { *this });
  168. return result;
  169. }
  170. #endif
  171. void saveIfNeededAndUserAgreesAsync (std::function<void (SaveResult)> callback)
  172. {
  173. SafeParentPointer parent { this, true };
  174. saveIfNeededAndUserAgreesImpl (parent,
  175. std::move (callback),
  176. [] (SafeParentPointer ptr, auto cb)
  177. {
  178. if (ptr != nullptr)
  179. ptr->askToSaveChanges (ptr, std::move (cb));
  180. },
  181. [parent] (bool askUserForFileIfNotSpecified,
  182. bool showMessageOnFailure,
  183. auto cb)
  184. {
  185. if (parent != nullptr)
  186. parent->saveAsync (askUserForFileIfNotSpecified,
  187. showMessageOnFailure,
  188. std::move (cb));
  189. });
  190. }
  191. //==============================================================================
  192. #if JUCE_MODAL_LOOPS_PERMITTED
  193. FileBasedDocument::SaveResult saveAs (const File& newFile,
  194. bool warnAboutOverwritingExistingFiles,
  195. bool askUserForFileIfNotSpecified,
  196. bool showMessageOnFailure,
  197. bool showWaitCursor = true)
  198. {
  199. SafeParentPointer parent { this, false };
  200. SaveResult result{};
  201. saveAsSyncImpl (parent,
  202. newFile,
  203. warnAboutOverwritingExistingFiles,
  204. askUserForFileIfNotSpecified,
  205. showMessageOnFailure,
  206. [&result] (SaveResult r) { result = r; },
  207. showWaitCursor);
  208. return result;
  209. }
  210. #endif
  211. void saveAsAsync (const File& newFile,
  212. bool warnAboutOverwritingExistingFiles,
  213. bool askUserForFileIfNotSpecified,
  214. bool showMessageOnFailure,
  215. std::function<void (SaveResult)> callback)
  216. {
  217. SafeParentPointer parent { this, true };
  218. saveAsAsyncImpl (parent,
  219. newFile,
  220. warnAboutOverwritingExistingFiles,
  221. askUserForFileIfNotSpecified,
  222. showMessageOnFailure,
  223. std::move (callback),
  224. false);
  225. }
  226. //==============================================================================
  227. #if JUCE_MODAL_LOOPS_PERMITTED
  228. FileBasedDocument::SaveResult saveAsInteractive (bool warnAboutOverwritingExistingFiles)
  229. {
  230. SafeParentPointer parent { this, false };
  231. SaveResult result{};
  232. saveAsInteractiveSyncImpl (parent,
  233. warnAboutOverwritingExistingFiles,
  234. [&result] (SaveResult r) { result = r; });
  235. return result;
  236. }
  237. #endif
  238. void saveAsInteractiveAsync (bool warnAboutOverwritingExistingFiles,
  239. std::function<void (SaveResult)> callback)
  240. {
  241. SafeParentPointer parent { this, true };
  242. saveAsInteractiveAsyncImpl (parent,
  243. warnAboutOverwritingExistingFiles,
  244. std::move (callback));
  245. }
  246. //==============================================================================
  247. const File& getFile() const
  248. {
  249. return documentFile;
  250. }
  251. void setFile (const File& newFile)
  252. {
  253. if (documentFile != newFile)
  254. {
  255. documentFile = newFile;
  256. changed();
  257. }
  258. }
  259. //==============================================================================
  260. const String& getFileExtension() const
  261. {
  262. return fileExtension;
  263. }
  264. private:
  265. //==============================================================================
  266. template <typename DoLoadDocument>
  267. void loadFromImpl (SafeParentPointer parent,
  268. const File& newFile,
  269. bool showMessageOnFailure,
  270. bool showWaitCursor,
  271. DoLoadDocument&& doLoadDocument,
  272. std::function<void (Result)> completed)
  273. {
  274. if (parent.shouldExitAsyncCallback())
  275. return;
  276. if (showWaitCursor)
  277. MouseCursor::showWaitCursor();
  278. auto oldFile = documentFile;
  279. documentFile = newFile;
  280. auto tidyUp = [parent, newFile, oldFile, showMessageOnFailure, showWaitCursor, completed] (Result result)
  281. {
  282. if (parent.shouldExitAsyncCallback())
  283. return;
  284. parent->documentFile = oldFile;
  285. if (showWaitCursor)
  286. MouseCursor::hideWaitCursor();
  287. if (showMessageOnFailure)
  288. AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
  289. TRANS ("Failed to open file..."),
  290. TRANS ("There was an error while trying to load the file: FLNM")
  291. .replace ("FLNM", "\n" + newFile.getFullPathName())
  292. + "\n\n"
  293. + result.getErrorMessage());
  294. if (completed != nullptr)
  295. completed (result);
  296. };
  297. if (newFile.existsAsFile())
  298. {
  299. auto afterLoading = [parent,
  300. showWaitCursor,
  301. newFile,
  302. completed = std::move (completed),
  303. tidyUp] (Result result)
  304. {
  305. if (result.wasOk())
  306. {
  307. parent->setChangedFlag (false);
  308. if (showWaitCursor)
  309. MouseCursor::hideWaitCursor();
  310. parent->document.setLastDocumentOpened (newFile);
  311. if (completed != nullptr)
  312. completed (result);
  313. return;
  314. }
  315. tidyUp (result);
  316. };
  317. doLoadDocument (newFile, std::move (afterLoading));
  318. return;
  319. }
  320. tidyUp (Result::fail (TRANS ("The file doesn't exist")));
  321. }
  322. //==============================================================================
  323. template <typename DoAskToSaveChanges, typename DoSave>
  324. void saveIfNeededAndUserAgreesImpl (SafeParentPointer parent,
  325. std::function<void (SaveResult)> completed,
  326. DoAskToSaveChanges&& doAskToSaveChanges,
  327. DoSave&& doSave)
  328. {
  329. if (parent.shouldExitAsyncCallback())
  330. return;
  331. if (! hasChangedSinceSaved())
  332. {
  333. if (completed != nullptr)
  334. completed (savedOk);
  335. return;
  336. }
  337. auto afterAsking = [doSave = std::forward<DoSave> (doSave),
  338. completed = std::move (completed)] (SafeParentPointer ptr,
  339. int alertResult)
  340. {
  341. if (ptr.shouldExitAsyncCallback())
  342. return;
  343. switch (alertResult)
  344. {
  345. case 1: // save changes
  346. doSave (true, true, [ptr, completed] (SaveResult result)
  347. {
  348. if (ptr.shouldExitAsyncCallback())
  349. return;
  350. if (completed != nullptr)
  351. completed (result);
  352. });
  353. return;
  354. case 2: // discard changes
  355. if (completed != nullptr)
  356. completed (savedOk);
  357. return;
  358. }
  359. if (completed != nullptr)
  360. completed (userCancelledSave);
  361. };
  362. doAskToSaveChanges (parent, std::move (afterAsking));
  363. }
  364. //==============================================================================
  365. int askToSaveChanges (SafeParentPointer parent,
  366. std::function<void (SafeParentPointer, int)> callback)
  367. {
  368. auto* modalCallback = callback == nullptr
  369. ? nullptr
  370. : ModalCallbackFunction::create ([parent, callback = std::move (callback)] (int alertResult)
  371. {
  372. if (parent != nullptr)
  373. callback (parent, alertResult);
  374. });
  375. return AlertWindow::showYesNoCancelBox (MessageBoxIconType::QuestionIcon,
  376. TRANS ("Closing document..."),
  377. TRANS ("Do you want to save the changes to \"DCNM\"?")
  378. .replace ("DCNM", document.getDocumentTitle()),
  379. TRANS ("Save"),
  380. TRANS ("Discard changes"),
  381. TRANS ("Cancel"),
  382. nullptr,
  383. modalCallback);
  384. }
  385. //==============================================================================
  386. template <typename DoSaveDocument>
  387. void saveInternal (SafeParentPointer parent,
  388. const File& newFile,
  389. bool showMessageOnFailure,
  390. bool showWaitCursor,
  391. std::function<void (SaveResult)> afterSave,
  392. DoSaveDocument&& doSaveDocument)
  393. {
  394. if (showWaitCursor)
  395. MouseCursor::showWaitCursor();
  396. auto oldFile = documentFile;
  397. documentFile = newFile;
  398. doSaveDocument (newFile, [parent,
  399. showMessageOnFailure,
  400. showWaitCursor,
  401. oldFile,
  402. newFile,
  403. afterSave = std::move (afterSave)] (Result result)
  404. {
  405. if (parent.shouldExitAsyncCallback())
  406. {
  407. if (showWaitCursor)
  408. MouseCursor::hideWaitCursor();
  409. return;
  410. }
  411. if (result.wasOk())
  412. {
  413. parent->setChangedFlag (false);
  414. if (showWaitCursor)
  415. MouseCursor::hideWaitCursor();
  416. parent->document.sendChangeMessage(); // because the filename may have changed
  417. if (afterSave != nullptr)
  418. afterSave (savedOk);
  419. return;
  420. }
  421. parent->documentFile = oldFile;
  422. if (showWaitCursor)
  423. MouseCursor::hideWaitCursor();
  424. if (showMessageOnFailure)
  425. AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
  426. TRANS ("Error writing to file..."),
  427. TRANS ("An error occurred while trying to save \"DCNM\" to the file: FLNM")
  428. .replace ("DCNM", parent->document.getDocumentTitle())
  429. .replace ("FLNM", "\n" + newFile.getFullPathName())
  430. + "\n\n"
  431. + result.getErrorMessage());
  432. parent->document.sendChangeMessage(); // because the filename may have changed
  433. if (afterSave != nullptr)
  434. afterSave (failedToWriteToFile);
  435. });
  436. }
  437. template <typename DoSaveAsInteractive, typename DoAskToOverwriteFile, typename DoSaveDocument>
  438. void saveAsImpl (SafeParentPointer parent,
  439. const File& newFile,
  440. bool warnAboutOverwritingExistingFiles,
  441. bool askUserForFileIfNotSpecified,
  442. bool showMessageOnFailure,
  443. std::function<void (SaveResult)> callback,
  444. bool showWaitCursor,
  445. DoSaveAsInteractive&& doSaveAsInteractive,
  446. DoAskToOverwriteFile&& doAskToOverwriteFile,
  447. DoSaveDocument&& doSaveDocument)
  448. {
  449. if (parent.shouldExitAsyncCallback())
  450. return;
  451. if (newFile == File())
  452. {
  453. if (askUserForFileIfNotSpecified)
  454. {
  455. doSaveAsInteractive (parent, true, std::move (callback));
  456. return;
  457. }
  458. // can't save to an unspecified file
  459. jassertfalse;
  460. if (callback != nullptr)
  461. callback (failedToWriteToFile);
  462. return;
  463. }
  464. auto saveInternalHelper = [parent,
  465. callback,
  466. newFile,
  467. showMessageOnFailure,
  468. showWaitCursor,
  469. doSaveDocument = std::forward<DoSaveDocument> (doSaveDocument)]
  470. {
  471. if (! parent.shouldExitAsyncCallback())
  472. parent->saveInternal (parent,
  473. newFile,
  474. showMessageOnFailure,
  475. showWaitCursor,
  476. callback,
  477. doSaveDocument);
  478. };
  479. if (warnAboutOverwritingExistingFiles && newFile.exists())
  480. {
  481. auto afterAsking = [callback = std::move (callback),
  482. saveInternalHelper] (SafeParentPointer ptr,
  483. bool shouldOverwrite)
  484. {
  485. if (ptr.shouldExitAsyncCallback())
  486. return;
  487. if (shouldOverwrite)
  488. saveInternalHelper();
  489. else if (callback != nullptr)
  490. callback (userCancelledSave);
  491. };
  492. doAskToOverwriteFile (parent, newFile, std::move (afterAsking));
  493. return;
  494. }
  495. saveInternalHelper();
  496. }
  497. void saveAsAsyncImpl (SafeParentPointer parent,
  498. const File& newFile,
  499. bool warnAboutOverwritingExistingFiles,
  500. bool askUserForFileIfNotSpecified,
  501. bool showMessageOnFailure,
  502. std::function<void (SaveResult)> callback,
  503. bool showWaitCursor)
  504. {
  505. saveAsImpl (parent,
  506. newFile,
  507. warnAboutOverwritingExistingFiles,
  508. askUserForFileIfNotSpecified,
  509. showMessageOnFailure,
  510. std::move (callback),
  511. showWaitCursor,
  512. [] (SafeParentPointer ptr, bool warnAboutOverwriting, auto cb)
  513. {
  514. if (ptr != nullptr)
  515. ptr->saveAsInteractiveAsyncImpl (ptr, warnAboutOverwriting, std::move (cb));
  516. },
  517. [] (SafeParentPointer ptr, const File& destination, std::function<void (SafeParentPointer, bool)> cb)
  518. {
  519. if (ptr != nullptr)
  520. ptr->askToOverwriteFile (ptr, destination, std::move (cb));
  521. },
  522. [parent] (const File& destination, std::function<void (Result)> cb)
  523. {
  524. if (parent != nullptr)
  525. parent->document.saveDocumentAsync (destination, std::move (cb));
  526. });
  527. }
  528. //==============================================================================
  529. void saveAsInteractiveAsyncImpl (SafeParentPointer parent,
  530. bool warnAboutOverwritingExistingFiles,
  531. std::function<void (SaveResult)> callback)
  532. {
  533. if (parent == nullptr)
  534. return;
  535. saveAsInteractiveImpl (parent,
  536. warnAboutOverwritingExistingFiles,
  537. std::move (callback),
  538. [] (SafeParentPointer ptr, bool warnAboutOverwriting, auto cb)
  539. {
  540. if (ptr != nullptr)
  541. ptr->getSaveAsFilenameAsync (ptr, warnAboutOverwriting, std::move (cb));
  542. },
  543. [] (SafeParentPointer ptr,
  544. const File& newFile,
  545. bool warnAboutOverwriting,
  546. bool askUserForFileIfNotSpecified,
  547. bool showMessageOnFailure,
  548. auto cb,
  549. bool showWaitCursor)
  550. {
  551. if (ptr != nullptr)
  552. ptr->saveAsAsyncImpl (ptr,
  553. newFile,
  554. warnAboutOverwriting,
  555. askUserForFileIfNotSpecified,
  556. showMessageOnFailure,
  557. std::move (cb),
  558. showWaitCursor);
  559. },
  560. [] (SafeParentPointer ptr, const File& destination, auto cb)
  561. {
  562. if (ptr != nullptr)
  563. ptr->askToOverwriteFile (ptr, destination, std::move (cb));
  564. });
  565. }
  566. //==============================================================================
  567. bool askToOverwriteFile (SafeParentPointer parent,
  568. const File& newFile,
  569. std::function<void (SafeParentPointer, bool)> callback)
  570. {
  571. if (parent == nullptr)
  572. return false;
  573. auto* modalCallback = callback == nullptr
  574. ? nullptr
  575. : ModalCallbackFunction::create ([parent, callback = std::move (callback)] (int r)
  576. {
  577. if (parent != nullptr)
  578. callback (parent, r == 1);
  579. });
  580. return AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon,
  581. TRANS ("File already exists"),
  582. TRANS ("There's already a file called: FLNM")
  583. .replace ("FLNM", newFile.getFullPathName())
  584. + "\n\n"
  585. + TRANS ("Are you sure you want to overwrite it?"),
  586. TRANS ("Overwrite"),
  587. TRANS ("Cancel"),
  588. nullptr,
  589. modalCallback);
  590. }
  591. //==============================================================================
  592. void getSaveAsFilenameAsync (SafeParentPointer parent,
  593. bool warnAboutOverwritingExistingFiles,
  594. std::function<void (SafeParentPointer, const File&)> callback)
  595. {
  596. asyncFc = getInteractiveFileChooser();
  597. auto flags = FileBrowserComponent::saveMode | FileBrowserComponent::canSelectFiles;
  598. if (warnAboutOverwritingExistingFiles)
  599. flags |= FileBrowserComponent::warnAboutOverwriting;
  600. asyncFc->launchAsync (flags, [parent, callback = std::move (callback)] (const FileChooser& fc)
  601. {
  602. callback (parent, fc.getResult());
  603. });
  604. }
  605. //==============================================================================
  606. template <typename DoSelectFilename, typename DoSaveAs, typename DoAskToOverwriteFile>
  607. void saveAsInteractiveImpl (SafeParentPointer parent,
  608. bool warnAboutOverwritingExistingFiles,
  609. std::function<void (SaveResult)> callback,
  610. DoSelectFilename&& doSelectFilename,
  611. DoSaveAs&& doSaveAs,
  612. DoAskToOverwriteFile&& doAskToOverwriteFile)
  613. {
  614. doSelectFilename (parent,
  615. warnAboutOverwritingExistingFiles,
  616. [doSaveAs = std::forward<DoSaveAs> (doSaveAs),
  617. doAskToOverwriteFile = std::forward<DoAskToOverwriteFile> (doAskToOverwriteFile),
  618. callback = std::move (callback)] (SafeParentPointer parentPtr, File chosen)
  619. {
  620. if (parentPtr.shouldExitAsyncCallback())
  621. return;
  622. if (chosen == File{})
  623. {
  624. if (callback != nullptr)
  625. callback (userCancelledSave);
  626. return;
  627. }
  628. auto updateAndSaveAs = [parentPtr, doSaveAs, callback] (const File& chosenFile)
  629. {
  630. if (parentPtr.shouldExitAsyncCallback())
  631. return;
  632. parentPtr->document.setLastDocumentOpened (chosenFile);
  633. doSaveAs (parentPtr, chosenFile, false, false, true, callback, false);
  634. };
  635. if (chosen.getFileExtension().isEmpty())
  636. {
  637. chosen = chosen.withFileExtension (parentPtr->fileExtension);
  638. if (chosen.exists())
  639. {
  640. auto afterAsking = [chosen, updateAndSaveAs, callback] (SafeParentPointer overwritePtr,
  641. bool overwrite)
  642. {
  643. if (overwritePtr.shouldExitAsyncCallback())
  644. return;
  645. if (overwrite)
  646. updateAndSaveAs (chosen);
  647. else if (callback != nullptr)
  648. callback (userCancelledSave);
  649. };
  650. doAskToOverwriteFile (parentPtr, chosen, std::move (afterAsking));
  651. return;
  652. }
  653. }
  654. updateAndSaveAs (chosen);
  655. });
  656. }
  657. //==============================================================================
  658. std::unique_ptr<FileChooser> getInteractiveFileChooser()
  659. {
  660. auto f = documentFile.existsAsFile() ? documentFile : document.getLastDocumentOpened();
  661. auto legalFilename = File::createLegalFileName (document.getDocumentTitle());
  662. if (legalFilename.isEmpty())
  663. legalFilename = "unnamed";
  664. f = (f.existsAsFile() || f.getParentDirectory().isDirectory())
  665. ? f.getSiblingFile (legalFilename)
  666. : File::getSpecialLocation (File::userDocumentsDirectory).getChildFile (legalFilename);
  667. f = document.getSuggestedSaveAsFile (f);
  668. return std::make_unique<FileChooser> (saveFileDialogTitle,
  669. f,
  670. fileWildcard);
  671. }
  672. //==============================================================================
  673. #if JUCE_MODAL_LOOPS_PERMITTED
  674. struct SaveAsInteractiveSyncImpl
  675. {
  676. template <typename... Ts>
  677. void operator() (Ts&&... ts) const noexcept
  678. {
  679. p.saveAsInteractiveSyncImpl (std::forward<Ts> (ts)...);
  680. }
  681. Pimpl& p;
  682. };
  683. struct AskToOverwriteFileSync
  684. {
  685. template <typename... Ts>
  686. void operator() (Ts&&... ts) const noexcept
  687. {
  688. p.askToOverwriteFileSync (std::forward<Ts> (ts)...);
  689. }
  690. Pimpl& p;
  691. };
  692. struct AskToSaveChangesSync
  693. {
  694. template <typename... Ts>
  695. void operator() (Ts&&... ts) const noexcept
  696. {
  697. p.askToSaveChangesSync (std::forward<Ts> (ts)...);
  698. }
  699. Pimpl& p;
  700. };
  701. struct SaveSync
  702. {
  703. template <typename... Ts>
  704. void operator() (Ts&&... ts) const noexcept
  705. {
  706. p.saveSync (std::forward<Ts> (ts)...);
  707. }
  708. Pimpl& p;
  709. };
  710. struct GetSaveAsFilenameSync
  711. {
  712. template <typename... Ts>
  713. void operator() (Ts&&... ts) const noexcept
  714. {
  715. p.getSaveAsFilenameSync (std::forward<Ts> (ts)...);
  716. }
  717. Pimpl& p;
  718. };
  719. struct SaveAsSyncImpl
  720. {
  721. template <typename... Ts>
  722. void operator() (Ts&&... ts) const noexcept
  723. {
  724. p.saveAsSyncImpl (std::forward<Ts> (ts)...);
  725. }
  726. Pimpl& p;
  727. };
  728. //==============================================================================
  729. void saveAsSyncImpl (SafeParentPointer parent,
  730. const File& newFile,
  731. bool warnAboutOverwritingExistingFiles,
  732. bool askUserForFileIfNotSpecified,
  733. bool showMessageOnFailure,
  734. std::function<void (SaveResult)> callback,
  735. bool showWaitCursor)
  736. {
  737. saveAsImpl (parent,
  738. newFile,
  739. warnAboutOverwritingExistingFiles,
  740. askUserForFileIfNotSpecified,
  741. showMessageOnFailure,
  742. std::move (callback),
  743. showWaitCursor,
  744. SaveAsInteractiveSyncImpl { *this },
  745. AskToOverwriteFileSync { *this },
  746. [this] (const File& file, const auto& cb) { cb (document.saveDocument (file)); });
  747. }
  748. //==============================================================================
  749. template <typename Callback>
  750. void askToSaveChangesSync (SafeParentPointer parent, Callback&& callback)
  751. {
  752. callback (parent, askToSaveChanges (parent, nullptr));
  753. }
  754. //==============================================================================
  755. void saveAsInteractiveSyncImpl (SafeParentPointer parent,
  756. bool warnAboutOverwritingExistingFiles,
  757. std::function<void (SaveResult)> callback)
  758. {
  759. saveAsInteractiveImpl (parent,
  760. warnAboutOverwritingExistingFiles,
  761. std::move (callback),
  762. GetSaveAsFilenameSync { *this },
  763. SaveAsSyncImpl { *this },
  764. AskToOverwriteFileSync { *this });
  765. }
  766. //==============================================================================
  767. template <typename Callback>
  768. void askToOverwriteFileSync (SafeParentPointer parent,
  769. const File& newFile,
  770. Callback&& callback)
  771. {
  772. callback (parent, askToOverwriteFile (parent, newFile, nullptr));
  773. }
  774. //==============================================================================
  775. template <typename Callback>
  776. void saveSync (bool askUserForFileIfNotSpecified,
  777. bool showMessageOnFailure,
  778. Callback&& callback)
  779. {
  780. callback (save (askUserForFileIfNotSpecified, showMessageOnFailure));
  781. }
  782. //==============================================================================
  783. template <typename Callback>
  784. void getSaveAsFilenameSync (SafeParentPointer parent,
  785. bool warnAboutOverwritingExistingFiles,
  786. Callback&& callback)
  787. {
  788. auto fc = getInteractiveFileChooser();
  789. if (fc->browseForFileToSave (warnAboutOverwritingExistingFiles))
  790. {
  791. callback (parent, fc->getResult());
  792. return;
  793. }
  794. callback (parent, {});
  795. }
  796. #endif
  797. //==============================================================================
  798. FileBasedDocument& document;
  799. File documentFile;
  800. bool changedSinceSave = false;
  801. String fileExtension, fileWildcard, openFileDialogTitle, saveFileDialogTitle;
  802. std::unique_ptr<FileChooser> asyncFc;
  803. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
  804. JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
  805. };
  806. //==============================================================================
  807. FileBasedDocument::FileBasedDocument (const String& fileExtension,
  808. const String& fileWildcard,
  809. const String& openFileDialogTitle,
  810. const String& saveFileDialogTitle)
  811. : pimpl (new Pimpl (*this,
  812. fileExtension,
  813. fileWildcard,
  814. openFileDialogTitle,
  815. saveFileDialogTitle))
  816. {
  817. }
  818. FileBasedDocument::~FileBasedDocument() = default;
  819. //==============================================================================
  820. bool FileBasedDocument::hasChangedSinceSaved() const
  821. {
  822. return pimpl->hasChangedSinceSaved();
  823. }
  824. void FileBasedDocument::setChangedFlag (bool hasChanged)
  825. {
  826. pimpl->setChangedFlag (hasChanged);
  827. }
  828. void FileBasedDocument::changed()
  829. {
  830. pimpl->changed();
  831. }
  832. //==============================================================================
  833. Result FileBasedDocument::loadFrom (const File& fileToLoadFrom,
  834. bool showMessageOnFailure,
  835. bool showWaitCursor)
  836. {
  837. return pimpl->loadFrom (fileToLoadFrom, showMessageOnFailure, showWaitCursor);
  838. }
  839. void FileBasedDocument::loadFromAsync (const File& fileToLoadFrom,
  840. bool showMessageOnFailure,
  841. std::function<void (Result)> callback)
  842. {
  843. pimpl->loadFromAsync (fileToLoadFrom, showMessageOnFailure, std::move (callback));
  844. }
  845. //==============================================================================
  846. #if JUCE_MODAL_LOOPS_PERMITTED
  847. Result FileBasedDocument::loadFromUserSpecifiedFile (bool showMessageOnFailure)
  848. {
  849. return pimpl->loadFromUserSpecifiedFile (showMessageOnFailure);
  850. }
  851. #endif
  852. void FileBasedDocument::loadFromUserSpecifiedFileAsync (const bool showMessageOnFailure,
  853. std::function<void (Result)> callback)
  854. {
  855. pimpl->loadFromUserSpecifiedFileAsync (showMessageOnFailure, std::move (callback));
  856. }
  857. //==============================================================================
  858. #if JUCE_MODAL_LOOPS_PERMITTED
  859. FileBasedDocument::SaveResult FileBasedDocument::save (bool askUserForFileIfNotSpecified,
  860. bool showMessageOnFailure)
  861. {
  862. return pimpl->save (askUserForFileIfNotSpecified, showMessageOnFailure);
  863. }
  864. #endif
  865. void FileBasedDocument::saveAsync (bool askUserForFileIfNotSpecified,
  866. bool showMessageOnFailure,
  867. std::function<void (SaveResult)> callback)
  868. {
  869. pimpl->saveAsync (askUserForFileIfNotSpecified, showMessageOnFailure, std::move (callback));
  870. }
  871. //==============================================================================
  872. #if JUCE_MODAL_LOOPS_PERMITTED
  873. FileBasedDocument::SaveResult FileBasedDocument::saveIfNeededAndUserAgrees()
  874. {
  875. return pimpl->saveIfNeededAndUserAgrees();
  876. }
  877. #endif
  878. void FileBasedDocument::saveIfNeededAndUserAgreesAsync (std::function<void (SaveResult)> callback)
  879. {
  880. pimpl->saveIfNeededAndUserAgreesAsync (std::move (callback));
  881. }
  882. //==============================================================================
  883. #if JUCE_MODAL_LOOPS_PERMITTED
  884. FileBasedDocument::SaveResult FileBasedDocument::saveAs (const File& newFile,
  885. bool warnAboutOverwritingExistingFiles,
  886. bool askUserForFileIfNotSpecified,
  887. bool showMessageOnFailure,
  888. bool showWaitCursor)
  889. {
  890. return pimpl->saveAs (newFile,
  891. warnAboutOverwritingExistingFiles,
  892. askUserForFileIfNotSpecified,
  893. showMessageOnFailure,
  894. showWaitCursor);
  895. }
  896. #endif
  897. void FileBasedDocument::saveAsAsync (const File& newFile,
  898. bool warnAboutOverwritingExistingFiles,
  899. bool askUserForFileIfNotSpecified,
  900. bool showMessageOnFailure,
  901. std::function<void (SaveResult)> callback)
  902. {
  903. pimpl->saveAsAsync (newFile,
  904. warnAboutOverwritingExistingFiles,
  905. askUserForFileIfNotSpecified,
  906. showMessageOnFailure,
  907. std::move (callback));
  908. }
  909. //==============================================================================
  910. #if JUCE_MODAL_LOOPS_PERMITTED
  911. FileBasedDocument::SaveResult FileBasedDocument::saveAsInteractive (bool warnAboutOverwritingExistingFiles)
  912. {
  913. return pimpl->saveAsInteractive (warnAboutOverwritingExistingFiles);
  914. }
  915. #endif
  916. void FileBasedDocument::saveAsInteractiveAsync (bool warnAboutOverwritingExistingFiles,
  917. std::function<void (SaveResult)> callback)
  918. {
  919. pimpl->saveAsInteractiveAsync (warnAboutOverwritingExistingFiles, std::move (callback));
  920. }
  921. //==============================================================================
  922. const File& FileBasedDocument::getFile() const
  923. {
  924. return pimpl->getFile();
  925. }
  926. void FileBasedDocument::setFile (const File& newFile)
  927. {
  928. pimpl->setFile (newFile);
  929. }
  930. //==============================================================================
  931. void FileBasedDocument::loadDocumentAsync (const File& file, std::function<void (Result)> callback)
  932. {
  933. const auto result = loadDocument (file);
  934. if (callback != nullptr)
  935. callback (result);
  936. }
  937. void FileBasedDocument::saveDocumentAsync (const File& file, std::function<void (Result)> callback)
  938. {
  939. const auto result = saveDocument (file);
  940. if (callback != nullptr)
  941. callback (result);
  942. }
  943. File FileBasedDocument::getSuggestedSaveAsFile (const File& defaultFile)
  944. {
  945. return defaultFile.withFileExtension (pimpl->getFileExtension()).getNonexistentSibling (true);
  946. }
  947. } // namespace juce