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.

1119 lines
43KB

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