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.

1140 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, 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->askToSaveChangesAsync (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. {
  294. auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
  295. TRANS ("Failed to open file..."),
  296. TRANS ("There was an error while trying to load the file: FLNM")
  297. .replace ("FLNM", "\n" + newFile.getFullPathName())
  298. + "\n\n"
  299. + result.getErrorMessage());
  300. parent->messageBox = AlertWindow::showScopedAsync (options, nullptr);
  301. }
  302. if (completed != nullptr)
  303. completed (result);
  304. };
  305. if (newFile.existsAsFile())
  306. {
  307. auto afterLoading = [parent,
  308. showWaitCursor,
  309. newFile,
  310. completed = std::move (completed),
  311. tidyUp] (Result result)
  312. {
  313. if (result.wasOk())
  314. {
  315. parent->setChangedFlag (false);
  316. if (showWaitCursor)
  317. MouseCursor::hideWaitCursor();
  318. parent->document.setLastDocumentOpened (newFile);
  319. if (completed != nullptr)
  320. completed (result);
  321. return;
  322. }
  323. tidyUp (result);
  324. };
  325. doLoadDocument (newFile, std::move (afterLoading));
  326. return;
  327. }
  328. tidyUp (Result::fail (TRANS ("The file doesn't exist")));
  329. }
  330. //==============================================================================
  331. template <typename DoAskToSaveChanges, typename DoSave>
  332. void saveIfNeededAndUserAgreesImpl (SafeParentPointer parent,
  333. std::function<void (SaveResult)> completed,
  334. DoAskToSaveChanges&& doAskToSaveChanges,
  335. DoSave&& doSave)
  336. {
  337. if (parent.shouldExitAsyncCallback())
  338. return;
  339. if (! hasChangedSinceSaved())
  340. {
  341. if (completed != nullptr)
  342. completed (savedOk);
  343. return;
  344. }
  345. auto afterAsking = [doSave = std::forward<DoSave> (doSave),
  346. completed = std::move (completed)] (SafeParentPointer ptr,
  347. int alertResult)
  348. {
  349. if (ptr.shouldExitAsyncCallback())
  350. return;
  351. switch (alertResult)
  352. {
  353. case 1: // save changes
  354. doSave (true, true, [ptr, completed] (SaveResult result)
  355. {
  356. if (ptr.shouldExitAsyncCallback())
  357. return;
  358. if (completed != nullptr)
  359. completed (result);
  360. });
  361. return;
  362. case 2: // discard changes
  363. if (completed != nullptr)
  364. completed (savedOk);
  365. return;
  366. }
  367. if (completed != nullptr)
  368. completed (userCancelledSave);
  369. };
  370. doAskToSaveChanges (parent, std::move (afterAsking));
  371. }
  372. //==============================================================================
  373. MessageBoxOptions getAskToSaveChangesOptions() const
  374. {
  375. return MessageBoxOptions::makeOptionsYesNoCancel (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. }
  383. void askToSaveChangesAsync (SafeParentPointer parent,
  384. std::function<void (SafeParentPointer, int)> callback)
  385. {
  386. messageBox = AlertWindow::showScopedAsync (getAskToSaveChangesOptions(),
  387. [parent, callback = std::move (callback)] (int alertResult)
  388. {
  389. if (parent != nullptr)
  390. callback (parent, alertResult);
  391. });
  392. }
  393. #if JUCE_MODAL_LOOPS_PERMITTED
  394. int askToSaveChangesSync()
  395. {
  396. return AlertWindow::show (getAskToSaveChangesOptions());
  397. }
  398. #endif
  399. //==============================================================================
  400. template <typename DoSaveDocument>
  401. void saveInternal (SafeParentPointer parent,
  402. const File& newFile,
  403. bool showMessageOnFailure,
  404. bool showWaitCursor,
  405. std::function<void (SaveResult)> afterSave,
  406. DoSaveDocument&& doSaveDocument)
  407. {
  408. if (showWaitCursor)
  409. MouseCursor::showWaitCursor();
  410. auto oldFile = documentFile;
  411. documentFile = newFile;
  412. doSaveDocument (newFile, [parent,
  413. showMessageOnFailure,
  414. showWaitCursor,
  415. oldFile,
  416. newFile,
  417. afterSave = std::move (afterSave)] (Result result)
  418. {
  419. if (parent.shouldExitAsyncCallback())
  420. {
  421. if (showWaitCursor)
  422. MouseCursor::hideWaitCursor();
  423. return;
  424. }
  425. if (result.wasOk())
  426. {
  427. parent->setChangedFlag (false);
  428. if (showWaitCursor)
  429. MouseCursor::hideWaitCursor();
  430. parent->document.sendChangeMessage(); // because the filename may have changed
  431. if (afterSave != nullptr)
  432. afterSave (savedOk);
  433. return;
  434. }
  435. parent->documentFile = oldFile;
  436. if (showWaitCursor)
  437. MouseCursor::hideWaitCursor();
  438. if (showMessageOnFailure)
  439. {
  440. auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
  441. TRANS ("Error writing to file..."),
  442. TRANS ("An error occurred while trying to save \"DCNM\" to the file: FLNM")
  443. .replace ("DCNM", parent->document.getDocumentTitle())
  444. .replace ("FLNM", "\n" + newFile.getFullPathName())
  445. + "\n\n"
  446. + result.getErrorMessage());
  447. parent->messageBox = AlertWindow::showScopedAsync (options, nullptr);
  448. }
  449. parent->document.sendChangeMessage(); // because the filename may have changed
  450. if (afterSave != nullptr)
  451. afterSave (failedToWriteToFile);
  452. });
  453. }
  454. template <typename DoSaveAsInteractive, typename DoAskToOverwriteFile, typename DoSaveDocument>
  455. void saveAsImpl (SafeParentPointer parent,
  456. const File& newFile,
  457. bool warnAboutOverwritingExistingFiles,
  458. bool askUserForFileIfNotSpecified,
  459. bool showMessageOnFailure,
  460. std::function<void (SaveResult)> callback,
  461. bool showWaitCursor,
  462. DoSaveAsInteractive&& doSaveAsInteractive,
  463. DoAskToOverwriteFile&& doAskToOverwriteFile,
  464. DoSaveDocument&& doSaveDocument)
  465. {
  466. if (parent.shouldExitAsyncCallback())
  467. return;
  468. if (newFile == File())
  469. {
  470. if (askUserForFileIfNotSpecified)
  471. {
  472. doSaveAsInteractive (parent, true, std::move (callback));
  473. return;
  474. }
  475. // can't save to an unspecified file
  476. jassertfalse;
  477. if (callback != nullptr)
  478. callback (failedToWriteToFile);
  479. return;
  480. }
  481. auto saveInternalHelper = [parent,
  482. callback,
  483. newFile,
  484. showMessageOnFailure,
  485. showWaitCursor,
  486. doSaveDocument = std::forward<DoSaveDocument> (doSaveDocument)]
  487. {
  488. if (! parent.shouldExitAsyncCallback())
  489. parent->saveInternal (parent,
  490. newFile,
  491. showMessageOnFailure,
  492. showWaitCursor,
  493. callback,
  494. doSaveDocument);
  495. };
  496. if (warnAboutOverwritingExistingFiles && newFile.exists())
  497. {
  498. auto afterAsking = [callback = std::move (callback),
  499. saveInternalHelper] (SafeParentPointer ptr,
  500. bool shouldOverwrite)
  501. {
  502. if (ptr.shouldExitAsyncCallback())
  503. return;
  504. if (shouldOverwrite)
  505. saveInternalHelper();
  506. else if (callback != nullptr)
  507. callback (userCancelledSave);
  508. };
  509. doAskToOverwriteFile (parent, newFile, std::move (afterAsking));
  510. return;
  511. }
  512. saveInternalHelper();
  513. }
  514. void saveAsAsyncImpl (SafeParentPointer parent,
  515. const File& newFile,
  516. bool warnAboutOverwritingExistingFiles,
  517. bool askUserForFileIfNotSpecified,
  518. bool showMessageOnFailure,
  519. std::function<void (SaveResult)> callback,
  520. bool showWaitCursor)
  521. {
  522. saveAsImpl (parent,
  523. newFile,
  524. warnAboutOverwritingExistingFiles,
  525. askUserForFileIfNotSpecified,
  526. showMessageOnFailure,
  527. std::move (callback),
  528. showWaitCursor,
  529. [] (SafeParentPointer ptr, bool warnAboutOverwriting, auto cb)
  530. {
  531. if (ptr != nullptr)
  532. ptr->saveAsInteractiveAsyncImpl (ptr, warnAboutOverwriting, std::move (cb));
  533. },
  534. [] (SafeParentPointer ptr, const File& destination, std::function<void (SafeParentPointer, bool)> cb)
  535. {
  536. if (ptr != nullptr)
  537. ptr->askToOverwriteFileAsync (ptr, destination, std::move (cb));
  538. },
  539. [parent] (const File& destination, std::function<void (Result)> cb)
  540. {
  541. if (parent != nullptr)
  542. parent->document.saveDocumentAsync (destination, std::move (cb));
  543. });
  544. }
  545. //==============================================================================
  546. void saveAsInteractiveAsyncImpl (SafeParentPointer parent,
  547. bool warnAboutOverwritingExistingFiles,
  548. std::function<void (SaveResult)> callback)
  549. {
  550. if (parent == nullptr)
  551. return;
  552. saveAsInteractiveImpl (parent,
  553. warnAboutOverwritingExistingFiles,
  554. std::move (callback),
  555. [] (SafeParentPointer ptr, bool warnAboutOverwriting, auto cb)
  556. {
  557. if (ptr != nullptr)
  558. ptr->getSaveAsFilenameAsync (ptr, warnAboutOverwriting, std::move (cb));
  559. },
  560. [] (SafeParentPointer ptr,
  561. const File& newFile,
  562. bool warnAboutOverwriting,
  563. bool askUserForFileIfNotSpecified,
  564. bool showMessageOnFailure,
  565. auto cb,
  566. bool showWaitCursor)
  567. {
  568. if (ptr != nullptr)
  569. ptr->saveAsAsyncImpl (ptr,
  570. newFile,
  571. warnAboutOverwriting,
  572. askUserForFileIfNotSpecified,
  573. showMessageOnFailure,
  574. std::move (cb),
  575. showWaitCursor);
  576. },
  577. [] (SafeParentPointer ptr, const File& destination, auto cb)
  578. {
  579. if (ptr != nullptr)
  580. ptr->askToOverwriteFileAsync (ptr, destination, std::move (cb));
  581. });
  582. }
  583. //==============================================================================
  584. MessageBoxOptions getAskToOverwriteFileOptions (const File& newFile) const
  585. {
  586. return MessageBoxOptions::makeOptionsOkCancel (MessageBoxIconType::WarningIcon,
  587. TRANS ("File already exists"),
  588. TRANS ("There's already a file called: FLNM")
  589. .replace ("FLNM", newFile.getFullPathName())
  590. + "\n\n"
  591. + TRANS ("Are you sure you want to overwrite it?"),
  592. TRANS ("Overwrite"),
  593. TRANS ("Cancel"));
  594. }
  595. void askToOverwriteFileAsync (SafeParentPointer parent,
  596. const File& newFile,
  597. std::function<void (SafeParentPointer, bool)> callback)
  598. {
  599. if (parent == nullptr)
  600. return;
  601. messageBox = AlertWindow::showScopedAsync (getAskToOverwriteFileOptions (newFile),
  602. [parent, callback = std::move (callback)] (int r)
  603. {
  604. if (parent != nullptr)
  605. callback (parent, r != 1);
  606. });
  607. }
  608. #if JUCE_MODAL_LOOPS_PERMITTED
  609. bool askToOverwriteFileSync (const File& newFile)
  610. {
  611. return AlertWindow::show (getAskToOverwriteFileOptions (newFile));
  612. }
  613. #endif
  614. //==============================================================================
  615. void getSaveAsFilenameAsync (SafeParentPointer parent,
  616. bool warnAboutOverwritingExistingFiles,
  617. std::function<void (SafeParentPointer, const File&)> callback)
  618. {
  619. asyncFc = getInteractiveFileChooser();
  620. auto flags = FileBrowserComponent::saveMode | FileBrowserComponent::canSelectFiles;
  621. if (warnAboutOverwritingExistingFiles)
  622. flags |= FileBrowserComponent::warnAboutOverwriting;
  623. asyncFc->launchAsync (flags, [parent, callback = std::move (callback)] (const FileChooser& fc)
  624. {
  625. callback (parent, fc.getResult());
  626. });
  627. }
  628. //==============================================================================
  629. template <typename DoSelectFilename, typename DoSaveAs, typename DoAskToOverwriteFile>
  630. void saveAsInteractiveImpl (SafeParentPointer parent,
  631. bool warnAboutOverwritingExistingFiles,
  632. std::function<void (SaveResult)> callback,
  633. DoSelectFilename&& doSelectFilename,
  634. DoSaveAs&& doSaveAs,
  635. DoAskToOverwriteFile&& doAskToOverwriteFile)
  636. {
  637. doSelectFilename (parent,
  638. warnAboutOverwritingExistingFiles,
  639. [doSaveAs = std::forward<DoSaveAs> (doSaveAs),
  640. doAskToOverwriteFile = std::forward<DoAskToOverwriteFile> (doAskToOverwriteFile),
  641. callback = std::move (callback)] (SafeParentPointer parentPtr, File chosen)
  642. {
  643. if (parentPtr.shouldExitAsyncCallback())
  644. return;
  645. if (chosen == File{})
  646. {
  647. if (callback != nullptr)
  648. callback (userCancelledSave);
  649. return;
  650. }
  651. auto updateAndSaveAs = [parentPtr, doSaveAs, callback] (const File& chosenFile)
  652. {
  653. if (parentPtr.shouldExitAsyncCallback())
  654. return;
  655. parentPtr->document.setLastDocumentOpened (chosenFile);
  656. doSaveAs (parentPtr, chosenFile, false, false, true, callback, false);
  657. };
  658. if (chosen.getFileExtension().isEmpty())
  659. {
  660. chosen = chosen.withFileExtension (parentPtr->fileExtension);
  661. if (chosen.exists())
  662. {
  663. auto afterAsking = [chosen, updateAndSaveAs, callback] (SafeParentPointer overwritePtr,
  664. bool overwrite)
  665. {
  666. if (overwritePtr.shouldExitAsyncCallback())
  667. return;
  668. if (overwrite)
  669. updateAndSaveAs (chosen);
  670. else if (callback != nullptr)
  671. callback (userCancelledSave);
  672. };
  673. doAskToOverwriteFile (parentPtr, chosen, std::move (afterAsking));
  674. return;
  675. }
  676. }
  677. updateAndSaveAs (chosen);
  678. });
  679. }
  680. //==============================================================================
  681. std::unique_ptr<FileChooser> getInteractiveFileChooser()
  682. {
  683. auto f = documentFile.existsAsFile() ? documentFile : document.getLastDocumentOpened();
  684. auto legalFilename = File::createLegalFileName (document.getDocumentTitle());
  685. if (legalFilename.isEmpty())
  686. legalFilename = "unnamed";
  687. f = (f.existsAsFile() || f.getParentDirectory().isDirectory())
  688. ? f.getSiblingFile (legalFilename)
  689. : File::getSpecialLocation (File::userDocumentsDirectory).getChildFile (legalFilename);
  690. f = document.getSuggestedSaveAsFile (f);
  691. return std::make_unique<FileChooser> (saveFileDialogTitle,
  692. f,
  693. fileWildcard);
  694. }
  695. //==============================================================================
  696. #if JUCE_MODAL_LOOPS_PERMITTED
  697. struct SaveAsInteractiveSyncImpl
  698. {
  699. template <typename... Ts>
  700. void operator() (Ts&&... ts) const noexcept
  701. {
  702. p.saveAsInteractiveSyncImpl (std::forward<Ts> (ts)...);
  703. }
  704. Pimpl& p;
  705. };
  706. struct AskToOverwriteFileSync
  707. {
  708. template <typename... Ts>
  709. void operator() (Ts&&... ts) const noexcept
  710. {
  711. p.askToOverwriteFileSync (std::forward<Ts> (ts)...);
  712. }
  713. Pimpl& p;
  714. };
  715. struct AskToSaveChangesSync
  716. {
  717. template <typename... Ts>
  718. void operator() (Ts&&... ts) const noexcept
  719. {
  720. p.askToSaveChangesSync (std::forward<Ts> (ts)...);
  721. }
  722. Pimpl& p;
  723. };
  724. struct SaveSync
  725. {
  726. template <typename... Ts>
  727. void operator() (Ts&&... ts) const noexcept
  728. {
  729. p.saveSync (std::forward<Ts> (ts)...);
  730. }
  731. Pimpl& p;
  732. };
  733. struct GetSaveAsFilenameSync
  734. {
  735. template <typename... Ts>
  736. void operator() (Ts&&... ts) const noexcept
  737. {
  738. p.getSaveAsFilenameSync (std::forward<Ts> (ts)...);
  739. }
  740. Pimpl& p;
  741. };
  742. struct SaveAsSyncImpl
  743. {
  744. template <typename... Ts>
  745. void operator() (Ts&&... ts) const noexcept
  746. {
  747. p.saveAsSyncImpl (std::forward<Ts> (ts)...);
  748. }
  749. Pimpl& p;
  750. };
  751. //==============================================================================
  752. void saveAsSyncImpl (SafeParentPointer parent,
  753. const File& newFile,
  754. bool warnAboutOverwritingExistingFiles,
  755. bool askUserForFileIfNotSpecified,
  756. bool showMessageOnFailure,
  757. std::function<void (SaveResult)> callback,
  758. bool showWaitCursor)
  759. {
  760. saveAsImpl (parent,
  761. newFile,
  762. warnAboutOverwritingExistingFiles,
  763. askUserForFileIfNotSpecified,
  764. showMessageOnFailure,
  765. std::move (callback),
  766. showWaitCursor,
  767. SaveAsInteractiveSyncImpl { *this },
  768. AskToOverwriteFileSync { *this },
  769. [this] (const File& file, const auto& cb) { cb (document.saveDocument (file)); });
  770. }
  771. //==============================================================================
  772. template <typename Callback>
  773. void askToSaveChangesSync (SafeParentPointer parent, Callback&& callback)
  774. {
  775. callback (parent, askToSaveChangesSync());
  776. }
  777. //==============================================================================
  778. void saveAsInteractiveSyncImpl (SafeParentPointer parent,
  779. bool warnAboutOverwritingExistingFiles,
  780. std::function<void (SaveResult)> callback)
  781. {
  782. saveAsInteractiveImpl (parent,
  783. warnAboutOverwritingExistingFiles,
  784. std::move (callback),
  785. GetSaveAsFilenameSync { *this },
  786. SaveAsSyncImpl { *this },
  787. AskToOverwriteFileSync { *this });
  788. }
  789. //==============================================================================
  790. template <typename Callback>
  791. void askToOverwriteFileSync (SafeParentPointer parent,
  792. const File& newFile,
  793. Callback&& callback)
  794. {
  795. callback (parent, askToOverwriteFileSync (newFile));
  796. }
  797. //==============================================================================
  798. template <typename Callback>
  799. void saveSync (bool askUserForFileIfNotSpecified,
  800. bool showMessageOnFailure,
  801. Callback&& callback)
  802. {
  803. callback (save (askUserForFileIfNotSpecified, showMessageOnFailure));
  804. }
  805. //==============================================================================
  806. template <typename Callback>
  807. void getSaveAsFilenameSync (SafeParentPointer parent,
  808. bool warnAboutOverwritingExistingFiles,
  809. Callback&& callback)
  810. {
  811. auto fc = getInteractiveFileChooser();
  812. if (fc->browseForFileToSave (warnAboutOverwritingExistingFiles))
  813. {
  814. callback (parent, fc->getResult());
  815. return;
  816. }
  817. callback (parent, {});
  818. }
  819. #endif
  820. //==============================================================================
  821. FileBasedDocument& document;
  822. File documentFile;
  823. bool changedSinceSave = false;
  824. String fileExtension, fileWildcard, openFileDialogTitle, saveFileDialogTitle;
  825. std::unique_ptr<FileChooser> asyncFc;
  826. ScopedMessageBox messageBox;
  827. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
  828. JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
  829. };
  830. //==============================================================================
  831. FileBasedDocument::FileBasedDocument (const String& fileExtension,
  832. const String& fileWildcard,
  833. const String& openFileDialogTitle,
  834. const String& saveFileDialogTitle)
  835. : pimpl (new Pimpl (*this,
  836. fileExtension,
  837. fileWildcard,
  838. openFileDialogTitle,
  839. saveFileDialogTitle))
  840. {
  841. }
  842. FileBasedDocument::~FileBasedDocument() = default;
  843. //==============================================================================
  844. bool FileBasedDocument::hasChangedSinceSaved() const
  845. {
  846. return pimpl->hasChangedSinceSaved();
  847. }
  848. void FileBasedDocument::setChangedFlag (bool hasChanged)
  849. {
  850. pimpl->setChangedFlag (hasChanged);
  851. }
  852. void FileBasedDocument::changed()
  853. {
  854. pimpl->changed();
  855. }
  856. //==============================================================================
  857. Result FileBasedDocument::loadFrom (const File& fileToLoadFrom,
  858. bool showMessageOnFailure,
  859. bool showWaitCursor)
  860. {
  861. return pimpl->loadFrom (fileToLoadFrom, showMessageOnFailure, showWaitCursor);
  862. }
  863. void FileBasedDocument::loadFromAsync (const File& fileToLoadFrom,
  864. bool showMessageOnFailure,
  865. std::function<void (Result)> callback)
  866. {
  867. pimpl->loadFromAsync (fileToLoadFrom, showMessageOnFailure, std::move (callback));
  868. }
  869. //==============================================================================
  870. #if JUCE_MODAL_LOOPS_PERMITTED
  871. Result FileBasedDocument::loadFromUserSpecifiedFile (bool showMessageOnFailure)
  872. {
  873. return pimpl->loadFromUserSpecifiedFile (showMessageOnFailure);
  874. }
  875. #endif
  876. void FileBasedDocument::loadFromUserSpecifiedFileAsync (const bool showMessageOnFailure,
  877. std::function<void (Result)> callback)
  878. {
  879. pimpl->loadFromUserSpecifiedFileAsync (showMessageOnFailure, std::move (callback));
  880. }
  881. //==============================================================================
  882. #if JUCE_MODAL_LOOPS_PERMITTED
  883. FileBasedDocument::SaveResult FileBasedDocument::save (bool askUserForFileIfNotSpecified,
  884. bool showMessageOnFailure)
  885. {
  886. return pimpl->save (askUserForFileIfNotSpecified, showMessageOnFailure);
  887. }
  888. #endif
  889. void FileBasedDocument::saveAsync (bool askUserForFileIfNotSpecified,
  890. bool showMessageOnFailure,
  891. std::function<void (SaveResult)> callback)
  892. {
  893. pimpl->saveAsync (askUserForFileIfNotSpecified, showMessageOnFailure, std::move (callback));
  894. }
  895. //==============================================================================
  896. #if JUCE_MODAL_LOOPS_PERMITTED
  897. FileBasedDocument::SaveResult FileBasedDocument::saveIfNeededAndUserAgrees()
  898. {
  899. return pimpl->saveIfNeededAndUserAgrees();
  900. }
  901. #endif
  902. void FileBasedDocument::saveIfNeededAndUserAgreesAsync (std::function<void (SaveResult)> callback)
  903. {
  904. pimpl->saveIfNeededAndUserAgreesAsync (std::move (callback));
  905. }
  906. //==============================================================================
  907. #if JUCE_MODAL_LOOPS_PERMITTED
  908. FileBasedDocument::SaveResult FileBasedDocument::saveAs (const File& newFile,
  909. bool warnAboutOverwritingExistingFiles,
  910. bool askUserForFileIfNotSpecified,
  911. bool showMessageOnFailure,
  912. bool showWaitCursor)
  913. {
  914. return pimpl->saveAs (newFile,
  915. warnAboutOverwritingExistingFiles,
  916. askUserForFileIfNotSpecified,
  917. showMessageOnFailure,
  918. showWaitCursor);
  919. }
  920. #endif
  921. void FileBasedDocument::saveAsAsync (const File& newFile,
  922. bool warnAboutOverwritingExistingFiles,
  923. bool askUserForFileIfNotSpecified,
  924. bool showMessageOnFailure,
  925. std::function<void (SaveResult)> callback)
  926. {
  927. pimpl->saveAsAsync (newFile,
  928. warnAboutOverwritingExistingFiles,
  929. askUserForFileIfNotSpecified,
  930. showMessageOnFailure,
  931. std::move (callback));
  932. }
  933. //==============================================================================
  934. #if JUCE_MODAL_LOOPS_PERMITTED
  935. FileBasedDocument::SaveResult FileBasedDocument::saveAsInteractive (bool warnAboutOverwritingExistingFiles)
  936. {
  937. return pimpl->saveAsInteractive (warnAboutOverwritingExistingFiles);
  938. }
  939. #endif
  940. void FileBasedDocument::saveAsInteractiveAsync (bool warnAboutOverwritingExistingFiles,
  941. std::function<void (SaveResult)> callback)
  942. {
  943. pimpl->saveAsInteractiveAsync (warnAboutOverwritingExistingFiles, std::move (callback));
  944. }
  945. //==============================================================================
  946. const File& FileBasedDocument::getFile() const
  947. {
  948. return pimpl->getFile();
  949. }
  950. void FileBasedDocument::setFile (const File& newFile)
  951. {
  952. pimpl->setFile (newFile);
  953. }
  954. //==============================================================================
  955. void FileBasedDocument::loadDocumentAsync (const File& file, std::function<void (Result)> callback)
  956. {
  957. const auto result = loadDocument (file);
  958. if (callback != nullptr)
  959. callback (result);
  960. }
  961. void FileBasedDocument::saveDocumentAsync (const File& file, std::function<void (Result)> callback)
  962. {
  963. const auto result = saveDocument (file);
  964. if (callback != nullptr)
  965. callback (result);
  966. }
  967. File FileBasedDocument::getSuggestedSaveAsFile (const File& defaultFile)
  968. {
  969. return defaultFile.withFileExtension (pimpl->getFileExtension()).getNonexistentSibling (true);
  970. }
  971. } // namespace juce