GCC Code Coverage Report


Directory: ./
File: frontend/src/widgets/settings_panel.cpp
Date: 2025-11-24 00:30:48
Exec Total Coverage
Lines: 0 626 0.0%
Functions: 0 56 0.0%
Branches: 0 574 0.0%

Line Branch Exec Source
1 #include "widgets/settings_panel.hpp"
2 #include "helpers/str_label.hpp"
3
4 #include <QButtonGroup>
5 #include <QCheckBox>
6 #include <QColorDialog>
7 #include <QComboBox>
8 #include <QDateTime>
9 #include <QFileDialog>
10 #include <QFormLayout>
11 #include <QGroupBox>
12 #include <QHBoxLayout>
13 #include <QHeaderView>
14 #include <QLineEdit>
15 #include <QPlainTextEdit>
16 #include <QPushButton>
17 #include <QRadioButton>
18 #include <QSignalBlocker>
19 #include <QTabWidget>
20 #include <QTreeWidget>
21
22 settings_panel::settings_panel(QWidget* parent)
23 : QWidget(parent)
24 , tabs(new QTabWidget(this))
25 , add_tab(nullptr)
26 , name_edit(nullptr)
27 , mode_group(new QButtonGroup(this))
28 , file_radio(nullptr)
29 , local_radio(nullptr)
30 , url_radio(nullptr)
31 , current_mode(input_mode::file)
32 , file_path_edit(nullptr)
33 , choose_file_btn(nullptr)
34 , loop_checkbox(nullptr)
35 , local_sources_combo(nullptr)
36 , refresh_local_btn(nullptr)
37 , url_edit(nullptr)
38 , add_btn(nullptr)
39 , add_log_view(nullptr)
40 , streams_tab(nullptr)
41 , streams_list(nullptr)
42 , event_log_view(nullptr) {
43
44 build_ui();
45 set_mode(input_mode::file);
46 update_add_enabled();
47 }
48
49 void settings_panel::set_existing_names(QSet<QString> names) {
50 existing_names = std::move(names);
51 on_name_changed(name_edit->text());
52 }
53
54 void settings_panel::add_existing_name(const QString& name) {
55 if (name.isEmpty()) {
56 return;
57 }
58 existing_names.insert(name);
59 on_name_changed(name_edit->text());
60 }
61
62 void settings_panel::remove_existing_name(const QString& name) {
63 existing_names.remove(name);
64 on_name_changed(name_edit->text());
65 }
66
67 void settings_panel::add_stream_entry(
68 const QString& name, const QString& source, const bool checked
69 ) const {
70 QSignalBlocker blocker(streams_list);
71 for (int i = 0; i < streams_list->topLevelItemCount(); ++i) {
72 const auto item = streams_list->topLevelItem(i);
73 if (item->text(1) == name) {
74 return;
75 }
76 }
77
78 const auto item = new QTreeWidgetItem();
79 item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
80 item->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked);
81 item->setText(1, name);
82 item->setText(2, source);
83 streams_list->addTopLevelItem(item);
84 }
85
86 void settings_panel::set_stream_checked(
87 const QString& name, const bool checked
88 ) const {
89 // QSignalBlocker blocker(streams_list);
90
91 for (int i = 0; i < streams_list->topLevelItemCount(); ++i) {
92 const auto item = streams_list->topLevelItem(i);
93 if (item->text(1) == name) {
94 item->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked);
95 break;
96 }
97 }
98 }
99
100 void settings_panel::remove_stream_entry(const QString& name) const {
101 for (int i = 0; i < streams_list->topLevelItemCount(); ++i) {
102 const auto item = streams_list->topLevelItem(i);
103 if (item->text(1) == name) {
104 delete streams_list->takeTopLevelItem(i);
105 break;
106 }
107 }
108 }
109
110 void settings_panel::clear_stream_entries() {
111 streams_list->clear();
112 existing_names.clear();
113 update_add_enabled();
114 }
115
116 void settings_panel::append_event(const QString& text) const {
117 auto ts = QDateTime::currentDateTime().toString("HH:mm:ss");
118 event_log_view->appendPlainText(QString("[%1] %2").arg(ts, text));
119 }
120
121 void settings_panel::set_local_sources(const QStringList& sources) const {
122 local_sources_combo->clear();
123 local_sources_combo->addItems(sources);
124 if (!sources.isEmpty()) {
125 local_sources_combo->setCurrentIndex(0);
126 }
127 update_add_enabled();
128 }
129
130 void settings_panel::clear_add_inputs() const {
131 name_edit->clear();
132 file_path_edit->clear();
133 url_edit->clear();
134 local_sources_combo->setCurrentIndex(-1);
135 set_name_error(false);
136 update_add_enabled();
137 }
138
139 void settings_panel::append_add_log(const QString& text) const {
140 add_log_view->appendPlainText(text);
141 }
142
143 void settings_panel::set_active_candidates(const QStringList& names) const {
144 if (!active_combo) {
145 return;
146 }
147
148 const QString none_text = str_label("none");
149 const QString current = (active_combo->currentText() == none_text)
150 ? QString()
151 : active_combo->currentText();
152
153 QStringList final_names = names;
154
155 if (!current.isEmpty() && !final_names.contains(current)) {
156 final_names.prepend(current);
157 }
158
159 active_combo->blockSignals(true);
160 active_combo->clear();
161 active_combo->addItem(none_text, QVariant());
162
163 for (const auto& n : final_names) {
164 if (n.isEmpty() || n == none_text) {
165 continue;
166 }
167 active_combo->addItem(n);
168 }
169
170 if (!current.isEmpty() && final_names.contains(current)) {
171 active_combo->setCurrentText(current);
172 } else {
173 active_combo->setCurrentText(none_text);
174 }
175
176 active_combo->blockSignals(false);
177 update_active_tools();
178 }
179
180 void settings_panel::set_active_current(const QString& name) const {
181 if (!active_combo) {
182 return;
183 }
184
185 active_combo->blockSignals(true);
186 const auto none_text = str_label("none");
187 if (name.isEmpty() || active_combo->findText(name) < 0) {
188 active_combo->setCurrentText(none_text);
189 } else {
190 active_combo->setCurrentText(name);
191 }
192 active_combo->blockSignals(false);
193
194 update_active_tools();
195 }
196
197 void settings_panel::add_template_candidate(const QString& name) const {
198 if (!active_template_combo || name.isEmpty()) {
199 return;
200 }
201
202 for (int i = 0; i < active_template_combo->count(); ++i) {
203 if (active_template_combo->itemText(i) == name) {
204 update_active_tools();
205 return;
206 }
207 }
208
209 active_template_combo->addItem(name);
210
211 if (active_template_combo->count() == 1) {
212 active_template_combo->setCurrentIndex(0);
213 }
214
215 update_active_tools();
216 }
217
218 void settings_panel::set_template_candidates(const QStringList& names) const {
219 if (!active_template_combo) {
220 return;
221 }
222
223 const QString none_text = str_label("none");
224
225 active_template_combo->blockSignals(true);
226
227 active_template_combo->clear();
228 active_template_combo->addItem(none_text, QVariant());
229
230 QSet<QString> seen;
231 for (const auto& n : names) {
232 const auto t = n.trimmed();
233 if (t.isEmpty()) {
234 continue;
235 }
236 if (t == none_text) {
237 continue;
238 }
239 if (seen.contains(t)) {
240 continue;
241 }
242 seen.insert(t);
243 active_template_combo->addItem(t);
244 }
245
246 active_template_combo->setCurrentIndex(0);
247
248 active_template_combo->blockSignals(false);
249
250 update_active_tools();
251 }
252
253 void settings_panel::reset_active_line_form() {
254 if (!active_line_name_edit || !active_line_closed_cb
255 || !active_line_color_btn) {
256 return;
257 }
258
259 active_line_name_edit->clear();
260
261 {
262 QSignalBlocker b(active_line_closed_cb);
263 active_line_closed_cb->setChecked(false);
264 }
265
266 active_line_color = Qt::red;
267 active_line_color_btn->setStyleSheet(
268 QString("background-color: %1;").arg(active_line_color.name())
269 );
270
271 emit active_line_params_changed(QString(), active_line_color, false);
272 }
273
274 void settings_panel::reset_active_template_form() {
275 if (!active_template_combo) {
276 return;
277 }
278 const int idx = active_template_combo->findText(str_label("none"));
279 if (idx >= 0) {
280 active_template_combo->setCurrentIndex(idx);
281 } else {
282 active_template_combo->setCurrentIndex(-1);
283 }
284 }
285
286 void settings_panel::set_active_line_closed(bool closed) const {
287 if (!active_line_closed_cb) {
288 return;
289 }
290 QSignalBlocker b(active_line_closed_cb);
291 active_line_closed_cb->setChecked(closed);
292 }
293
294 QString settings_panel::active_template_current() const {
295 if (!active_template_combo) {
296 return {};
297 }
298 return active_template_combo->currentText().trimmed();
299 }
300
301 QColor settings_panel::active_template_preview_color() const {
302 return active_template_color;
303 }
304
305 void settings_panel::append_active_log(const QString& msg) const {
306 if (!active_log_view) {
307 return;
308 }
309
310 const auto ts = QDateTime::currentDateTime().toString("HH:mm:ss");
311 active_log_view->appendPlainText(QString("[%1] %2").arg(ts, msg));
312 }
313
314 void settings_panel::clear_active_log() const {
315 if (!active_log_view) {
316 return;
317 }
318 active_log_view->clear();
319 }
320
321 void settings_panel::build_ui() {
322 const auto root_layout = new QVBoxLayout(this);
323 root_layout->setContentsMargins(8, 8, 8, 8);
324 root_layout->addWidget(tabs);
325 // setLayout(root_layout);
326
327 add_tab = build_add_tab();
328 streams_tab = build_streams_tab();
329 active_tab = build_active_tab();
330
331 tabs->addTab(add_tab, str_label("add stream"));
332 tabs->addTab(streams_tab, str_label("streams"));
333 tabs->addTab(active_tab, str_label("active"));
334 }
335
336 QWidget* settings_panel::build_add_tab() {
337 const auto w = new QWidget(this);
338 const auto layout = new QVBoxLayout(w);
339 layout->setSpacing(10);
340
341 const auto name_box = new QGroupBox(str_label("name (optional)"), w);
342 const auto name_layout = new QVBoxLayout(name_box);
343 name_edit = new QLineEdit(name_box);
344 name_layout->addWidget(name_edit);
345 name_box->setLayout(name_layout);
346 layout->addWidget(name_box);
347
348 connect(
349 name_edit, &QLineEdit::textChanged, this,
350 &settings_panel::on_name_changed
351 );
352
353 const auto mode_box = new QGroupBox(str_label("source"), w);
354 const auto mode_layout = new QHBoxLayout(mode_box);
355 file_radio = new QRadioButton(str_label("file"), mode_box);
356 local_radio = new QRadioButton(str_label("local"), mode_box);
357 url_radio = new QRadioButton(str_label("url"), mode_box);
358
359 mode_group->addButton(file_radio, static_cast<int>(input_mode::file));
360 mode_group->addButton(local_radio, static_cast<int>(input_mode::local));
361 mode_group->addButton(url_radio, static_cast<int>(input_mode::url));
362
363 mode_layout->addWidget(file_radio);
364 mode_layout->addWidget(local_radio);
365 mode_layout->addWidget(url_radio);
366 mode_box->setLayout(mode_layout);
367 layout->addWidget(mode_box);
368
369 connect(mode_group, &QButtonGroup::idClicked, this, [this](int id) {
370 set_mode(static_cast<input_mode>(id));
371 update_add_enabled();
372 });
373
374 add_file_box = new QGroupBox(str_label("file stream"), w);
375 const auto file_layout = new QVBoxLayout(add_file_box);
376 const auto file_form = new QFormLayout();
377 file_path_edit = new QLineEdit(add_file_box);
378 file_path_edit->setReadOnly(true);
379 loop_checkbox = new QCheckBox(str_label("loop"), add_file_box);
380 loop_checkbox->setChecked(true);
381 file_form->addRow(str_label("path"), file_path_edit);
382 file_form->addRow(QString(), loop_checkbox);
383 file_layout->addLayout(file_form);
384
385 const auto file_btn_row = new QHBoxLayout();
386 choose_file_btn = new QPushButton(str_label("choose file"), add_file_box);
387 file_btn_row->addWidget(choose_file_btn);
388 file_layout->addLayout(file_btn_row);
389
390 add_file_box->setLayout(file_layout);
391 layout->addWidget(add_file_box);
392
393 connect(
394 choose_file_btn, &QPushButton::clicked, this,
395 &settings_panel::on_choose_file
396 );
397
398 add_local_box = new QGroupBox(str_label("local sources"), w);
399 const auto local_layout = new QVBoxLayout(add_local_box);
400 local_sources_combo = new QComboBox(add_local_box);
401 local_sources_combo->setEditable(false);
402 local_layout->addWidget(local_sources_combo);
403 refresh_local_btn = new QPushButton(str_label("refresh"), add_local_box);
404 local_layout->addWidget(refresh_local_btn);
405 add_local_box->setLayout(local_layout);
406 layout->addWidget(add_local_box);
407
408 connect(
409 refresh_local_btn, &QPushButton::clicked, this,
410 &settings_panel::on_refresh_local
411 );
412 connect(
413 local_sources_combo, &QComboBox::currentTextChanged, this,
414 [this]() { update_add_enabled(); }
415 );
416
417 add_url_box = new QGroupBox(str_label("url stream"), w);
418 const auto url_layout = new QVBoxLayout(add_url_box);
419 const auto url_form = new QFormLayout();
420 url_edit = new QLineEdit(add_url_box);
421 url_form->addRow(str_label("url"), url_edit);
422 url_layout->addLayout(url_form);
423 add_url_box->setLayout(url_layout);
424 layout->addWidget(add_url_box);
425
426 connect(url_edit, &QLineEdit::textChanged, this, [this]() {
427 update_add_enabled();
428 });
429
430 add_btn = new QPushButton(str_label("add"), w);
431 layout->addWidget(add_btn);
432
433 connect(
434 add_btn, &QPushButton::clicked, this, &settings_panel::on_add_clicked
435 );
436
437 add_log_view = new QPlainTextEdit(w);
438 add_log_view->setReadOnly(true);
439 add_log_view->setMinimumHeight(120);
440 layout->addWidget(add_log_view);
441
442 w->setLayout(layout);
443 update_add_tools();
444 update_add_enabled();
445 return w;
446 }
447
448 QWidget* settings_panel::build_streams_tab() {
449 const auto w = new QWidget(this);
450 const auto layout = new QVBoxLayout(w);
451 layout->setSpacing(10);
452
453 streams_list = new QTreeWidget(w);
454 streams_list->setColumnCount(3);
455 streams_list->setHeaderLabels(
456 { str_label("show"), str_label("name"), str_label("source") }
457 );
458 streams_list->header()->setSectionResizeMode(
459 0, QHeaderView::ResizeToContents
460 );
461 streams_list->header()->setSectionResizeMode(1, QHeaderView::Stretch);
462 streams_list->header()->setSectionResizeMode(2, QHeaderView::Stretch);
463 layout->addWidget(streams_list);
464
465 connect(
466 streams_list, &QTreeWidget::itemChanged, this,
467 &settings_panel::on_stream_item_changed
468 );
469
470 event_log_view = new QPlainTextEdit(w);
471 event_log_view->setReadOnly(true);
472 event_log_view->setMinimumHeight(160);
473 layout->addWidget(event_log_view);
474
475 w->setLayout(layout);
476 return w;
477 }
478
479 QWidget* settings_panel::build_active_tab() {
480 const auto w = new QWidget(this);
481 const auto layout = new QVBoxLayout(w);
482 layout->setSpacing(10);
483
484 layout->addWidget(build_active_stream_box(w));
485 layout->addWidget(build_edit_mode_box(w));
486 layout->addWidget(build_new_line_box(w));
487 layout->addWidget(build_templates_box(w));
488
489 active_log_view = new QPlainTextEdit(w);
490 active_log_view->setReadOnly(true);
491 active_log_view->setMinimumHeight(160);
492 active_log_view->setSizePolicy(
493 QSizePolicy::Expanding, QSizePolicy::Expanding
494 );
495 layout->addWidget(active_log_view, 1);
496
497 w->setLayout(layout);
498 return w;
499 }
500
501 QWidget* settings_panel::build_active_stream_box(QWidget* parent) {
502 const auto box = new QGroupBox(str_label("active stream"), parent);
503 const auto box_layout = new QVBoxLayout(box);
504
505 active_combo = new QComboBox(box);
506 active_combo->setEditable(false);
507 active_combo->addItem(str_label("none"), QVariant());
508
509 box_layout->addWidget(active_combo);
510 box->setLayout(box_layout);
511
512 active_labels_cb = new QCheckBox(str_label("labels"), box);
513 active_labels_cb->setChecked(true);
514 box_layout->addWidget(active_labels_cb);
515
516 connect(
517 active_labels_cb, &QCheckBox::toggled, this,
518 &settings_panel::active_labels_enabled_changed
519 );
520
521 connect(
522 active_combo, &QComboBox::currentTextChanged, this,
523 &settings_panel::on_active_combo_changed
524 );
525
526 return box;
527 }
528
529 QWidget* settings_panel::build_edit_mode_box(QWidget* parent) {
530 active_mode_box = new QGroupBox(str_label("edit mode"), parent);
531 const auto h = new QHBoxLayout(active_mode_box);
532
533 active_mode_group = new QButtonGroup(active_mode_box);
534 active_mode_draw_radio
535 = new QRadioButton(str_label("draw new"), active_mode_box);
536 active_mode_template_radio
537 = new QRadioButton(str_label("use template"), active_mode_box);
538
539 active_mode_group->addButton(active_mode_draw_radio, 0);
540 active_mode_group->addButton(active_mode_template_radio, 1);
541
542 active_mode_draw_radio->setChecked(true);
543
544 h->addWidget(active_mode_draw_radio);
545 h->addWidget(active_mode_template_radio);
546 active_mode_box->setLayout(h);
547
548 connect(
549 active_mode_group, &QButtonGroup::idClicked, this,
550 &settings_panel::on_active_mode_clicked
551 );
552
553 return active_mode_box;
554 }
555
556 QWidget* settings_panel::build_new_line_box(QWidget* parent) {
557 active_line_box = new QGroupBox(str_label("new line"), parent);
558 const auto v = new QVBoxLayout(active_line_box);
559
560 active_line_name_edit = new QLineEdit(active_line_box);
561 active_line_name_edit->setPlaceholderText(
562 str_label("template name (optional)")
563 );
564 v->addWidget(active_line_name_edit);
565
566 active_line_closed_cb = new QCheckBox(str_label("closed"), active_line_box);
567 active_line_closed_cb->setChecked(false);
568 v->addWidget(active_line_closed_cb);
569
570 active_line_color_btn
571 = new QPushButton(str_label("color"), active_line_box);
572 set_btn_color(active_line_color_btn, active_line_color);
573 v->addWidget(active_line_color_btn);
574
575 active_line_undo_btn
576 = new QPushButton(str_label("undo point"), active_line_box);
577 v->addWidget(active_line_undo_btn);
578
579 connect(
580 active_line_undo_btn, &QPushButton::clicked, this,
581 &settings_panel::on_active_line_undo_clicked
582 );
583
584 active_line_save_btn
585 = new QPushButton(str_label("add line"), active_line_box);
586 v->addWidget(active_line_save_btn);
587
588 active_line_box->setLayout(v);
589
590 connect(
591 active_line_color_btn, &QPushButton::clicked, this,
592 &settings_panel::on_active_line_color_clicked
593 );
594
595 connect(
596 active_line_name_edit, &QLineEdit::editingFinished, this,
597 &settings_panel::on_active_line_name_finished
598 );
599
600 connect(
601 active_line_closed_cb, &QCheckBox::toggled, this,
602 &settings_panel::on_active_line_closed_toggled
603 );
604
605 connect(
606 active_line_save_btn, &QPushButton::clicked, this,
607 &settings_panel::on_active_line_save_clicked
608 );
609
610 update_active_tools();
611 return active_line_box;
612 }
613
614 QWidget* settings_panel::build_templates_box(QWidget* parent) {
615 active_templates_box = new QGroupBox(str_label("templates"), parent);
616 const auto v = new QVBoxLayout(active_templates_box);
617
618 active_template_combo = new QComboBox(active_templates_box);
619 active_template_combo->setEditable(false);
620 active_template_combo->addItem(str_label("none"), QVariant());
621 v->addWidget(active_template_combo);
622
623 connect(
624 active_template_combo, &QComboBox::currentTextChanged, this,
625 &settings_panel::on_active_template_combo_changed
626 );
627
628 active_template_color_btn
629 = new QPushButton(str_label("color"), active_templates_box);
630 set_btn_color(active_template_color_btn, active_template_color);
631 v->addWidget(active_template_color_btn);
632
633 active_template_add_btn
634 = new QPushButton(str_label("add template"), active_templates_box);
635 v->addWidget(active_template_add_btn);
636
637 active_templates_box->setLayout(v);
638
639 connect(
640 active_template_color_btn, &QPushButton::clicked, this,
641 &settings_panel::on_active_template_color_clicked
642 );
643
644 connect(
645 active_template_add_btn, &QPushButton::clicked, this,
646 &settings_panel::on_active_template_add_clicked
647 );
648
649 update_active_tools();
650 return active_templates_box;
651 }
652
653 void settings_panel::set_mode(const input_mode mode) {
654 current_mode = mode;
655
656 file_radio->setChecked(mode == input_mode::file);
657 local_radio->setChecked(mode == input_mode::local);
658 url_radio->setChecked(mode == input_mode::url);
659
660 file_path_edit->setEnabled(mode == input_mode::file);
661 choose_file_btn->setEnabled(mode == input_mode::file);
662 loop_checkbox->setEnabled(mode == input_mode::file);
663
664 local_sources_combo->setEnabled(mode == input_mode::local);
665 refresh_local_btn->setEnabled(mode == input_mode::local);
666
667 url_edit->setEnabled(mode == input_mode::url);
668 update_add_tools();
669 update_add_enabled();
670 }
671
672 void settings_panel::update_add_tools() const {
673 if (!add_file_box || !add_local_box || !add_url_box) {
674 return;
675 }
676
677 const bool file_on = current_mode == input_mode::file;
678 const bool local_on = current_mode == input_mode::local;
679 const bool url_on = current_mode == input_mode::url;
680
681 add_file_box->setVisible(file_on);
682 add_file_box->setEnabled(file_on);
683
684 add_local_box->setVisible(local_on);
685 add_local_box->setEnabled(local_on);
686
687 add_url_box->setVisible(url_on);
688 add_url_box->setEnabled(url_on);
689 }
690
691 void settings_panel::update_add_enabled() const {
692 const auto name = resolved_name_for_current_input();
693 const auto unique = name_is_unique(name);
694 const auto input_ok = current_input_valid();
695 add_btn->setEnabled(unique && input_ok);
696 }
697
698 void settings_panel::on_choose_file() {
699 const auto filters = str_label(
700 "Video files (*.mp4 *.mkv *.avi *.mov *.webm *.m4v);;All files (*)"
701 );
702 auto path = QFileDialog::getOpenFileName(
703 this, str_label("choose video"), QString(), filters
704 );
705 if (!path.isEmpty()) {
706 file_path_edit->setText(path);
707 auto ts = QDateTime::currentDateTime().toString("HH:mm:ss");
708 append_add_log(QString("[%1] file selected: %2").arg(ts, path));
709 }
710 update_add_enabled();
711 }
712
713 void settings_panel::on_add_clicked() {
714 auto ts = QDateTime::currentDateTime().toString("HH:mm:ss");
715
716 const auto name = resolved_name_for_current_input();
717 if (!name_is_unique(name)) {
718 append_add_log(QString("[%1] error: name already exists").arg(ts));
719 set_name_error(true);
720 update_add_enabled();
721 return;
722 }
723
724 if (!current_input_valid()) {
725 append_add_log(QString("[%1] error: input is incomplete").arg(ts));
726 update_add_enabled();
727 return;
728 }
729
730 switch (current_mode) {
731 case input_mode::file: {
732 auto path = file_path_edit->text().trimmed();
733 const auto loop = loop_checkbox->isChecked();
734 append_add_log(QString("[%1] request add file: %2").arg(ts, path));
735 emit add_file_stream(path, name, loop);
736 break;
737 }
738 case input_mode::local: {
739 auto source = local_sources_combo->currentText().trimmed();
740 append_add_log(QString("[%1] request add local: %2").arg(ts, source));
741 emit add_local_stream(source, name);
742 break;
743 }
744 case input_mode::url: {
745 auto url = url_edit->text().trimmed();
746 append_add_log(QString("[%1] request add url: %2").arg(ts, url));
747 emit add_url_stream(url, name);
748 break;
749 }
750 }
751 }
752
753 void settings_panel::on_refresh_local() {
754 emit detect_local_sources_requested();
755 const auto ts = QDateTime::currentDateTime().toString("HH:mm:ss");
756 append_add_log(QString("[%1] detect local sources requested").arg(ts));
757 }
758
759 void settings_panel::on_name_changed(QString) const {
760 const auto name = resolved_name_for_current_input();
761 const auto unique = name_is_unique(name);
762 set_name_error(!unique);
763 update_add_enabled();
764 }
765
766 QString settings_panel::resolved_name_for_current_input() const {
767 return name_edit->text().trimmed();
768 }
769
770 bool settings_panel::name_is_unique(const QString& name) const {
771 if (name.isEmpty()) {
772 return true;
773 }
774 if (name.compare(str_label("none"), Qt::CaseInsensitive) == 0) {
775 return false;
776 }
777 return !existing_names.contains(name);
778 }
779
780 bool settings_panel::current_input_valid() const {
781 switch (current_mode) {
782 case input_mode::file: {
783 return !file_path_edit->text().trimmed().isEmpty();
784 }
785 case input_mode::local: {
786 return !local_sources_combo->currentText().trimmed().isEmpty();
787 }
788 case input_mode::url: {
789 return !url_edit->text().trimmed().isEmpty();
790 }
791 }
792 return false;
793 }
794
795 void settings_panel::set_name_error(const bool error) const {
796 if (!error) {
797 name_edit->setStyleSheet(QString());
798 name_edit->setToolTip(QString());
799 return;
800 }
801 name_edit->setStyleSheet("border: 1px solid red;");
802 name_edit->setToolTip(str_label("name is already taken"));
803 }
804
805 void settings_panel::update_active_tools() const {
806 if (!active_combo) {
807 return;
808 }
809
810 const bool has_active = active_combo->currentText() != str_label("none");
811 const bool drawing_mode
812 = active_mode_draw_radio && active_mode_draw_radio->isChecked();
813
814 if (active_mode_box) {
815 active_mode_box->setVisible(has_active);
816 active_mode_box->setEnabled(has_active);
817 }
818
819 if (active_line_box) {
820 const bool show_line = has_active && drawing_mode;
821 active_line_box->setVisible(show_line);
822 active_line_box->setEnabled(show_line);
823 }
824
825 const bool has_templates
826 = active_template_combo && active_template_combo->count() > 0;
827
828 if (active_templates_box) {
829 const bool show_tpl = has_active && has_templates && !drawing_mode;
830 active_templates_box->setVisible(show_tpl);
831 active_templates_box->setEnabled(show_tpl);
832 }
833 }
834
835 void settings_panel::set_btn_color(QPushButton* btn, const QColor& c) const {
836 if (!btn) {
837 return;
838 }
839 btn->setStyleSheet(QString("background-color: %1;").arg(c.name()));
840 }
841
842 void settings_panel::on_active_combo_changed(const QString& text) {
843 if (text == str_label("none")) {
844 emit active_stream_selected(QString());
845 } else {
846 emit active_stream_selected(text);
847 }
848 update_active_tools();
849 }
850
851 void settings_panel::on_active_mode_clicked(int id) {
852 emit active_edit_mode_changed(id == 0);
853 update_active_tools();
854 }
855
856 void settings_panel::on_active_line_color_clicked() {
857 const auto c = QColorDialog::getColor(
858 active_line_color, this, str_label("choose color")
859 );
860 if (!c.isValid()) {
861 return;
862 }
863
864 active_line_color = c;
865 set_btn_color(active_line_color_btn, active_line_color);
866
867 emit active_line_params_changed(
868 active_line_name_edit->text().trimmed(), active_line_color,
869 active_line_closed_cb->isChecked()
870 );
871 }
872
873 void settings_panel::on_active_line_undo_clicked() {
874 emit active_line_undo_requested();
875 }
876
877 void settings_panel::on_active_line_save_clicked() {
878 emit active_line_save_requested(
879 active_line_name_edit->text().trimmed(),
880 active_line_closed_cb->isChecked()
881 );
882 }
883
884 void settings_panel::on_active_line_name_finished() {
885 emit active_line_params_changed(
886 active_line_name_edit->text().trimmed(), active_line_color,
887 active_line_closed_cb->isChecked()
888 );
889 }
890
891 void settings_panel::on_active_line_closed_toggled(bool checked) {
892 Q_UNUSED(checked);
893
894 emit active_line_params_changed(
895 active_line_name_edit->text().trimmed(), active_line_color,
896 active_line_closed_cb->isChecked()
897 );
898 }
899
900 void settings_panel::on_active_template_combo_changed(const QString& text) {
901 if (text.isEmpty() || text == str_label("none")) {
902 emit active_template_selected(QString());
903 } else {
904 emit active_template_selected(text);
905 }
906 }
907
908 void settings_panel::on_active_template_color_clicked() {
909 const auto c = QColorDialog::getColor(
910 active_template_color, this, str_label("choose color")
911 );
912 if (!c.isValid()) {
913 return;
914 }
915
916 active_template_color = c;
917 set_btn_color(active_template_color_btn, active_template_color);
918 emit active_template_color_changed(active_template_color);
919 }
920
921 void settings_panel::on_active_template_add_clicked() {
922 if (!active_template_combo) {
923 return;
924 }
925
926 const auto t = active_template_combo->currentText();
927 if (t.isEmpty() || t == str_label("none")) {
928 return;
929 }
930
931 emit active_template_add_requested(t, active_template_color);
932 }
933
934 void settings_panel::on_stream_item_changed(QTreeWidgetItem* item, int column) {
935 if (!item) {
936 return;
937 }
938 if (column != 0) {
939 return;
940 }
941
942 const auto name = item->text(1);
943 const bool show = item->checkState(0) == Qt::Checked;
944
945 emit show_stream_changed(name, show);
946
947 append_event(
948 QString("show in grid: %1 = %2").arg(name, show ? "true" : "false")
949 );
950 }
951