Extract file menu action planning
This commit is contained in:
@@ -239,6 +239,7 @@ add_library(pp_app_core STATIC
|
||||
src/app_core/document_route.cpp
|
||||
src/app_core/document_sharing.h
|
||||
src/app_core/document_session.cpp
|
||||
src/app_core/file_menu.h
|
||||
src/app_core/grid_ui.h
|
||||
src/app_core/history_ui.h
|
||||
src/app_core/quick_ui.h)
|
||||
|
||||
@@ -22,7 +22,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| DEBT-0001 | Open | Modernization | Existing platform build files remain alongside new CMake | Required for incremental migration without losing platform coverage | Existing platform builds plus new CMake configure | Remove after all platform builds consume shared CMake targets |
|
||||
| DEBT-0002 | Open | Modernization | Vendored SDK and patched libraries retained initially | Some dependencies are SDK-only, patched, or have platform-specific binaries | Dependency inventory and platform build smoke tests | Replace with vcpkg packages or document permanent vendored status after triplet evaluation |
|
||||
| DEBT-0003 | Open | Modernization | Existing singletons remain during initial split; `App::open_document`, `App::request_close`, `App::share_file`, `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, file-menu save actions, `NodeCanvas` save hotkeys, new/open/browse dirty-document workflow prompts, new-document target/resolution/overwrite decisions, save-as document file naming and overwrite decisions, save-version target decisions, export start/menu/target naming/path decisions, share-file saved-path decisions, file/image/save/directory picker selected-path decisions, display-file external-open decisions, virtual-keyboard visibility decisions, recording lifecycle/export progress decisions, cloud-upload prompt/save-before-upload decisions, cloud-browse availability and selected-download decisions, bulk cloud-upload progress decisions, tools/options app preference decisions, app status/display decisions, document resize decisions, layer rename decisions, `pano_cli classify-open`, `pano_cli plan-open-route`, `pano_cli plan-new-document`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-export-start`, `pano_cli plan-export-menu`, `pano_cli plan-export-target`, `pano_cli plan-recording-session`, `pano_cli plan-app-preferences`, `pano_cli plan-app-status`, `pano_cli plan-document-resize`, `pano_cli plan-layer-rename`, `pano_cli plan-share-file`, `pano_cli plan-picked-path`, `pano_cli plan-display-file`, `pano_cli plan-keyboard-visibility`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-browse`, `pano_cli plan-cloud-upload-all`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session/export/recording/preferences/status/share/platform-I/O/display/keyboard/cloud/resize/layer contracts, but document creation/loading, brush import execution, saving, export execution, tools/options UI execution, status/display UI rendering, document resize execution, layer rename execution, settings persistence, platform share service execution, picker service execution, display-file service execution, keyboard service execution, recording/MP4 execution, cloud upload execution, and cloud browse/download execution still reach legacy `Canvas::I`/UI/network/video/platform singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_recording_tests`; `pp_app_core_app_preferences_tests`; `pp_app_core_app_status_tests`; `pp_app_core_document_resize_tests`; `pp_app_core_document_layer_tests`; `pp_app_core_document_sharing_tests`; `pp_app_core_document_platform_io_tests`; `pp_app_core_document_cloud_tests`; `pp_app_core_document_session_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli plan-open-route --path D:/Paint/demo.ppi --unsaved`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3 --target-exists`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind animation-mp4 --demo`; `pano_cli plan-export-target --kind file --work-dir D:/Paint --doc-name demo --extension .png`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-app-status --doc-name demo --unsaved --resolution 2048 --resolution-index 3 --zoom 1.25 --history-bytes 1572864 --recording-running --encoder-available --encoded-frames 12`; `pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4`; `pano_cli plan-layer-rename --old-name Base --new-name Paint`; `pano_cli plan-share-file --path D:/Paint/demo.ppi`; `pano_cli plan-picked-path --path D:/Paint/demo.ppi`; `pano_cli plan-display-file --path D:/Paint/export.png`; `pano_cli plan-keyboard-visibility --visible`; `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`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries |
|
||||
| DEBT-0003 | Open | Modernization | Existing singletons remain during initial split; `App::open_document`, `App::request_close`, `App::share_file`, `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, file-menu save actions, `NodeCanvas` save hotkeys, new/open/browse dirty-document workflow prompts, new-document target/resolution/overwrite decisions, save-as document file naming and overwrite decisions, save-version target decisions, export start/menu/target naming/path decisions, share-file saved-path decisions, file/image/save/directory picker selected-path decisions, display-file external-open decisions, virtual-keyboard visibility decisions, recording lifecycle/export progress decisions, cloud-upload prompt/save-before-upload decisions, cloud-browse availability and selected-download decisions, bulk cloud-upload progress decisions, tools/options app preference decisions, app status/display decisions, document resize decisions, layer rename decisions, `pano_cli classify-open`, `pano_cli plan-open-route`, `pano_cli plan-file-menu`, `pano_cli plan-new-document`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-export-start`, `pano_cli plan-export-menu`, `pano_cli plan-export-target`, `pano_cli plan-recording-session`, `pano_cli plan-app-preferences`, `pano_cli plan-app-status`, `pano_cli plan-document-resize`, `pano_cli plan-layer-rename`, `pano_cli plan-share-file`, `pano_cli plan-picked-path`, `pano_cli plan-display-file`, `pano_cli plan-keyboard-visibility`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-browse`, `pano_cli plan-cloud-upload-all`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session/export/recording/preferences/status/share/platform-I/O/display/keyboard/cloud/resize/layer contracts, but document creation/loading, brush import execution, saving, export execution, tools/options UI execution, status/display UI rendering, document resize execution, layer rename execution, settings persistence, platform share service execution, picker service execution, display-file service execution, keyboard service execution, recording/MP4 execution, cloud upload execution, and cloud browse/download execution still reach legacy `Canvas::I`/UI/network/video/platform singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_file_menu_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_recording_tests`; `pp_app_core_app_preferences_tests`; `pp_app_core_app_status_tests`; `pp_app_core_document_resize_tests`; `pp_app_core_document_layer_tests`; `pp_app_core_document_sharing_tests`; `pp_app_core_document_platform_io_tests`; `pp_app_core_document_cloud_tests`; `pp_app_core_document_session_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli plan-open-route --path D:/Paint/demo.ppi --unsaved`; `pano_cli plan-file-menu --command save-as`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3 --target-exists`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind animation-mp4 --demo`; `pano_cli plan-export-target --kind file --work-dir D:/Paint --doc-name demo --extension .png`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-app-status --doc-name demo --unsaved --resolution 2048 --resolution-index 3 --zoom 1.25 --history-bytes 1572864 --recording-running --encoder-available --encoded-frames 12`; `pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4`; `pano_cli plan-layer-rename --old-name Base --new-name Paint`; `pano_cli plan-share-file --path D:/Paint/demo.ppi`; `pano_cli plan-picked-path --path D:/Paint/demo.ppi`; `pano_cli plan-display-file --path D:/Paint/export.png`; `pano_cli plan-keyboard-visibility --visible`; `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`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries |
|
||||
| DEBT-0004 | Open | Modernization | Android, Linux, WebGL, Apple, and AppX build files remain platform-specific until root CMake alignment reaches them | Prevent platform regressions during incremental migration; raw Windows `.sln/.vcxproj` files were removed on 2026-05-31 by user decision | `cmake --preset windows-msvc-default`; platform-specific configure/build smoke checks as each platform is migrated | Root CMake owns every platform source list and package path |
|
||||
| DEBT-0005 | Open | Modernization | Temporary local CTest harness is used before Catch2 is wired through vcpkg | `vcpkg` is not currently on PATH, but headless tests need to run now | `ctest --preset desktop-fast --build-config Debug` | Replace `tests/test_harness.h` tests with Catch2 tests once vcpkg toolchain/presets are validated |
|
||||
| DEBT-0007 | Open | Modernization | `vcpkg.json` and `windows-msvc-vcpkg-headless` are validated for the headless Windows component matrix, but app targets still use vendored libraries and Android/Apple triplets are not proven | Dependency migration must stay incremental while SDK/patched/vendor dependencies remain in use | `$env:VCPKG_ROOT="C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg"; cmake --preset windows-msvc-vcpkg-headless`; `ctest --preset desktop-fast-vcpkg --build-config Debug` | Component targets consume vcpkg packages where reliable and desktop app, Android, and Apple triplets are validated or explicitly documented as permanent vendor exceptions |
|
||||
@@ -48,6 +48,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| DEBT-0028 | Open | Modernization | Canvas clear command planning now consumes pure `pp_app_core` through `App::init_toolbar_main` and `pano_cli plan-canvas-clear`, but live execution still calls legacy `Canvas::clear`, which records `ActionLayerClear`, clears the current layer/frame, and marks legacy `Canvas::I` unsaved directly | Preserve clear-current-layer behavior while canvas/document commands move toward document/app command services | `pp_app_core_document_canvas_tests`; `pano_cli plan-canvas-clear --r 0 --g 0.1 --b 0.2 --a 0.3`; `pano_cli plan-canvas-clear --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Canvas clear execution, undo recording, dirty-state updates, and clear color handling are owned by document/app services with toolbar callbacks acting only as adapters |
|
||||
| DEBT-0029 | Open | Modernization | Image import route planning now consumes pure `pp_app_core` through the File menu and `pano_cli plan-image-import`, but live execution still calls legacy `Canvas::import_equirectangular` or legacy import transform mode setup directly after image loading | Preserve current File > Import behavior while image import moves toward document/app/asset command services | `pp_app_core_document_import_tests`; `pano_cli plan-image-import --width 4096 --height 2048`; `pano_cli plan-image-import --width 1024 --height 1024`; `ctest --preset desktop-fast --build-config Debug` | Image loading, equirectangular import, transform-placement import, and failure reporting are owned by document/app/asset services with File-menu callbacks acting only as adapters |
|
||||
| DEBT-0030 | Open | Modernization | File export menu action planning now consumes pure `pp_app_core` through the File menu and `pano_cli plan-export-menu`, but live execution still opens legacy export dialogs and then reaches legacy canvas/render/video export code | Preserve current export menu behavior while export command execution moves toward document/app/renderer/video services | `pp_app_core_document_export_tests`; `pano_cli plan-export-menu --kind png`; `pano_cli plan-export-menu --kind animation-mp4 --demo`; `pano_cli plan-export-menu --kind layers --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Export menu routing, license gating, target creation, image/layer/cube/depth/animation/timelapse execution, and error reporting are owned by document/app services with File-menu callbacks acting only as adapters |
|
||||
| DEBT-0031 | Open | Modernization | Top-level File menu command planning now consumes pure `pp_app_core` through `App::init_menu_file` and `pano_cli plan-file-menu`, but live execution still invokes legacy dialogs, platform pickers, cloud code, share code, and canvas import/export paths directly | Preserve File menu behavior while app workflows move toward app/document/platform command services | `pp_app_core_file_menu_tests`; `pano_cli plan-file-menu --command save-as`; `pano_cli plan-file-menu --command import`; `pano_cli plan-file-menu --command cloud-upload`; `ctest --preset desktop-fast --build-config Debug` | File menu routing, picker dispatch, save/share/cloud/resize/export execution, and image/project import execution are owned by app/document/platform services with `App::init_menu_file` acting only as a UI adapter |
|
||||
|
||||
## Closed Debt
|
||||
|
||||
|
||||
@@ -516,6 +516,10 @@ route decisions, including wide equirectangular images, legacy vertical cube
|
||||
strips, regular transform-placement images, and invalid image dimensions before
|
||||
legacy `Canvas::import_equirectangular` or import transform-mode execution
|
||||
continues.
|
||||
`pano_cli plan-file-menu` exposes app-core planning for the top-level File menu
|
||||
commands, including new/open/import, save/save-as/save-version, share, resize,
|
||||
cloud upload/browse, JPEG export, and export-submenu routing before legacy
|
||||
dialogs, pickers, platform services, cloud code, and canvas workflows continue.
|
||||
`pano_cli plan-export-menu` exposes app-core planning for File menu export
|
||||
choices, including image, layer, cube-face, depth, animation-frame, MP4, and
|
||||
timelapse dialog routing plus license/canvas gating before legacy export dialogs
|
||||
@@ -1207,6 +1211,15 @@ Results:
|
||||
`pano_cli_plan_image_import_transform_smoke`, and
|
||||
`pano_cli_plan_image_import_rejects_invalid_dimensions` passed and expose File
|
||||
> Import route planning as JSON automation.
|
||||
- `pp_app_core_file_menu_tests` passed, covering top-level File menu routing for
|
||||
creation/open/import, save intents, export/submenu/cloud actions, and unknown
|
||||
command rejection.
|
||||
- `pano_cli_plan_file_menu_import_smoke`,
|
||||
`pano_cli_plan_file_menu_save_as_smoke`,
|
||||
`pano_cli_plan_file_menu_export_smoke`,
|
||||
`pano_cli_plan_file_menu_cloud_upload_smoke`, and
|
||||
`pano_cli_plan_file_menu_rejects_unknown` passed and expose top-level File
|
||||
menu routing as JSON automation.
|
||||
- `pp_app_core_document_export_tests` passed, now also covering export menu
|
||||
dialog routing, demo-mode MP4/timelapse license gating, and missing-canvas
|
||||
handling before legacy export dialogs continue.
|
||||
|
||||
149
src/app_core/file_menu.h
Normal file
149
src/app_core/file_menu.h
Normal file
@@ -0,0 +1,149 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_core/document_export.h"
|
||||
#include "app_core/document_session.h"
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class FileMenuCommand {
|
||||
new_document,
|
||||
import_image,
|
||||
open_project,
|
||||
browse_cloud,
|
||||
save,
|
||||
save_as,
|
||||
save_version,
|
||||
export_jpeg,
|
||||
export_submenu,
|
||||
share,
|
||||
resize,
|
||||
cloud_upload,
|
||||
cloud_browse,
|
||||
};
|
||||
|
||||
enum class FileMenuAction {
|
||||
show_new_document_dialog,
|
||||
pick_image_for_import,
|
||||
pick_project_file,
|
||||
show_cloud_browser_dialog,
|
||||
save_document,
|
||||
show_export_jpeg_dialog,
|
||||
show_export_submenu,
|
||||
share_document,
|
||||
show_resize_dialog,
|
||||
upload_to_cloud,
|
||||
browse_cloud_documents,
|
||||
};
|
||||
|
||||
struct FileMenuPlan {
|
||||
FileMenuCommand command = FileMenuCommand::new_document;
|
||||
FileMenuAction action = FileMenuAction::show_new_document_dialog;
|
||||
DocumentSaveIntent save_intent = DocumentSaveIntent::save;
|
||||
DocumentExportMenuKind export_kind = DocumentExportMenuKind::jpeg;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr FileMenuPlan plan_file_menu_command(FileMenuCommand command) noexcept
|
||||
{
|
||||
FileMenuPlan plan;
|
||||
plan.command = command;
|
||||
|
||||
switch (command) {
|
||||
case FileMenuCommand::new_document:
|
||||
plan.action = FileMenuAction::show_new_document_dialog;
|
||||
break;
|
||||
case FileMenuCommand::import_image:
|
||||
plan.action = FileMenuAction::pick_image_for_import;
|
||||
break;
|
||||
case FileMenuCommand::open_project:
|
||||
plan.action = FileMenuAction::pick_project_file;
|
||||
break;
|
||||
case FileMenuCommand::browse_cloud:
|
||||
plan.action = FileMenuAction::show_cloud_browser_dialog;
|
||||
break;
|
||||
case FileMenuCommand::save:
|
||||
plan.action = FileMenuAction::save_document;
|
||||
plan.save_intent = DocumentSaveIntent::save;
|
||||
break;
|
||||
case FileMenuCommand::save_as:
|
||||
plan.action = FileMenuAction::save_document;
|
||||
plan.save_intent = DocumentSaveIntent::save_as;
|
||||
break;
|
||||
case FileMenuCommand::save_version:
|
||||
plan.action = FileMenuAction::save_document;
|
||||
plan.save_intent = DocumentSaveIntent::save_version;
|
||||
break;
|
||||
case FileMenuCommand::export_jpeg:
|
||||
plan.action = FileMenuAction::show_export_jpeg_dialog;
|
||||
plan.export_kind = DocumentExportMenuKind::jpeg;
|
||||
break;
|
||||
case FileMenuCommand::export_submenu:
|
||||
plan.action = FileMenuAction::show_export_submenu;
|
||||
break;
|
||||
case FileMenuCommand::share:
|
||||
plan.action = FileMenuAction::share_document;
|
||||
break;
|
||||
case FileMenuCommand::resize:
|
||||
plan.action = FileMenuAction::show_resize_dialog;
|
||||
break;
|
||||
case FileMenuCommand::cloud_upload:
|
||||
plan.action = FileMenuAction::upload_to_cloud;
|
||||
break;
|
||||
case FileMenuCommand::cloud_browse:
|
||||
plan.action = FileMenuAction::browse_cloud_documents;
|
||||
break;
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<FileMenuCommand> parse_file_menu_command(
|
||||
std::string_view command) noexcept
|
||||
{
|
||||
if (command == "new" || command == "new-document") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::new_document);
|
||||
}
|
||||
if (command == "import" || command == "import-image") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::import_image);
|
||||
}
|
||||
if (command == "open" || command == "open-project") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::open_project);
|
||||
}
|
||||
if (command == "browse") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::browse_cloud);
|
||||
}
|
||||
if (command == "save") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::save);
|
||||
}
|
||||
if (command == "save-as") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::save_as);
|
||||
}
|
||||
if (command == "save-version") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::save_version);
|
||||
}
|
||||
if (command == "export" || command == "export-jpeg") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::export_jpeg);
|
||||
}
|
||||
if (command == "export-submenu") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::export_submenu);
|
||||
}
|
||||
if (command == "share") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::share);
|
||||
}
|
||||
if (command == "resize") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::resize);
|
||||
}
|
||||
if (command == "cloud-upload") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::cloud_upload);
|
||||
}
|
||||
if (command == "cloud-browse") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::cloud_browse);
|
||||
}
|
||||
|
||||
return pp::foundation::Result<FileMenuCommand>::failure(
|
||||
pp::foundation::Status::invalid_argument("unknown file menu command"));
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "app_core/document_canvas.h"
|
||||
#include "app_core/document_export.h"
|
||||
#include "app_core/document_import.h"
|
||||
#include "app_core/file_menu.h"
|
||||
#include "app_core/app_status.h"
|
||||
#include "app_core/history_ui.h"
|
||||
#include "settings.h"
|
||||
@@ -123,6 +124,72 @@ bool apply_document_export_menu_plan(App& app, pp::app::DocumentExportMenuKind k
|
||||
return false;
|
||||
}
|
||||
|
||||
void apply_file_menu_plan(App& app, pp::app::FileMenuCommand command)
|
||||
{
|
||||
const auto plan = pp::app::plan_file_menu_command(command);
|
||||
switch (plan.action)
|
||||
{
|
||||
case pp::app::FileMenuAction::show_new_document_dialog:
|
||||
app.dialog_newdoc();
|
||||
break;
|
||||
case pp::app::FileMenuAction::pick_image_for_import:
|
||||
{
|
||||
auto* app_ptr = &app;
|
||||
app.pick_image([app_ptr](std::string path) {
|
||||
Image img;
|
||||
img.load_file(path);
|
||||
const auto import_plan = pp::app::plan_document_image_import(img.width, img.height);
|
||||
if (!import_plan)
|
||||
return;
|
||||
|
||||
if (import_plan.value().imports_equirectangular)
|
||||
{
|
||||
Canvas::I->import_equirectangular(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto m = static_cast<CanvasModeTransform*>(app_ptr->canvas->m_canvas->modes[(int)kCanvasMode::Import][0]);
|
||||
m->m_action = CanvasModeTransform::ActionType::Import;
|
||||
m->m_source_image = std::move(img);
|
||||
Canvas::set_mode(kCanvasMode::Import);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case pp::app::FileMenuAction::pick_project_file:
|
||||
{
|
||||
auto* app_ptr = &app;
|
||||
app.pick_file({ "ppi" }, [app_ptr](std::string path) {
|
||||
app_ptr->open_document(path);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case pp::app::FileMenuAction::show_cloud_browser_dialog:
|
||||
app.dialog_browse();
|
||||
break;
|
||||
case pp::app::FileMenuAction::save_document:
|
||||
app.save_document(plan.save_intent);
|
||||
break;
|
||||
case pp::app::FileMenuAction::show_export_jpeg_dialog:
|
||||
apply_document_export_menu_plan(app, plan.export_kind);
|
||||
break;
|
||||
case pp::app::FileMenuAction::show_export_submenu:
|
||||
break;
|
||||
case pp::app::FileMenuAction::share_document:
|
||||
app.share_file(app.doc_path);
|
||||
break;
|
||||
case pp::app::FileMenuAction::show_resize_dialog:
|
||||
app.dialog_resize();
|
||||
break;
|
||||
case pp::app::FileMenuAction::upload_to_cloud:
|
||||
app.cloud_upload();
|
||||
break;
|
||||
case pp::app::FileMenuAction::browse_cloud_documents:
|
||||
app.cloud_browse();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void App::title_update()
|
||||
@@ -783,70 +850,49 @@ void App::init_menu_file()
|
||||
|
||||
if (auto b = popup->find<NodeButtonCustom>("file-newdoc"))
|
||||
b->on_click = [this, popup](Node*) {
|
||||
dialog_newdoc();
|
||||
apply_file_menu_plan(*this, pp::app::FileMenuCommand::new_document);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
if (auto b = popup->find<NodeButtonCustom>("file-import"))
|
||||
b->on_click = [this, popup](Node*) {
|
||||
pick_image([this](std::string path){
|
||||
Image img;
|
||||
img.load_file(path);
|
||||
const auto import_plan = pp::app::plan_document_image_import(img.width, img.height);
|
||||
if (!import_plan)
|
||||
return;
|
||||
|
||||
if (import_plan.value().imports_equirectangular)
|
||||
{
|
||||
Canvas::I->import_equirectangular(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto m = static_cast<CanvasModeTransform*>(canvas->m_canvas->modes[(int)kCanvasMode::Import][0]);
|
||||
m->m_action = CanvasModeTransform::ActionType::Import;
|
||||
m->m_source_image = std::move(img);
|
||||
Canvas::set_mode(kCanvasMode::Import);
|
||||
}
|
||||
});
|
||||
apply_file_menu_plan(*this, pp::app::FileMenuCommand::import_image);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
if (auto b = popup->find<NodeButtonCustom>("file-open"))
|
||||
b->on_click = [this, popup](Node*) {
|
||||
//dialog_open();
|
||||
pick_file({"ppi"}, [this](std::string path){
|
||||
open_document(path);
|
||||
});
|
||||
apply_file_menu_plan(*this, pp::app::FileMenuCommand::open_project);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
if (auto b = popup->find<NodeButtonCustom>("file-browse"))
|
||||
b->on_click = [this, popup](Node*) {
|
||||
dialog_browse();
|
||||
apply_file_menu_plan(*this, pp::app::FileMenuCommand::browse_cloud);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
if (auto b = popup->find<NodeButtonCustom>("file-save"))
|
||||
b->on_click = [this, popup](Node*) {
|
||||
save_document(pp::app::DocumentSaveIntent::save);
|
||||
apply_file_menu_plan(*this, pp::app::FileMenuCommand::save);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
if (auto b = popup->find<NodeButtonCustom>("file-save-as"))
|
||||
b->on_click = [this, popup](Node*) {
|
||||
save_document(pp::app::DocumentSaveIntent::save_as);
|
||||
apply_file_menu_plan(*this, pp::app::FileMenuCommand::save_as);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
if (auto b = popup->find<NodeButtonCustom>("file-save-ver"))
|
||||
b->on_click = [this, popup](Node*) {
|
||||
save_document(pp::app::DocumentSaveIntent::save_version);
|
||||
apply_file_menu_plan(*this, pp::app::FileMenuCommand::save_version);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
if (auto b = popup->find<NodeButtonCustom>("file-export"))
|
||||
b->on_click = [this, popup](Node*) {
|
||||
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::jpeg);
|
||||
apply_file_menu_plan(*this, pp::app::FileMenuCommand::export_jpeg);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
@@ -912,25 +958,25 @@ void App::init_menu_file()
|
||||
};
|
||||
if (auto b = popup->find<NodeButtonCustom>("file-share"))
|
||||
b->on_click = [this, popup](Node*) {
|
||||
share_file(doc_path);
|
||||
apply_file_menu_plan(*this, pp::app::FileMenuCommand::share);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
if (auto b = popup->find<NodeButtonCustom>("file-resize"))
|
||||
b->on_click = [this, popup](Node*) {
|
||||
dialog_resize();
|
||||
apply_file_menu_plan(*this, pp::app::FileMenuCommand::resize);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
if (auto b = popup->find<NodeButtonCustom>("file-cloud-upload"))
|
||||
b->on_click = [this, popup](Node*) {
|
||||
cloud_upload();
|
||||
apply_file_menu_plan(*this, pp::app::FileMenuCommand::cloud_upload);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
if (auto b = popup->find<NodeButtonCustom>("file-cloud-browse"))
|
||||
b->on_click = [this, popup](Node*) {
|
||||
cloud_browse();
|
||||
apply_file_menu_plan(*this, pp::app::FileMenuCommand::cloud_browse);
|
||||
popup->mouse_release();
|
||||
popup->destroy();
|
||||
};
|
||||
|
||||
@@ -328,6 +328,16 @@ add_test(NAME pp_app_core_document_route_tests COMMAND pp_app_core_document_rout
|
||||
set_tests_properties(pp_app_core_document_route_tests PROPERTIES
|
||||
LABELS "app;desktop-fast;fuzz")
|
||||
|
||||
add_executable(pp_app_core_file_menu_tests
|
||||
app_core/file_menu_tests.cpp)
|
||||
target_link_libraries(pp_app_core_file_menu_tests PRIVATE
|
||||
pp_app_core
|
||||
pp_test_harness)
|
||||
|
||||
add_test(NAME pp_app_core_file_menu_tests COMMAND pp_app_core_file_menu_tests)
|
||||
set_tests_properties(pp_app_core_file_menu_tests PROPERTIES
|
||||
LABELS "app;desktop-fast;fuzz")
|
||||
|
||||
add_executable(pp_app_core_document_canvas_tests
|
||||
app_core/document_canvas_tests.cpp)
|
||||
target_link_libraries(pp_app_core_document_canvas_tests PRIVATE
|
||||
@@ -569,6 +579,36 @@ if(TARGET pano_cli)
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-open-route\".*\"kind\":\"import-abr\".*\"unsaved\":true.*\"action\":\"prompt-import-abr\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_file_menu_import_smoke
|
||||
COMMAND pano_cli plan-file-menu --command import)
|
||||
set_tests_properties(pano_cli_plan_file_menu_import_smoke PROPERTIES
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-file-menu\".*\"command\":\"import\".*\"action\":\"pick-image-for-import\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_file_menu_save_as_smoke
|
||||
COMMAND pano_cli plan-file-menu --command save-as)
|
||||
set_tests_properties(pano_cli_plan_file_menu_save_as_smoke PROPERTIES
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-file-menu\".*\"command\":\"save-as\".*\"action\":\"save-document\".*\"saveIntent\":\"save-as\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_file_menu_export_smoke
|
||||
COMMAND pano_cli plan-file-menu --command export)
|
||||
set_tests_properties(pano_cli_plan_file_menu_export_smoke PROPERTIES
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-file-menu\".*\"command\":\"export\".*\"action\":\"show-export-jpeg-dialog\".*\"exportKind\":\"jpeg\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_file_menu_cloud_upload_smoke
|
||||
COMMAND pano_cli plan-file-menu --command cloud-upload)
|
||||
set_tests_properties(pano_cli_plan_file_menu_cloud_upload_smoke PROPERTIES
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-file-menu\".*\"command\":\"cloud-upload\".*\"action\":\"upload-to-cloud\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_file_menu_rejects_unknown
|
||||
COMMAND pano_cli plan-file-menu --command nope)
|
||||
set_tests_properties(pano_cli_plan_file_menu_rejects_unknown PROPERTIES
|
||||
LABELS "app;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_document_file_save_now_smoke
|
||||
COMMAND pano_cli plan-document-file --work-dir D:/Paint --name demo)
|
||||
set_tests_properties(pano_cli_plan_document_file_save_now_smoke PROPERTIES
|
||||
|
||||
70
tests/app_core/file_menu_tests.cpp
Normal file
70
tests/app_core/file_menu_tests.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "app_core/file_menu.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void file_menu_routes_document_creation_and_opening(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto new_doc = pp::app::plan_file_menu_command(pp::app::FileMenuCommand::new_document);
|
||||
const auto open = pp::app::plan_file_menu_command(pp::app::FileMenuCommand::open_project);
|
||||
const auto import = pp::app::plan_file_menu_command(pp::app::FileMenuCommand::import_image);
|
||||
|
||||
PP_EXPECT(harness, new_doc.action == pp::app::FileMenuAction::show_new_document_dialog);
|
||||
PP_EXPECT(harness, open.action == pp::app::FileMenuAction::pick_project_file);
|
||||
PP_EXPECT(harness, import.action == pp::app::FileMenuAction::pick_image_for_import);
|
||||
}
|
||||
|
||||
void file_menu_preserves_save_intents(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto save = pp::app::plan_file_menu_command(pp::app::FileMenuCommand::save);
|
||||
const auto save_as = pp::app::plan_file_menu_command(pp::app::FileMenuCommand::save_as);
|
||||
const auto save_version = pp::app::plan_file_menu_command(pp::app::FileMenuCommand::save_version);
|
||||
|
||||
PP_EXPECT(harness, save.action == pp::app::FileMenuAction::save_document);
|
||||
PP_EXPECT(harness, save.save_intent == pp::app::DocumentSaveIntent::save);
|
||||
PP_EXPECT(harness, save_as.save_intent == pp::app::DocumentSaveIntent::save_as);
|
||||
PP_EXPECT(harness, save_version.save_intent == pp::app::DocumentSaveIntent::save_version);
|
||||
}
|
||||
|
||||
void file_menu_routes_export_and_cloud_actions(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto jpeg = pp::app::plan_file_menu_command(pp::app::FileMenuCommand::export_jpeg);
|
||||
const auto submenu = pp::app::plan_file_menu_command(pp::app::FileMenuCommand::export_submenu);
|
||||
const auto upload = pp::app::plan_file_menu_command(pp::app::FileMenuCommand::cloud_upload);
|
||||
const auto browse = pp::app::plan_file_menu_command(pp::app::FileMenuCommand::cloud_browse);
|
||||
|
||||
PP_EXPECT(harness, jpeg.action == pp::app::FileMenuAction::show_export_jpeg_dialog);
|
||||
PP_EXPECT(harness, jpeg.export_kind == pp::app::DocumentExportMenuKind::jpeg);
|
||||
PP_EXPECT(harness, submenu.action == pp::app::FileMenuAction::show_export_submenu);
|
||||
PP_EXPECT(harness, upload.action == pp::app::FileMenuAction::upload_to_cloud);
|
||||
PP_EXPECT(harness, browse.action == pp::app::FileMenuAction::browse_cloud_documents);
|
||||
}
|
||||
|
||||
void file_menu_parser_accepts_legacy_button_names(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto export_jpeg = pp::app::parse_file_menu_command("export");
|
||||
const auto cloud_upload = pp::app::parse_file_menu_command("cloud-upload");
|
||||
const auto invalid = pp::app::parse_file_menu_command("not-a-command");
|
||||
|
||||
PP_EXPECT(harness, export_jpeg);
|
||||
if (export_jpeg) {
|
||||
PP_EXPECT(harness, export_jpeg.value() == pp::app::FileMenuCommand::export_jpeg);
|
||||
}
|
||||
PP_EXPECT(harness, cloud_upload);
|
||||
if (cloud_upload) {
|
||||
PP_EXPECT(harness, cloud_upload.value() == pp::app::FileMenuCommand::cloud_upload);
|
||||
}
|
||||
PP_EXPECT(harness, !invalid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
pp::tests::Harness harness;
|
||||
harness.run("file menu routes document creation and opening", file_menu_routes_document_creation_and_opening);
|
||||
harness.run("file menu preserves save intents", file_menu_preserves_save_intents);
|
||||
harness.run("file menu routes export and cloud actions", file_menu_routes_export_and_cloud_actions);
|
||||
harness.run("file menu parser accepts legacy button names", file_menu_parser_accepts_legacy_button_names);
|
||||
return harness.finish();
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "app_core/document_route.h"
|
||||
#include "app_core/document_sharing.h"
|
||||
#include "app_core/document_session.h"
|
||||
#include "app_core/file_menu.h"
|
||||
#include "app_core/grid_ui.h"
|
||||
#include "app_core/history_ui.h"
|
||||
#include "app_core/quick_ui.h"
|
||||
@@ -158,6 +159,10 @@ struct PlanExportMenuArgs {
|
||||
bool has_canvas = true;
|
||||
};
|
||||
|
||||
struct PlanFileMenuArgs {
|
||||
std::string command = "new";
|
||||
};
|
||||
|
||||
struct PlanCloudUploadArgs {
|
||||
bool has_canvas = true;
|
||||
bool new_document = false;
|
||||
@@ -572,6 +577,70 @@ const char* document_workflow_decision_name(pp::app::DocumentWorkflowDecision de
|
||||
return "unavailable";
|
||||
}
|
||||
|
||||
const char* file_menu_command_name(pp::app::FileMenuCommand command) noexcept
|
||||
{
|
||||
switch (command) {
|
||||
case pp::app::FileMenuCommand::new_document:
|
||||
return "new-document";
|
||||
case pp::app::FileMenuCommand::import_image:
|
||||
return "import-image";
|
||||
case pp::app::FileMenuCommand::open_project:
|
||||
return "open-project";
|
||||
case pp::app::FileMenuCommand::browse_cloud:
|
||||
return "browse";
|
||||
case pp::app::FileMenuCommand::save:
|
||||
return "save";
|
||||
case pp::app::FileMenuCommand::save_as:
|
||||
return "save-as";
|
||||
case pp::app::FileMenuCommand::save_version:
|
||||
return "save-version";
|
||||
case pp::app::FileMenuCommand::export_jpeg:
|
||||
return "export-jpeg";
|
||||
case pp::app::FileMenuCommand::export_submenu:
|
||||
return "export-submenu";
|
||||
case pp::app::FileMenuCommand::share:
|
||||
return "share";
|
||||
case pp::app::FileMenuCommand::resize:
|
||||
return "resize";
|
||||
case pp::app::FileMenuCommand::cloud_upload:
|
||||
return "cloud-upload";
|
||||
case pp::app::FileMenuCommand::cloud_browse:
|
||||
return "cloud-browse";
|
||||
}
|
||||
|
||||
return "new-document";
|
||||
}
|
||||
|
||||
const char* file_menu_action_name(pp::app::FileMenuAction action) noexcept
|
||||
{
|
||||
switch (action) {
|
||||
case pp::app::FileMenuAction::show_new_document_dialog:
|
||||
return "show-new-document-dialog";
|
||||
case pp::app::FileMenuAction::pick_image_for_import:
|
||||
return "pick-image-for-import";
|
||||
case pp::app::FileMenuAction::pick_project_file:
|
||||
return "pick-project-file";
|
||||
case pp::app::FileMenuAction::show_cloud_browser_dialog:
|
||||
return "show-cloud-browser-dialog";
|
||||
case pp::app::FileMenuAction::save_document:
|
||||
return "save-document";
|
||||
case pp::app::FileMenuAction::show_export_jpeg_dialog:
|
||||
return "show-export-jpeg-dialog";
|
||||
case pp::app::FileMenuAction::show_export_submenu:
|
||||
return "show-export-submenu";
|
||||
case pp::app::FileMenuAction::share_document:
|
||||
return "share-document";
|
||||
case pp::app::FileMenuAction::show_resize_dialog:
|
||||
return "show-resize-dialog";
|
||||
case pp::app::FileMenuAction::upload_to_cloud:
|
||||
return "upload-to-cloud";
|
||||
case pp::app::FileMenuAction::browse_cloud_documents:
|
||||
return "browse-cloud-documents";
|
||||
}
|
||||
|
||||
return "show-new-document-dialog";
|
||||
}
|
||||
|
||||
const char* document_layer_rename_action_name(pp::app::DocumentLayerRenameAction action) noexcept
|
||||
{
|
||||
switch (action) {
|
||||
@@ -1119,6 +1188,7 @@ void print_help()
|
||||
<< " inspect-project --path FILE\n"
|
||||
<< " classify-open --path FILE\n"
|
||||
<< " plan-open-route --path FILE [--unsaved]\n"
|
||||
<< " plan-file-menu --command new|import|open|browse|save|save-as|save-version|export|export-submenu|share|resize|cloud-upload|cloud-browse\n"
|
||||
<< " plan-new-document --work-dir DIR --name NAME [--resolution-index N] [--target-exists]\n"
|
||||
<< " plan-document-file --work-dir DIR --name NAME [--target-exists]\n"
|
||||
<< " plan-document-version --directory DIR --doc-name NAME [--existing-path FILE]\n"
|
||||
@@ -2045,6 +2115,52 @@ int plan_open_route(int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_file_menu_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
PlanFileMenuArgs& args)
|
||||
{
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
const std::string_view key(argv[i]);
|
||||
if (key == "--command") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
args.command = argv[++i];
|
||||
} else {
|
||||
return pp::foundation::Status::invalid_argument("unknown option");
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
int plan_file_menu(int argc, char** argv)
|
||||
{
|
||||
PlanFileMenuArgs args;
|
||||
const auto status = parse_plan_file_menu_args(argc, argv, args);
|
||||
if (!status.ok()) {
|
||||
print_error("plan-file-menu", status.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto command = pp::app::parse_file_menu_command(args.command);
|
||||
if (!command) {
|
||||
print_error("plan-file-menu", command.status().message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto plan = pp::app::plan_file_menu_command(command.value());
|
||||
std::cout << "{\"ok\":true,\"command\":\"plan-file-menu\""
|
||||
<< ",\"state\":{\"command\":\"" << json_escape(args.command)
|
||||
<< "\"},\"plan\":{\"command\":\"" << file_menu_command_name(plan.command)
|
||||
<< "\",\"action\":\"" << file_menu_action_name(plan.action)
|
||||
<< "\",\"saveIntent\":\"" << document_save_intent_name(plan.save_intent)
|
||||
<< "\",\"exportKind\":\"" << document_export_menu_kind_name(plan.export_kind)
|
||||
<< "\"}}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_document_file_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
@@ -6262,6 +6378,10 @@ int main(int argc, char** argv)
|
||||
return plan_open_route(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-file-menu") {
|
||||
return plan_file_menu(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-document-file") {
|
||||
return plan_document_file(argc, argv);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user