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.

1116 lines
44KB

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