| 329 | } |
| 330 | |
| 331 | std::optional<QString> CatalogModel::stringValueAt(const QString& key, double display_seconds) const { |
| 332 | if (impl_->session == nullptr) { |
| 333 | return std::nullopt; |
| 334 | } |
| 335 | const auto it = impl_->items.find(key); |
| 336 | if (it == impl_->items.end()) { |
| 337 | return std::nullopt; |
| 338 | } |
| 339 | const ScalarFieldPayload* scalar = asScalarField(it->second); |
| 340 | if (scalar == nullptr || !scalar->is_string) { |
| 341 | return std::nullopt; // only string fields carry a string value |
| 342 | } |
| 343 | const DisplayOffset offset = impl_->session->displayOffset(it->second.dataset_id); |
| 344 | const Timestamp raw_ns = displaySecondsToRaw(DisplaySeconds{display_seconds}, offset); |
| 345 | const DataReader reader(impl_->session->dataEngine()); |
| 346 | const auto value_or = |
| 347 | reader.latestStringAt(QueryPoint{.topic_id = scalar->topic_id, .t = raw_ns}, scalar->column_index); |
| 348 | if (!value_or.has_value() || !value_or->has_value()) { |
| 349 | return std::nullopt; // read error, or no row at/before the query time |
| 350 | } |
| 351 | return QString::fromStdString(**value_or); |
| 352 | } |
| 353 | |
| 354 | std::vector<CurveDescriptor> CatalogModel::curves() const { |
| 355 | std::vector<CurveDescriptor> curves; |