From: Steinar H. Gunderson Date: Sat, 22 Apr 2017 08:35:28 +0000 (+0200) Subject: Initial checkin (sans Nageru theme). X-Git-Url: https://git.sesse.net/?p=ultimatescore;a=commitdiff_plain;h=cb8156b80723bcd177fef7fb08ea2bfeec99a323 Initial checkin (sans Nageru theme). --- cb8156b80723bcd177fef7fb08ea2bfeec99a323 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 0000000..ea954d9 Binary files /dev/null and b/lowerthird-bg.png differ diff --git a/lowerthird-bg2.png b/lowerthird-bg2.png new file mode 100644 index 0000000..4b80dff Binary files /dev/null and b/lowerthird-bg2.png differ diff --git a/score.html b/score.html new file mode 100644 index 0000000..0764d5d --- /dev/null +++ b/score.html @@ -0,0 +1,596 @@ + + + + + + + + + +
+ + + + + + + + +
PCL17 â€“ 11NMBUI
+ + + + +
25:00
+ + + + +
Pagacap: First to 9 points
+
+ John Doe
Ola Nordmann +
+
+ Commentators, Trøndisk 2016 +
+ + + + +