diff --git a/docs/dialog-sdk-reference.md b/docs/dialog-sdk-reference.md index 6f05f7c..c7f8f58 100644 --- a/docs/dialog-sdk-reference.md +++ b/docs/dialog-sdk-reference.md @@ -57,7 +57,7 @@ For the full tutorial, see [dialog-plugin-guide.md](../pj_plugins/docs/dialog-pl | `setButtonIconNamed(name, icon_id)` | Set a button icon by id, resolved from the host's themed icon set (consistent tinting; unknown id → no icon) | | `setShortcut(name, key_sequence)` | Assign keyboard shortcut (e.g. `"Ctrl+A"`) | | `setFilePicker(name, text, filter, title)` | Turn into an **open** file picker (existing file) | -| `setSaveFilePicker(name, text, filter, title)` | Turn into a **save-as** file picker (navigate + type a new filename); reports via `onFileSelected` | +| `setSaveFilePicker(name, text, filter, title, default_suffix="")` | Turn into a **save-as** file picker (navigate + type a new filename); reports via `onFileSelected`. `default_suffix` is appended when the typed name has none | | `setFolderPicker(name, text, title)` | Turn into folder picker | ### QListWidget @@ -150,7 +150,7 @@ Override these in your `DialogPluginTyped` subclass. Return `true` when state ch | `onValueChanged(name, int)` | QSpinBox | New integer value | | `onValueChanged(name, double)` | QDoubleSpinBox | New double value | | `onClicked(name)` | QPushButton | (no payload) | -| `onFileSelected(name, path)` | QPushButton (file picker) | Selected file path | +| `onFileSelected(name, path)` | QPushButton (file picker or save-file picker) | Selected file path | | `onFolderSelected(name, path)` | QPushButton (folder picker) | Selected folder path | | `onSelectionChanged(name, items)` | QListWidget, QTableWidget | Vector of selected item texts | | `onItemDoubleClicked(name, index)` | QListWidget, QTableWidget | Row index of double-clicked item | diff --git a/pj_plugins/dialog_protocol/include/pj_plugins/host/widget_data_view.hpp b/pj_plugins/dialog_protocol/include/pj_plugins/host/widget_data_view.hpp index ec8334d..f9c12c3 100644 --- a/pj_plugins/dialog_protocol/include/pj_plugins/host/widget_data_view.hpp +++ b/pj_plugins/dialog_protocol/include/pj_plugins/host/widget_data_view.hpp @@ -322,6 +322,11 @@ class WidgetDataView { return it != w->end() && it->is_string() && it->get() == "save_file_picker"; } + /// The extension appended to a save-file picker's typed name when it has none. + [[nodiscard]] std::optional saveFilePickerDefaultSuffix(std::string_view name) const { + return getString(name, "default_suffix"); + } + [[nodiscard]] std::optional filePickerFilter(std::string_view name) const { return getString(name, "filter"); } diff --git a/pj_plugins/dialog_protocol/include/pj_plugins/sdk/widget_data.hpp b/pj_plugins/dialog_protocol/include/pj_plugins/sdk/widget_data.hpp index 94e53e8..c79c5b8 100644 --- a/pj_plugins/dialog_protocol/include/pj_plugins/sdk/widget_data.hpp +++ b/pj_plugins/dialog_protocol/include/pj_plugins/sdk/widget_data.hpp @@ -418,14 +418,17 @@ class WidgetData { /// (which opens an existing file), this lets the user navigate to a directory AND /// type a new filename, so a not-yet-existing rule/config file can be created. The /// chosen path is reported through the same onFileSelected(name, path) event — the - /// plugin distinguishes save from open by the widget name. + /// plugin distinguishes save from open by the widget name. `default_suffix` is + /// appended when the typed name carries no extension. WidgetData& setSaveFilePicker( - std::string_view name, std::string_view button_text, std::string_view filter, std::string_view title) { + std::string_view name, std::string_view button_text, std::string_view filter, std::string_view title, + std::string_view default_suffix = "") { auto& e = entry(name); e["button_text"] = button_text; e["action"] = "save_file_picker"; e["filter"] = filter; e["title"] = title; + e["default_suffix"] = default_suffix; return *this; } diff --git a/pj_plugins/docs/dialog-plugin-guide.md b/pj_plugins/docs/dialog-plugin-guide.md index 7c09f8d..b13312e 100644 --- a/pj_plugins/docs/dialog-plugin-guide.md +++ b/pj_plugins/docs/dialog-plugin-guide.md @@ -354,6 +354,7 @@ work like polling a server for available topics. | QDoubleSpinBox | `setValue(double)` | `onValueChanged(name, double)` | | QPushButton | `setButtonText` | `onClicked(name)` | | QPushButton (file picker) | `setFilePicker` | `onFileSelected(name, path)` | +| QPushButton (save-file picker) | `setSaveFilePicker` | `onFileSelected(name, path)` | | QPushButton (folder picker) | `setFolderPicker` | `onFolderSelected(name, path)` | | QLabel | `setLabel` | (none — display only) | | QListWidget | `setListItems`, `setSelectedItems` | `onSelectionChanged(name, items)`, `onItemDoubleClicked(name, index)` | @@ -457,6 +458,28 @@ bool onFileSelected(std::string_view name, std::string_view path) override { } ``` +### Save-file picker + +For an *export* button, use `setSaveFilePicker()` instead. The host shows a +native save dialog (the user types a name and picks a location) and delivers the +chosen path through the same `onFileSelected()` handler — distinguish the button +by its `objectName`. The optional `default_suffix` is appended when the typed +name carries no extension. + +```cpp +// In widget_data(): +wd.setSaveFilePicker("export_btn", "Export...", "*.json", "Export Library", "json"); + +// Same handler as the file picker, routed by name: +bool onFileSelected(std::string_view name, std::string_view path) override { + if (name == "export_btn") { + writeLibraryTo(path); + return true; + } + return false; +} +``` + ### Dynamic visibility and enabled state Control widget visibility and enabled state from `widget_data()` to build