Centralize legacy document open bridge
This commit is contained in:
@@ -84,6 +84,8 @@ set(PP_PANOPAINTER_APP_SOURCES
|
|||||||
src/app_vr.cpp
|
src/app_vr.cpp
|
||||||
src/legacy_cloud_services.cpp
|
src/legacy_cloud_services.cpp
|
||||||
src/legacy_cloud_services.h
|
src/legacy_cloud_services.h
|
||||||
|
src/legacy_document_open_services.cpp
|
||||||
|
src/legacy_document_open_services.h
|
||||||
src/platform_legacy/legacy_platform_services.cpp
|
src/platform_legacy/legacy_platform_services.cpp
|
||||||
src/platform_legacy/legacy_platform_services.h
|
src/platform_legacy/legacy_platform_services.h
|
||||||
src/version.cpp
|
src/version.cpp
|
||||||
|
|||||||
@@ -583,10 +583,15 @@ Known local toolchain state:
|
|||||||
decisions, plus bulk upload progress visibility, zero-file, and clamped
|
decisions, plus bulk upload progress visibility, zero-file, and clamped
|
||||||
progress-total decisions.
|
progress-total decisions.
|
||||||
- `pp_app_core_document_session_tests` covers clean and dirty app session,
|
- `pp_app_core_document_session_tests` covers clean and dirty app session,
|
||||||
document-open action planning, save-request, save-before-workflow,
|
document-open action planning and executor dispatch/rejection, save-request,
|
||||||
new-document target/resolution/overwrite planning, document file target,
|
save-before-workflow, new-document target/resolution/overwrite planning,
|
||||||
combined save-file overwrite planning, and save-version target decisions
|
document file target, combined save-file overwrite planning, and save-version
|
||||||
without requiring a window, canvas, or message box.
|
target decisions without requiring a window, canvas, or message box.
|
||||||
|
- `src/legacy_document_open_services.*` is the current app-shell bridge between
|
||||||
|
`pp_app_core` document-open plans and live ABR/PPBR import prompts,
|
||||||
|
unsaved-project discard prompts, project opening, layer UI refresh, title
|
||||||
|
updates, and action-history clearing; remaining legacy execution ownership is
|
||||||
|
tracked by `DEBT-0039`.
|
||||||
- `src/legacy_history_services.*` is the current app-shell bridge between
|
- `src/legacy_history_services.*` is the current app-shell bridge between
|
||||||
`pp_app_core` history plans and legacy `ActionManager`; toolbar and
|
`pp_app_core` history plans and legacy `ActionManager`; toolbar and
|
||||||
`NodeCanvas` hotkeys share it while document-history extraction remains
|
`NodeCanvas` hotkeys share it while document-history extraction remains
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
|||||||
| DEBT-0036 | Open | Modernization | `pp_renderer_api`, `pp_paint_renderer`, `pano_cli plan-paint-feedback`, and `pano_cli plan-stroke-composite` can choose backend-neutral complex paint feedback strategies for fixed-function blending, framebuffer-fetch-capable renderers, or ping-pong render targets. OpenGL extension detection now stores `pp::renderer::RenderDeviceFeatures` through `ShaderManager`, using `pp_renderer_gl::render_device_features` as the backend conversion point. `pp_paint_renderer::plan_canvas_blend_gate` owns the compatibility mapping from persisted layer/brush blend indices to the extracted stroke-composite planner, and live `Canvas::draw_merge` plus `NodeCanvas` panorama rendering both call it with the stored renderer-neutral feature set for their existing shader-blend gates and destination-copy versus framebuffer-fetch decisions. `pp_paint_renderer::plan_canvas_stroke_feedback` also owns the current destination-feedback decision, and live `Canvas::stroke_draw`, thumbnail layer blending, and `NodeStrokePreview` brush-preview rendering use it for framebuffer-fetch versus destination-copy decisions. Actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, and brush-preview compositing still use legacy OpenGL canvas/UI execution | Preserve current painting behavior while the renderer boundary matures for OpenGL parity and later Vulkan/Metal experiments | `pp_renderer_api_tests`; `pp_renderer_gl_capabilities_tests`; `pp_paint_renderer_compositor_tests`; `pano_cli plan-paint-feedback --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-paint-feedback --texture-copy`; `pano_cli plan-stroke-composite --stroke-blend 10 --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-stroke-composite --layer-blend 4 --dual-blend --texture-copy`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Live stroke/layer compositing chooses its feedback path through `pp_paint_renderer` and renderer services, with OpenGL golden parity and Vulkan/Metal lab tests covering framebuffer-fetch and ping-pong behavior |
|
| DEBT-0036 | Open | Modernization | `pp_renderer_api`, `pp_paint_renderer`, `pano_cli plan-paint-feedback`, and `pano_cli plan-stroke-composite` can choose backend-neutral complex paint feedback strategies for fixed-function blending, framebuffer-fetch-capable renderers, or ping-pong render targets. OpenGL extension detection now stores `pp::renderer::RenderDeviceFeatures` through `ShaderManager`, using `pp_renderer_gl::render_device_features` as the backend conversion point. `pp_paint_renderer::plan_canvas_blend_gate` owns the compatibility mapping from persisted layer/brush blend indices to the extracted stroke-composite planner, and live `Canvas::draw_merge` plus `NodeCanvas` panorama rendering both call it with the stored renderer-neutral feature set for their existing shader-blend gates and destination-copy versus framebuffer-fetch decisions. `pp_paint_renderer::plan_canvas_stroke_feedback` also owns the current destination-feedback decision, and live `Canvas::stroke_draw`, thumbnail layer blending, and `NodeStrokePreview` brush-preview rendering use it for framebuffer-fetch versus destination-copy decisions. Actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, and brush-preview compositing still use legacy OpenGL canvas/UI execution | Preserve current painting behavior while the renderer boundary matures for OpenGL parity and later Vulkan/Metal experiments | `pp_renderer_api_tests`; `pp_renderer_gl_capabilities_tests`; `pp_paint_renderer_compositor_tests`; `pano_cli plan-paint-feedback --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-paint-feedback --texture-copy`; `pano_cli plan-stroke-composite --stroke-blend 10 --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-stroke-composite --layer-blend 4 --dual-blend --texture-copy`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Live stroke/layer compositing chooses its feedback path through `pp_paint_renderer` and renderer services, with OpenGL golden parity and Vulkan/Metal lab tests covering framebuffer-fetch and ping-pong behavior |
|
||||||
| DEBT-0037 | Open | Modernization | Recording lifecycle/export planning and execution dispatch now consume pure `pp_app_core` through `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, `pano_cli plan-recording-session`, and the `RecordingServices` boundary; live execution is centralized in `src/legacy_recording_services.*`, but the bridge still owns legacy recording thread startup/shutdown, platform recorded-file cleanup, progress UI, PBO readback through `App::rec_loop`, and `MP4Encoder::write_mp4` execution | Preserve current timelapse/MP4 behavior while recording moves toward app/document/renderer/video services | `pp_app_core_document_recording_tests`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-recording-session --platform-clears-files`; `ctest --preset desktop-fast --build-config Debug` | Recording thread lifecycle, frame readback, platform cleanup, progress reporting, and MP4 writing are owned by injected app/renderer/video services with `App` methods acting only as adapters |
|
| DEBT-0037 | Open | Modernization | Recording lifecycle/export planning and execution dispatch now consume pure `pp_app_core` through `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, `pano_cli plan-recording-session`, and the `RecordingServices` boundary; live execution is centralized in `src/legacy_recording_services.*`, but the bridge still owns legacy recording thread startup/shutdown, platform recorded-file cleanup, progress UI, PBO readback through `App::rec_loop`, and `MP4Encoder::write_mp4` execution | Preserve current timelapse/MP4 behavior while recording moves toward app/document/renderer/video services | `pp_app_core_document_recording_tests`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-recording-session --platform-clears-files`; `ctest --preset desktop-fast --build-config Debug` | Recording thread lifecycle, frame readback, platform cleanup, progress reporting, and MP4 writing are owned by injected app/renderer/video services with `App` methods acting only as adapters |
|
||||||
| DEBT-0038 | Open | Modernization | Cloud upload/browse/bulk planning and execution dispatch now consume pure `pp_app_core` through `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-upload-all`, `pano_cli plan-cloud-browse`, and the `CloudServices` boundary; live execution is centralized in `src/legacy_cloud_services.*`, but the bridge still uses legacy save-before-upload, `upload`/`download` network helpers, progress/message UI, OpenGL context guarding, `NodeDialogCloud`, `Canvas` project open, layer refresh, and `ActionManager` reset | Preserve current cloud behavior while cloud/network/document import flows move toward app/document/platform services | `pp_app_core_document_cloud_tests`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `ctest --preset desktop-fast --build-config Debug` | Cloud upload/download, save-before-upload, progress reporting, cloud browse dialog, downloaded project opening, layer refresh, OpenGL context ownership, and action-history reset are owned by injected app/document/network/platform/renderer services with `App` methods acting only as adapters |
|
| DEBT-0038 | Open | Modernization | Cloud upload/browse/bulk planning and execution dispatch now consume pure `pp_app_core` through `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-upload-all`, `pano_cli plan-cloud-browse`, and the `CloudServices` boundary; live execution is centralized in `src/legacy_cloud_services.*`, but the bridge still uses legacy save-before-upload, `upload`/`download` network helpers, progress/message UI, OpenGL context guarding, `NodeDialogCloud`, `Canvas` project open, layer refresh, and `ActionManager` reset | Preserve current cloud behavior while cloud/network/document import flows move toward app/document/platform services | `pp_app_core_document_cloud_tests`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `ctest --preset desktop-fast --build-config Debug` | Cloud upload/download, save-before-upload, progress reporting, cloud browse dialog, downloaded project opening, layer refresh, OpenGL context ownership, and action-history reset are owned by injected app/document/network/platform/renderer services with `App` methods acting only as adapters |
|
||||||
|
| DEBT-0039 | Open | Modernization | Document-open planning and execution dispatch now consume pure `pp_app_core` through `App::open_document`, `pano_cli plan-open-route`, `DocumentOpenServices`, and `src/legacy_document_open_services.*`, but the bridge still opens ABR/PPBR import prompts, launches legacy brush preset import threads, applies unsaved-project discard prompts, calls legacy project-open execution, refreshes layer UI, updates the app title, and clears legacy history directly | Preserve current file-open/import behavior while document loading and brush import move toward app/document/asset/UI services | `pp_app_core_document_route_tests`; `pp_app_core_document_session_tests`; `pano_cli plan-open-route --path D:/Paint/Scenes/demo.ppi --unsaved`; `pano_cli plan-open-route --path D:/Paint/Brushes/clouds.ABR --unsaved`; `ctest --preset desktop-fast --build-config Debug` | Brush import prompting/execution, project-open execution, unsaved-project discard prompting, layer refresh, title updates, and history clearing are owned by injected app/document/asset/UI services with `App::open_document` acting only as an adapter |
|
||||||
|
|
||||||
## Closed Debt
|
## Closed Debt
|
||||||
|
|
||||||
|
|||||||
@@ -728,6 +728,11 @@ layer-refresh, and action-history work remains tracked under `DEBT-0038`.
|
|||||||
`pano_cli parse-layout` exercises the XML layout path. Continue expanding
|
`pano_cli parse-layout` exercises the XML layout path. Continue expanding
|
||||||
document behavior toward legacy Canvas parity and then port OpenGL classes
|
document behavior toward legacy Canvas parity and then port OpenGL classes
|
||||||
behind the renderer boundary.
|
behind the renderer boundary.
|
||||||
|
`App::open_document` now routes through the app-core document-open executor and
|
||||||
|
`src/legacy_document_open_services.*`, preserving ABR/PPBR import prompts,
|
||||||
|
unsaved-project discard prompts, project open, layer refresh, title updates,
|
||||||
|
and history clearing while those live effects remain tracked under
|
||||||
|
`DEBT-0039`.
|
||||||
|
|
||||||
Implementation tasks:
|
Implementation tasks:
|
||||||
|
|
||||||
@@ -1260,6 +1265,13 @@ Results:
|
|||||||
- `scripts/automation/package-smoke.ps1 -Preset windows-msvc-default
|
- `scripts/automation/package-smoke.ps1 -Preset windows-msvc-default
|
||||||
-Configuration Debug` passed executable/data checks after the cloud bridge
|
-Configuration Debug` passed executable/data checks after the cloud bridge
|
||||||
split; package target migration blockers remain under `DEBT-0011`.
|
split; package target migration blockers remain under `DEBT-0011`.
|
||||||
|
- `PanoPainter`, `pp_app_core_document_session_tests`, and `pano_cli` built
|
||||||
|
after `App::open_document` moved live execution behind the document-open
|
||||||
|
services bridge. A clean rebuild was required once because MSVC reported the
|
||||||
|
known Debug PDB `LNK1103` corruption, after which the build passed.
|
||||||
|
- Focused document-open CTest coverage passed for
|
||||||
|
`pp_app_core_document_route_tests`, `pp_app_core_document_session_tests`, and
|
||||||
|
the `pano_cli_plan_open_route_*` smoke tests after the live bridge split.
|
||||||
- `pp_app_core_document_recording_tests` passed, covering recording start/stop,
|
- `pp_app_core_document_recording_tests` passed, covering recording start/stop,
|
||||||
clear, platform recorded-file cleanup, frame-count reset, export progress
|
clear, platform recorded-file cleanup, frame-count reset, export progress
|
||||||
totals, and oversized progress-total clamping.
|
totals, and oversized progress-total clamping.
|
||||||
|
|||||||
64
src/app.cpp
64
src/app.cpp
@@ -10,7 +10,7 @@
|
|||||||
#include "app_core/document_recording.h"
|
#include "app_core/document_recording.h"
|
||||||
#include "app_core/document_route.h"
|
#include "app_core/document_route.h"
|
||||||
#include "app_core/document_session.h"
|
#include "app_core/document_session.h"
|
||||||
#include "legacy_history_services.h"
|
#include "legacy_document_open_services.h"
|
||||||
#include "legacy_recording_services.h"
|
#include "legacy_recording_services.h"
|
||||||
#include "platform_api/platform_services.h"
|
#include "platform_api/platform_services.h"
|
||||||
#include "renderer_gl/opengl_capabilities.h"
|
#include "renderer_gl/opengl_capabilities.h"
|
||||||
@@ -197,65 +197,9 @@ void App::open_document(std::string path)
|
|||||||
const bool has_unsaved_project =
|
const bool has_unsaved_project =
|
||||||
route.value().kind == pp::app::DocumentOpenKind::open_project && Canvas::I->m_unsaved;
|
route.value().kind == pp::app::DocumentOpenKind::open_project && Canvas::I->m_unsaved;
|
||||||
const auto open_plan = pp::app::plan_document_open(route.value().kind, has_unsaved_project);
|
const auto open_plan = pp::app::plan_document_open(route.value().kind, has_unsaved_project);
|
||||||
if (open_plan == pp::app::DocumentOpenPlanAction::prompt_import_abr)
|
const auto status = pp::panopainter::execute_legacy_document_open_plan(*this, open_plan, route.value());
|
||||||
{
|
if (!status.ok())
|
||||||
auto mb = message_box("Import ABR", "Would you like to import the brushes?", true);
|
LOG("Document open action failed: %s", status.message);
|
||||||
mb->on_submit = [this, path] (Node* target) {
|
|
||||||
std::thread(&NodePanelBrushPreset::import_abr, presets, path).detach();
|
|
||||||
target->destroy();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (open_plan == pp::app::DocumentOpenPlanAction::prompt_import_ppbr)
|
|
||||||
{
|
|
||||||
auto mb = message_box("Import PPBR", "Would you like to import the brushes?", true);
|
|
||||||
mb->on_submit = [this, path] (Node* target) {
|
|
||||||
std::thread(&NodePanelBrushPreset::import_ppbr, presets, path).detach();
|
|
||||||
target->destroy();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const std::string base = route.value().directory;
|
|
||||||
const std::string name = route.value().name;
|
|
||||||
auto open_action = [this, path, base, name] {
|
|
||||||
doc_name = name;
|
|
||||||
doc_dir = base;
|
|
||||||
doc_path = path;
|
|
||||||
canvas->reset_camera();
|
|
||||||
layers->clear();
|
|
||||||
canvas->m_canvas->project_open(path, [this](bool success){
|
|
||||||
// on complete
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
title_update();
|
|
||||||
for (int layer_index = 0; layer_index < canvas->m_canvas->m_layers.size(); layer_index++)
|
|
||||||
{
|
|
||||||
auto l = layers->add_layer(canvas->m_canvas->m_layers[layer_index]->m_name.c_str(), false);
|
|
||||||
l->m_visibility->set_value(canvas->m_canvas->m_layers[layer_index]->m_visible);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
message_box("Open Document Error",
|
|
||||||
"There was an error opening the document.\n"
|
|
||||||
"It may be inaccessible or corrupted.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
pp::panopainter::clear_legacy_history();
|
|
||||||
};
|
|
||||||
if (open_plan == pp::app::DocumentOpenPlanAction::open_project_now)
|
|
||||||
{
|
|
||||||
open_action();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto mb = message_box("Unsaved document", "Do you want to close the unsaved document before opening the file?", true);
|
|
||||||
mb->on_submit = [this, open_action] (Node* target) {
|
|
||||||
open_action();
|
|
||||||
target->destroy();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool App::request_close()
|
bool App::request_close()
|
||||||
|
|||||||
@@ -55,6 +55,16 @@ enum class DocumentOpenPlanAction {
|
|||||||
prompt_import_ppbr,
|
prompt_import_ppbr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DocumentOpenServices {
|
||||||
|
public:
|
||||||
|
virtual ~DocumentOpenServices() = default;
|
||||||
|
|
||||||
|
virtual void prompt_import_abr(const DocumentOpenRoute& route) = 0;
|
||||||
|
virtual void prompt_import_ppbr(const DocumentOpenRoute& route) = 0;
|
||||||
|
virtual void open_project_now(const DocumentOpenRoute& route) = 0;
|
||||||
|
virtual void prompt_discard_unsaved_project(const DocumentOpenRoute& route) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct DocumentFileTarget {
|
struct DocumentFileTarget {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string directory;
|
std::string directory;
|
||||||
@@ -102,6 +112,41 @@ struct NewDocumentPlan {
|
|||||||
return DocumentOpenPlanAction::open_project_now;
|
return DocumentOpenPlanAction::open_project_now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline pp::foundation::Status execute_document_open_plan(
|
||||||
|
DocumentOpenPlanAction action,
|
||||||
|
const DocumentOpenRoute& route,
|
||||||
|
DocumentOpenServices& services)
|
||||||
|
{
|
||||||
|
switch (action) {
|
||||||
|
case DocumentOpenPlanAction::open_project_now:
|
||||||
|
if (route.kind != DocumentOpenKind::open_project) {
|
||||||
|
return pp::foundation::Status::invalid_argument("open-project action requires a project route");
|
||||||
|
}
|
||||||
|
services.open_project_now(route);
|
||||||
|
return pp::foundation::Status::success();
|
||||||
|
case DocumentOpenPlanAction::prompt_discard_unsaved_project:
|
||||||
|
if (route.kind != DocumentOpenKind::open_project) {
|
||||||
|
return pp::foundation::Status::invalid_argument("discard prompt requires a project route");
|
||||||
|
}
|
||||||
|
services.prompt_discard_unsaved_project(route);
|
||||||
|
return pp::foundation::Status::success();
|
||||||
|
case DocumentOpenPlanAction::prompt_import_abr:
|
||||||
|
if (route.kind != DocumentOpenKind::import_abr) {
|
||||||
|
return pp::foundation::Status::invalid_argument("ABR import prompt requires an ABR route");
|
||||||
|
}
|
||||||
|
services.prompt_import_abr(route);
|
||||||
|
return pp::foundation::Status::success();
|
||||||
|
case DocumentOpenPlanAction::prompt_import_ppbr:
|
||||||
|
if (route.kind != DocumentOpenKind::import_ppbr) {
|
||||||
|
return pp::foundation::Status::invalid_argument("PPBR import prompt requires a PPBR route");
|
||||||
|
}
|
||||||
|
services.prompt_import_ppbr(route);
|
||||||
|
return pp::foundation::Status::success();
|
||||||
|
}
|
||||||
|
|
||||||
|
return pp::foundation::Status::invalid_argument("unknown document open action");
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr CloseRequestDecision plan_close_request(
|
[[nodiscard]] constexpr CloseRequestDecision plan_close_request(
|
||||||
bool has_unsaved_changes,
|
bool has_unsaved_changes,
|
||||||
bool close_prompt_already_open) noexcept
|
bool close_prompt_already_open) noexcept
|
||||||
|
|||||||
101
src/legacy_document_open_services.cpp
Normal file
101
src/legacy_document_open_services.cpp
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "legacy_document_open_services.h"
|
||||||
|
|
||||||
|
#include "app.h"
|
||||||
|
#include "legacy_history_services.h"
|
||||||
|
#include "node_panel_brush.h"
|
||||||
|
#include "node_panel_layer.h"
|
||||||
|
|
||||||
|
namespace pp::panopainter {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void open_legacy_project(App& app, const pp::app::DocumentOpenRoute& route)
|
||||||
|
{
|
||||||
|
app.doc_name = route.name;
|
||||||
|
app.doc_dir = route.directory;
|
||||||
|
app.doc_path = route.path;
|
||||||
|
app.canvas->reset_camera();
|
||||||
|
app.layers->clear();
|
||||||
|
app.canvas->m_canvas->project_open(route.path, [&app](bool success) {
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
app.title_update();
|
||||||
|
for (std::size_t layer_index = 0; layer_index < app.canvas->m_canvas->m_layers.size(); ++layer_index)
|
||||||
|
{
|
||||||
|
auto layer = app.layers->add_layer(app.canvas->m_canvas->m_layers[layer_index]->m_name.c_str(), false);
|
||||||
|
layer->m_visibility->set_value(app.canvas->m_canvas->m_layers[layer_index]->m_visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
app.message_box(
|
||||||
|
"Open Document Error",
|
||||||
|
"There was an error opening the document.\n"
|
||||||
|
"It may be inaccessible or corrupted.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pp::panopainter::clear_legacy_history();
|
||||||
|
}
|
||||||
|
|
||||||
|
class LegacyDocumentOpenServices final : public pp::app::DocumentOpenServices {
|
||||||
|
public:
|
||||||
|
explicit LegacyDocumentOpenServices(App& app) noexcept
|
||||||
|
: app_(app)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void prompt_import_abr(const pp::app::DocumentOpenRoute& route) override
|
||||||
|
{
|
||||||
|
auto* app = &app_;
|
||||||
|
auto mb = app_.message_box("Import ABR", "Would you like to import the brushes?", true);
|
||||||
|
mb->on_submit = [app, path = route.path](Node* target) {
|
||||||
|
std::thread(&NodePanelBrushPreset::import_abr, app->presets, path).detach();
|
||||||
|
target->destroy();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void prompt_import_ppbr(const pp::app::DocumentOpenRoute& route) override
|
||||||
|
{
|
||||||
|
auto* app = &app_;
|
||||||
|
auto mb = app_.message_box("Import PPBR", "Would you like to import the brushes?", true);
|
||||||
|
mb->on_submit = [app, path = route.path](Node* target) {
|
||||||
|
std::thread(&NodePanelBrushPreset::import_ppbr, app->presets, path).detach();
|
||||||
|
target->destroy();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void open_project_now(const pp::app::DocumentOpenRoute& route) override
|
||||||
|
{
|
||||||
|
open_legacy_project(app_, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
void prompt_discard_unsaved_project(const pp::app::DocumentOpenRoute& route) override
|
||||||
|
{
|
||||||
|
auto* app = &app_;
|
||||||
|
auto mb = app_.message_box(
|
||||||
|
"Unsaved document",
|
||||||
|
"Do you want to close the unsaved document before opening the file?",
|
||||||
|
true);
|
||||||
|
mb->on_submit = [app, route](Node* target) {
|
||||||
|
open_legacy_project(*app, route);
|
||||||
|
target->destroy();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
App& app_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
pp::foundation::Status execute_legacy_document_open_plan(
|
||||||
|
App& app,
|
||||||
|
pp::app::DocumentOpenPlanAction action,
|
||||||
|
const pp::app::DocumentOpenRoute& route)
|
||||||
|
{
|
||||||
|
LegacyDocumentOpenServices services(app);
|
||||||
|
return pp::app::execute_document_open_plan(action, route, services);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pp::panopainter
|
||||||
15
src/legacy_document_open_services.h
Normal file
15
src/legacy_document_open_services.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "app_core/document_session.h"
|
||||||
|
#include "foundation/result.h"
|
||||||
|
|
||||||
|
class App;
|
||||||
|
|
||||||
|
namespace pp::panopainter {
|
||||||
|
|
||||||
|
[[nodiscard]] pp::foundation::Status execute_legacy_document_open_plan(
|
||||||
|
App& app,
|
||||||
|
pp::app::DocumentOpenPlanAction action,
|
||||||
|
const pp::app::DocumentOpenRoute& route);
|
||||||
|
|
||||||
|
} // namespace pp::panopainter
|
||||||
@@ -1,8 +1,81 @@
|
|||||||
#include "app_core/document_session.h"
|
#include "app_core/document_session.h"
|
||||||
#include "test_harness.h"
|
#include "test_harness.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
class FakeDocumentOpenServices final : public pp::app::DocumentOpenServices {
|
||||||
|
public:
|
||||||
|
void prompt_import_abr(const pp::app::DocumentOpenRoute& route) override
|
||||||
|
{
|
||||||
|
abr_prompts += 1;
|
||||||
|
last_path = route.path;
|
||||||
|
call_order += "abr;";
|
||||||
|
}
|
||||||
|
|
||||||
|
void prompt_import_ppbr(const pp::app::DocumentOpenRoute& route) override
|
||||||
|
{
|
||||||
|
ppbr_prompts += 1;
|
||||||
|
last_path = route.path;
|
||||||
|
call_order += "ppbr;";
|
||||||
|
}
|
||||||
|
|
||||||
|
void open_project_now(const pp::app::DocumentOpenRoute& route) override
|
||||||
|
{
|
||||||
|
project_opens += 1;
|
||||||
|
last_path = route.path;
|
||||||
|
call_order += "open;";
|
||||||
|
}
|
||||||
|
|
||||||
|
void prompt_discard_unsaved_project(const pp::app::DocumentOpenRoute& route) override
|
||||||
|
{
|
||||||
|
discard_prompts += 1;
|
||||||
|
last_path = route.path;
|
||||||
|
call_order += "discard;";
|
||||||
|
}
|
||||||
|
|
||||||
|
int abr_prompts = 0;
|
||||||
|
int ppbr_prompts = 0;
|
||||||
|
int project_opens = 0;
|
||||||
|
int discard_prompts = 0;
|
||||||
|
std::string last_path;
|
||||||
|
std::string call_order;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] pp::app::DocumentOpenRoute project_route()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
.kind = pp::app::DocumentOpenKind::open_project,
|
||||||
|
.path = "D:/Paint/demo.ppi",
|
||||||
|
.directory = "D:/Paint",
|
||||||
|
.name = "demo",
|
||||||
|
.extension = "ppi",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] pp::app::DocumentOpenRoute abr_route()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
.kind = pp::app::DocumentOpenKind::import_abr,
|
||||||
|
.path = "D:/Paint/clouds.abr",
|
||||||
|
.directory = "D:/Paint",
|
||||||
|
.name = "clouds",
|
||||||
|
.extension = "abr",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] pp::app::DocumentOpenRoute ppbr_route()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
.kind = pp::app::DocumentOpenKind::import_ppbr,
|
||||||
|
.path = "D:/Paint/brush.ppbr",
|
||||||
|
.directory = "D:/Paint",
|
||||||
|
.name = "brush",
|
||||||
|
.extension = "ppbr",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void project_open_clean_document_executes_immediately(pp::tests::Harness& harness)
|
void project_open_clean_document_executes_immediately(pp::tests::Harness& harness)
|
||||||
{
|
{
|
||||||
PP_EXPECT(harness, pp::app::plan_project_open(false) == pp::app::ProjectOpenDecision::open_now);
|
PP_EXPECT(harness, pp::app::plan_project_open(false) == pp::app::ProjectOpenDecision::open_now);
|
||||||
@@ -47,6 +120,78 @@ void document_open_brush_imports_prompt_regardless_of_unsaved_state(pp::tests::H
|
|||||||
== pp::app::DocumentOpenPlanAction::prompt_import_ppbr);
|
== pp::app::DocumentOpenPlanAction::prompt_import_ppbr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void document_open_executor_dispatches_all_actions(pp::tests::Harness& harness)
|
||||||
|
{
|
||||||
|
FakeDocumentOpenServices services;
|
||||||
|
|
||||||
|
const auto project = project_route();
|
||||||
|
const auto abr = abr_route();
|
||||||
|
const auto ppbr = ppbr_route();
|
||||||
|
PP_EXPECT(
|
||||||
|
harness,
|
||||||
|
pp::app::execute_document_open_plan(
|
||||||
|
pp::app::DocumentOpenPlanAction::open_project_now,
|
||||||
|
project,
|
||||||
|
services)
|
||||||
|
.ok());
|
||||||
|
PP_EXPECT(
|
||||||
|
harness,
|
||||||
|
pp::app::execute_document_open_plan(
|
||||||
|
pp::app::DocumentOpenPlanAction::prompt_discard_unsaved_project,
|
||||||
|
project,
|
||||||
|
services)
|
||||||
|
.ok());
|
||||||
|
PP_EXPECT(
|
||||||
|
harness,
|
||||||
|
pp::app::execute_document_open_plan(
|
||||||
|
pp::app::DocumentOpenPlanAction::prompt_import_abr,
|
||||||
|
abr,
|
||||||
|
services)
|
||||||
|
.ok());
|
||||||
|
PP_EXPECT(
|
||||||
|
harness,
|
||||||
|
pp::app::execute_document_open_plan(
|
||||||
|
pp::app::DocumentOpenPlanAction::prompt_import_ppbr,
|
||||||
|
ppbr,
|
||||||
|
services)
|
||||||
|
.ok());
|
||||||
|
|
||||||
|
PP_EXPECT(harness, services.project_opens == 1);
|
||||||
|
PP_EXPECT(harness, services.discard_prompts == 1);
|
||||||
|
PP_EXPECT(harness, services.abr_prompts == 1);
|
||||||
|
PP_EXPECT(harness, services.ppbr_prompts == 1);
|
||||||
|
PP_EXPECT(harness, services.last_path == "D:/Paint/brush.ppbr");
|
||||||
|
PP_EXPECT(harness, services.call_order == "open;discard;abr;ppbr;");
|
||||||
|
}
|
||||||
|
|
||||||
|
void document_open_executor_rejects_mismatched_routes(pp::tests::Harness& harness)
|
||||||
|
{
|
||||||
|
FakeDocumentOpenServices services;
|
||||||
|
|
||||||
|
PP_EXPECT(
|
||||||
|
harness,
|
||||||
|
!pp::app::execute_document_open_plan(
|
||||||
|
pp::app::DocumentOpenPlanAction::open_project_now,
|
||||||
|
abr_route(),
|
||||||
|
services)
|
||||||
|
.ok());
|
||||||
|
PP_EXPECT(
|
||||||
|
harness,
|
||||||
|
!pp::app::execute_document_open_plan(
|
||||||
|
pp::app::DocumentOpenPlanAction::prompt_import_abr,
|
||||||
|
project_route(),
|
||||||
|
services)
|
||||||
|
.ok());
|
||||||
|
PP_EXPECT(
|
||||||
|
harness,
|
||||||
|
!pp::app::execute_document_open_plan(
|
||||||
|
pp::app::DocumentOpenPlanAction::prompt_import_ppbr,
|
||||||
|
abr_route(),
|
||||||
|
services)
|
||||||
|
.ok());
|
||||||
|
PP_EXPECT(harness, services.call_order.empty());
|
||||||
|
}
|
||||||
|
|
||||||
void close_clean_document_executes_immediately(pp::tests::Harness& harness)
|
void close_clean_document_executes_immediately(pp::tests::Harness& harness)
|
||||||
{
|
{
|
||||||
PP_EXPECT(
|
PP_EXPECT(
|
||||||
@@ -335,6 +480,8 @@ int main()
|
|||||||
harness.run(
|
harness.run(
|
||||||
"document open brush imports prompt regardless of unsaved state",
|
"document open brush imports prompt regardless of unsaved state",
|
||||||
document_open_brush_imports_prompt_regardless_of_unsaved_state);
|
document_open_brush_imports_prompt_regardless_of_unsaved_state);
|
||||||
|
harness.run("document open executor dispatches all actions", document_open_executor_dispatches_all_actions);
|
||||||
|
harness.run("document open executor rejects mismatched routes", document_open_executor_rejects_mismatched_routes);
|
||||||
harness.run("close clean document executes immediately", close_clean_document_executes_immediately);
|
harness.run("close clean document executes immediately", close_clean_document_executes_immediately);
|
||||||
harness.run("close dirty document opens one prompt", close_dirty_document_opens_one_prompt);
|
harness.run("close dirty document opens one prompt", close_dirty_document_opens_one_prompt);
|
||||||
harness.run("save clean existing document is no op", save_clean_existing_document_is_no_op);
|
harness.run("save clean existing document is no op", save_clean_existing_document_is_no_op);
|
||||||
|
|||||||
Reference in New Issue
Block a user