1#include "helpers/controller.hpp"
3#include "helpers/str_label.hpp"
5#include "widgets/settings_panel.hpp"
7#include <QCameraDevice>
12#include <QMediaDevices>
21#include "widgets/board.hpp"
22#include "widgets/grid_view.hpp"
23#include "widgets/stream_cell.hpp"
40 stream_mgr->set_frame_processor(
41 yodau::backend::opencv_motion_processor
48 return std::vector<yodau::backend::event> {};
52 [
this](
const std::vector<yodau::backend::event>& evs) {
53 on_backend_events(evs);
60 settings->set_active_current(QString());
72 const int n =
static_cast<
int>(
grid->stream_names().size());
74 stream_mgr->set_analysis_interval_ms(interval);
84 const auto backend_names =
stream_mgr->stream_names();
85 for (
auto& n : backend_names) {
86 const auto qname = QString::fromStdString(n);
90 const auto s = stream_mgr->find_stream(n);
93 const auto path = QString::fromStdString(s->get_path());
94 const auto type = QString::fromStdString(
95 yodau::backend::stream::type_name(s->get_type())
97 desc = QString(
"%1:%2").arg(type, path);
100 settings->add_stream_entry(qname, desc);
103 settings->set_existing_names(names);
107 const QString& path,
const QString& name,
const bool loop
109 handle_add_stream_common(path, name,
"file", loop);
112void controller::handle_add_local(
const QString& source,
const QString& name) {
113 handle_add_stream_common(source, name,
"local",
true);
116void controller::handle_add_url(
const QString& url,
const QString& name) {
117 handle_add_stream_common(url, name,
"url",
true);
125 const auto ts = now_ts();
129 const auto backend_names =
stream_mgr->stream_names();
130 for (
const auto& n : backend_names) {
131 const auto qn = QString::fromStdString(n);
132 if (qn.startsWith(
"video")) {
137 settings->set_local_sources(locals);
139 QString(
"[%1] ok: detected %2 local sources").arg(ts).arg(locals.size())
142 const auto cams = QMediaDevices::videoInputs();
149 const QString& name,
const bool show
159 tile, &stream_cell::frame_ready,
this,
160 &controller::on_gui_frame, Qt::UniqueConnection
162 tile->set_persistent_lines(per_stream_lines.value(name));
164 const auto s =
stream_mgr->find_stream(name.toStdString());
167 const auto path = QString::fromStdString(s->get_path());
168 const auto type = s->get_type();
171 tile->set_camera_id(path.toUtf8());
173 tile->set_source(QUrl::fromLocalFile(path));
175 tile->set_source(QUrl(path));
181 if (!active_name.isEmpty() && active_name == name && main_zone) {
187 settings->set_active_current(QString());
206void controller::on_active_stream_selected(
const QString& name) {
213 if (name.isEmpty()) {
226 cell->set_draft_params(
227 draft_line_name, draft_line_color, draft_line_closed
230 apply_template_preview(settings->active_template_current());
251 cell->set_draft_params(
252 draft_line_name, draft_line_color, draft_line_closed
255 apply_template_preview(settings->active_template_current());
261 QString(
"edit mode: %1")
262 .arg(drawing_new ?
"draw new" :
"use template")
268 const QString& name,
const QColor& color,
bool closed
270 draft_line_name = name;
271 draft_line_color = color;
276 cell->set_draft_params(
277 draft_line_name, draft_line_color, draft_line_closed
283 settings->append_active_log(
284 QString(
"active line params: name='%1' color=%2 closed=%3")
285 .arg(draft_line_name)
286 .arg(draft_line_color.name())
287 .arg(draft_line_closed ?
"true" :
"false")
293 const QString& name,
const bool closed
295 log_active(QString(
"save click: name='%1' closed=%2 active='%3'")
297 .arg(closed ?
"true" :
"false")
300 auto* cell = active_cell_checked(
"add line");
305 const auto pts = cell->draft_points_pct();
306 if (pts.size() < 2) {
307 log_active(
"add line failed: need at least 2 points");
311 const auto points_str = points_str_from_pct(pts);
312 log_active(QString(
"points_str = %1").arg(points_str));
316 points_str.toStdString(), closed, name.toStdString()
319 const auto final_name = QString::fromStdString(lp->name);
320 apply_added_line(cell, final_name, pts, closed);
321 }
catch (
const std::exception& e) {
322 log_active(QString(
"add line failed: %1").arg(e.what()));
326void controller::on_active_template_selected(
const QString& template_name) {
330 apply_template_preview(template_name);
343 const auto t =
settings->active_template_current();
348 apply_template_preview(t);
352 const QString& template_name,
const QColor& color
354 auto* cell = active_cell_checked(
"add template");
359 if (!templates.contains(template_name)) {
362 QString(
"add template failed: unknown template '%1'")
369 const auto tpl = templates.value(template_name);
372 stream_mgr->set_line(
373 active_name.toStdString(), template_name.toStdString()
375 }
catch (
const std::exception& e) {
378 QString(
"add template failed: %1").arg(e.what())
384 stream_cell::line_instance inst;
385 inst.template_name = template_name;
387 inst.closed = tpl.closed;
388 inst.pts_pct = tpl.pts_pct;
390 per_stream_lines[active_name].push_back(inst);
391 cell->add_persistent_line(inst);
395 QString(
"template added to active: %1").arg(template_name)
418 auto pts = cell->draft_points_pct();
424 cell->set_draft_points_pct(pts);
445 settings, &settings_panel::active_stream_selected,
this,
446 &controller::on_active_stream_selected
455 settings, &settings_panel::active_line_params_changed,
this,
456 &controller::on_active_line_params_changed
460 settings, &settings_panel::active_line_save_requested,
this,
461 &controller::on_active_line_save_requested
465 settings, &settings_panel::active_template_add_requested,
this,
466 &controller::on_active_template_add_requested
470 settings, &settings_panel::active_template_selected,
this,
471 &controller::on_active_template_selected
475 settings, &settings_panel::active_template_color_changed,
this,
476 &controller::on_active_template_color_changed
495 connect(grid, &grid_view::stream_closed,
this, [
this](
const QString& name) {
497 settings->set_stream_checked(name,
false);
503 &controller::handle_enlarge_requested
508 const QString& source,
const QString& name,
const QString& type,
bool loop
514 const auto ts = now_ts();
517 const QUrl url(source);
518 const auto scheme = url.scheme().toLower();
520 if (!url.isValid() || scheme.isEmpty()) {
522 QString(
"[%1] error: invalid url '%2'").arg(ts, source)
527 if (scheme !=
"rtsp" && scheme !=
"http" && scheme !=
"https") {
529 QString(
"[%1] error: unsupported url scheme '%2'")
538 source.toStdString(), name.toStdString(), type.toStdString(), loop
541 const auto final_name = QString::fromStdString(s.get_name());
542 const auto source_desc = QString(
"%1:%2").arg(type, source);
545 if (type ==
"file" || type ==
"local") {
546 url = QUrl::fromLocalFile(source);
550 stream_sources[final_name] = url;
551 stream_loops[final_name] = loop;
554 QString(
"[%1] ok: added %2 as %3").arg(ts, source_desc, final_name)
557 register_stream_in_ui(final_name, source_desc);
558 }
catch (
const std::exception& e) {
560 QString(
"[%1] error: add %2 failed: %3").arg(ts, type, e.what())
566 const QString& final_name,
const QString& source_desc
572 settings->add_existing_name(final_name);
573 settings->add_stream_entry(final_name, source_desc);
581 return QDateTime::currentDateTime().toString(
"HH:mm:ss");
584void controller::handle_enlarge_requested(
const QString& name) {
585 if (name.isEmpty()) {
589 if (!active_name.isEmpty() && active_name == name) {
594 on_active_stream_selected(name);
602 on_active_stream_selected(QString());
605 settings->set_active_current(QString());
610 handle_enlarge_requested(name);
614 if (!stream_mgr || !main_zone || active_name.isEmpty()) {
617 QString(
"%1 failed: no active stream").arg(fail_prefix)
627 QString(
"%1 failed: active cell not found").arg(fail_prefix)
637 if (!main_zone || active_name.isEmpty()) {
639 settings->set_template_candidates({});
650 const auto used = used_template_names_for_stream(active_name);
651 settings->set_template_candidates(template_candidates_excluding(used));
654void controller::apply_template_preview(
const QString& template_name) {
665 if (template_name.isEmpty() || !templates.contains(template_name)) {
669 const auto tpl = templates.value(template_name);
673 c =
settings->active_template_preview_color();
676 cell->set_draft_params(template_name, c, tpl.closed);
677 cell->set_draft_points_pct(tpl.pts_pct);
686QString
controller::points_str_from_pct(
const std::vector<QPointF>& pts) {
688 parts.reserve(
static_cast<
int>(pts.size()));
689 for (
const auto& p : pts) {
690 parts << QString(
"(%1,%2)").arg(p.x(), 0,
'f', 3).arg(p.y(), 0,
'f', 3);
692 return parts.join(
"; ");
697 const std::vector<QPointF>& pts,
const bool closed
699 stream_cell::line_instance inst;
700 inst.template_name = final_name;
701 inst.color = draft_line_color;
702 inst.closed = closed;
705 per_stream_lines[active_name].push_back(inst);
706 cell->add_persistent_line(inst);
708 templates[final_name] = tpl_line { pts, closed };
710 stream_mgr->set_line(active_name.toStdString(), final_name.toStdString());
713 cell->set_draft_params(QString(), QColor(Qt::red),
false);
715 draft_line_name.clear();
716 draft_line_color = Qt::red;
721 settings->add_template_candidate(final_name);
726 QString(
"line added: %1 (%2 points)").arg(final_name).arg(pts.size())
738 cell->set_persistent_lines(per_stream_lines.value(active_name));
743controller::used_template_names_for_stream(
const QString& stream)
const {
746 const auto current_lines = per_stream_lines.value(stream);
747 for (
const auto& inst : current_lines) {
748 const auto tn = inst.template_name.trimmed();
759 QStringList candidates;
760 candidates.reserve(templates.size());
762 for (
auto it = templates.begin(); it != templates.end(); ++it) {
763 const QString name = it.key();
764 if (!used.contains(name)) {
777 const auto names =
grid->stream_names();
778 const int n =
static_cast<
int>(names.size());
781 for (
const auto& name : names) {
782 if (
auto* tile = grid->peek_stream_cell(name)) {
783 tile->set_repaint_interval_ms(interval);
787 if (!active_name.isEmpty()) {
788 if (
auto* cell = grid->peek_stream_cell(active_name)) {
795 const std::vector<yodau::backend::event>& evs
797 for (
const auto& e : evs) {
819 if (main_zone && !active_name.isEmpty() && active_name == name) {
837 if (img.format() != QImage::Format_RGB888) {
838 img = img.convertToFormat(QImage::Format_RGB888);
844 f
.stride =
static_cast<
int>(img.bytesPerLine());
846 f.ts = std::chrono::steady_clock::now();
848 const auto* ptr = img.constBits();
849 const int bytes =
static_cast<
int>(img.sizeInBytes());
850 if (ptr && bytes > 0) {
851 f.data.assign(ptr, ptr + bytes);
857void controller::on_gui_frame(
const QString& stream_name,
const QImage& image) {
862 auto f = frame_from_image(image);
863 stream_mgr->push_frame(stream_name.toStdString(), std::move(f));
867 if (QThread::currentThread() != thread()) {
869 QMetaObject::invokeMethod(
870 this, [
this, copy]() { on_backend_event(copy); },
876 const auto name = QString::fromStdString(e.stream_name);
877 auto* tile = tile_for_stream_name(name);
882 if (!e.pos_pct.has_value()) {
888 const auto ln = QString::fromStdString(e.line_name);
889 const auto& p = *e.pos_pct;
890 tile->highlight_line_at(ln, QPointF(p.x, p.y));
894 const auto& p = *e.pos_pct;
895 tile->add_event(QPointF(p.x, p.y), Qt::gray);
void clear_active()
Clear active mode and return the active cell to the grid.
board(QWidget *parent=nullptr)
Construct the board widget.
void set_active_stream(const QString &name)
Make a stream active by name.
stream_cell * active_cell() const
Get the currently active (focused) stream cell, if any.
grid_view * grid_mode() const
Access the grid view (thumbnail mode).
stream_cell * take_active_cell()
Detach and return the active cell without putting it back to grid.
bool drawing_new_mode
True if active edit mode is "draw new line".
void on_active_line_undo_requested()
Handler for undoing last draft point.
void update_analysis_caps()
Update backend analysis interval based on visible tile count.
void update_repaint_caps()
Update repaint interval caps for all visible tiles.
void on_backend_event(const yodau::backend::event &e)
Handle a single backend event (GUI-thread safe).
void on_active_template_color_changed(const QColor &color)
Handler for changing template preview color.
QMap< QString, std::vector< stream_cell::line_instance > > per_stream_lines
Per-stream persistent line instances keyed by stream name.
void on_active_edit_mode_changed(bool drawing_new)
Handler for toggling active edit mode.
void on_backend_events(const std::vector< yodau::backend::event > &evs)
Handle a batch of backend events.
yodau::backend::stream_manager * stream_mgr
Backend stream manager (non-owning).
void setup_grid_connections()
Connect grid_view signals to controller handlers.
void handle_detect_local_sources()
Handler for detecting available local sources.
QStringList template_candidates_excluding(const QSet< QString > &used) const
List templates not present in a given used set.
void handle_back_to_grid()
Return to grid mode (clear active stream).
void on_active_labels_enabled_changed(bool on)
Handler for toggling persistent label visibility in active view.
void sync_active_cell_lines() const
Push per-stream persistent lines into the active UI cell.
void sync_active_persistent()
Sync persistent line overlays and template candidates for active stream.
int active_interval_ms
Repaint interval for active (focused) stream in ms.
grid_view * grid
Grid view extracted from board (non-owning).
yodau::backend::frame frame_from_image(const QImage &image) const
Convert a QImage into backend frame.
void setup_settings_connections()
Connect settings_panel signals to controller slots.
settings_panel * settings
Settings panel (non-owning).
bool active_labels_enabled
Whether labels are enabled in active cell.
static int repaint_interval_for_count(int n)
Choose repaint interval given number of visible streams.
board * main_zone
Main board/zone widget (non-owning).
void init_from_backend()
Populate settings UI from backend at startup.
bool draft_line_closed
Draft line closed flag.
grid_view(QWidget *parent=nullptr)
Construct an empty grid view.
void remove_stream(const QString &name)
Remove a stream cell from the grid.
void add_stream(const QString &name)
Add a new stream cell to the grid.
stream_cell * peek_stream_cell(const QString &name) const
Get a pointer to a cell without removing it.
void stream_enlarge(const QString &name)
Emitted when a stream cell requests focus/enlargement.
void active_line_undo_requested()
Emitted when user requests undo of the last draft point.
settings_panel(QWidget *parent=nullptr)
Construct the settings panel.
void clear_add_inputs() const
Clear all add-tab input fields and reset validation.
void reset_active_template_form()
Reset the templates form to "none" selection.
void reset_active_line_form()
Reset the "new line" form in the active tab.
void active_edit_mode_changed(bool drawing_new)
Emitted when edit mode changes.
void active_labels_enabled_changed(bool on)
Emitted when label visibility toggle changes.
void set_loop(bool on)
Enable or disable looping for file-based playback.
void set_labels_enabled(bool on)
Enable or disable rendering of persistent line labels.
void clear_draft()
Clear all draft data (points, hover point, preview flag).
void set_drawing_enabled(bool on)
Enable or disable interactive drawing on this cell.
Central coordinator for streams, geometry, frame processing and events.
Represents a single video stream and its analytic connections.
event_kind
High-level classification of backend events.
pixel_format
Pixel format of a frame buffer.
stream_type
Source/transport type of a video stream.
#define str_label(text)
Create a user-visible localized label.
Instance of a persistent (saved) line to be rendered on the stream.
Generic event produced by the backend.
event_kind kind
Type of the event.
std::string line_name
Name of the line / ROI / rule responsible for this event.
int width
Frame width in pixels.
int stride
Number of bytes per row.
int height
Frame height in pixels.
pixel_format format
Pixel format of the buffer.