Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
All notable changes to `plotjuggler_sdk` are recorded here. Versioning policy is in
[`CLAUDE.md`](./CLAUDE.md) → "Release Versioning".

## [0.18.0]

### Feature: QDateTimeEdit event surface (MINOR)

The dialog protocol's QDateTimeEdit setters (`setDateTime` / `setDateTimeRange`,
shipped earlier) gain their missing event direction, so the widget becomes an
input, not just a display (backward-compatible JSON addition; no C ABI change,
`PJ_DIALOG_PROTOCOL_VERSION` unchanged):

- `WidgetEvent` key: `datetime_iso` (string — the edited datetime, wall-clock
local ISO-8601; fractional seconds only for ms-precision editors), with
`WidgetEventBuilder::dateTimeChanged()` on the host side,
`WidgetEvent::dateTimeChanged()` on the plugin side, and typed dispatch via
`DialogPluginTyped::onDateTimeChanged()`.
- Docs: `dialog-sdk-reference.md` gains the previously missing QDateTimeEdit
section; the setter contract is clarified (empty/unparsable strings are
ignored — the widget keeps its current value; `QDateEdit`/`QTimeEdit`
subclasses share the binding).

## [0.17.0]

### Feature: dialog-protocol additions for deletable lists and chart/list placeholders (MINOR)
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ endif()
if(PJ_INSTALL_SDK)
include(CMakePackageConfigHelpers)

set(PJ_PACKAGE_VERSION "0.17.0")
set(PJ_PACKAGE_VERSION "0.18.0")
set(PJ_PACKAGE_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/plotjuggler_sdk)

