1#include "widgets/settings_panel.hpp"
2#include "helpers/str_label.hpp"
15#include <QPlainTextEdit>
17#include <QRadioButton>
18#include <QSignalBlocker>
24 , tabs(
new QTabWidget(
this))
27 , mode_group(
new QButtonGroup(
this))
40 , streams_tab(
nullptr)
50 existing_names = std::move(names);
51 on_name_changed(name_edit->text());
58 existing_names.insert(name);
59 on_name_changed(name_edit->text());
63 existing_names.remove(name);
64 on_name_changed(name_edit->text());
68 const QString& name,
const QString& source,
const bool checked
71 for (
int i = 0; i <
streams_list->topLevelItemCount(); ++i) {
73 if (item->text(1) == name) {
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);
87 const QString& name,
const bool checked
91 for (
int i = 0; i <
streams_list->topLevelItemCount(); ++i) {
93 if (item->text(1) == name) {
94 item->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked);
101 for (
int i = 0; i <
streams_list->topLevelItemCount(); ++i) {
103 if (item->text(1) == name) {
112 existing_names.clear();
117 auto ts = QDateTime::currentDateTime().toString(
"HH:mm:ss");
118 event_log_view->appendPlainText(QString(
"[%1] %2").arg(ts, text));
124 if (!sources.isEmpty()) {
148 const QString none_text =
str_label(
"none");
149 const QString current = (
active_combo->currentText() == none_text)
153 QStringList final_names = names;
155 if (!current.isEmpty() && !final_names.contains(current)) {
156 final_names.prepend(current);
163 for (
const auto& n : final_names) {
164 if (n.isEmpty() || n == none_text) {
167 active_combo->addItem(n);
170 if (!current.isEmpty() && final_names.contains(current)) {
186 const auto none_text =
str_label(
"none");
187 if (name.isEmpty() ||
active_combo->findText(name) < 0) {
223 const QString none_text =
str_label(
"none");
231 for (
const auto& n : names) {
232 const auto t = n.trimmed();
236 if (t == none_text) {
239 if (seen.contains(t)) {
243 active_template_combo->addItem(t);
266 active_line_color = Qt::red;
267 active_line_color_btn->setStyleSheet(
268 QString(
"background-color: %1;").arg(active_line_color.name())
271 emit active_line_params_changed(QString(), active_line_color,
false);
302 return active_template_color;
310 const auto ts = QDateTime::currentDateTime().toString(
"HH:mm:ss");
322 const auto root_layout =
new QVBoxLayout(
this);
323 root_layout->setContentsMargins(8, 8, 8, 8);
324 root_layout->addWidget(
tabs);
327 add_tab = build_add_tab();
328 streams_tab = build_streams_tab();
329 active_tab = build_active_tab();
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"));
337 const auto w =
new QWidget(
this);
338 const auto layout =
new QVBoxLayout(w);
339 layout->setSpacing(10);
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);
345 name_box->setLayout(name_layout);
346 layout->addWidget(name_box);
349 name_edit, &QLineEdit::textChanged,
this,
350 &settings_panel::on_name_changed
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);
366 mode_box->setLayout(mode_layout);
367 layout->addWidget(mode_box);
369 connect(mode_group, &QButtonGroup::idClicked,
this, [
this](
int id) {
370 set_mode(
static_cast<input_mode>(id));
371 update_add_enabled();
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);
379 loop_checkbox =
new QCheckBox(
str_label(
"loop"), add_file_box);
383 file_layout->addLayout(file_form);
385 const auto file_btn_row =
new QHBoxLayout();
386 choose_file_btn =
new QPushButton(
str_label(
"choose file"), add_file_box);
388 file_layout->addLayout(file_btn_row);
390 add_file_box->setLayout(file_layout);
391 layout->addWidget(add_file_box);
394 choose_file_btn, &QPushButton::clicked,
this,
395 &settings_panel::on_choose_file
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);
403 refresh_local_btn =
new QPushButton(
str_label(
"refresh"), add_local_box);
405 add_local_box->setLayout(local_layout);
406 layout->addWidget(add_local_box);
409 refresh_local_btn, &QPushButton::clicked,
this,
410 &settings_panel::on_refresh_local
413 local_sources_combo, &QComboBox::currentTextChanged,
this,
414 [
this]() { update_add_enabled(); }
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);
422 url_layout->addLayout(url_form);
423 add_url_box->setLayout(url_layout);
424 layout->addWidget(add_url_box);
426 connect(url_edit, &QLineEdit::textChanged,
this, [
this]() {
427 update_add_enabled();
430 add_btn =
new QPushButton(
str_label(
"add"), w);
434 add_btn, &QPushButton::clicked,
this, &settings_panel::on_add_clicked
437 add_log_view =
new QPlainTextEdit(w);
442 w->setLayout(layout);
449 const auto w =
new QWidget(
this);
450 const auto layout =
new QVBoxLayout(w);
451 layout->setSpacing(10);
453 streams_list =
new QTreeWidget(w);
458 streams_list->header()->setSectionResizeMode(
459 0, QHeaderView::ResizeToContents
461 streams_list->header()->setSectionResizeMode(1, QHeaderView::Stretch);
462 streams_list->header()->setSectionResizeMode(2, QHeaderView::Stretch);
466 streams_list, &QTreeWidget::itemChanged,
this,
467 &settings_panel::on_stream_item_changed
470 event_log_view =
new QPlainTextEdit(w);
475 w->setLayout(layout);
480 const auto w =
new QWidget(
this);
481 const auto layout =
new QVBoxLayout(w);
482 layout->setSpacing(10);
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));
489 active_log_view =
new QPlainTextEdit(w);
492 active_log_view->setSizePolicy(
493 QSizePolicy::Expanding, QSizePolicy::Expanding
497 w->setLayout(layout);
502 const auto box =
new QGroupBox(
str_label(
"active stream"), parent);
503 const auto box_layout =
new QVBoxLayout(box);
505 active_combo =
new QComboBox(box);
510 box->setLayout(box_layout);
512 active_labels_cb =
new QCheckBox(
str_label(
"labels"), box);
517 active_labels_cb, &QCheckBox::toggled,
this,
518 &settings_panel::active_labels_enabled_changed
522 active_combo, &QComboBox::currentTextChanged,
this,
523 &settings_panel::on_active_combo_changed
530 active_mode_box =
new QGroupBox(
str_label(
"edit mode"), parent);
531 const auto h =
new QHBoxLayout(active_mode_box);
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);
546 active_mode_box->setLayout(h);
549 active_mode_group, &QButtonGroup::idClicked,
this,
550 &settings_panel::on_active_mode_clicked
553 return active_mode_box;
557 active_line_box =
new QGroupBox(
str_label(
"new line"), parent);
558 const auto v =
new QVBoxLayout(active_line_box);
560 active_line_name_edit =
new QLineEdit(active_line_box);
566 active_line_closed_cb =
new QCheckBox(
str_label(
"closed"), active_line_box);
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);
576 =
new QPushButton(
str_label(
"undo point"), active_line_box);
580 active_line_undo_btn, &QPushButton::clicked,
this,
581 &settings_panel::on_active_line_undo_clicked
585 =
new QPushButton(
str_label(
"add line"), active_line_box);
588 active_line_box->setLayout(v);
591 active_line_color_btn, &QPushButton::clicked,
this,
592 &settings_panel::on_active_line_color_clicked
596 active_line_name_edit, &QLineEdit::editingFinished,
this,
597 &settings_panel::on_active_line_name_finished
601 active_line_closed_cb, &QCheckBox::toggled,
this,
602 &settings_panel::on_active_line_closed_toggled
606 active_line_save_btn, &QPushButton::clicked,
this,
607 &settings_panel::on_active_line_save_clicked
611 return active_line_box;
615 active_templates_box =
new QGroupBox(
str_label(
"templates"), parent);
616 const auto v =
new QVBoxLayout(active_templates_box);
618 active_template_combo =
new QComboBox(active_templates_box);
624 active_template_combo, &QComboBox::currentTextChanged,
this,
625 &settings_panel::on_active_template_combo_changed
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);
633 active_template_add_btn
634 =
new QPushButton(
str_label(
"add template"), active_templates_box);
637 active_templates_box->setLayout(v);
640 active_template_color_btn, &QPushButton::clicked,
this,
641 &settings_panel::on_active_template_color_clicked
645 active_template_add_btn, &QPushButton::clicked,
this,
646 &settings_panel::on_active_template_add_clicked
650 return active_templates_box;
673 if (!add_file_box || !add_local_box || !add_url_box) {
681 add_file_box->setVisible(file_on);
682 add_file_box->setEnabled(file_on);
684 add_local_box->setVisible(local_on);
685 add_local_box->setEnabled(local_on);
687 add_url_box->setVisible(url_on);
688 add_url_box->setEnabled(url_on);
692 const auto name = resolved_name_for_current_input();
693 const auto unique = name_is_unique(name);
695 add_btn->setEnabled(unique && input_ok);
700 "Video files (*.mp4 *.mkv *.avi *.mov *.webm *.m4v);;All files (*)"
702 auto path = QFileDialog::getOpenFileName(
703 this,
str_label(
"choose video"), QString(), filters
705 if (!path.isEmpty()) {
707 auto ts = QDateTime::currentDateTime().toString(
"HH:mm:ss");
708 append_add_log(QString(
"[%1] file selected: %2").arg(ts, path));
714 auto ts = QDateTime::currentDateTime().toString(
"HH:mm:ss");
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));
725 append_add_log(QString(
"[%1] error: input is incomplete").arg(ts));
734 append_add_log(QString(
"[%1] request add file: %2").arg(ts, path));
735 emit add_file_stream(path, name, loop);
740 append_add_log(QString(
"[%1] request add local: %2").arg(ts, source));
741 emit add_local_stream(source, name);
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);
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));
760 const auto name = resolved_name_for_current_input();
761 const auto unique = name_is_unique(name);
771 if (name.isEmpty()) {
774 if (name.compare(
str_label(
"none"), Qt::CaseInsensitive) == 0) {
777 return !existing_names.contains(name);
789 return !
url_edit->text().trimmed().isEmpty();
801 name_edit->setStyleSheet(
"border: 1px solid red;");
811 const bool drawing_mode
814 if (active_mode_box) {
815 active_mode_box->setVisible(has_active);
816 active_mode_box->setEnabled(has_active);
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);
825 const bool has_templates
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);
839 btn->setStyleSheet(QString(
"background-color: %1;").arg(c.name()));
844 emit active_stream_selected(QString());
846 emit active_stream_selected(text);
852 emit active_edit_mode_changed(id == 0);
857 const auto c = QColorDialog::getColor(
858 active_line_color,
this,
str_label(
"choose color")
864 active_line_color = c;
865 set_btn_color(active_line_color_btn, active_line_color);
867 emit active_line_params_changed(
868 active_line_name_edit->text().trimmed(), active_line_color,
869 active_line_closed_cb->isChecked()
874 emit active_line_undo_requested();
878 emit active_line_save_requested(
885 emit active_line_params_changed(
886 active_line_name_edit->text().trimmed(), active_line_color,
887 active_line_closed_cb->isChecked()
894 emit active_line_params_changed(
895 active_line_name_edit->text().trimmed(), active_line_color,
896 active_line_closed_cb->isChecked()
901 if (text.isEmpty() || text ==
str_label(
"none")) {
902 emit active_template_selected(QString());
904 emit active_template_selected(text);
909 const auto c = QColorDialog::getColor(
910 active_template_color,
this,
str_label(
"choose color")
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);
927 if (t.isEmpty() || t ==
str_label(
"none")) {
931 emit active_template_add_requested(t, active_template_color);
942 const auto name = item->text(1);
943 const bool show = item->checkState(0) == Qt::Checked;
945 emit show_stream_changed(name, show);
948 QString(
"show in grid: %1 = %2").arg(name, show ?
"true" :
"false")
void add_stream_entry(const QString &name, const QString &source, bool checked=false) const
Add a stream row to the streams list.
QWidget * build_streams_tab()
Build the "streams" tab.
QPushButton * active_line_undo_btn
void on_active_template_add_clicked()
"Add template" click handler.
QPushButton * choose_file_btn
settings_panel(QWidget *parent=nullptr)
Construct the settings panel.
void append_event(const QString &text) const
Append a line to the streams-tab event log.
QLineEdit * active_line_name_edit
void set_btn_color(QPushButton *btn, const QColor &c) const
Paint a QPushButton background to match a chosen color.
void append_active_log(const QString &msg) const
Append a line to the active-tab log.
void clear_add_inputs() const
Clear all add-tab input fields and reset validation.
bool current_input_valid() const
Validate that required input fields for current mode are filled.
QWidget * build_templates_box(QWidget *parent)
Build templates form box.
void reset_active_template_form()
Reset the templates form to "none" selection.
QPushButton * refresh_local_btn
void reset_active_line_form()
Reset the "new line" form in the active tab.
QWidget * build_active_tab()
Build the "active" tab.
void update_add_enabled() const
Update enabled state of the "add" button based on validation.
void build_ui()
Build all tabs and root layout.
QPlainTextEdit * event_log_view
void on_active_template_color_clicked()
"Choose template color" click handler.
void set_stream_checked(const QString &name, bool checked) const
Set the "show" checkbox state for a stream entry.
QPlainTextEdit * add_log_view
void set_name_error(bool error) const
Apply/remove UI error styling for name edit.
void on_active_line_color_clicked()
"Choose line color" click handler.
void on_active_line_closed_toggled(bool checked)
Closed-checkbox toggle handler.
void remove_stream_entry(const QString &name) const
Remove a stream entry from the streams list.
QPushButton * active_template_add_btn
void on_add_clicked()
"Add" button handler.
QTreeWidget * streams_list
QTabWidget * tabs
Tab widget hosting add/streams/active tabs.
void append_add_log(const QString &text) const
Append a line to the add-tab log.
void on_active_template_combo_changed(const QString &text)
Template combo change handler.
void clear_stream_entries()
Remove all stream entries and clear name reservations.
QWidget * build_active_stream_box(QWidget *parent)
Build active-stream selection box.
void add_existing_name(const QString &name)
Add one name to the existing-name set.
QPushButton * active_template_color_btn
void set_active_line_closed(bool closed) const
Programmatically set active draft "closed" checkbox.
QPushButton * active_line_save_btn
bool name_is_unique(const QString &name) const
Check if name is not already reserved.
QString resolved_name_for_current_input() const
Resolve the name for the current input (trimmed).
void on_active_mode_clicked(int id)
Edit-mode radio click handler.
QCheckBox * active_line_closed_cb
void set_local_sources(const QStringList &sources) const
Set detected local sources (e.g., /dev/video*).
input_mode
Input mode for the add-tab.
QRadioButton * active_mode_draw_radio
void update_add_tools() const
Update visibility/enabled state of add-tab tool boxes.
QCheckBox * loop_checkbox
QCheckBox * active_labels_cb
void on_active_line_save_clicked()
"Add line" click handler.
void remove_existing_name(const QString &name)
Remove one name from the existing-name set.
QString active_template_current() const
Get currently selected template name.
void set_template_candidates(const QStringList &names) const
Replace the list of template candidates.
QColor active_template_preview_color() const
Get current preview color for templates.
QComboBox * local_sources_combo
void add_template_candidate(const QString &name) const
Add one template name as a candidate in the templates combo.
QButtonGroup * active_mode_group
QWidget * build_edit_mode_box(QWidget *parent)
Build edit-mode radio group.
QWidget * build_add_tab()
Build the "add stream" tab.
QRadioButton * local_radio
void set_active_candidates(const QStringList &names) const
Set the list of streams that can be selected as active.
void on_active_line_name_finished()
Line name edit finished handler.
void set_mode(input_mode mode)
Switch add-tab mode and refresh enabled/visible controls.
QRadioButton * file_radio
void on_name_changed(QString text) const
Handler for name edit changes (validation).
void on_refresh_local()
"Refresh local sources" handler.
void on_active_line_undo_clicked()
"Undo point" click handler.
void clear_active_log() const
Clear the active-tab log view.
void set_existing_names(QSet< QString > names)
Replace the set of existing (reserved) names.
QPushButton * active_line_color_btn
QPlainTextEdit * active_log_view
QLineEdit * file_path_edit
QButtonGroup * mode_group
void on_stream_item_changed(QTreeWidgetItem *item, int column)
Streams list item change handler (checkbox column).
void update_active_tools() const
Show/hide active-tab tool boxes based on selection/mode.
void set_active_current(const QString &name) const
Programmatically select the active stream.
QRadioButton * active_mode_template_radio
QWidget * build_new_line_box(QWidget *parent)
Build "new line" form box.
void on_choose_file()
File chooser handler.
QComboBox * active_template_combo
#define str_label(text)
Create a user-visible localized label.