From cb8156b80723bcd177fef7fb08ea2bfeec99a323 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sat, 22 Apr 2017 10:35:28 +0200 Subject: [PATCH] Initial checkin (sans Nageru theme). --- README | 16 + bg.svg | 312 ++++++++++++++++++ bg2.svg | 325 +++++++++++++++++++ casparcg.config | 168 ++++++++++ client/Makefile | 30 ++ client/acmp_client.cpp | 213 +++++++++++++ client/acmp_client.h | 34 ++ client/main.cpp | 12 + client/mainwindow.cpp | 262 +++++++++++++++ client/mainwindow.h | 46 +++ client/mainwindow.ui | 459 +++++++++++++++++++++++++++ client/post_to_main_thread.h | 16 + client/ui_mainwindow.h | 529 +++++++++++++++++++++++++++++++ lowerthird-bg.png | Bin 0 -> 9559 bytes lowerthird-bg2.png | Bin 0 -> 2574 bytes score.html | 596 +++++++++++++++++++++++++++++++++++ 16 files changed, 3018 insertions(+) create mode 100644 README create mode 100644 bg.svg create mode 100644 bg2.svg create mode 100644 casparcg.config create mode 100644 client/Makefile create mode 100644 client/acmp_client.cpp create mode 100644 client/acmp_client.h create mode 100644 client/main.cpp create mode 100644 client/mainwindow.cpp create mode 100644 client/mainwindow.h create mode 100644 client/mainwindow.ui create mode 100644 client/post_to_main_thread.h create mode 100644 client/ui_mainwindow.h create mode 100644 lowerthird-bg.png create mode 100644 lowerthird-bg2.png create mode 100644 score.html diff --git a/README b/README new file mode 100644 index 0000000..35a8e6f --- /dev/null +++ b/README @@ -0,0 +1,16 @@ +How to prepare: + + Copy all .html and .png files into your CasparCG templates/ directory. + Copy casparcg.config into your CasparCG directory. cd into client/ + and run make to compile the client. + +How to run: + + 1. rm /tmp/caspar.sock. + 2. Run CasparCG. It will hang until Nageru connects. + 3. Start Nageru with -c 3 -t ultimate.lua. + 4. Start the client. + 5. Switch to channel 5 and turn on the overlay when it's started. + +If you need to restart CasparCG for any reason, you'll need to redo steps 1 and 2 +in order. Nageru and the client can stay up; they will reconnect automatically. diff --git a/bg.svg b/bg.svg new file mode 100644 index 0000000..2897c28 --- /dev/null +++ b/bg.svg @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bg2.svg b/bg2.svg new file mode 100644 index 0000000..ff6d119 --- /dev/null +++ b/bg2.svg @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/casparcg.config b/casparcg.config new file mode 100644 index 0000000..633eee7 --- /dev/null +++ b/casparcg.config @@ -0,0 +1,168 @@ + + + + media/ + log/ + data/ + template/ + thumbnail/ + font/ + + secret + + + 720p5994 + stereo + + + + 1 + unix:///tmp/caspar.sock + + -c:v rawvideo -vf format=pix_fmts=bgra -f nut -listen 1 + + + + + + + + 5250 + AMCP + + + 3250 + LOG + + + + + diff --git a/client/Makefile b/client/Makefile new file mode 100644 index 0000000..e76e5b8 --- /dev/null +++ b/client/Makefile @@ -0,0 +1,30 @@ +CXX=g++ +PROTOC=protoc +INSTALL=install +EMBEDDED_BMUSB=no +PKG_MODULES := Qt5Core Qt5Gui Qt5Widgets +CXXFLAGS ?= -O2 -g -Wall # Will be overridden by environment. +CXXFLAGS += -std=gnu++11 -fPIC $(shell pkg-config --cflags $(PKG_MODULES)) -pthread +LDLIBS=$(shell pkg-config --libs $(PKG_MODULES)) -pthread + +OBJS_WITH_MOC = mainwindow.o +OBJS += $(OBJS_WITH_MOC) main.o acmp_client.o +OBJS += $(OBJS_WITH_MOC:.o=.moc.o) + +%.o: %.cpp + $(CXX) -MMD -MP $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $< +%.o: %.cc + $(CXX) -MMD -MP $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $< + +ui_%.h: %.ui + uic $< -o $@ + +%.moc.cpp: %.h + moc $< -o $@ + +all: ultimatescore + +ultimatescore: $(OBJS) + $(CXX) -o $@ $^ $(LDFLAGS) $(LDLIBS) + +mainwindow.o: ui_mainwindow.h diff --git a/client/acmp_client.cpp b/client/acmp_client.cpp new file mode 100644 index 0000000..bb1e56c --- /dev/null +++ b/client/acmp_client.cpp @@ -0,0 +1,213 @@ +#include "acmp_client.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +ACMPClient::ACMPClient(const string &host, int port) + : host(host), port(port) {} + +void ACMPClient::add_init_command(const string &cmd) +{ + init_commands.push_back(cmd + "\r\n"); +} + +void ACMPClient::start() +{ + t = thread(&ACMPClient::thread_func, this); +} + +void ACMPClient::end() +{ + t.join(); +} + +void ACMPClient::send_command(const string &cmd) +{ + lock_guard lock(mu); + queued_commands.push_back(cmd + "\r\n"); +} + +void ACMPClient::change_server(const string &host, int port) +{ + lock_guard lock(mu); + queued_commands.push_back(""); // Marker for disconnect. + this->host = host; + this->port = port; +} + +void ACMPClient::set_connection_callback(const std::function &callback) +{ + connection_callback = callback; +} + +namespace { + +int lookup_and_connect(const char *host, int port) +{ + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + addrinfo *res; + char portstr[16]; + snprintf(portstr, sizeof(portstr), "%d", port); + int err = getaddrinfo(host, portstr, &hints, &res); + if (err != 0) { + fprintf(stderr, "Lookup of %s:%d failed: %s\n", host, port, strerror(errno)); + return -1; + } + + for (addrinfo *p = res; p != NULL; p = p->ai_next) { + int sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sock == -1) { + perror("socket"); + continue; + } + + if (connect(sock, p->ai_addr, p->ai_addrlen) == -1) { + perror("connect"); + close(sock); + continue; + } + + // Success! + freeaddrinfo(res); + return sock; + } + + freeaddrinfo(res); + return -1; +} + +} // namespace + +void ACMPClient::thread_func() +{ + if (connection_callback) { + connection_callback(false); + } + for ( ;; ) { + int sock, port_copy; + string host_copy; + { + lock_guard lock(mu); + host_copy = host; + port_copy = port; + } + sock = lookup_and_connect(host_copy.c_str(), port_copy); + if (sock == -1) { + sleep(1); + continue; + } + + int one = 1; + if (ioctl(sock, FIONBIO, &one) == 1) { + perror("ioctl(FIONBIO)"); + close(sock); + sleep(1); + continue; + } + + printf("Connected to CasparCG.\n"); + if (connection_callback) { + connection_callback(true); + } + + bool first = true; + + for ( ;; ) { + vector commands; + if (first) { + commands = init_commands; + first = false; + } else { + lock_guard lock(mu); + swap(commands, queued_commands); + } + + bool broken = false; + string buf; + for (const string &cmd : commands) { + buf += cmd; + if (cmd.empty()) { + printf("Closing CasparCG socket for reconnection.\n"); + broken = true; + break; + } + } + + if (broken) { + break; + } + if (!buf.empty()) { + printf("Writing: '%s'\n", buf.c_str()); + } + + size_t pos = 0; + do { + // Consume until there is no more. + char junk[1024]; + int err = read(sock, junk, sizeof(junk)); + if (err == -1) { + if (err == EAGAIN) { + perror("read"); + broken = true; + break; + } + } + if (err == 0) { + // Closed. + printf("Server closed connection.\n"); + broken = true; + break; + } + if (err > 0) { + // Try again. + junk[err] = 0; + printf("From server: '%s'\n", junk); + continue; + } + + if (pos < buf.size()) { + // Now write as much as we can. + err = write(sock, buf.data() + pos, buf.size() - pos); + if (err == -1) { + perror("write"); + broken = true; + break; + } + if (err == 0) { + // Uh-oh. Buffer full for some reason? + usleep(10000); + } + pos += err; + } + } while (pos < buf.size()); + + if (broken) { + break; + } + if (buf.empty()) { + usleep(100000); + continue; + } + } + + close(sock); + if (connection_callback) { + connection_callback(false); + } + sleep(1); + } +} diff --git a/client/acmp_client.h b/client/acmp_client.h new file mode 100644 index 0000000..a386b27 --- /dev/null +++ b/client/acmp_client.h @@ -0,0 +1,34 @@ +#ifndef _ACMP_CLIENT_H +#define _ACMP_CLIENT_H 1 + +#include +#include +#include +#include +#include + +class ACMPClient { +public: + ACMPClient(const std::string &host, int port); + + void add_init_command(const std::string &cmd); + void start(); + void end(); + void send_command(const std::string &cmd); // Thread-safe. + void change_server(const std::string &host, int port); // Thread-safe. + void set_connection_callback(const std::function &callback); + +private: + void thread_func(); + + std::thread t; + std::vector init_commands; + std::function connection_callback; + + std::mutex mu; + std::string host; // Protected by mu. + int port; // Protected by mu. + std::vector queued_commands; // Protected by mu. +}; + +#endif // !defined(_ACMP_CLIENT_H) diff --git a/client/main.cpp b/client/main.cpp new file mode 100644 index 0000000..edd02fc --- /dev/null +++ b/client/main.cpp @@ -0,0 +1,12 @@ +#include "acmp_client.h" +#include "mainwindow.h" +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp new file mode 100644 index 0000000..3dd3715 --- /dev/null +++ b/client/mainwindow.cpp @@ -0,0 +1,262 @@ +#include "mainwindow.h" +#include "post_to_main_thread.h" +#include "ui_mainwindow.h" + +#include + +using namespace std; + +string escape_html(const string &str) +{ + string s = ""; + for (char ch : str) { + if (ch == '<') { + s += "<"; + } else if (ch == '>') { + s += ">"; + } else if (ch == '&') { + s += "&"; + } else { + s += ch; + } + } + return s; +} + +string escape_unicode(const string &str) +{ + string s = ""; + for (size_t pos = 0; pos < str.size(); ) { + wchar_t wc; + int len = mbtowc(&wc, str.data() + pos, str.size() - pos); + if (len == -1) { + wc = '?'; + len = 1; + } + pos += len; + + if (wc == '\\') { + s += "\\\\"; + } else if (isprint(wc)) { + s += wc; + } else { + char buf[16]; + snprintf(buf, sizeof(buf), "\\u%04x", wc); + s += buf; + } + } + return s; +} + +string escape_quotes(const string &str) +{ + string s = ""; + for (char ch : str) { + if (ch == '"' || ch == '\\') { + s += '\\'; + } + s += ch; + } + return s; +} + +string serialize_as_json(const map ¶m) +{ + string s = "{"; + + bool first = true; + for (const auto &key_value : param) { + if (!first) s += ", "; + first = false; + + s += '"'; + s += escape_quotes(escape_unicode(key_value.first)); + s += "\": \""; + s += escape_quotes(escape_unicode(key_value.second)); + s += '"'; + } + s += "}"; + return s; +} + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + acmp = new ACMPClient("127.0.0.1", 5250); + acmp->add_init_command("MIXER 1 STRAIGHT_ALPHA_OUTPUT 1"); + acmp->add_init_command("CG 1 ADD 0 \"score\" 1 \"\""); + acmp->set_connection_callback([this](bool connected) { + string msg = connected ? "Connected" : "Not connected"; + post_to_main_thread([this, msg]() { + ui->casparcg_connected_label->setText(QString::fromStdString(msg)); + }); + }); + + acmp->start(); + + connect(ui->casparcg_reconnect_btn, &QPushButton::clicked, this, &MainWindow::casparcg_reconnect_clicked); + connect(ui->set_initials_btn, &QPushButton::clicked, this, &MainWindow::set_initials_clicked); + connect(ui->set_color_btn, &QPushButton::clicked, this, &MainWindow::set_color_clicked); + connect(ui->set_score_btn, &QPushButton::clicked, this, &MainWindow::set_score_clicked); + connect(ui->set_all_scorebug_btn, &QPushButton::clicked, this, &MainWindow::set_all_scorebug_clicked); + connect(ui->goal_1_btn, &QPushButton::clicked, this, [this]() { add_goal(ui->score_1_box, 1); }); + connect(ui->ungoal_1_btn, &QPushButton::clicked, this, [this]() { add_goal(ui->score_1_box, -1); }); + connect(ui->goal_2_btn, &QPushButton::clicked, this, [this]() { add_goal(ui->score_2_box, 1); }); + connect(ui->ungoal_2_btn, &QPushButton::clicked, this, [this]() { add_goal(ui->score_2_box, -1); }); + + connect(ui->set_clock_btn, &QPushButton::clicked, this, &MainWindow::set_clock_clicked); + connect(ui->start_and_show_clock_btn, &QPushButton::clicked, this, &MainWindow::start_and_show_clock_clicked); + connect(ui->stop_clock_btn, &QPushButton::clicked, this, &MainWindow::stop_clock_clicked); + connect(ui->show_clock_btn, &QPushButton::clicked, this, &MainWindow::show_clock_clicked); + connect(ui->hide_clock_btn, &QPushButton::clicked, this, &MainWindow::hide_clock_clicked); + + connect(ui->set_comment_btn, &QPushButton::clicked, this, &MainWindow::set_comment_clicked); + connect(ui->set_and_show_comment_btn, &QPushButton::clicked, this, &MainWindow::set_and_show_comment_clicked); + connect(ui->hide_comment_btn, &QPushButton::clicked, this, &MainWindow::hide_comment_clicked); + connect(ui->set_and_show_autocomment_btn, &QPushButton::clicked, this, &MainWindow::set_and_show_autocomment_clicked); + + connect(ui->show_lower_third_btn, &QPushButton::clicked, this, &MainWindow::show_lower_third_clicked); + connect(ui->hide_lower_third_btn, &QPushButton::clicked, this, &MainWindow::hide_lower_third_clicked); + + autocomment_update(); +} + +MainWindow::~MainWindow() +{ + acmp->end(); + delete ui; +} + +void MainWindow::casparcg_reconnect_clicked() +{ + acmp->change_server(ui->casparcg_host_box->text().toStdString(), + stoi(ui->casparcg_port_box->text().toStdString())); +} + +void MainWindow::set_initials_clicked() +{ + map param; + param["team1"] = escape_html(ui->initials_1_edit->text().toStdString()); + param["team2"] = escape_html(ui->initials_2_edit->text().toStdString()); + acmp->send_command("cg 1 update 1 \"" + escape_quotes(serialize_as_json(param)) + "\""); + acmp->send_command("cg 1 invoke 1 setteams"); +} + +void MainWindow::set_color_clicked() +{ + map param; + param["team1color"] = ui->color_1_edit->text().toStdString(); // Should maybe be escaped, but meh. + param["team2color"] = ui->color_2_edit->text().toStdString(); + acmp->send_command("cg 1 update 1 \"" + escape_quotes(serialize_as_json(param)) + "\""); + acmp->send_command("cg 1 invoke 1 setcolors"); +} + +void MainWindow::set_score_clicked() +{ + map param; + param["score1"] = to_string(ui->score_1_box->value()); + param["score2"] = to_string(ui->score_2_box->value()); + acmp->send_command("cg 1 update 1 \"" + escape_quotes(serialize_as_json(param)) + "\""); + acmp->send_command("cg 1 invoke 1 setscore"); + autocomment_update(); +} + +void MainWindow::set_all_scorebug_clicked() +{ + set_initials_clicked(); + set_color_clicked(); + set_score_clicked(); +} + +void MainWindow::add_goal(QSpinBox *box, int delta) +{ + box->setValue(box->value() + delta); + set_score_clicked(); +} + +void MainWindow::set_clock_clicked() +{ + map param; + param["clock_min"] = to_string(ui->clock_min_box->value()); + param["clock_sec"] = to_string(ui->clock_sec_box->value()); + acmp->send_command("cg 1 update 1 \"" + escape_quotes(serialize_as_json(param)) + "\""); + acmp->send_command("cg 1 invoke 1 setclockfromstate"); +} + +void MainWindow::start_and_show_clock_clicked() +{ + acmp->send_command("cg 1 invoke 1 startclock"); // Also shows. +} + +void MainWindow::stop_clock_clicked() +{ + acmp->send_command("cg 1 invoke 1 stopclock"); +} + +void MainWindow::show_clock_clicked() +{ + acmp->send_command("cg 1 invoke 1 showclock"); +} + +void MainWindow::hide_clock_clicked() +{ + acmp->send_command("cg 1 invoke 1 hideclock"); +} + +void MainWindow::set_comment_clicked() +{ + map param; + param["comment"] = ui->comment_edit->text().toStdString(); + acmp->send_command("cg 1 update 1 \"" + escape_quotes(serialize_as_json(param)) + "\""); + acmp->send_command("cg 1 invoke 1 setcomment"); +} + +void MainWindow::set_and_show_comment_clicked() +{ + set_comment_clicked(); + acmp->send_command("cg 1 invoke 1 showcomment"); +} + +void MainWindow::set_and_show_autocomment_clicked() +{ + ui->comment_edit->setText(ui->autocomment_edit->text()); + set_and_show_comment_clicked(); +} + +void MainWindow::hide_comment_clicked() +{ + acmp->send_command("cg 1 invoke 1 hidecomment"); +} + +void MainWindow::show_lower_third_clicked() +{ + map param; + param["text1"] = ui->lowerthird_heading_edit->text().toStdString(); + param["text2"] = ui->lowerthird_subheading_edit->text().toStdString(); + acmp->send_command("cg 1 update 1 \"" + escape_quotes(serialize_as_json(param)) + "\""); + acmp->send_command("cg 1 invoke 1 setandshowlowerthird"); +} + +void MainWindow::hide_lower_third_clicked() +{ + acmp->send_command("cg 1 invoke 1 hidelowerthird"); +} + +void MainWindow::autocomment_update() +{ + int score1 = ui->score_1_box->value(); + int score2 = ui->score_2_box->value(); + string msg; + if (abs(score1 - score2) >= 3) { + msg = "Game ends after this point"; + } else if (score1 >= 12 || score2 >= 12) { + msg = "Point cap: First to 13"; + } else { + char buf[32]; + snprintf(buf, sizeof(buf), "Pagacap: First to %d", max(score1, score2) + 1); + msg = buf; + } + ui->autocomment_edit->setText(QString::fromStdString(msg)); +} diff --git a/client/mainwindow.h b/client/mainwindow.h new file mode 100644 index 0000000..f2b0a67 --- /dev/null +++ b/client/mainwindow.h @@ -0,0 +1,46 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +#include "acmp_client.h" + +namespace Ui { +class MainWindow; +} + +class QSpinBox; + +class MainWindow : public QMainWindow +{ +Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private: + void casparcg_reconnect_clicked(); + void set_initials_clicked(); + void set_color_clicked(); + void set_score_clicked(); + void set_all_scorebug_clicked(); + void add_goal(QSpinBox *box, int delta); + void set_clock_clicked(); + void start_and_show_clock_clicked(); + void stop_clock_clicked(); + void show_clock_clicked(); + void hide_clock_clicked(); + void set_comment_clicked(); + void set_and_show_comment_clicked(); + void hide_comment_clicked(); + void set_and_show_autocomment_clicked(); + void show_lower_third_clicked(); + void hide_lower_third_clicked(); + void autocomment_update(); + + Ui::MainWindow *ui; + ACMPClient *acmp; +}; + +#endif // MAINWINDOW_H diff --git a/client/mainwindow.ui b/client/mainwindow.ui new file mode 100644 index 0000000..d512948 --- /dev/null +++ b/client/mainwindow.ui @@ -0,0 +1,459 @@ + + + MainWindow + + + + 0 + 0 + 720 + 552 + + + + MainWindow + + + + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + + CasparCG server: + + + + + + + localhost + + + + + + + + 50 + 16777215 + + + + 5250 + + + + + + + Reconnect + + + + + + + + 100 + 0 + + + + Not connected + + + Qt::AlignCenter + + + + + + + + + + + + + + + +1 point + + + + + + + -1 point + + + + + + + + + Score + + + + + + + + + + Set + + + + + + + CSS color + + + + + + + PCL + + + + + + + + + +1 point + + + + + + + -1 point + + + + + + + + + red + + + + + + + + + + Team 2 + + + + + + + Team 1 + + + + + + + TFK + + + + + + + Initials + + + + + + + Set + + + + + + + yellow + + + + + + + Set + + + + + + + Set all + + + + + + + + + + + Qt::Horizontal + + + + + + + + + Set clock to: + + + + + + + + 0 + 0 + + + + 25 + + + + + + + + 0 + 0 + + + + : + + + + + + + + 0 + 0 + + + + 59 + + + + + + + Set + + + + + + + + + + + Start (and show) clock + + + + + + + Stop clock + + + + + + + Show clock + + + + + + + Hide clock + + + + + + + + + Qt::Horizontal + + + + + + + + + Comment: + + + + + + + Pagacap: First to 9 points + + + + + + + Set + + + + + + + Set+show + + + + + + + Hide + + + + + + + + + + + Suggested autocomment if game ends right now: + + + + + + + true + + + + + + + + + + Set+show + + + + + + + + + Qt::Horizontal + + + + + + + + + Lower third heading (HTML allowed): + + + + + + + + + + + + + + Lower third subheading (HTML allowed): + + + + + + + + + + + + + + Set + show lower third + + + + + + + Hide lower third + + + + + + + + + + + + + + + diff --git a/client/post_to_main_thread.h b/client/post_to_main_thread.h new file mode 100644 index 0000000..0462c7b --- /dev/null +++ b/client/post_to_main_thread.h @@ -0,0 +1,16 @@ +#ifndef _POST_TO_MAIN_THREAD_H +#define _POST_TO_MAIN_THREAD_H 1 + +#include +#include +#include + +// http://stackoverflow.com/questions/21646467/how-to-execute-a-functor-in-a-given-thread-in-qt-gcd-style +template +static inline void post_to_main_thread(F &&fun) +{ + QObject signalSource; + QObject::connect(&signalSource, &QObject::destroyed, qApp, std::move(fun)); +} + +#endif // !defined(_POST_TO_MAIN_THREAD_H) diff --git a/client/ui_mainwindow.h b/client/ui_mainwindow.h new file mode 100644 index 0000000..4732e1e --- /dev/null +++ b/client/ui_mainwindow.h @@ -0,0 +1,529 @@ +/******************************************************************************** +** Form generated from reading UI file 'mainwindow.ui' +** +** Created by: Qt User Interface Compiler version 5.7.1 +** +** WARNING! All changes made in this file will be lost when recompiling UI file! +********************************************************************************/ + +#ifndef UI_MAINWINDOW_H +#define UI_MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Ui_MainWindow +{ +public: + QWidget *centralWidget; + QVBoxLayout *verticalLayout_2; + QVBoxLayout *verticalLayout; + QHBoxLayout *horizontalLayout_10; + QLabel *label_11; + QLineEdit *casparcg_host_box; + QLineEdit *casparcg_port_box; + QPushButton *casparcg_reconnect_btn; + QLabel *casparcg_connected_label; + QHBoxLayout *horizontalLayout_3; + QGridLayout *gridLayout; + QHBoxLayout *horizontalLayout_4; + QPushButton *goal_2_btn; + QPushButton *ungoal_2_btn; + QLabel *label_6; + QSpinBox *score_2_box; + QPushButton *set_score_btn; + QLabel *label_7; + QLineEdit *initials_1_edit; + QHBoxLayout *horizontalLayout_5; + QPushButton *goal_1_btn; + QPushButton *ungoal_1_btn; + QLineEdit *color_1_edit; + QSpinBox *score_1_box; + QLabel *label_4; + QLabel *label_3; + QLineEdit *initials_2_edit; + QLabel *label_5; + QPushButton *set_initials_btn; + QLineEdit *color_2_edit; + QPushButton *set_color_btn; + QPushButton *set_all_scorebug_btn; + QFrame *line_3; + QHBoxLayout *horizontalLayout_2; + QLabel *label; + QSpinBox *clock_min_box; + QLabel *label_2; + QSpinBox *clock_sec_box; + QPushButton *set_clock_btn; + QHBoxLayout *horizontalLayout; + QPushButton *start_and_show_clock_btn; + QPushButton *stop_clock_btn; + QPushButton *show_clock_btn; + QPushButton *hide_clock_btn; + QFrame *line_2; + QHBoxLayout *horizontalLayout_6; + QLabel *label_8; + QLineEdit *comment_edit; + QPushButton *set_comment_btn; + QPushButton *set_and_show_comment_btn; + QPushButton *hide_comment_btn; + QHBoxLayout *horizontalLayout_11; + QLabel *label_12; + QLineEdit *autocomment_edit; + QPushButton *set_and_show_autocomment_btn; + QFrame *line; + QHBoxLayout *horizontalLayout_7; + QLabel *label_9; + QLineEdit *lowerthird_heading_edit; + QHBoxLayout *horizontalLayout_8; + QLabel *label_10; + QLineEdit *lowerthird_subheading_edit; + QHBoxLayout *horizontalLayout_9; + QPushButton *show_lower_third_btn; + QPushButton *hide_lower_third_btn; + QStatusBar *statusBar; + + void setupUi(QMainWindow *MainWindow) + { + if (MainWindow->objectName().isEmpty()) + MainWindow->setObjectName(QStringLiteral("MainWindow")); + MainWindow->resize(720, 552); + centralWidget = new QWidget(MainWindow); + centralWidget->setObjectName(QStringLiteral("centralWidget")); + verticalLayout_2 = new QVBoxLayout(centralWidget); + verticalLayout_2->setSpacing(6); + verticalLayout_2->setContentsMargins(11, 11, 11, 11); + verticalLayout_2->setObjectName(QStringLiteral("verticalLayout_2")); + verticalLayout = new QVBoxLayout(); + verticalLayout->setSpacing(6); + verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + verticalLayout->setContentsMargins(6, 6, 6, 6); + horizontalLayout_10 = new QHBoxLayout(); + horizontalLayout_10->setSpacing(6); + horizontalLayout_10->setObjectName(QStringLiteral("horizontalLayout_10")); + label_11 = new QLabel(centralWidget); + label_11->setObjectName(QStringLiteral("label_11")); + + horizontalLayout_10->addWidget(label_11); + + casparcg_host_box = new QLineEdit(centralWidget); + casparcg_host_box->setObjectName(QStringLiteral("casparcg_host_box")); + + horizontalLayout_10->addWidget(casparcg_host_box); + + casparcg_port_box = new QLineEdit(centralWidget); + casparcg_port_box->setObjectName(QStringLiteral("casparcg_port_box")); + casparcg_port_box->setMaximumSize(QSize(50, 16777215)); + + horizontalLayout_10->addWidget(casparcg_port_box); + + casparcg_reconnect_btn = new QPushButton(centralWidget); + casparcg_reconnect_btn->setObjectName(QStringLiteral("casparcg_reconnect_btn")); + + horizontalLayout_10->addWidget(casparcg_reconnect_btn); + + casparcg_connected_label = new QLabel(centralWidget); + casparcg_connected_label->setObjectName(QStringLiteral("casparcg_connected_label")); + casparcg_connected_label->setMinimumSize(QSize(100, 0)); + casparcg_connected_label->setAlignment(Qt::AlignCenter); + + horizontalLayout_10->addWidget(casparcg_connected_label); + + + verticalLayout->addLayout(horizontalLayout_10); + + horizontalLayout_3 = new QHBoxLayout(); + horizontalLayout_3->setSpacing(6); + horizontalLayout_3->setObjectName(QStringLiteral("horizontalLayout_3")); + gridLayout = new QGridLayout(); + gridLayout->setSpacing(6); + gridLayout->setObjectName(QStringLiteral("gridLayout")); + horizontalLayout_4 = new QHBoxLayout(); + horizontalLayout_4->setSpacing(6); + horizontalLayout_4->setObjectName(QStringLiteral("horizontalLayout_4")); + goal_2_btn = new QPushButton(centralWidget); + goal_2_btn->setObjectName(QStringLiteral("goal_2_btn")); + + horizontalLayout_4->addWidget(goal_2_btn); + + ungoal_2_btn = new QPushButton(centralWidget); + ungoal_2_btn->setObjectName(QStringLiteral("ungoal_2_btn")); + + horizontalLayout_4->addWidget(ungoal_2_btn); + + + gridLayout->addLayout(horizontalLayout_4, 4, 2, 1, 1); + + label_6 = new QLabel(centralWidget); + label_6->setObjectName(QStringLiteral("label_6")); + + gridLayout->addWidget(label_6, 3, 0, 1, 1); + + score_2_box = new QSpinBox(centralWidget); + score_2_box->setObjectName(QStringLiteral("score_2_box")); + + gridLayout->addWidget(score_2_box, 3, 2, 1, 1); + + set_score_btn = new QPushButton(centralWidget); + set_score_btn->setObjectName(QStringLiteral("set_score_btn")); + + gridLayout->addWidget(set_score_btn, 3, 3, 1, 1); + + label_7 = new QLabel(centralWidget); + label_7->setObjectName(QStringLiteral("label_7")); + + gridLayout->addWidget(label_7, 2, 0, 1, 1); + + initials_1_edit = new QLineEdit(centralWidget); + initials_1_edit->setObjectName(QStringLiteral("initials_1_edit")); + + gridLayout->addWidget(initials_1_edit, 1, 1, 1, 1); + + horizontalLayout_5 = new QHBoxLayout(); + horizontalLayout_5->setSpacing(6); + horizontalLayout_5->setObjectName(QStringLiteral("horizontalLayout_5")); + goal_1_btn = new QPushButton(centralWidget); + goal_1_btn->setObjectName(QStringLiteral("goal_1_btn")); + + horizontalLayout_5->addWidget(goal_1_btn); + + ungoal_1_btn = new QPushButton(centralWidget); + ungoal_1_btn->setObjectName(QStringLiteral("ungoal_1_btn")); + + horizontalLayout_5->addWidget(ungoal_1_btn); + + + gridLayout->addLayout(horizontalLayout_5, 4, 1, 1, 1); + + color_1_edit = new QLineEdit(centralWidget); + color_1_edit->setObjectName(QStringLiteral("color_1_edit")); + + gridLayout->addWidget(color_1_edit, 2, 1, 1, 1); + + score_1_box = new QSpinBox(centralWidget); + score_1_box->setObjectName(QStringLiteral("score_1_box")); + + gridLayout->addWidget(score_1_box, 3, 1, 1, 1); + + label_4 = new QLabel(centralWidget); + label_4->setObjectName(QStringLiteral("label_4")); + + gridLayout->addWidget(label_4, 0, 2, 1, 1); + + label_3 = new QLabel(centralWidget); + label_3->setObjectName(QStringLiteral("label_3")); + + gridLayout->addWidget(label_3, 0, 1, 1, 1); + + initials_2_edit = new QLineEdit(centralWidget); + initials_2_edit->setObjectName(QStringLiteral("initials_2_edit")); + + gridLayout->addWidget(initials_2_edit, 1, 2, 1, 1); + + label_5 = new QLabel(centralWidget); + label_5->setObjectName(QStringLiteral("label_5")); + + gridLayout->addWidget(label_5, 1, 0, 1, 1); + + set_initials_btn = new QPushButton(centralWidget); + set_initials_btn->setObjectName(QStringLiteral("set_initials_btn")); + + gridLayout->addWidget(set_initials_btn, 1, 3, 1, 1); + + color_2_edit = new QLineEdit(centralWidget); + color_2_edit->setObjectName(QStringLiteral("color_2_edit")); + + gridLayout->addWidget(color_2_edit, 2, 2, 1, 1); + + set_color_btn = new QPushButton(centralWidget); + set_color_btn->setObjectName(QStringLiteral("set_color_btn")); + + gridLayout->addWidget(set_color_btn, 2, 3, 1, 1); + + set_all_scorebug_btn = new QPushButton(centralWidget); + set_all_scorebug_btn->setObjectName(QStringLiteral("set_all_scorebug_btn")); + + gridLayout->addWidget(set_all_scorebug_btn, 4, 3, 1, 1); + + + horizontalLayout_3->addLayout(gridLayout); + + + verticalLayout->addLayout(horizontalLayout_3); + + line_3 = new QFrame(centralWidget); + line_3->setObjectName(QStringLiteral("line_3")); + line_3->setFrameShape(QFrame::HLine); + line_3->setFrameShadow(QFrame::Sunken); + + verticalLayout->addWidget(line_3); + + horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->setSpacing(6); + horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); + label = new QLabel(centralWidget); + label->setObjectName(QStringLiteral("label")); + + horizontalLayout_2->addWidget(label); + + clock_min_box = new QSpinBox(centralWidget); + clock_min_box->setObjectName(QStringLiteral("clock_min_box")); + QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(clock_min_box->sizePolicy().hasHeightForWidth()); + clock_min_box->setSizePolicy(sizePolicy); + clock_min_box->setValue(25); + + horizontalLayout_2->addWidget(clock_min_box); + + label_2 = new QLabel(centralWidget); + label_2->setObjectName(QStringLiteral("label_2")); + QSizePolicy sizePolicy1(QSizePolicy::Fixed, QSizePolicy::Preferred); + sizePolicy1.setHorizontalStretch(0); + sizePolicy1.setVerticalStretch(0); + sizePolicy1.setHeightForWidth(label_2->sizePolicy().hasHeightForWidth()); + label_2->setSizePolicy(sizePolicy1); + + horizontalLayout_2->addWidget(label_2); + + clock_sec_box = new QSpinBox(centralWidget); + clock_sec_box->setObjectName(QStringLiteral("clock_sec_box")); + sizePolicy.setHeightForWidth(clock_sec_box->sizePolicy().hasHeightForWidth()); + clock_sec_box->setSizePolicy(sizePolicy); + clock_sec_box->setMaximum(59); + + horizontalLayout_2->addWidget(clock_sec_box); + + set_clock_btn = new QPushButton(centralWidget); + set_clock_btn->setObjectName(QStringLiteral("set_clock_btn")); + + horizontalLayout_2->addWidget(set_clock_btn); + + + verticalLayout->addLayout(horizontalLayout_2); + + horizontalLayout = new QHBoxLayout(); + horizontalLayout->setSpacing(6); + horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + start_and_show_clock_btn = new QPushButton(centralWidget); + start_and_show_clock_btn->setObjectName(QStringLiteral("start_and_show_clock_btn")); + + horizontalLayout->addWidget(start_and_show_clock_btn); + + stop_clock_btn = new QPushButton(centralWidget); + stop_clock_btn->setObjectName(QStringLiteral("stop_clock_btn")); + + horizontalLayout->addWidget(stop_clock_btn); + + show_clock_btn = new QPushButton(centralWidget); + show_clock_btn->setObjectName(QStringLiteral("show_clock_btn")); + + horizontalLayout->addWidget(show_clock_btn); + + hide_clock_btn = new QPushButton(centralWidget); + hide_clock_btn->setObjectName(QStringLiteral("hide_clock_btn")); + + horizontalLayout->addWidget(hide_clock_btn); + + + verticalLayout->addLayout(horizontalLayout); + + line_2 = new QFrame(centralWidget); + line_2->setObjectName(QStringLiteral("line_2")); + line_2->setFrameShape(QFrame::HLine); + line_2->setFrameShadow(QFrame::Sunken); + + verticalLayout->addWidget(line_2); + + horizontalLayout_6 = new QHBoxLayout(); + horizontalLayout_6->setSpacing(6); + horizontalLayout_6->setObjectName(QStringLiteral("horizontalLayout_6")); + label_8 = new QLabel(centralWidget); + label_8->setObjectName(QStringLiteral("label_8")); + + horizontalLayout_6->addWidget(label_8); + + comment_edit = new QLineEdit(centralWidget); + comment_edit->setObjectName(QStringLiteral("comment_edit")); + + horizontalLayout_6->addWidget(comment_edit); + + set_comment_btn = new QPushButton(centralWidget); + set_comment_btn->setObjectName(QStringLiteral("set_comment_btn")); + + horizontalLayout_6->addWidget(set_comment_btn); + + set_and_show_comment_btn = new QPushButton(centralWidget); + set_and_show_comment_btn->setObjectName(QStringLiteral("set_and_show_comment_btn")); + + horizontalLayout_6->addWidget(set_and_show_comment_btn); + + hide_comment_btn = new QPushButton(centralWidget); + hide_comment_btn->setObjectName(QStringLiteral("hide_comment_btn")); + + horizontalLayout_6->addWidget(hide_comment_btn); + + + verticalLayout->addLayout(horizontalLayout_6); + + horizontalLayout_11 = new QHBoxLayout(); + horizontalLayout_11->setSpacing(6); + horizontalLayout_11->setObjectName(QStringLiteral("horizontalLayout_11")); + label_12 = new QLabel(centralWidget); + label_12->setObjectName(QStringLiteral("label_12")); + + horizontalLayout_11->addWidget(label_12); + + autocomment_edit = new QLineEdit(centralWidget); + autocomment_edit->setObjectName(QStringLiteral("autocomment_edit")); + autocomment_edit->setEnabled(true); + + horizontalLayout_11->addWidget(autocomment_edit); + + set_and_show_autocomment_btn = new QPushButton(centralWidget); + set_and_show_autocomment_btn->setObjectName(QStringLiteral("set_and_show_autocomment_btn")); + + horizontalLayout_11->addWidget(set_and_show_autocomment_btn); + + + verticalLayout->addLayout(horizontalLayout_11); + + line = new QFrame(centralWidget); + line->setObjectName(QStringLiteral("line")); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + + verticalLayout->addWidget(line); + + horizontalLayout_7 = new QHBoxLayout(); + horizontalLayout_7->setSpacing(6); + horizontalLayout_7->setObjectName(QStringLiteral("horizontalLayout_7")); + label_9 = new QLabel(centralWidget); + label_9->setObjectName(QStringLiteral("label_9")); + + horizontalLayout_7->addWidget(label_9); + + lowerthird_heading_edit = new QLineEdit(centralWidget); + lowerthird_heading_edit->setObjectName(QStringLiteral("lowerthird_heading_edit")); + + horizontalLayout_7->addWidget(lowerthird_heading_edit); + + + verticalLayout->addLayout(horizontalLayout_7); + + horizontalLayout_8 = new QHBoxLayout(); + horizontalLayout_8->setSpacing(6); + horizontalLayout_8->setObjectName(QStringLiteral("horizontalLayout_8")); + label_10 = new QLabel(centralWidget); + label_10->setObjectName(QStringLiteral("label_10")); + + horizontalLayout_8->addWidget(label_10); + + lowerthird_subheading_edit = new QLineEdit(centralWidget); + lowerthird_subheading_edit->setObjectName(QStringLiteral("lowerthird_subheading_edit")); + + horizontalLayout_8->addWidget(lowerthird_subheading_edit); + + + verticalLayout->addLayout(horizontalLayout_8); + + horizontalLayout_9 = new QHBoxLayout(); + horizontalLayout_9->setSpacing(6); + horizontalLayout_9->setObjectName(QStringLiteral("horizontalLayout_9")); + show_lower_third_btn = new QPushButton(centralWidget); + show_lower_third_btn->setObjectName(QStringLiteral("show_lower_third_btn")); + + horizontalLayout_9->addWidget(show_lower_third_btn); + + hide_lower_third_btn = new QPushButton(centralWidget); + hide_lower_third_btn->setObjectName(QStringLiteral("hide_lower_third_btn")); + + horizontalLayout_9->addWidget(hide_lower_third_btn); + + + verticalLayout->addLayout(horizontalLayout_9); + + + verticalLayout_2->addLayout(verticalLayout); + + MainWindow->setCentralWidget(centralWidget); + statusBar = new QStatusBar(MainWindow); + statusBar->setObjectName(QStringLiteral("statusBar")); + MainWindow->setStatusBar(statusBar); + + retranslateUi(MainWindow); + + QMetaObject::connectSlotsByName(MainWindow); + } // setupUi + + void retranslateUi(QMainWindow *MainWindow) + { + MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", Q_NULLPTR)); + label_11->setText(QApplication::translate("MainWindow", "CasparCG server:", Q_NULLPTR)); + casparcg_host_box->setText(QApplication::translate("MainWindow", "localhost", Q_NULLPTR)); + casparcg_port_box->setText(QApplication::translate("MainWindow", "5250", Q_NULLPTR)); + casparcg_reconnect_btn->setText(QApplication::translate("MainWindow", "Reconnect", Q_NULLPTR)); + casparcg_connected_label->setText(QApplication::translate("MainWindow", "Not connected", Q_NULLPTR)); + goal_2_btn->setText(QApplication::translate("MainWindow", "+1 point", Q_NULLPTR)); + ungoal_2_btn->setText(QApplication::translate("MainWindow", "-1 point", Q_NULLPTR)); + label_6->setText(QApplication::translate("MainWindow", "Score", Q_NULLPTR)); + set_score_btn->setText(QApplication::translate("MainWindow", "Set", Q_NULLPTR)); + label_7->setText(QApplication::translate("MainWindow", "CSS color", Q_NULLPTR)); + initials_1_edit->setText(QApplication::translate("MainWindow", "PCL", Q_NULLPTR)); + goal_1_btn->setText(QApplication::translate("MainWindow", "+1 point", Q_NULLPTR)); + ungoal_1_btn->setText(QApplication::translate("MainWindow", "-1 point", Q_NULLPTR)); + color_1_edit->setText(QApplication::translate("MainWindow", "red", Q_NULLPTR)); + label_4->setText(QApplication::translate("MainWindow", "Team 2", Q_NULLPTR)); + label_3->setText(QApplication::translate("MainWindow", "Team 1", Q_NULLPTR)); + initials_2_edit->setText(QApplication::translate("MainWindow", "TFK", Q_NULLPTR)); + label_5->setText(QApplication::translate("MainWindow", "Initials", Q_NULLPTR)); + set_initials_btn->setText(QApplication::translate("MainWindow", "Set", Q_NULLPTR)); + color_2_edit->setText(QApplication::translate("MainWindow", "yellow", Q_NULLPTR)); + set_color_btn->setText(QApplication::translate("MainWindow", "Set", Q_NULLPTR)); + set_all_scorebug_btn->setText(QApplication::translate("MainWindow", "Set all", Q_NULLPTR)); + label->setText(QApplication::translate("MainWindow", "Set clock to:", Q_NULLPTR)); + label_2->setText(QApplication::translate("MainWindow", ":", Q_NULLPTR)); + set_clock_btn->setText(QApplication::translate("MainWindow", "Set", Q_NULLPTR)); + start_and_show_clock_btn->setText(QApplication::translate("MainWindow", "Start (and show) clock", Q_NULLPTR)); + stop_clock_btn->setText(QApplication::translate("MainWindow", "Stop clock", Q_NULLPTR)); + show_clock_btn->setText(QApplication::translate("MainWindow", "Show clock", Q_NULLPTR)); + hide_clock_btn->setText(QApplication::translate("MainWindow", "Hide clock", Q_NULLPTR)); + label_8->setText(QApplication::translate("MainWindow", "Comment:", Q_NULLPTR)); + comment_edit->setText(QApplication::translate("MainWindow", "Pagacap: First to 9 points", Q_NULLPTR)); + set_comment_btn->setText(QApplication::translate("MainWindow", "Set", Q_NULLPTR)); + set_and_show_comment_btn->setText(QApplication::translate("MainWindow", "Set+show", Q_NULLPTR)); + hide_comment_btn->setText(QApplication::translate("MainWindow", "Hide", Q_NULLPTR)); + label_12->setText(QApplication::translate("MainWindow", "Suggested autocomment if game ends right now:", Q_NULLPTR)); + autocomment_edit->setText(QString()); + set_and_show_autocomment_btn->setText(QApplication::translate("MainWindow", "Set+show", Q_NULLPTR)); + label_9->setText(QApplication::translate("MainWindow", "Lower third heading (HTML allowed):", Q_NULLPTR)); + label_10->setText(QApplication::translate("MainWindow", "Lower third subheading (HTML allowed):", Q_NULLPTR)); + show_lower_third_btn->setText(QApplication::translate("MainWindow", "Set + show lower third", Q_NULLPTR)); + hide_lower_third_btn->setText(QApplication::translate("MainWindow", "Hide lower third", Q_NULLPTR)); + } // retranslateUi + +}; + +namespace Ui { + class MainWindow: public Ui_MainWindow {}; +} // namespace Ui + +QT_END_NAMESPACE + +#endif // UI_MAINWINDOW_H diff --git a/lowerthird-bg.png b/lowerthird-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..ea954d9b9e758deef87a5993f2c22190a28ac059 GIT binary patch literal 9559 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 literal 0 HcmV?d00001 diff --git a/lowerthird-bg2.png b/lowerthird-bg2.png new file mode 100644 index 0000000000000000000000000000000000000000..4b80dffc5f819ac9c9dc982f436c862664ef58e1 GIT binary patch literal 2574 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 kw0{|u + + + + + + + + +
+ + + + + + + + +
PCL17 â€“ 11NMBUI
+ + + + +
25:00
+ + + + +
Pagacap: First to 9 points
+
+ John Doe
Ola Nordmann +
+
+ Commentators, Trøndisk 2016 +
+ + + + + -- 2.39.2