install(EXPORT plotjuggler_sdkTargets
Expand Down
5 changes: 4 additions & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ class PlotjugglerSdkConan(ConanFile):
# 0.17.0 extends the dialog protocol with backward-compatible additions
# (list_deletable / list_placeholder / chart_placeholder keys and the
# item_delete_index event) — MINOR. See CHANGELOG.md.
version = "0.17.0"
# 0.18.0 adds the QDateTimeEdit event surface (datetime_iso event,
# WidgetEvent::dateTimeChanged, DialogPluginTyped::onDateTimeChanged) —
# MINOR, header-only additions. See CHANGELOG.md.
version = "0.18.0"
# Apache-2.0 covers the whole SDK (pj_base + pj_plugins). See LICENSE.
license = "Apache-2.0"
url = "https://github.com/PlotJuggler/plotjuggler_sdk"
Expand Down
12 changes: 12 additions & 0 deletions docs/dialog-sdk-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ For the full tutorial, see [dialog-plugin-guide.md](../pj_plugins/docs/dialog-pl
|--------|-------------|
| `setTabIndex(name, int)` | Set active tab index |

### QDateTimeEdit

Also binds the `QDateEdit` / `QTimeEdit` subclasses. Datetimes are wall-clock
local time exchanged verbatim; events always carry a full ISO datetime, with
fractional seconds only when the editor's display format includes them.

| Method | Description |
|--------|-------------|
| `setDateTime(name, iso8601)` | Set the displayed datetime (ISO-8601, e.g. `"2026-05-21T13:45:00"`); empty/unparsable strings are ignored |
| `setDateTimeRange(name, min_iso, max_iso)` | Set the allowed [min, max] datetime range |

### QDialogButtonBox

| Method | Description |
Expand Down Expand Up @@ -165,6 +176,7 @@ Override these in your `DialogPluginTyped` subclass. Return `true` when state ch
| `onChartViewChanged(name, x_min, x_max, y_min, y_max)` | QFrame chart container | Visible chart range |
| `onMarkerTimelineChanged(name, marks)` | MarkerTimeline | Full `std::vector<TimelineMark>` set after a drag/resize/delete |
| `onTabChanged(name, index)` | QTabWidget | New tab index |
| `onDateTimeChanged(name, iso8601)` | QDateTimeEdit (incl. QDateEdit/QTimeEdit) | Edited datetime as ISO-8601 string (local wall-clock) |

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@ struct WidgetEventBuilder {
return j.dump();
}

/// QDateTimeEdit: displayed datetime edited (ISO-8601, e.g. "2026-05-21T13:45:00").
/// Wall-clock local time; include fractional seconds only when meaningful.
[[nodiscard]] static std::string dateTimeChanged(std::string_view iso8601) {
nlohmann::json j;
j["datetime_iso"] = iso8601;
return j.dump();
}

/// RangeSlider: lower/upper handle position changed (slider units).
[[nodiscard]] static std::string rangeChanged(int lower, int upper) {
nlohmann::json j;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ class DialogPluginTyped : public DialogPluginBase {
return false;
}

/// QDateTimeEdit (and its QDateEdit/QTimeEdit subclasses): the displayed
/// datetime was edited. `iso8601` is wall-clock local time, always a full
/// datetime; fractional seconds appear only for ms-precision editors.
virtual bool onDateTimeChanged(std::string_view /*widget_name*/, std::string_view /*iso8601*/) {
return false;
}

private:
/// Parses event_json and dispatches to the appropriate typed virtual above.
bool onWidgetEvent(std::string_view widget_name, std::string_view event_json) final {
Expand All @@ -144,6 +151,9 @@ class DialogPluginTyped : public DialogPluginBase {
if (auto v = event.dateRangeChanged()) {
return onDateRangeChanged(widget_name, v->from_iso, v->to_iso);
}
if (auto v = event.dateTimeChanged()) {
return onDateTimeChanged(widget_name, *v);
}
if (auto v = event.itemsDropped()) {
return onItemsDropped(widget_name, *v);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,10 @@ class WidgetData {

// --- QDateTimeEdit ---
/// Set the displayed date+time as an ISO-8601 string (e.g. "2026-05-21T13:45:00").
/// Empty string clears any prior value (widget falls back to its default).
/// Datetimes are exchanged verbatim as wall-clock values: a suffixless string is
/// host-local time; an explicit UTC offset is honored on parse. Empty or
/// unparsable strings are ignored — the widget keeps its current value (a
/// QDateTimeEdit always displays one).
WidgetData& setDateTime(std::string_view name, std::string_view iso8601) {
entry(name)["datetime"] = std::string(iso8601);
return *this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ class WidgetEvent {
return DateRangeFilter{from->get<std::string>(), to->get<std::string>()};
}

/// QDateTimeEdit: edited datetime as an ISO-8601 string.
std::optional<std::string> dateTimeChanged() const {
return getString("datetime_iso");
}

/// RangeSlider: lower/upper handle positions (slider units).
struct RangeValues {
int lower;
Expand Down
14 changes: 14 additions & 0 deletions pj_plugins/dialog_protocol/tests/dialog_plugin_typed_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ class RecordingPlugin : public PJ::DialogPluginTyped {
return true;
}

bool onDateTimeChanged(std::string_view widget_name, std::string_view iso8601) override {
last_handler = "date_time_changed";
last_widget = std::string(widget_name);
last_text = std::string(iso8601);
return true;
}

bool onCodeChangedWithCursor(std::string_view widget_name, std::string_view code, int cursor) override {
last_handler = "code_changed";
last_widget = std::string(widget_name);
Expand Down Expand Up @@ -220,6 +227,13 @@ TEST_F(TypedDispatchTest, DateRangeChanged) {
EXPECT_EQ(plugin_.last_date_to, "");
}

TEST_F(TypedDispatchTest, DateTimeChanged) {
EXPECT_TRUE(dispatch(plugin_, "startTime", R"({"datetime_iso": "2026-01-02T03:04:05"})"));
EXPECT_EQ(plugin_.last_handler, "date_time_changed");
EXPECT_EQ(plugin_.last_widget, "startTime");
EXPECT_EQ(plugin_.last_text, "2026-01-02T03:04:05");
}

// --- Edge cases ---

TEST_F(TypedDispatchTest, UnrecognizedFieldReturnsFalse) {
Expand Down
19 changes: 19 additions & 0 deletions pj_plugins/dialog_protocol/tests/widget_event_builder_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,25 @@ TEST(WidgetEventTest_DateRange, MissingFieldYieldsNullopt) {
EXPECT_FALSE(ev.dateRangeChanged().has_value());
}

TEST(WidgetEventBuilderTest, DateTimeChanged) {
std::string json = PJ::WidgetEventBuilder::dateTimeChanged("2026-05-21T13:45:00");
PJ::WidgetEvent ev(json);
ASSERT_TRUE(ev.dateTimeChanged().has_value());
EXPECT_EQ(*ev.dateTimeChanged(), "2026-05-21T13:45:00");
// A datetime event must not be routed as plain text.
EXPECT_FALSE(ev.text().has_value());
}

TEST(WidgetEventTest_DateTime, MissingFieldYieldsNullopt) {
PJ::WidgetEvent ev("{}");
EXPECT_FALSE(ev.dateTimeChanged().has_value());
}

TEST(WidgetEventTest_DateTime, WrongTypeYieldsNullopt) {
PJ::WidgetEvent ev(R"({"datetime_iso": 42})");
EXPECT_FALSE(ev.dateTimeChanged().has_value());
}

// --- Verify JSON is parseable and contains only expected fields ---

TEST(WidgetEventBuilderTest, ClickedHasNoExtraFields) {
Expand Down
2 changes: 1 addition & 1 deletion pj_plugins/docs/dialog-plugin-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ work like polling a server for available topics.
| QTableWidget | `setTableHeaders`, `setTableRows`, `setSelectedRows`, `setVisibleRows`, `setRowColor`, `setCellTooltip` | `onSelectionChanged(name, items)`, `onHeaderClicked(name, section)` |
| QPlainTextEdit | `setPlainText`, `setCodeContent`, `setCodeLanguage`, `setCodeCursor`, `setCodeCaretTracking` | `onCodeChanged(name, code)`, or `onCodeChangedWithCursor(name, code, cursor)` when the editor opts into caret tracking |
| QFrame (chart container) | `setChartSeries`, `clearChart`, `setChartZoomEnabled` | `onChartViewChanged(name, x_min, x_max, y_min, y_max)` |
| QDateTimeEdit | `setDateTime`, `setDateTimeRange` | (none — input only) |
| QDateTimeEdit (incl. QDateEdit/QTimeEdit) | `setDateTime`, `setDateTimeRange` | `onDateTimeChanged(name, iso8601)` |
| RangeSlider (two-handle) | `setRangeSliderBounds`, `setRangeSliderValues`, `setRangeSliderTimeSpan` | `onRangeChanged(name, lower, upper)` |
| DateRangePicker (date range) | `setDateRangePlaceholder` | `onDateRangeChanged(name, from_iso, to_iso)` |
| QTabWidget | `setTabIndex` | `onTabChanged(name, index)` |
Expand Down
2 changes: 1 addition & 1 deletion recipe.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
schema_version: 1

context:
version: "0.17.0"
version: "0.18.0"

package:
name: plotjuggler_sdk
Expand Down
Loading