GH-2053 basics of the servers.dat management
This commit is contained in:
parent
6284f070c1
commit
72ff342d63
@ -100,6 +100,8 @@ SET(MULTIMC_SOURCES
|
||||
pages/instance/ScreenshotsPage.h
|
||||
pages/instance/OtherLogsPage.cpp
|
||||
pages/instance/OtherLogsPage.h
|
||||
pages/instance/ServersPage.cpp
|
||||
pages/instance/ServersPage.h
|
||||
pages/instance/LegacyUpgradePage.cpp
|
||||
pages/instance/LegacyUpgradePage.h
|
||||
pages/instance/WorldListPage.cpp
|
||||
@ -231,6 +233,7 @@ SET(MULTIMC_UIS
|
||||
pages/instance/ScreenshotsPage.ui
|
||||
pages/instance/OtherLogsPage.ui
|
||||
pages/instance/LegacyUpgradePage.ui
|
||||
pages/instance/ServersPage.ui
|
||||
pages/instance/WorldListPage.ui
|
||||
|
||||
# Global settings pages
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "pages/instance/OtherLogsPage.h"
|
||||
#include "pages/instance/LegacyUpgradePage.h"
|
||||
#include "pages/instance/WorldListPage.h"
|
||||
#include "pages/instance/ServersPage.h"
|
||||
|
||||
|
||||
class InstancePageProvider : public QObject, public BasePageProvider
|
||||
@ -43,6 +44,7 @@ public:
|
||||
values.append(new TexturePackPage(onesix.get()));
|
||||
values.append(new NotesPage(onesix.get()));
|
||||
values.append(new WorldListPage(onesix.get(), onesix->worldList(), "worlds", "worlds", tr("Worlds"), "Worlds"));
|
||||
values.append(new ServersPage(onesix.get()));
|
||||
values.append(new ScreenshotsPage(FS::PathCombine(onesix->minecraftRoot(), "screenshots")));
|
||||
values.append(new InstanceSettingsPage(onesix.get()));
|
||||
}
|
||||
|
@ -181,7 +181,7 @@ void InstanceWindow::closeEvent(QCloseEvent *event)
|
||||
|
||||
bool InstanceWindow::saveAll()
|
||||
{
|
||||
return m_container->prepareToClose();
|
||||
return m_container->saveAll();
|
||||
}
|
||||
|
||||
void InstanceWindow::on_btnKillMinecraft_clicked()
|
||||
|
743
application/pages/instance/ServersPage.cpp
Normal file
743
application/pages/instance/ServersPage.cpp
Normal file
@ -0,0 +1,743 @@
|
||||
#include "ServersPage.h"
|
||||
#include "ui_ServersPage.h"
|
||||
|
||||
#include <FileSystem.h>
|
||||
#include <sstream>
|
||||
#include <io/stream_reader.h>
|
||||
#include <tag_string.h>
|
||||
#include <tag_primitive.h>
|
||||
#include <tag_list.h>
|
||||
#include <tag_compound.h>
|
||||
#include <minecraft/MinecraftInstance.h>
|
||||
|
||||
#include <QFileSystemWatcher>
|
||||
|
||||
static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things.
|
||||
|
||||
struct Server
|
||||
{
|
||||
// Types
|
||||
enum class AcceptsTextures : int
|
||||
{
|
||||
ASK = 0,
|
||||
ALWAYS = 1,
|
||||
NEVER = 2
|
||||
};
|
||||
|
||||
// Methods
|
||||
Server()
|
||||
{
|
||||
m_name = QObject::tr("Minecraft Server");
|
||||
}
|
||||
Server(const QString & name, const QString & address)
|
||||
{
|
||||
m_name = name;
|
||||
m_address = address;
|
||||
}
|
||||
Server(nbt::tag_compound& server)
|
||||
{
|
||||
std::string addressStr(server["ip"]);
|
||||
m_address = QString::fromUtf8(addressStr.c_str());
|
||||
|
||||
std::string nameStr(server["name"]);
|
||||
m_name = QString::fromUtf8(nameStr.c_str());
|
||||
|
||||
if(server["icon"])
|
||||
{
|
||||
std::string base64str(server["icon"]);
|
||||
m_icon = QByteArray::fromBase64(base64str.c_str());
|
||||
}
|
||||
|
||||
if(server.has_key("acceptTextures", nbt::tag_type::Byte))
|
||||
{
|
||||
bool value = server["acceptTextures"].as<nbt::tag_byte>().get();
|
||||
if(value)
|
||||
{
|
||||
m_acceptsTextures = AcceptsTextures::ALWAYS;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_acceptsTextures = AcceptsTextures::NEVER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void serialize(nbt::tag_compound& server)
|
||||
{
|
||||
server.insert("name", m_name.toUtf8().toStdString());
|
||||
server.insert("ip", m_address.toUtf8().toStdString());
|
||||
if(m_icon.size())
|
||||
{
|
||||
server.insert("icon", m_icon.toBase64().toStdString());
|
||||
}
|
||||
if(m_acceptsTextures != AcceptsTextures::ASK)
|
||||
{
|
||||
server.insert("acceptTextures", nbt::tag_byte(m_acceptsTextures == AcceptsTextures::ALWAYS));
|
||||
}
|
||||
}
|
||||
|
||||
// Data - persistent and user changeable
|
||||
QString m_name;
|
||||
QString m_address;
|
||||
AcceptsTextures m_acceptsTextures = AcceptsTextures::ASK;
|
||||
|
||||
// Data - persistent and automatically updated
|
||||
QByteArray m_icon;
|
||||
|
||||
// Data - temporary
|
||||
bool m_checked = false;
|
||||
bool m_up = false;
|
||||
QString m_motd; // https://mctools.org/motd-creator
|
||||
int m_ping = 0;
|
||||
int m_currentPlayers = 0;
|
||||
int m_maxPlayers = 0;
|
||||
};
|
||||
|
||||
static std::unique_ptr <nbt::tag_compound> parseServersDat(const QString& filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
QByteArray input = FS::read(filename);
|
||||
std::istringstream foo(std::string(input.constData(), input.size()));
|
||||
auto pair = nbt::io::read_compound(foo);
|
||||
|
||||
if(pair.first != "")
|
||||
return nullptr;
|
||||
|
||||
if(pair.second == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return std::move(pair.second);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static bool serializeServerDat(const QString& filename, nbt::tag_compound * levelInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::ostringstream s;
|
||||
nbt::io::write_tag("", *levelInfo, s);
|
||||
QByteArray val(s.str().data(), (int) s.str().size() );
|
||||
FS::write(filename, val);
|
||||
return true;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class ServersModel: public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
ServerPtrRole = Qt::UserRole,
|
||||
};
|
||||
explicit ServersModel(const QString &path, QObject *parent = 0)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
m_path = path;
|
||||
m_watcher = new QFileSystemWatcher(this);
|
||||
connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &ServersModel::fileChanged);
|
||||
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &ServersModel::dirChanged);
|
||||
m_saveTimer.setSingleShot(true);
|
||||
m_saveTimer.setInterval(5000);
|
||||
connect(&m_saveTimer, &QTimer::timeout, this, &ServersModel::save_internal);
|
||||
}
|
||||
virtual ~ServersModel() {};
|
||||
|
||||
void observe()
|
||||
{
|
||||
if(m_observed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_observed = true;
|
||||
|
||||
if(!m_loaded)
|
||||
{
|
||||
load();
|
||||
}
|
||||
|
||||
updateFSObserver();
|
||||
}
|
||||
|
||||
void unobserve()
|
||||
{
|
||||
if(!m_observed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_observed = false;
|
||||
|
||||
updateFSObserver();
|
||||
}
|
||||
|
||||
void lock()
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
saveNow();
|
||||
|
||||
m_locked = true;
|
||||
updateFSObserver();
|
||||
}
|
||||
|
||||
void unlock()
|
||||
{
|
||||
if(!m_locked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_locked = false;
|
||||
|
||||
updateFSObserver();
|
||||
}
|
||||
|
||||
int addEmptyRow(int position)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if(position < 0 || position >= rowCount())
|
||||
{
|
||||
position = rowCount();
|
||||
}
|
||||
beginInsertRows(QModelIndex(), position, position);
|
||||
m_servers.insert(position, Server());
|
||||
endInsertRows();
|
||||
scheduleSave();
|
||||
return position;
|
||||
}
|
||||
|
||||
bool removeRow(int row)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(row < 0 || row >= rowCount())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
m_servers.removeAt(row);
|
||||
endRemoveRows(); // does absolutely nothing, the selected server stays as the next line...
|
||||
scheduleSave();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool moveUp(int row)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(row <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
beginMoveRows(QModelIndex(), row, row, QModelIndex(), row - 1);
|
||||
m_servers.swap(row-1, row);
|
||||
endMoveRows();
|
||||
scheduleSave();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool moveDown(int row)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int count = rowCount();
|
||||
if(row + 1 >= count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
beginMoveRows(QModelIndex(), row, row, QModelIndex(), row + 2);
|
||||
m_servers.swap(row+1, row);
|
||||
endMoveRows();
|
||||
scheduleSave();
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
|
||||
{
|
||||
if (section < 0 || section >= COLUMN_COUNT)
|
||||
return QVariant();
|
||||
|
||||
if(role == Qt::DisplayRole)
|
||||
{
|
||||
switch(section)
|
||||
{
|
||||
case 0:
|
||||
return tr("Name");
|
||||
case 1:
|
||||
return tr("Address");
|
||||
case 2:
|
||||
return tr("Latency");
|
||||
}
|
||||
}
|
||||
|
||||
return QAbstractListModel::headerData(section, orientation, role);
|
||||
}
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
int row = index.row();
|
||||
int column = index.column();
|
||||
if(column < 0 || column >= COLUMN_COUNT)
|
||||
return QVariant();
|
||||
|
||||
if (row < 0 || row >= m_servers.size())
|
||||
return QVariant();
|
||||
|
||||
switch(column)
|
||||
{
|
||||
case 0:
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DecorationRole:
|
||||
{
|
||||
auto & bytes = m_servers[row].m_icon;
|
||||
if(bytes.size())
|
||||
{
|
||||
QPixmap px;
|
||||
if(px.loadFromData(bytes))
|
||||
return QIcon(px);
|
||||
}
|
||||
return MMC->getThemedIcon("unknown_server");
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
return m_servers[row].m_name;
|
||||
case ServerPtrRole:
|
||||
return QVariant::fromValue<void *>((void *)&m_servers[row]);
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
case 1:
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
return m_servers[row].m_address;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
case 2:
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
return m_servers[row].m_ping;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
return m_servers.size();
|
||||
}
|
||||
int columnCount(const QModelIndex & parent) const override
|
||||
{
|
||||
return COLUMN_COUNT;
|
||||
}
|
||||
|
||||
Server * at(int index)
|
||||
{
|
||||
if(index < 0 || index >= rowCount())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return &m_servers[index];
|
||||
}
|
||||
|
||||
void setName(int row, const QString & name)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto server = at(row);
|
||||
if(!server || server->m_name == name)
|
||||
{
|
||||
return;
|
||||
}
|
||||
server->m_name = name;
|
||||
emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1));
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
void setAddress(int row, const QString & address)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto server = at(row);
|
||||
if(!server || server->m_address == address)
|
||||
{
|
||||
return;
|
||||
}
|
||||
server->m_address = address;
|
||||
emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1));
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
void setAcceptsTextures(int row, Server::AcceptsTextures textures)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto server = at(row);
|
||||
if(!server || server->m_acceptsTextures == textures)
|
||||
{
|
||||
return;
|
||||
}
|
||||
server->m_acceptsTextures = textures;
|
||||
emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1));
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
cancelSave();
|
||||
beginResetModel();
|
||||
QList<Server> servers;
|
||||
auto serversDat = parseServersDat(serversPath());
|
||||
if(serversDat)
|
||||
{
|
||||
auto &serversList = serversDat->at("servers").as<nbt::tag_list>();
|
||||
for(auto iter = serversList.begin(); iter != serversList.end(); iter++)
|
||||
{
|
||||
auto & serverTag = (*iter).as<nbt::tag_compound>();
|
||||
Server s(serverTag);
|
||||
servers.append(s);
|
||||
}
|
||||
}
|
||||
m_servers.swap(servers);
|
||||
m_loaded = true;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void saveNow()
|
||||
{
|
||||
if(saveIsScheduled())
|
||||
{
|
||||
save_internal();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public slots:
|
||||
void dirChanged(const QString& path)
|
||||
{
|
||||
qDebug() << "Changed:" << path;
|
||||
load();
|
||||
}
|
||||
void fileChanged(const QString& path)
|
||||
{
|
||||
qDebug() << "Changed:" << path;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void save_internal()
|
||||
{
|
||||
cancelSave();
|
||||
qDebug() << "Server list save is performed for" << m_path;
|
||||
|
||||
nbt::tag_compound out;
|
||||
nbt::tag_list list;
|
||||
for(auto & server: m_servers)
|
||||
{
|
||||
nbt::tag_compound serverNbt;
|
||||
server.serialize(serverNbt);
|
||||
list.push_back(std::move(serverNbt));
|
||||
}
|
||||
out.insert("servers", nbt::value(std::move(list)));
|
||||
|
||||
if(!serializeServerDat(serversPath(), &out))
|
||||
{
|
||||
qDebug() << "Failed to save server list:" << m_path << "Will try again.";
|
||||
scheduleSave();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void scheduleSave()
|
||||
{
|
||||
if(!m_loaded)
|
||||
{
|
||||
qDebug() << "Server list should never save if it didn't successfully load, path:" << m_path;
|
||||
return;
|
||||
}
|
||||
if(!m_dirty)
|
||||
{
|
||||
m_dirty = true;
|
||||
qDebug() << "Server list save is scheduled for" << m_path;
|
||||
}
|
||||
m_saveTimer.start();
|
||||
}
|
||||
|
||||
void cancelSave()
|
||||
{
|
||||
m_dirty = false;
|
||||
m_saveTimer.stop();
|
||||
}
|
||||
|
||||
bool saveIsScheduled() const
|
||||
{
|
||||
return m_dirty;
|
||||
}
|
||||
|
||||
void updateFSObserver()
|
||||
{
|
||||
bool observingFS = m_watcher->directories().contains(m_path);
|
||||
if(m_observed && m_locked)
|
||||
{
|
||||
if(!observingFS)
|
||||
{
|
||||
qWarning() << "Will watch" << m_path;
|
||||
if(!m_watcher->addPath(m_path))
|
||||
{
|
||||
qWarning() << "Failed to start watching" << m_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(observingFS)
|
||||
{
|
||||
qWarning() << "Will stop watching" << m_path;
|
||||
if(!m_watcher->removePath(m_path))
|
||||
{
|
||||
qWarning() << "Failed to stop watching" << m_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString serversPath()
|
||||
{
|
||||
QFileInfo foo(FS::PathCombine(m_path, "servers.dat"));
|
||||
return foo.canonicalFilePath();
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_loaded = false;
|
||||
bool m_locked = false;
|
||||
bool m_observed = false;
|
||||
bool m_dirty = false;
|
||||
QString m_path;
|
||||
QList<Server> m_servers;
|
||||
QFileSystemWatcher *m_watcher = nullptr;
|
||||
QTimer m_saveTimer;
|
||||
};
|
||||
|
||||
ServersPage::ServersPage(MinecraftInstance * inst, QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::ServersPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->tabBar()->hide();
|
||||
m_inst = inst;
|
||||
m_model = new ServersModel(inst->minecraftRoot(), this);
|
||||
ui->serversView->setIconSize(QSize(64,64));
|
||||
ui->serversView->setModel(m_model);
|
||||
auto head = ui->serversView->header();
|
||||
if(head->count())
|
||||
{
|
||||
head->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
for(int i = 1; i < head->count(); i++)
|
||||
{
|
||||
head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
|
||||
}
|
||||
}
|
||||
|
||||
auto selectionModel = ui->serversView->selectionModel();
|
||||
connect(selectionModel, &QItemSelectionModel::currentChanged, this, &ServersPage::currentChanged);
|
||||
connect(m_inst, &MinecraftInstance::runningStatusChanged, this, &ServersPage::on_RunningState_changed);
|
||||
connect(ui->nameLine, &QLineEdit::textEdited, this, &ServersPage::nameEdited);
|
||||
connect(ui->addressLine, &QLineEdit::textEdited, this, &ServersPage::addressEdited);
|
||||
connect(ui->resourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resourceIndexChanged(int)));
|
||||
connect(m_model, &QAbstractItemModel::rowsRemoved, this, &ServersPage::rowsRemoved);
|
||||
|
||||
m_locked = m_inst->isRunning();
|
||||
if(m_locked)
|
||||
{
|
||||
m_model->lock();
|
||||
}
|
||||
|
||||
updateState();
|
||||
}
|
||||
|
||||
ServersPage::~ServersPage()
|
||||
{
|
||||
m_model->saveNow();
|
||||
}
|
||||
|
||||
void ServersPage::on_RunningState_changed(bool running)
|
||||
{
|
||||
if(m_locked == running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_locked = running;
|
||||
if(m_locked)
|
||||
{
|
||||
m_model->lock();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_model->unlock();
|
||||
}
|
||||
updateState();
|
||||
}
|
||||
|
||||
void ServersPage::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
|
||||
{
|
||||
int nextServer = -1;
|
||||
if (!current.isValid())
|
||||
{
|
||||
nextServer = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextServer = current.row();
|
||||
}
|
||||
currentServer = nextServer;
|
||||
updateState();
|
||||
}
|
||||
|
||||
// WARNING: this is here because currentChanged is not accurate when removing rows. the current item needs to be fixed up after removal.
|
||||
void ServersPage::rowsRemoved(const QModelIndex& parent, int first, int last)
|
||||
{
|
||||
if(currentServer < first)
|
||||
{
|
||||
// current was before the removal
|
||||
return;
|
||||
}
|
||||
else if(currentServer >= first && currentServer <= last)
|
||||
{
|
||||
// current got removed...
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// current was past the removal
|
||||
int count = last - first + 1;
|
||||
currentServer -= count;
|
||||
}
|
||||
}
|
||||
|
||||
void ServersPage::nameEdited(const QString& name)
|
||||
{
|
||||
m_model->setName(currentServer, name);
|
||||
}
|
||||
|
||||
void ServersPage::addressEdited(const QString& address)
|
||||
{
|
||||
m_model->setAddress(currentServer, address);
|
||||
}
|
||||
|
||||
void ServersPage::resourceIndexChanged(int index)
|
||||
{
|
||||
auto acceptsTextures = Server::AcceptsTextures(index);
|
||||
m_model->setAcceptsTextures(currentServer, acceptsTextures);
|
||||
}
|
||||
|
||||
void ServersPage::updateState()
|
||||
{
|
||||
auto server = m_model->at(currentServer);
|
||||
|
||||
bool serverEditEnabled = server && !m_locked;
|
||||
ui->addressLine->setEnabled(serverEditEnabled);
|
||||
ui->nameLine->setEnabled(serverEditEnabled);
|
||||
ui->resourceComboBox->setEnabled(serverEditEnabled);
|
||||
ui->moveDownBtn->setEnabled(serverEditEnabled);
|
||||
ui->moveUpBtn->setEnabled(serverEditEnabled);
|
||||
ui->removeBtn->setEnabled(serverEditEnabled);
|
||||
|
||||
if(server)
|
||||
{
|
||||
ui->addressLine->setText(server->m_address);
|
||||
ui->nameLine->setText(server->m_name);
|
||||
ui->resourceComboBox->setCurrentIndex(int(server->m_acceptsTextures));
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->addressLine->setText(QString());
|
||||
ui->nameLine->setText(QString());
|
||||
ui->resourceComboBox->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
ui->addBtn->setDisabled(m_locked);
|
||||
}
|
||||
|
||||
void ServersPage::openedImpl()
|
||||
{
|
||||
m_model->observe();
|
||||
}
|
||||
|
||||
void ServersPage::closedImpl()
|
||||
{
|
||||
m_model->unobserve();
|
||||
}
|
||||
|
||||
void ServersPage::on_addBtn_clicked()
|
||||
{
|
||||
int position = m_model->addEmptyRow(currentServer + 1);
|
||||
if(position < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// select the new row
|
||||
ui->serversView->selectionModel()->setCurrentIndex(
|
||||
m_model->index(position),
|
||||
QItemSelectionModel::SelectCurrent | QItemSelectionModel::Clear | QItemSelectionModel::Rows
|
||||
);
|
||||
currentServer = position;
|
||||
}
|
||||
|
||||
void ServersPage::on_removeBtn_clicked()
|
||||
{
|
||||
m_model->removeRow(currentServer);
|
||||
}
|
||||
|
||||
void ServersPage::on_moveUpBtn_clicked()
|
||||
{
|
||||
if(m_model->moveUp(currentServer))
|
||||
{
|
||||
currentServer --;
|
||||
}
|
||||
}
|
||||
|
||||
void ServersPage::on_moveDownBtn_clicked()
|
||||
{
|
||||
if(m_model->moveDown(currentServer))
|
||||
{
|
||||
currentServer ++;
|
||||
}
|
||||
}
|
||||
|
||||
void ServersPage::on_refreshBtn_clicked()
|
||||
{
|
||||
m_model->load();
|
||||
}
|
||||
|
||||
#include "ServersPage.moc"
|
87
application/pages/instance/ServersPage.h
Normal file
87
application/pages/instance/ServersPage.h
Normal file
@ -0,0 +1,87 @@
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
|
||||
#include "pages/BasePage.h"
|
||||
#include <MultiMC.h>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class ServersPage;
|
||||
}
|
||||
|
||||
struct Server;
|
||||
class ServersModel;
|
||||
class MinecraftInstance;
|
||||
|
||||
class ServersPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ServersPage(MinecraftInstance *inst, QWidget *parent = 0);
|
||||
virtual ~ServersPage();
|
||||
|
||||
void openedImpl() override;
|
||||
void closedImpl() override;
|
||||
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Servers");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return MMC->getThemedIcon("unknown_server");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "servers";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Servers-management";
|
||||
}
|
||||
private:
|
||||
void updateState();
|
||||
void scheduleSave();
|
||||
bool saveIsScheduled() const;
|
||||
|
||||
private slots:
|
||||
void currentChanged(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void rowsRemoved(const QModelIndex &parent, int first, int last);
|
||||
|
||||
void on_addBtn_clicked();
|
||||
void on_removeBtn_clicked();
|
||||
void on_moveUpBtn_clicked();
|
||||
void on_moveDownBtn_clicked();
|
||||
void on_refreshBtn_clicked();
|
||||
void on_RunningState_changed(bool running);
|
||||
|
||||
void nameEdited(const QString & name);
|
||||
void addressEdited(const QString & address);
|
||||
void resourceIndexChanged(int index);
|
||||
|
||||
private: // data
|
||||
int currentServer = -1;
|
||||
bool m_locked = true;
|
||||
Ui::ServersPage *ui = nullptr;
|
||||
ServersModel * m_model = nullptr;
|
||||
MinecraftInstance * m_inst = nullptr;
|
||||
};
|
||||
|
207
application/pages/instance/ServersPage.ui
Normal file
207
application/pages/instance/ServersPage.ui
Normal file
@ -0,0 +1,207 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ServersPage</class>
|
||||
<widget class="QWidget" name="ServersPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>706</width>
|
||||
<height>575</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<attribute name="title">
|
||||
<string notr="true">Tab 1</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="resourceComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Ask to download</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Always download</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Never download</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="resourcesLabel">
|
||||
<property name="text">
|
||||
<string>Reso&urces</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>resourceComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="addressLine"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="nameLabel">
|
||||
<property name="text">
|
||||
<string>&Name</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>nameLine</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="nameLine"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="addressLabel">
|
||||
<property name="text">
|
||||
<string>Address</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>addressLine</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QTreeView" name="serversView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="addBtn">
|
||||
<property name="text">
|
||||
<string>&Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeBtn">
|
||||
<property name="text">
|
||||
<string>&Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="moveUpBtn">
|
||||
<property name="text">
|
||||
<string>Move Up</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="moveDownBtn">
|
||||
<property name="text">
|
||||
<string>Move Down</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="refreshBtn">
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>serversView</tabstop>
|
||||
<tabstop>nameLine</tabstop>
|
||||
<tabstop>addressLine</tabstop>
|
||||
<tabstop>resourceComboBox</tabstop>
|
||||
<tabstop>addBtn</tabstop>
|
||||
<tabstop>removeBtn</tabstop>
|
||||
<tabstop>moveUpBtn</tabstop>
|
||||
<tabstop>moveDownBtn</tabstop>
|
||||
<tabstop>refreshBtn</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
BIN
application/resources/multimc/128x128/unknown_server.png
Normal file
BIN
application/resources/multimc/128x128/unknown_server.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -33,6 +33,11 @@ Size=50
|
||||
[64x64]
|
||||
Size=64
|
||||
|
||||
[128x128]
|
||||
Size=128
|
||||
MinSize=33
|
||||
MaxSize=128
|
||||
|
||||
[128x128/instances]
|
||||
Size=128
|
||||
MinSize=33
|
||||
|
@ -239,6 +239,9 @@
|
||||
<file>48x48/log.png</file>
|
||||
<file>64x64/log.png</file>
|
||||
|
||||
<!-- placeholder for minecraft servers -->
|
||||
<file>128x128/unknown_server.png</file>
|
||||
|
||||
<!-- placeholder when loading screenshot images -->
|
||||
<file>scalable/screenshot-placeholder.svg</file>
|
||||
|
||||
|
@ -218,10 +218,9 @@ void PageContainer::currentChanged(const QModelIndex ¤t)
|
||||
|
||||
bool PageContainer::prepareToClose()
|
||||
{
|
||||
for (auto page : m_model->pages())
|
||||
if(!saveAll())
|
||||
{
|
||||
if (!page->apply())
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
if (m_currentPage)
|
||||
{
|
||||
@ -229,3 +228,13 @@ bool PageContainer::prepareToClose()
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PageContainer::saveAll()
|
||||
{
|
||||
for (auto page : m_model->pages())
|
||||
{
|
||||
if (!page->apply())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ public:
|
||||
* @return true if everything can be saved, false if there is something that requires attention
|
||||
*/
|
||||
bool prepareToClose();
|
||||
bool saveAll();
|
||||
|
||||
/* request close - used by individual pages */
|
||||
bool requestClose() override
|
||||
|
Loading…
Reference in New Issue
Block a user