/*
* The Mana Client
* Copyright (C) 2004-2009 The Mana World Development Team
* Copyright (C) 2009-2012 The Mana Developers
*
* This file is part of The Mana Client.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "gui/serverdialog.h"
#include "chatlogger.h"
#include "client.h"
#include "configuration.h"
#include "gui.h"
#include "log.h"
#include "gui/customserverdialog.h"
#include "gui/okdialog.h"
#include "gui/sdlinput.h"
#include "gui/widgets/button.h"
#include "gui/widgets/label.h"
#include "gui/widgets/layout.h"
#include "gui/widgets/listbox.h"
#include "gui/widgets/scrollarea.h"
#include "resources/theme.h"
#include "utils/gettext.h"
#include "utils/stringutils.h"
#include
#include
#include
ServersListModel::ServersListModel(ServerInfos *servers, ServerDialog *parent):
mServers(servers),
mVersionStrings(servers->size(), VersionString(0, std::string())),
mParent(parent)
{
}
int ServersListModel::getNumberOfElements()
{
return mServers->size();
}
std::string ServersListModel::getElementAt(int elementIndex)
{
const ServerInfo &server = mServers->at(elementIndex);
std::string myServer;
myServer += server.hostname;
myServer += ":";
myServer += toString(server.port);
return myServer;
}
void ServersListModel::setVersionString(int index, const std::string &version)
{
if (version.empty())
mVersionStrings[index] = VersionString(0, std::string());
else
{
int width = gui->getFont()->getWidth(version);
mVersionStrings[index] = VersionString(width, version);
}
}
class ServersListBox : public ListBox
{
public:
ServersListBox(ServersListModel *model):
ListBox(model)
{
}
void draw(gcn::Graphics *graphics) override
{
if (!mListModel)
return;
auto *model = static_cast(mListModel);
graphics->setFont(getFont());
const int height = getRowHeight();
// Draw filled rectangle around the selected list element
if (mSelected >= 0)
{
auto highlightColor = Theme::getThemeColor(Theme::HIGHLIGHT);
highlightColor.a = gui->getTheme()->getGuiAlpha();
graphics->setColor(highlightColor);
graphics->fillRectangle(gcn::Rectangle(0, height * mSelected,
getWidth(), height));
}
// Draw the list elements
for (int i = 0, y = 0; i < model->getNumberOfElements();
++i, y += height)
{
const ServerInfo &info = model->getServer(i);
graphics->setColor(Theme::getThemeColor(Theme::TEXT));
if (!info.name.empty())
{
graphics->setFont(boldFont);
graphics->drawText(info.name, 2, y);
}
graphics->setFont(getFont());
int top = y + height / 2;
graphics->drawText(model->getElementAt(i), 2, top);
if (info.version.first > 0)
{
auto unsupportedColor = Theme::getThemeColor(Theme::SERVER_VERSION_NOT_SUPPORTED);
unsupportedColor.a = gui->getTheme()->getGuiAlpha();
graphics->setColor(unsupportedColor);
graphics->drawText(info.version.second,
getWidth() - info.version.first - 2, top);
}
}
}
unsigned int getRowHeight() const override
{
return 2 * getFont()->getHeight();
}
};
ServerDialog::ServerDialog(ServerInfo *serverInfo, const std::string &dir):
Window(_("Choose Your Server")),
mDir(dir),
mServerInfo(serverInfo)
{
setWindowName("ServerDialog");
loadCustomServers();
mServersListModel = std::make_unique(&mServers, this);
mServersList = new ServersListBox(mServersListModel.get());
auto *usedScroll = new ScrollArea(mServersList);
usedScroll->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
mDescription = new Label(std::string());
mDownloadText = new Label(std::string());
mQuitButton = new Button(_("Quit"), "quit", this);
mConnectButton = new Button(_("Connect"), "connect", this);
mManualEntryButton = new Button(_("Add custom Server..."), "addEntry", this);
mModifyButton = new Button(_("Modify..."), "modify", this);
mDeleteButton = new Button(_("Delete"), "remove", this);
mServersList->setActionEventId("connect");
mServersList->addSelectionListener(this);
usedScroll->setVerticalScrollAmount(0);
place(0, 0, usedScroll, 6, 5).setPadding(3);
place(0, 5, mDescription, 6);
place(0, 6, mDownloadText, 6);
place(0, 7, mManualEntryButton);
place(1, 7, mModifyButton);
place(2, 7, mDeleteButton);
place(4, 7, mQuitButton);
place(5, 7, mConnectButton);
// Make sure the list has enough height
getLayout().setRowHeight(3, 80);
// Do this manually instead of calling reflowLayout so we can enforce a
// minimum width.
int width = 0, height = 0;
getLayout().reflow(width, height);
if (width < 400)
{
width = 400;
getLayout().reflow(width, height);
}
setContentSize(width, height);
setMinWidth(getWidth());
setMinHeight(getHeight());
setDefaultSize(getWidth(), getHeight(), WindowAlignment::Center);
setResizable(true);
addKeyListener(this);
loadWindowState();
mServersList->setSelected(0); // Do this after for the Delete button
setVisible(true);
mServersList->requestFocus();
downloadServerList();
}
ServerDialog::~ServerDialog() = default;
void ServerDialog::action(const gcn::ActionEvent &event)
{
if (event.getId() == "ok")
{
// Give focus back to the server dialog.
mServersList->requestFocus();
}
else if (event.getId() == "connect")
{
int index = mServersList->getSelected();
// Check login
if (index < 0
#ifndef MANASERV_SUPPORT
|| mServersListModel->getServer(index).type == ServerType::ManaServ
#endif
)
{
auto *dlg = new OkDialog(_("Error"),
_("Please select a valid server."));
dlg->addActionListener(this);
}
else
{
mDownload->cancel();
mQuitButton->setEnabled(false);
mConnectButton->setEnabled(false);
mDeleteButton->setEnabled(false);
mManualEntryButton->setEnabled(false);
mModifyButton->setEnabled(false);
const ServerInfo &serverInfo = mServersListModel->getServer(index);
mServerInfo->hostname = serverInfo.hostname;
mServerInfo->name = serverInfo.name;
mServerInfo->port = serverInfo.port;
mServerInfo->type = serverInfo.type;
// Save the selected server
mServerInfo->save = true;
saveCustomServers(*mServerInfo);
chatLogger->setServerName(mServerInfo->hostname);
Client::setState(STATE_CONNECT_SERVER);
}
}
else if (event.getId() == "quit")
{
mDownload->cancel();
Client::setState(STATE_FORCE_QUIT);
}
else if (event.getId() == "addEntry")
{
// Add a custom server: It will delete itself using guichan logic.
new CustomServerDialog(this);
}
else if (event.getId() == "modify")
{
int index = mServersList->getSelected();
// Check whether a server is selected.
if (index < 0)
{
auto *dlg = new OkDialog(_("Error"),
_("Please select a custom server."));
dlg->addActionListener(this);
}
else
{
new CustomServerDialog(this, index);
}
}
else if (event.getId() == "remove")
{
int index = mServersList->getSelected();
mServers.erase(mServers.begin() + index);
mServersList->setSelected(0);
saveCustomServers();
}
}
void ServerDialog::keyPressed(gcn::KeyEvent &keyEvent)
{
gcn::Key key = keyEvent.getKey();
if (key.getValue() == Key::ESCAPE)
{
Client::setState(STATE_EXIT);
}
else if (key.getValue() == Key::ENTER)
{
action(gcn::ActionEvent(nullptr, mConnectButton->getActionEventId()));
}
}
void ServerDialog::valueChanged(const gcn::SelectionEvent &)
{
const int index = mServersList->getSelected();
if (index == -1)
{
mDeleteButton->setEnabled(false);
mModifyButton->setEnabled(false);
return;
}
// Update the server and post fields according to the new selection
const ServerInfo &myServer = mServersListModel->getServer(index);
mDescription->setCaption(myServer.description);
mDeleteButton->setEnabled(myServer.save);
mModifyButton->setEnabled(myServer.save);
}
void ServerDialog::mouseClicked(gcn::MouseEvent &mouseEvent)
{
if (mouseEvent.getSource() == mServersList &&
isDoubleClick(mServersList->getSelected()))
{
action(gcn::ActionEvent(mConnectButton,
mConnectButton->getActionEventId()));
}
}
void ServerDialog::logic()
{
Window::logic();
if (mDownloadDone)
return;
auto state = mDownload->getState();
switch (state.status) {
case DownloadStatus::InProgress:
mDownloadText->setCaption(strprintf(_("Downloading server list..."
"%2.0f%%"),
state.progress * 100));
break;
case DownloadStatus::Canceled:
case DownloadStatus::Error:
mDownloadDone = true;
Log::info("Error retrieving server list: %s", mDownload->getError());
mDownloadText->setCaption(_("Error retrieving server list!"));
break;
case DownloadStatus::Complete:
mDownloadDone = true;
loadServers();
if (mServers.empty())
{
mDownloadText->setCaption(_("No servers found!"));
}
else
{
mDownloadText->setCaption(std::string());
mDescription->setCaption(mServers[0].description);
}
break;
}
}
void ServerDialog::downloadServerList()
{
// Try to load the configuration value for the onlineServerList
std::string listFile = branding.getStringValue("onlineServerList");
if (listFile.empty())
listFile = config.onlineServerList;
// Fall back to manasource.org when neither branding nor config set it
if (listFile.empty())
listFile = "https://www.manasource.org/serverlist.xml";
mDownload = std::make_unique(listFile);
mDownload->setFile(mDir + "/serverlist.xml");
mDownload->start();
}
void ServerDialog::loadServers()
{
XML::Document doc(mDir + "/serverlist.xml", false);
XML::Node rootNode = doc.rootNode();
if (!rootNode || rootNode.name() != "serverlist")
{
Log::info("Error loading server list!");
return;
}
int version = rootNode.getProperty("version", 0);
if (version != 1)
{
Log::error("Unsupported online server list version: %d", version);
return;
}
for (auto serverNode : rootNode.children())
{
if (serverNode.name() == "server")
loadServer(serverNode);
}
}
void ServerDialog::loadServer(XML::Node serverNode)
{
ServerInfo server;
std::string type = serverNode.getProperty("type", "unknown");
server.type = ServerInfo::parseType(type);
// Ignore unknown server types
if (server.type == ServerType::Unknown
#ifndef MANASERV_SUPPORT
|| server.type == ServerType::ManaServ
#endif
)
{
Log::info("Ignoring server entry with unknown type: %s",
type.c_str());
return;
}
server.name = serverNode.getProperty("name", std::string());
std::string version = serverNode.getProperty("minimumVersion",
std::string());
bool meetsMinimumVersion = strcmp(version.c_str(), PACKAGE_VERSION) <= 0;
// For display in the list
if (meetsMinimumVersion)
version.clear();
else if (version.empty())
version = _("requires a newer version");
else
version = strprintf(_("requires v%s"), version.c_str());
for (auto subNode : serverNode.children())
{
if (subNode.name() == "connection")
{
server.hostname = subNode.getProperty("hostname", std::string());
server.port = subNode.getProperty("port", 0);
if (server.port == 0)
{
// If no port is given, use the default for the given type
server.port = ServerInfo::defaultPortForServerType(server.type);
}
}
else if (subNode.name() == "description")
{
server.description = subNode.textContent();
}
else if (subNode.name() == "persistentIp")
{
const auto text = subNode.textContent();
server.persistentIp = text == "1" || text == "true";
}
}
server.version.first = gui->getFont()->getWidth(version);
server.version.second = version;
// Add the server to the local list if it's not already present
bool found = false;
int i = 0;
for (auto &s : mServers)
{
if (s == server)
{
// Use the name listed in the server list
s.name = server.name;
s.version = server.version;
s.description = server.description;
mServersListModel->setVersionString(i, version);
found = true;
break;
}
++i;
}
if (!found)
mServers.push_back(server);
}
void ServerDialog::loadCustomServers()
{
for (auto &server : config.servers)
if (server.isValid())
mServers.push_back(server);
}
void ServerDialog::saveCustomServers(const ServerInfo ¤tServer, int index)
{
// Make sure the current server is mentioned first
if (currentServer.isValid())
{
if (index > -1)
{
mServers[index] = currentServer;
}
else
{
for (auto it = mServers.begin(), it_end = mServers.end(); it != it_end; ++it)
{
if (*it == currentServer)
{
mServers.erase(it);
break;
}
}
mServers.push_front(currentServer);
}
}
config.servers = mServers;
// Restore the correct description
if (index < 0)
index = 0;
if (static_cast(index) < mServers.size())
mDescription->setCaption(mServers[index].description);
}