#pragma once #include "Document/EditorSession.h" #include "IO/ImageExporter.h" #include "IO/ProjectController+ExternalChanges.h" #include #include #include #include #include #include #include class ProjectWorkspace; class QDialog; // One project's file operations: save, open, close, drops. class ProjectController : public QObject { Q_OBJECT public: explicit ProjectController(EditorSession &session); // Its owner declares the session first: it outlives this. EditorSession &session; // Dialogs open over it; without one they stand alone. QPointer window; // With one, opening and closing are the workspace's to do. QPointer workspace; static bool isProject(const QUrl &url); // One place on disk, whatever links lead to it. static bool samePlace(const std::optional &path, const QString &other); bool canStart() const; // Each `done` runs from the event loop, never inline. void save(bool asNew = false, std::function done = {}); void open(std::optional path = std::nullopt, std::function done = {}); void newCanvas(); void close(QWidget *closing); void confirmQuit(std::function done); void receive(const QList &urls, std::optional point = std::nullopt, std::function done = {}); // Swift's sheets over the window; resizes off the UI thread. void canvasSize(std::function done = {}); void imageSize(std::function done = {}); void trim(std::function done = {}); void exportPNG(std::function done = {}); void exportJPEG(std::function done = {}); // Swift's ProjectController+ExternalChanges: the package kept in step. ExternalChangeState externalChanges; void watchProject(const QString &path); void stopWatchingProject(); // Remembers the package as it is now, then calls `then`. void rememberProjectDigest(const QString &path, std::function then); // Its tab came to the front: ask a held question. void resumeExternalChangeCheck(); private: struct Incoming { QList urls; std::optional point; std::function done; }; struct Loaded { std::optional snapshot; QString failure; }; using Resized = Loaded; struct Rendered { std::optional raster; QString failure; }; struct Prepared { ProjectSnapshot snapshot; QString destination; QUuid revision; }; bool begin(); void finish(const std::function &done, bool value); void saveCurrent(bool asNew, std::function then); // Swift's prepareSave: the document now, and where it goes. void prepareSave(bool asNew, std::function)> then); void write(const Prepared &prepared, std::function then); // A save still writing finishes first; then `then` runs. void finishWriting(std::function then); void load(const QString &path, std::function then); void confirmReplacement(std::function then); void showError(const QString &title, const QString &message, std::function then); // Swift's beginSheet: window-modal over the window; Escape cancels. QDialog *sheet(const QString &title); // Off the UI thread, then landed, skipped or explained. void resizeProject(std::function(const ProjectSnapshot &)> resize, std::function land, const QString &failure, std::function done); void exportPanel(const QString &title, const QString &filter, const QStringList &suffixes, const QString &name, const QString &failure, std::function)> then); void exportWork(std::function work, const QString &failure, std::function end); void drainIncoming(); void handle(const Incoming &request); std::optional ownTab() const; void noteExternalChange(); void checkExternalChange(); bool isFrontmost() const; void scheduleRecheck(); void reloadFromDisk(const QString &path, std::function then); void askToRevert(std::function then); int m_saveGeneration = 0; // Swift's `writing`: a save writes in the background. bool m_writing = false; std::vector> m_writeWaiters; std::deque m_incoming; bool m_processing = false; };