#include "mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), socket(new QTcpSocket(this)) { setupUI(); updateConnectionStatus(); loadSavedServers(); statusTimer = new QTimer(this); lockedTimeTimer = new QTimer(this); connect(statusTimer, &QTimer::timeout, this, &MainWindow::updateConnectionStatus); connect(lockedTimeTimer, &QTimer::timeout, this, &MainWindow::resendLockedServerTime); statusTimer->start(1000); connect(socket, &QTcpSocket::readyRead, this, &MainWindow::readServerOutput); } MainWindow::~MainWindow() { if (socket->state() == QAbstractSocket::ConnectedState) { socket->write("exit\n"); socket->disconnectFromHost(); if (socket->state() != QAbstractSocket::UnconnectedState) { socket->waitForDisconnected(1000); } } } void MainWindow::attemptConnection() { socket->abort(); isAuthenticated = false; socket->connectToHost(serverEdit->text(), portEdit->text().toUInt()); if (!socket->waitForConnected(3000)) { QMessageBox::critical(this, "Connection Failed", "Could not connect to server."); } updateConnectionStatus(); } void MainWindow::readServerOutput() { QByteArray response = socket->readAll(); QString text = QString::fromUtf8(response); serverLogTextEdit->append(text); if (awaitingUsersResponse) { usersResponseBuffer += text; if (usersResponseBuffer.contains(QRegularExpression(R"(Total of \d+ in the game)"))) { parseUsersResponse(usersResponseBuffer); usersResponseBuffer.clear(); awaitingUsersResponse = false; } } if (awaitingBansResponse) { bansResponseBuffer += text; if (bansResponseBuffer.contains("Ban list entries:") && bansResponseBuffer.contains(QRegularExpression(R"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} - )"))) { parseBansResponse(bansResponseBuffer); bansResponseBuffer.clear(); awaitingBansResponse = false; } } if (text.contains("Please enter password:", Qt::CaseInsensitive)) { socket->write(passwordEdit->text().toUtf8() + "\n"); socket->waitForBytesWritten(1000); return; } if (!text.contains("Please enter password:", Qt::CaseInsensitive)) { isAuthenticated = true; } } bool MainWindow::sendTelnetCommand(const QString &command) { if (socket->state() != QAbstractSocket::ConnectedState) { QMessageBox::warning(this, "Not Connected", "Please connect to the server first."); return false; } if (!isAuthenticated) { QMessageBox::warning(this, "Not Authenticated", "Server is waiting for a password. Please wait or reconnect."); return false; } socket->write(command.toUtf8() + "\n"); socket->waitForBytesWritten(1000); const QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); historyTextEdit->append(QString("[%1] %2").arg(timestamp, command)); return true; } QString MainWindow::serverConfigFilePath() const { const QString configRoot = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); return QDir(configRoot).filePath("7d2d-commandutil/servers.xml"); } void MainWindow::loadSavedServers() { savedServers.clear(); savedServersCombo->clear(); savedServersCombo->addItem("Select Server"); savedServersCombo->setCurrentIndex(0); QFile file(serverConfigFilePath()); if (!file.exists()) { return; } if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::warning(this, "Load Failed", "Could not open saved servers file."); return; } QXmlStreamReader xml(&file); while (!xml.atEnd()) { xml.readNext(); if (!xml.isStartElement() || xml.name() != "server") { continue; } SavedServerEntry entry; entry.name = xml.attributes().value("name").toString(); while (!(xml.isEndElement() && xml.name() == "server") && !xml.atEnd()) { xml.readNext(); if (!xml.isStartElement()) { continue; } if (xml.name() == "host") { entry.server = xml.readElementText(); } else if (xml.name() == "port") { entry.port = xml.readElementText(); } else if (xml.name() == "password") { entry.password = xml.readElementText(); } } if (!entry.name.isEmpty()) { savedServers.append(entry); } } if (xml.hasError()) { QMessageBox::warning(this, "Load Failed", "The saved servers file is not valid XML."); return; } std::sort(savedServers.begin(), savedServers.end(), [](const SavedServerEntry &left, const SavedServerEntry &right) { return QString::compare(left.name, right.name, Qt::CaseInsensitive) < 0; }); for (const SavedServerEntry &entry : savedServers) { savedServersCombo->addItem(entry.name); } } void MainWindow::parseUsersResponse(const QString &text) { static const QRegularExpression userLinePattern( R"(^\d+\.\s+id=([^,]+),\s+([^,]+),\s+pos=\(([^)]*)\),\s+rot=\(([^)]*)\),\s+remote=([^,]+),\s+health=([^,]+),\s+deaths=([^,]+),\s+zombies=([^,]+),\s+players=([^,]+),\s+score=([^,]+),\s+level=([^,]+),\s+pltfmid=([^,]+),\s+crossid=([^,]+),\s+ip=([^,]+),\s+ping=([^,\s]+)\s*$)" ); users.clear(); usersListWidget->clear(); clearUserInfo(); const QStringList lines = text.split(QRegularExpression(R"(\r?\n)"), Qt::SkipEmptyParts); for (const QString &line : lines) { const QRegularExpressionMatch match = userLinePattern.match(line.trimmed()); if (!match.hasMatch()) { continue; } UserEntry entry{ match.captured(1).trimmed(), match.captured(2).trimmed(), match.captured(3).trimmed(), match.captured(4).trimmed(), match.captured(5).trimmed(), match.captured(6).trimmed(), match.captured(7).trimmed(), match.captured(8).trimmed(), match.captured(9).trimmed(), match.captured(10).trimmed(), match.captured(11).trimmed(), match.captured(12).trimmed(), match.captured(13).trimmed(), match.captured(14).trimmed(), match.captured(15).trimmed() }; users.append(entry); usersListWidget->addItem(entry.username); } } void MainWindow::clearUserInfo() { for (QLabel *label : userInfoValueLabels) { label->clear(); } } void MainWindow::clearBanInfo() { for (QLabel *label : banInfoValueLabels) { label->clear(); } } void MainWindow::refreshUsers() { users.clear(); usersListWidget->clear(); usersResponseBuffer.clear(); clearUserInfo(); if (!sendTelnetCommand("lp")) { return; } awaitingUsersResponse = true; } void MainWindow::displaySelectedUserInfo(int row) { if (row < 0 || row >= users.size()) { clearUserInfo(); return; } const UserEntry &entry = users.at(row); userInfoValueLabels.value("Player ID")->setText(entry.playerId); userInfoValueLabels.value("Username")->setText(entry.username); userInfoValueLabels.value("Position")->setText(entry.position); userInfoValueLabels.value("Rotation")->setText(entry.rotation); userInfoValueLabels.value("Remote")->setText(entry.remoteState); userInfoValueLabels.value("Health")->setText(entry.health); userInfoValueLabels.value("Deaths")->setText(entry.deaths); userInfoValueLabels.value("Zombies")->setText(entry.zombies); userInfoValueLabels.value("Players")->setText(entry.players); userInfoValueLabels.value("Score")->setText(entry.score); userInfoValueLabels.value("Level")->setText(entry.level); userInfoValueLabels.value("Platform ID")->setText(entry.platformId); userInfoValueLabels.value("EOS ID")->setText(entry.eosId); userInfoValueLabels.value("IP Address")->setText(entry.ipAddress); userInfoValueLabels.value("Ping")->setText(entry.ping); } void MainWindow::parseBansResponse(const QString &text) { static const QRegularExpression banLinePattern( R"(^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) - ([^(]+) \(([^)]*)\) - (.*)$)" ); bans.clear(); bansListWidget->clear(); clearBanInfo(); const QStringList lines = text.split(QRegularExpression(R"(\r?\n)"), Qt::SkipEmptyParts); for (const QString &line : lines) { const QRegularExpressionMatch match = banLinePattern.match(line.trimmed()); if (!match.hasMatch()) { continue; } BanEntry entry{ match.captured(1).trimmed(), match.captured(2).trimmed(), match.captured(3).trimmed(), match.captured(4).trimmed() }; bans.append(entry); bansListWidget->addItem(entry.username); } } void MainWindow::refreshBans() { bans.clear(); bansListWidget->clear(); bansResponseBuffer.clear(); clearBanInfo(); if (!sendTelnetCommand("ban list")) { return; } awaitingBansResponse = true; } void MainWindow::displaySelectedBanInfo(int row) { if (row < 0 || row >= bans.size()) { clearBanInfo(); return; } const BanEntry &entry = bans.at(row); banInfoValueLabels.value("Banned Until")->setText(entry.bannedUntil); banInfoValueLabels.value("Ban Reason")->setText(entry.reason); } void MainWindow::removeSelectedBan() { const int row = bansListWidget->currentRow(); if (row < 0 || row >= bans.size()) { QMessageBox::warning(this, "No Ban Selected", "Please select a banned user first."); return; } sendTelnetCommand(QString("ban remove %1").arg(bans.at(row).userId)); } void MainWindow::kickSelectedUser() { const int row = usersListWidget->currentRow(); if (row < 0 || row >= users.size()) { QMessageBox::warning(this, "No User Selected", "Please select a user first."); return; } bool ok = false; const QString reason = QInputDialog::getText( this, "Kick User", "Reason?", QLineEdit::Normal, QString(), &ok ); if (!ok) { return; } const QString trimmedReason = reason.trimmed(); const QString playerId = users.at(row).playerId; QString escapedReason = trimmedReason; const QString command = trimmedReason.isEmpty() ? QString("kick %1").arg(playerId) : QString("kick %1 \"%2\"").arg(playerId, escapedReason.replace("\"", "\\\"")); sendTelnetCommand(command); } void MainWindow::banSelectedUser() { const int row = usersListWidget->currentRow(); if (row < 0 || row >= users.size()) { QMessageBox::warning(this, "No User Selected", "Please select a user first."); return; } QDialog dialog(this); dialog.setWindowTitle("Ban User"); QVBoxLayout *dialogLayout = new QVBoxLayout(&dialog); QHBoxLayout *durationLayout = new QHBoxLayout(); QLineEdit *durationEdit = new QLineEdit(); durationEdit->setValidator(new QIntValidator(0, 999999999, durationEdit)); QComboBox *durationUnitCombo = new QComboBox(); durationUnitCombo->addItems({"minutes", "hours", "days", "weeks", "months", "years"}); durationLayout->addWidget(durationEdit); durationLayout->addWidget(durationUnitCombo); dialogLayout->addLayout(durationLayout); QHBoxLayout *reasonLayout = new QHBoxLayout(); reasonLayout->addWidget(new QLabel("Reason")); QLineEdit *reasonEdit = new QLineEdit(); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); reasonLayout->addWidget(reasonEdit); reasonLayout->addWidget(buttonBox); dialogLayout->addLayout(reasonLayout); connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); if (dialog.exec() != QDialog::Accepted) { return; } const QString duration = durationEdit->text().trimmed(); if (duration.isEmpty()) { QMessageBox::warning(this, "Invalid Duration", "Please enter a duration."); return; } const QString playerId = users.at(row).playerId; QString escapedReason = reasonEdit->text().trimmed(); const QString command = QString("ban add %1 %2 %3 \"%4\"") .arg(playerId, duration, durationUnitCombo->currentText(), escapedReason.replace("\"", "\\\"")); sendTelnetCommand(command); } bool MainWindow::writeSavedServers() const { const QString configFile = serverConfigFilePath(); QDir configDir = QFileInfo(configFile).dir(); if (!configDir.exists() && !configDir.mkpath(".")) { return false; } QFile file(configFile); if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { return false; } QXmlStreamWriter xml(&file); xml.setAutoFormatting(true); xml.writeStartDocument(); xml.writeStartElement("servers"); for (const SavedServerEntry &entry : savedServers) { xml.writeStartElement("server"); xml.writeAttribute("name", entry.name); xml.writeTextElement("host", entry.server); xml.writeTextElement("port", entry.port); xml.writeTextElement("password", entry.password); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndDocument(); return !xml.hasError(); } void MainWindow::saveServerEntry() { const QString server = serverEdit->text().trimmed(); const QString port = portEdit->text().trimmed(); if (server.isEmpty() || port.isEmpty()) { QMessageBox::warning(this, "Missing Information", "Server and port must be filled in before saving."); return; } bool ok = false; const QString name = QInputDialog::getText( this, "Save Server", "Server name:", QLineEdit::Normal, QString(), &ok ).trimmed(); if (!ok || name.isEmpty()) { return; } SavedServerEntry entry{name, server, port, passwordEdit->text()}; bool replaced = false; for (SavedServerEntry &savedEntry : savedServers) { if (savedEntry.name == name) { savedEntry = entry; replaced = true; break; } } if (!replaced) { savedServers.append(entry); } if (!writeSavedServers()) { QMessageBox::warning(this, "Save Failed", "Could not write the saved servers file."); return; } loadSavedServers(); } void MainWindow::loadSelectedServer() { const int index = savedServersCombo->currentIndex() - 1; if (index < 0 || index >= savedServers.size()) { QMessageBox::warning(this, "Load Failed", "Please select a saved server first."); return; } const SavedServerEntry &entry = savedServers.at(index); serverEdit->setText(entry.server); portEdit->setText(entry.port); passwordEdit->setText(entry.password); } void MainWindow::deleteSelectedServer() { const int index = savedServersCombo->currentIndex() - 1; if (index < 0 || index >= savedServers.size()) { QMessageBox::warning(this, "Delete Failed", "Please select a saved server first."); return; } savedServers.removeAt(index); if (!writeSavedServers()) { QMessageBox::warning(this, "Delete Failed", "Could not update the saved servers file."); return; } loadSavedServers(); } bool MainWindow::sendValidatedTimeCommand() { bool dayOk = false; bool hourOk = false; bool minuteOk = false; const int day = dayEdit->text().toInt(&dayOk); const int hour = hourEdit->text().toInt(&hourOk); const int minute = minuteEdit->text().toInt(&minuteOk); if (!dayOk || !hourOk || !minuteOk || day < 0 || day > 9999 || hour < 0 || hour > 23 || minute < 0 || minute > 59) { QMessageBox::warning(this, "Invalid Time", "Invalid Time"); return false; } QString cmd = QString("st %1 %2 %3") .arg(day) .arg(hour) .arg(minute); return sendTelnetCommand(cmd); } void MainWindow::sendTimeCommand() { sendValidatedTimeCommand(); } void MainWindow::updateServerTimeLockState(bool locked) { isServerTimeLocked = locked; dayEdit->setEnabled(!locked); hourEdit->setEnabled(!locked); minuteEdit->setEnabled(!locked); QPalette palette = serverTimeStatusLabel->palette(); if (locked) { serverTimeStatusLabel->setText("Server Time Locked"); serverTimeLockButton->setText("Unlock Server Time"); palette.setColor(QPalette::WindowText, Qt::red); } else { serverTimeStatusLabel->setText("Server Time Unlocked"); serverTimeLockButton->setText("Lock Server Time"); palette.setColor(QPalette::WindowText, Qt::darkGreen); } serverTimeStatusLabel->setPalette(palette); } void MainWindow::lockServerTime() { if (!sendValidatedTimeCommand()) { return; } lockedTimeTimer->start(5000); updateServerTimeLockState(true); } void MainWindow::unlockServerTime() { if (!isServerTimeLocked) { return; } lockedTimeTimer->stop(); updateServerTimeLockState(false); } void MainWindow::resendLockedServerTime() { if (!isServerTimeLocked) { return; } sendValidatedTimeCommand(); } void MainWindow::sendSpeedCommand() { static const QMap values = { {1, 1}, {2, 4}, {3, 6}, {4, 12}, {5, 500} }; int val = values.value(speedSlider->value(), 6); QString cmd = QString("setgamestat TimeOfDayIncPerSec %1").arg(val); sendTelnetCommand(cmd); } void MainWindow::sendCustomCommand() { QString cmd = customCommandEdit->text().trimmed(); if (cmd.isEmpty()) { QMessageBox::warning(this, "Input Error", "Please enter a custom command."); return; } sendTelnetCommand(cmd); customCommandEdit->clear(); } void MainWindow::updateConnectionStatus() { QPalette palette; QString connectionStatus; if (socket->state() == QAbstractSocket::ConnectedState) { connectionStatus = "Connected"; connectionStatusLabel->setText(connectionStatus); palette.setColor(QPalette::WindowText, Qt::darkGreen); } else { connectionStatus = "Disconnected"; connectionStatusLabel->setText(connectionStatus); palette.setColor(QPalette::WindowText, Qt::red); } connectionStatusLabel->setPalette(palette); setWindowTitle(QString("7D2D-CommandUtil - %1").arg(connectionStatus)); } void MainWindow::disconnectFromServer() { if (socket->state() == QAbstractSocket::ConnectedState) { socket->write("exit\n"); socket->disconnectFromHost(); } updateConnectionStatus(); } void MainWindow::updateSliderLabel(int value) { static const QMap labels = { {1, "Slow-Mo"}, {2, "90"}, {3, "60"}, {4, "Double"}, {5, "Giga"} }; sliderLabel->setText(labels.value(value, QString::number(value))); } void MainWindow::setupUI() { tabWidget = new QTabWidget(this); setCentralWidget(tabWidget); QWidget *mainTab = new QWidget; QVBoxLayout *mainLayout = new QVBoxLayout(mainTab); QIntValidator *dayValidator = new QIntValidator(0, 9999, this); QIntValidator *hourValidator = new QIntValidator(0, 23, this); QIntValidator *minuteValidator = new QIntValidator(0, 59, this); QGroupBox *timeBox = new QGroupBox("Set Server Time"); QVBoxLayout *timeBoxLayout = new QVBoxLayout(timeBox); serverTimeStatusLabel = new QLabel("Server Time Unlocked"); serverTimeStatusLabel->setAlignment(Qt::AlignCenter); timeBoxLayout->addWidget(serverTimeStatusLabel); QHBoxLayout *timeLayout = new QHBoxLayout(); dayEdit = new QLineEdit(); dayEdit->setMaxLength(4); dayEdit->setValidator(dayValidator); hourEdit = new QLineEdit(); hourEdit->setMaxLength(2); hourEdit->setValidator(hourValidator); minuteEdit = new QLineEdit(); minuteEdit->setMaxLength(2); minuteEdit->setValidator(minuteValidator); QPushButton *timeButton = new QPushButton("Set Server Time"); connect(timeButton, &QPushButton::clicked, this, &MainWindow::sendTimeCommand); timeLayout->addWidget(new QLabel("Day:")); timeLayout->addWidget(dayEdit); timeLayout->addWidget(new QLabel("Hour:")); timeLayout->addWidget(hourEdit); timeLayout->addWidget(new QLabel("Minute:")); timeLayout->addWidget(minuteEdit); timeLayout->addStretch(); timeLayout->addWidget(timeButton); timeBoxLayout->addLayout(timeLayout); QHBoxLayout *timeLockButtonLayout = new QHBoxLayout(); serverTimeLockButton = new QPushButton("Lock Server Time"); connect(serverTimeLockButton, &QPushButton::clicked, this, [this]() { if (isServerTimeLocked) { unlockServerTime(); } else { lockServerTime(); } }); timeLockButtonLayout->addStretch(); timeLockButtonLayout->addWidget(serverTimeLockButton); timeLockButtonLayout->addStretch(); timeBoxLayout->addLayout(timeLockButtonLayout); mainLayout->addWidget(timeBox); updateServerTimeLockState(false); QGroupBox *speedBox = new QGroupBox("Set Game Speed"); QVBoxLayout *speedLayout = new QVBoxLayout(speedBox); QHBoxLayout *sliderLayout = new QHBoxLayout(); speedSlider = new QSlider(Qt::Horizontal); speedSlider->setMinimum(1); speedSlider->setMaximum(5); speedSlider->setTickInterval(1); speedSlider->setTickPosition(QSlider::TicksBelow); connect(speedSlider, &QSlider::valueChanged, this, &MainWindow::updateSliderLabel); sliderLayout->addWidget(speedSlider); QPushButton *speedButton = new QPushButton("Set Time Speed"); connect(speedButton, &QPushButton::clicked, this, &MainWindow::sendSpeedCommand); sliderLayout->addWidget(speedButton); sliderLabel = new QLabel("60"); sliderLabel->setAlignment(Qt::AlignCenter); speedLayout->addLayout(sliderLayout); speedLayout->addWidget(sliderLabel); mainLayout->addWidget(speedBox); QWidget *usersTab = new QWidget; QHBoxLayout *usersTabLayout = new QHBoxLayout(usersTab); QVBoxLayout *usersLeftLayout = new QVBoxLayout(); QGroupBox *usersBox = new QGroupBox("Users"); QVBoxLayout *usersBoxLayout = new QVBoxLayout(usersBox); usersListWidget = new QListWidget(); QPushButton *refreshUsersButton = new QPushButton("Refresh"); connect(usersListWidget, &QListWidget::currentRowChanged, this, &MainWindow::displaySelectedUserInfo); connect(refreshUsersButton, &QPushButton::clicked, this, &MainWindow::refreshUsers); usersBoxLayout->addWidget(usersListWidget); usersBoxLayout->addWidget(refreshUsersButton); usersLeftLayout->addWidget(usersBox); QGroupBox *actionsBox = new QGroupBox("Actions"); QVBoxLayout *actionsBoxLayout = new QVBoxLayout(actionsBox); QPushButton *kickUserButton = new QPushButton("Kick"); QPushButton *banUserButton = new QPushButton("Ban"); connect(kickUserButton, &QPushButton::clicked, this, &MainWindow::kickSelectedUser); connect(banUserButton, &QPushButton::clicked, this, &MainWindow::banSelectedUser); actionsBoxLayout->addWidget(kickUserButton); actionsBoxLayout->addWidget(banUserButton); actionsBoxLayout->addStretch(); usersLeftLayout->addWidget(actionsBox); QGroupBox *userInfoBox = new QGroupBox("User Info"); QFormLayout *userInfoBoxLayout = new QFormLayout(userInfoBox); const QStringList userInfoFields = { "Player ID", "Username", "Position", "Rotation", "Remote", "Health", "Deaths", "Zombies", "Players", "Score", "Level", "Platform ID", "EOS ID", "IP Address", "Ping" }; for (const QString &fieldName : userInfoFields) { QLabel *valueLabel = new QLabel(); valueLabel->setWordWrap(true); valueLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); valueLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); userInfoValueLabels.insert(fieldName, valueLabel); userInfoBoxLayout->addRow(fieldName + ":", valueLabel); } clearUserInfo(); usersTabLayout->addLayout(usersLeftLayout, 1); usersTabLayout->addWidget(userInfoBox, 1); tabWidget->addTab(usersTab, "Users"); QWidget *bansTab = new QWidget; QHBoxLayout *bansTabLayout = new QHBoxLayout(bansTab); QVBoxLayout *bansLeftLayout = new QVBoxLayout(); QGroupBox *bansBox = new QGroupBox("Bans"); QVBoxLayout *bansBoxLayout = new QVBoxLayout(bansBox); bansListWidget = new QListWidget(); QPushButton *refreshBansButton = new QPushButton("Refresh"); connect(bansListWidget, &QListWidget::currentRowChanged, this, &MainWindow::displaySelectedBanInfo); connect(refreshBansButton, &QPushButton::clicked, this, &MainWindow::refreshBans); bansBoxLayout->addWidget(bansListWidget); bansBoxLayout->addWidget(refreshBansButton); bansLeftLayout->addWidget(bansBox); QGroupBox *banActionsBox = new QGroupBox("Actions"); QVBoxLayout *banActionsBoxLayout = new QVBoxLayout(banActionsBox); QPushButton *removeBanButton = new QPushButton("Remove"); connect(removeBanButton, &QPushButton::clicked, this, &MainWindow::removeSelectedBan); banActionsBoxLayout->addWidget(removeBanButton); banActionsBoxLayout->addStretch(); bansLeftLayout->addWidget(banActionsBox); QGroupBox *banInfoBox = new QGroupBox("Ban Info"); QFormLayout *banInfoBoxLayout = new QFormLayout(banInfoBox); const QStringList banInfoFields = {"Banned Until", "Ban Reason"}; for (const QString &fieldName : banInfoFields) { QLabel *valueLabel = new QLabel(); valueLabel->setWordWrap(true); valueLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); valueLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); banInfoValueLabels.insert(fieldName, valueLabel); banInfoBoxLayout->addRow(fieldName + ":", valueLabel); } clearBanInfo(); bansTabLayout->addLayout(bansLeftLayout, 1); bansTabLayout->addWidget(banInfoBox, 1); tabWidget->addTab(bansTab, "Bans"); QWidget *worldTab = new QWidget; tabWidget->addTab(worldTab, "World"); tabWidget->addTab(mainTab, "Time"); QWidget *historyTab = new QWidget; QVBoxLayout *historyLayout = new QVBoxLayout(historyTab); historyTextEdit = new QTextEdit(); historyTextEdit->setReadOnly(true); historyTextEdit->setTextInteractionFlags(Qt::TextSelectableByMouse); historyLayout->addWidget(historyTextEdit); tabWidget->addTab(historyTab, "History"); QWidget *logTab = new QWidget; QVBoxLayout *logLayout = new QVBoxLayout(logTab); serverLogTextEdit = new QTextEdit(); serverLogTextEdit->setReadOnly(true); logLayout->addWidget(serverLogTextEdit); QGroupBox *customBox = new QGroupBox(); QHBoxLayout *customLayout = new QHBoxLayout(customBox); customCommandEdit = new QLineEdit(); QPushButton *customButton = new QPushButton("Send Command"); connect(customButton, &QPushButton::clicked, this, &MainWindow::sendCustomCommand); connect(customCommandEdit, &QLineEdit::returnPressed, this, &MainWindow::sendCustomCommand); customLayout->addWidget(customCommandEdit); customLayout->addWidget(customButton); logLayout->addWidget(customBox); tabWidget->addTab(logTab, "Console"); QWidget *connectionTab = new QWidget; QVBoxLayout *connectionLayout = new QVBoxLayout(connectionTab); QGroupBox *serverBox = new QGroupBox("Server Info"); QVBoxLayout *serverVBox = new QVBoxLayout(serverBox); QHBoxLayout *serverLayout = new QHBoxLayout(); serverEdit = new QLineEdit(); portEdit = new QLineEdit(); passwordEdit = new QLineEdit(); passwordEdit->setEchoMode(QLineEdit::Password); serverLayout->addWidget(new QLabel("Server:")); serverLayout->addWidget(serverEdit); serverLayout->addWidget(new QLabel("Port:")); serverLayout->addWidget(portEdit); serverLayout->addWidget(new QLabel("Password:")); serverLayout->addWidget(passwordEdit); serverVBox->addLayout(serverLayout); QHBoxLayout *statusLayout = new QHBoxLayout(); connectionStatusLabel = new QLabel("Disconnected"); QPalette palette; palette.setColor(QPalette::WindowText, Qt::red); connectionStatusLabel->setPalette(palette); statusLayout->addWidget(connectionStatusLabel); statusLayout->addStretch(); connectButton = new QPushButton("Connect"); disconnectButton = new QPushButton("Disconnect"); connect(connectButton, &QPushButton::clicked, this, &MainWindow::attemptConnection); connect(disconnectButton, &QPushButton::clicked, this, &MainWindow::disconnectFromServer); statusLayout->addWidget(connectButton); statusLayout->addWidget(disconnectButton); serverVBox->addLayout(statusLayout); connectionLayout->addWidget(serverBox); QGroupBox *savedServersBox = new QGroupBox("Servers"); QVBoxLayout *savedServersBoxLayout = new QVBoxLayout(savedServersBox); QHBoxLayout *savedServersLayout = new QHBoxLayout(); savedServersCombo = new QComboBox(); savedServersCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); QPushButton *saveServerButton = new QPushButton("Save"); QPushButton *loadServerButton = new QPushButton("Load"); QPushButton *deleteServerButton = new QPushButton("Delete"); deleteServerButton->setStyleSheet("color: red;"); connect(saveServerButton, &QPushButton::clicked, this, &MainWindow::saveServerEntry); connect(loadServerButton, &QPushButton::clicked, this, &MainWindow::loadSelectedServer); connect(deleteServerButton, &QPushButton::clicked, this, &MainWindow::deleteSelectedServer); savedServersLayout->addWidget(savedServersCombo); savedServersLayout->addWidget(saveServerButton); savedServersLayout->addWidget(loadServerButton); savedServersBoxLayout->addLayout(savedServersLayout); savedServersBoxLayout->addWidget(deleteServerButton, 0, Qt::AlignHCenter); connectionLayout->addWidget(savedServersBox); connectionLayout->addStretch(); tabWidget->addTab(connectionTab, "Connection"); connect(tabWidget, &QTabWidget::currentChanged, this, [this, usersTab, bansTab](int index) { if (tabWidget->widget(index) == usersTab) { refreshUsers(); } else if (tabWidget->widget(index) == bansTab) { refreshBans(); } }); tabWidget->setCurrentWidget(connectionTab); updateSliderLabel(speedSlider->value()); }