NOISSUE Allow joining servers from the servers page
This commit is contained in:
parent
f33fe05e5f
commit
ea6c42a93c
@ -147,7 +147,8 @@ public:
|
|||||||
virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0;
|
virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0;
|
||||||
|
|
||||||
/// returns a valid launcher (task container)
|
/// returns a valid launcher (task container)
|
||||||
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
|
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(
|
||||||
|
AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) = 0;
|
||||||
|
|
||||||
/// returns the current launch task (if any)
|
/// returns the current launch task (if any)
|
||||||
shared_qobject_ptr<LaunchTask> getLaunchTask();
|
shared_qobject_ptr<LaunchTask> getLaunchTask();
|
||||||
|
@ -238,6 +238,7 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/launch/ExtractNatives.h
|
minecraft/launch/ExtractNatives.h
|
||||||
minecraft/launch/LauncherPartLaunch.cpp
|
minecraft/launch/LauncherPartLaunch.cpp
|
||||||
minecraft/launch/LauncherPartLaunch.h
|
minecraft/launch/LauncherPartLaunch.h
|
||||||
|
minecraft/launch/MinecraftServerTarget.cpp
|
||||||
minecraft/launch/MinecraftServerTarget.h
|
minecraft/launch/MinecraftServerTarget.h
|
||||||
minecraft/launch/PrintInstanceInfo.cpp
|
minecraft/launch/PrintInstanceInfo.cpp
|
||||||
minecraft/launch/PrintInstanceInfo.h
|
minecraft/launch/PrintInstanceInfo.h
|
||||||
|
@ -27,7 +27,7 @@ public:
|
|||||||
{
|
{
|
||||||
return instanceRoot();
|
return instanceRoot();
|
||||||
};
|
};
|
||||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
|
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr, MinecraftServerTargetPtr) override
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -822,7 +822,7 @@ shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session)
|
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
|
||||||
{
|
{
|
||||||
// FIXME: get rid of shared_from_this ...
|
// FIXME: get rid of shared_from_this ...
|
||||||
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this()));
|
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this()));
|
||||||
@ -854,66 +854,19 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
|||||||
process->appendStep(new CreateGameFolders(pptr));
|
process->appendStep(new CreateGameFolders(pptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
MinecraftServerTargetPtr serverToJoin = std::make_shared<MinecraftServerTarget>();
|
if (!serverToJoin && m_settings->get("JoinServerOnLaunch").toBool())
|
||||||
|
|
||||||
if (m_settings->get("JoinServerOnLaunch").toBool())
|
|
||||||
{
|
{
|
||||||
QString fullAddress = m_settings->get("JoinServerOnLaunchAddress").toString();
|
QString fullAddress = m_settings->get("JoinServerOnLaunchAddress").toString();
|
||||||
QStringList split = fullAddress.split(":");
|
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress)));
|
||||||
|
}
|
||||||
|
|
||||||
// The logic below replicates the exact logic minecraft uses for parsing server addresses.
|
if(serverToJoin && serverToJoin->port == 25565)
|
||||||
// While the conversion is not lossless and eats errors, it ensures the same behavior
|
{
|
||||||
// within Minecraft and MultiMC when entering server addresses.
|
// Resolve server address to join on launch
|
||||||
if (fullAddress.startsWith("["))
|
auto *step = new LookupServerAddress(pptr);
|
||||||
{
|
step->setLookupAddress(serverToJoin->address);
|
||||||
int bracket = fullAddress.indexOf("]");
|
step->setOutputAddressPtr(serverToJoin);
|
||||||
if (bracket > 0)
|
process->appendStep(step);
|
||||||
{
|
|
||||||
QString ipv6 = fullAddress.mid(1, bracket - 1);
|
|
||||||
QString port = fullAddress.mid(bracket + 1).trimmed();
|
|
||||||
|
|
||||||
if (port.startsWith(":") && !ipv6.isEmpty())
|
|
||||||
{
|
|
||||||
port = port.mid(1);
|
|
||||||
split = QStringList({ ipv6, port });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
split = QStringList({ipv6});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (split.size() > 2)
|
|
||||||
{
|
|
||||||
split = QStringList({fullAddress});
|
|
||||||
}
|
|
||||||
|
|
||||||
QString realAddress = split[0];
|
|
||||||
|
|
||||||
quint16 realPort = 25565;
|
|
||||||
if (split.size() > 1)
|
|
||||||
{
|
|
||||||
bool ok;
|
|
||||||
realPort = split[1].toUInt(&ok);
|
|
||||||
|
|
||||||
if (!ok)
|
|
||||||
{
|
|
||||||
realPort = 25565;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serverToJoin->port = realPort;
|
|
||||||
serverToJoin->address = realAddress;
|
|
||||||
|
|
||||||
if(realPort == 25565)
|
|
||||||
{
|
|
||||||
// Resolve server address to join on launch
|
|
||||||
auto *step = new LookupServerAddress(pptr);
|
|
||||||
step->setLookupAddress(realAddress);
|
|
||||||
step->setOutputAddressPtr(serverToJoin);
|
|
||||||
process->appendStep(step);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// run pre-launch command if that's needed
|
// run pre-launch command if that's needed
|
||||||
|
@ -77,7 +77,7 @@ public:
|
|||||||
|
|
||||||
////// Launch stuff //////
|
////// Launch stuff //////
|
||||||
shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
|
shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
|
||||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
|
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override;
|
||||||
QStringList extraArguments() const override;
|
QStringList extraArguments() const override;
|
||||||
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override;
|
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override;
|
||||||
QList<Mod> getJarMods() const;
|
QList<Mod> getJarMods() const;
|
||||||
|
66
api/logic/minecraft/launch/MinecraftServerTarget.cpp
Normal file
66
api/logic/minecraft/launch/MinecraftServerTarget.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/* Copyright 2013-2021 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "MinecraftServerTarget.h"
|
||||||
|
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
MinecraftServerTarget MinecraftServerTarget::parse(const QString &fullAddress) {
|
||||||
|
QStringList split = fullAddress.split(":");
|
||||||
|
|
||||||
|
// The logic below replicates the exact logic minecraft uses for parsing server addresses.
|
||||||
|
// While the conversion is not lossless and eats errors, it ensures the same behavior
|
||||||
|
// within Minecraft and MultiMC when entering server addresses.
|
||||||
|
if (fullAddress.startsWith("["))
|
||||||
|
{
|
||||||
|
int bracket = fullAddress.indexOf("]");
|
||||||
|
if (bracket > 0)
|
||||||
|
{
|
||||||
|
QString ipv6 = fullAddress.mid(1, bracket - 1);
|
||||||
|
QString port = fullAddress.mid(bracket + 1).trimmed();
|
||||||
|
|
||||||
|
if (port.startsWith(":") && !ipv6.isEmpty())
|
||||||
|
{
|
||||||
|
port = port.mid(1);
|
||||||
|
split = QStringList({ ipv6, port });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
split = QStringList({ipv6});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (split.size() > 2)
|
||||||
|
{
|
||||||
|
split = QStringList({fullAddress});
|
||||||
|
}
|
||||||
|
|
||||||
|
QString realAddress = split[0];
|
||||||
|
|
||||||
|
quint16 realPort = 25565;
|
||||||
|
if (split.size() > 1)
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
realPort = split[1].toUInt(&ok);
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
realPort = 25565;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MinecraftServerTarget { realAddress, realPort };
|
||||||
|
}
|
@ -17,9 +17,14 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <multimc_logic_export.h>
|
||||||
|
|
||||||
struct MinecraftServerTarget {
|
struct MinecraftServerTarget {
|
||||||
QString address;
|
QString address;
|
||||||
quint16 port;
|
quint16 port;
|
||||||
|
|
||||||
|
static MULTIMC_LOGIC_EXPORT MinecraftServerTarget parse(const QString &fullAddress);
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::shared_ptr<MinecraftServerTarget> MinecraftServerTargetPtr;
|
typedef std::shared_ptr<MinecraftServerTarget> MinecraftServerTargetPtr;
|
||||||
|
@ -111,7 +111,8 @@ public:
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override
|
shared_qobject_ptr<LaunchTask> createLaunchTask(
|
||||||
|
AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ public:
|
|||||||
values.append(new TexturePackPage(onesix.get()));
|
values.append(new TexturePackPage(onesix.get()));
|
||||||
values.append(new NotesPage(onesix.get()));
|
values.append(new NotesPage(onesix.get()));
|
||||||
values.append(new WorldListPage(onesix.get(), onesix->worldList()));
|
values.append(new WorldListPage(onesix.get(), onesix->worldList()));
|
||||||
values.append(new ServersPage(onesix.get()));
|
values.append(new ServersPage(onesix));
|
||||||
// values.append(new GameOptionsPage(onesix.get()));
|
// values.append(new GameOptionsPage(onesix.get()));
|
||||||
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
|
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
|
||||||
values.append(new InstanceSettingsPage(onesix.get()));
|
values.append(new InstanceSettingsPage(onesix.get()));
|
||||||
|
@ -197,7 +197,7 @@ void LaunchController::launchInstance()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_launcher = m_instance->createLaunchTask(m_session);
|
m_launcher = m_instance->createLaunchTask(m_session, m_serverToJoin);
|
||||||
if (!m_launcher)
|
if (!m_launcher)
|
||||||
{
|
{
|
||||||
emitFailed(tr("Couldn't instantiate a launcher."));
|
emitFailed(tr("Couldn't instantiate a launcher."));
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
#include <BaseInstance.h>
|
#include <BaseInstance.h>
|
||||||
#include <tools/BaseProfiler.h>
|
#include <tools/BaseProfiler.h>
|
||||||
|
|
||||||
|
#include "minecraft/launch/MinecraftServerTarget.h"
|
||||||
|
|
||||||
class InstanceWindow;
|
class InstanceWindow;
|
||||||
class LaunchController: public Task
|
class LaunchController: public Task
|
||||||
{
|
{
|
||||||
@ -33,6 +35,10 @@ public:
|
|||||||
{
|
{
|
||||||
m_parentWidget = widget;
|
m_parentWidget = widget;
|
||||||
}
|
}
|
||||||
|
void setServerToJoin(MinecraftServerTargetPtr serverToJoin)
|
||||||
|
{
|
||||||
|
m_serverToJoin = std::move(serverToJoin);
|
||||||
|
}
|
||||||
QString id()
|
QString id()
|
||||||
{
|
{
|
||||||
return m_instance->id();
|
return m_instance->id();
|
||||||
@ -58,4 +64,5 @@ private:
|
|||||||
InstanceWindow *m_console = nullptr;
|
InstanceWindow *m_console = nullptr;
|
||||||
AuthSessionPtr m_session;
|
AuthSessionPtr m_session;
|
||||||
shared_qobject_ptr<LaunchTask> m_launcher;
|
shared_qobject_ptr<LaunchTask> m_launcher;
|
||||||
|
MinecraftServerTargetPtr m_serverToJoin;
|
||||||
};
|
};
|
||||||
|
@ -1014,8 +1014,12 @@ bool MultiMC::openJsonEditor(const QString &filename)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *profiler)
|
bool MultiMC::launch(
|
||||||
{
|
InstancePtr instance,
|
||||||
|
bool online,
|
||||||
|
BaseProfilerFactory *profiler,
|
||||||
|
MinecraftServerTargetPtr serverToJoin
|
||||||
|
) {
|
||||||
if(m_updateRunning)
|
if(m_updateRunning)
|
||||||
{
|
{
|
||||||
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
|
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
|
||||||
@ -1036,6 +1040,7 @@ bool MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *pro
|
|||||||
controller->setInstance(instance);
|
controller->setInstance(instance);
|
||||||
controller->setOnline(online);
|
controller->setOnline(online);
|
||||||
controller->setProfiler(profiler);
|
controller->setProfiler(profiler);
|
||||||
|
controller->setServerToJoin(serverToJoin);
|
||||||
if(window)
|
if(window)
|
||||||
{
|
{
|
||||||
controller->setParentWidget(window);
|
controller->setParentWidget(window);
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
#include <BaseInstance.h>
|
#include <BaseInstance.h>
|
||||||
|
|
||||||
|
#include "minecraft/launch/MinecraftServerTarget.h"
|
||||||
|
|
||||||
class LaunchController;
|
class LaunchController;
|
||||||
class LocalPeer;
|
class LocalPeer;
|
||||||
class InstanceWindow;
|
class InstanceWindow;
|
||||||
@ -150,7 +152,12 @@ signals:
|
|||||||
void globalSettingsClosed();
|
void globalSettingsClosed();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
bool launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr);
|
bool launch(
|
||||||
|
InstancePtr instance,
|
||||||
|
bool online = true,
|
||||||
|
BaseProfilerFactory *profiler = nullptr,
|
||||||
|
MinecraftServerTargetPtr serverToJoin = nullptr
|
||||||
|
);
|
||||||
bool kill(InstancePtr instance);
|
bool kill(InstancePtr instance);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
<enum>QTabWidget::Rounded</enum>
|
<enum>QTabWidget::Rounded</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>4</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="minecraftTab">
|
<widget class="QWidget" name="minecraftTab">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
|
@ -556,7 +556,7 @@ private:
|
|||||||
QTimer m_saveTimer;
|
QTimer m_saveTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
ServersPage::ServersPage(MinecraftInstance * inst, QWidget* parent)
|
ServersPage::ServersPage(InstancePtr inst, QWidget* parent)
|
||||||
: QMainWindow(parent), ui(new Ui::ServersPage)
|
: QMainWindow(parent), ui(new Ui::ServersPage)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
@ -579,7 +579,7 @@ ServersPage::ServersPage(MinecraftInstance * inst, QWidget* parent)
|
|||||||
|
|
||||||
auto selectionModel = ui->serversView->selectionModel();
|
auto selectionModel = ui->serversView->selectionModel();
|
||||||
connect(selectionModel, &QItemSelectionModel::currentChanged, this, &ServersPage::currentChanged);
|
connect(selectionModel, &QItemSelectionModel::currentChanged, this, &ServersPage::currentChanged);
|
||||||
connect(m_inst, &MinecraftInstance::runningStatusChanged, this, &ServersPage::on_RunningState_changed);
|
connect(m_inst.get(), &MinecraftInstance::runningStatusChanged, this, &ServersPage::on_RunningState_changed);
|
||||||
connect(ui->nameLine, &QLineEdit::textEdited, this, &ServersPage::nameEdited);
|
connect(ui->nameLine, &QLineEdit::textEdited, this, &ServersPage::nameEdited);
|
||||||
connect(ui->addressLine, &QLineEdit::textEdited, this, &ServersPage::addressEdited);
|
connect(ui->addressLine, &QLineEdit::textEdited, this, &ServersPage::addressEdited);
|
||||||
connect(ui->resourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resourceIndexChanged(int)));
|
connect(ui->resourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resourceIndexChanged(int)));
|
||||||
@ -695,6 +695,7 @@ void ServersPage::updateState()
|
|||||||
ui->actionMove_Down->setEnabled(serverEditEnabled);
|
ui->actionMove_Down->setEnabled(serverEditEnabled);
|
||||||
ui->actionMove_Up->setEnabled(serverEditEnabled);
|
ui->actionMove_Up->setEnabled(serverEditEnabled);
|
||||||
ui->actionRemove->setEnabled(serverEditEnabled);
|
ui->actionRemove->setEnabled(serverEditEnabled);
|
||||||
|
ui->actionJoin->setEnabled(serverEditEnabled);
|
||||||
|
|
||||||
if(server)
|
if(server)
|
||||||
{
|
{
|
||||||
@ -758,4 +759,10 @@ void ServersPage::on_actionMove_Down_triggered()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServersPage::on_actionJoin_triggered()
|
||||||
|
{
|
||||||
|
const auto &address = m_model->at(currentServer)->m_address;
|
||||||
|
MMC->launch(m_inst, true, nullptr, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address)));
|
||||||
|
}
|
||||||
|
|
||||||
#include "ServersPage.moc"
|
#include "ServersPage.moc"
|
||||||
|
@ -35,7 +35,7 @@ class ServersPage : public QMainWindow, public BasePage
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ServersPage(MinecraftInstance *inst, QWidget *parent = 0);
|
explicit ServersPage(InstancePtr inst, QWidget *parent = 0);
|
||||||
virtual ~ServersPage();
|
virtual ~ServersPage();
|
||||||
|
|
||||||
void openedImpl() override;
|
void openedImpl() override;
|
||||||
@ -74,6 +74,7 @@ private slots:
|
|||||||
void on_actionRemove_triggered();
|
void on_actionRemove_triggered();
|
||||||
void on_actionMove_Up_triggered();
|
void on_actionMove_Up_triggered();
|
||||||
void on_actionMove_Down_triggered();
|
void on_actionMove_Down_triggered();
|
||||||
|
void on_actionJoin_triggered();
|
||||||
|
|
||||||
void on_RunningState_changed(bool running);
|
void on_RunningState_changed(bool running);
|
||||||
|
|
||||||
@ -88,6 +89,6 @@ private: // data
|
|||||||
bool m_locked = true;
|
bool m_locked = true;
|
||||||
Ui::ServersPage *ui = nullptr;
|
Ui::ServersPage *ui = nullptr;
|
||||||
ServersModel * m_model = nullptr;
|
ServersModel * m_model = nullptr;
|
||||||
MinecraftInstance * m_inst = nullptr;
|
InstancePtr m_inst = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -148,6 +148,7 @@
|
|||||||
<addaction name="actionRemove"/>
|
<addaction name="actionRemove"/>
|
||||||
<addaction name="actionMove_Up"/>
|
<addaction name="actionMove_Up"/>
|
||||||
<addaction name="actionMove_Down"/>
|
<addaction name="actionMove_Down"/>
|
||||||
|
<addaction name="actionJoin"/>
|
||||||
</widget>
|
</widget>
|
||||||
<action name="actionAdd">
|
<action name="actionAdd">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -169,6 +170,11 @@
|
|||||||
<string>Move Down</string>
|
<string>Move Down</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionJoin">
|
||||||
|
<property name="text">
|
||||||
|
<string>Join</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
Loading…
Reference in New Issue
Block a user