c86b8b0f70
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
340 lines
12 KiB
C++
340 lines
12 KiB
C++
// SPDX-License-Identifier: GPL-3.0-only
|
|
/*
|
|
* Prism Launcher - Minecraft Launcher
|
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
|
*
|
|
* 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, version 3.
|
|
*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <QFileDialog>
|
|
#include <QFileInfo>
|
|
#include <QMimeDatabase>
|
|
#include <QPainter>
|
|
|
|
#include <FileSystem.h>
|
|
#include <QAction>
|
|
#include <QDialog>
|
|
#include <QFileInfo>
|
|
#include <QKeyEvent>
|
|
#include <QListView>
|
|
|
|
#include "Application.h"
|
|
#include "DesktopServices.h"
|
|
#include "QObjectPtr.h"
|
|
#include "SkinManageDialog.h"
|
|
|
|
#include "minecraft/auth/AccountTask.h"
|
|
#include "minecraft/skins/CapeChange.h"
|
|
#include "minecraft/skins/SkinDelete.h"
|
|
#include "minecraft/skins/SkinList.h"
|
|
#include "minecraft/skins/SkinModel.h"
|
|
#include "minecraft/skins/SkinUpload.h"
|
|
|
|
#include "net/NetJob.h"
|
|
#include "tasks/Task.h"
|
|
|
|
#include "ui/dialogs/CustomMessageBox.h"
|
|
#include "ui/dialogs/ProgressDialog.h"
|
|
#include "ui/instanceview/InstanceDelegate.h"
|
|
#include "ui_SkinManageDialog.h"
|
|
|
|
SkinManageDialog::SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct)
|
|
: QDialog(parent), m_acct(acct), ui(new Ui::SkinManageDialog), m_list(this, APPLICATION->settings()->get("SkinsDir").toString(), acct)
|
|
{
|
|
ui->setupUi(this);
|
|
|
|
setWindowModality(Qt::WindowModal);
|
|
|
|
auto contentsWidget = ui->listView;
|
|
contentsWidget->setViewMode(QListView::IconMode);
|
|
contentsWidget->setFlow(QListView::LeftToRight);
|
|
contentsWidget->setIconSize(QSize(48, 48));
|
|
contentsWidget->setMovement(QListView::Static);
|
|
contentsWidget->setResizeMode(QListView::Adjust);
|
|
contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
contentsWidget->setSpacing(5);
|
|
contentsWidget->setWordWrap(false);
|
|
contentsWidget->setWrapping(true);
|
|
contentsWidget->setUniformItemSizes(true);
|
|
contentsWidget->setTextElideMode(Qt::ElideRight);
|
|
contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
|
contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
contentsWidget->installEventFilter(this);
|
|
contentsWidget->setItemDelegate(new ListViewDelegate());
|
|
|
|
contentsWidget->setAcceptDrops(true);
|
|
contentsWidget->setDropIndicatorShown(true);
|
|
contentsWidget->viewport()->setAcceptDrops(true);
|
|
contentsWidget->setDragDropMode(QAbstractItemView::DropOnly);
|
|
contentsWidget->setDefaultDropAction(Qt::CopyAction);
|
|
|
|
contentsWidget->installEventFilter(this);
|
|
contentsWidget->setModel(&m_list);
|
|
|
|
connect(contentsWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(activated(QModelIndex)));
|
|
|
|
connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
|
|
SLOT(selectionChanged(QItemSelection, QItemSelection)));
|
|
connect(ui->listView, &QListView::customContextMenuRequested, this, &SkinManageDialog::show_context_menu);
|
|
|
|
setupCapes();
|
|
|
|
ui->listView->setCurrentIndex(m_list.index(m_list.getSelectedAccountSkin()));
|
|
}
|
|
|
|
SkinManageDialog::~SkinManageDialog()
|
|
{
|
|
delete ui;
|
|
}
|
|
|
|
void SkinManageDialog::activated(QModelIndex index)
|
|
{
|
|
m_selected_skin = index.data(Qt::UserRole).toString();
|
|
accept();
|
|
}
|
|
|
|
void SkinManageDialog::selectionChanged(QItemSelection selected, QItemSelection deselected)
|
|
{
|
|
if (selected.empty())
|
|
return;
|
|
|
|
QString key = selected.first().indexes().first().data(Qt::UserRole).toString();
|
|
if (key.isEmpty())
|
|
return;
|
|
m_selected_skin = key;
|
|
auto skin = m_list.skin(key);
|
|
if (!skin)
|
|
return;
|
|
ui->selectedModel->setPixmap(skin->getTexture().scaled(128, 128, Qt::KeepAspectRatio, Qt::FastTransformation));
|
|
ui->capeCombo->setCurrentIndex(m_capes_idx.value(skin->getCapeId()));
|
|
ui->steveBtn->setChecked(skin->getModel() == SkinModel::CLASSIC);
|
|
ui->alexBtn->setChecked(skin->getModel() == SkinModel::SLIM);
|
|
}
|
|
|
|
void SkinManageDialog::delayed_scroll(QModelIndex model_index)
|
|
{
|
|
auto contentsWidget = ui->listView;
|
|
contentsWidget->scrollTo(model_index);
|
|
}
|
|
|
|
void SkinManageDialog::on_openDirBtn_clicked()
|
|
{
|
|
DesktopServices::openDirectory(m_list.getDir(), true);
|
|
}
|
|
|
|
void SkinManageDialog::on_addBtn_clicked()
|
|
{
|
|
auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString();
|
|
QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter);
|
|
if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) {
|
|
return;
|
|
}
|
|
if (!SkinModel(raw_path).isValid()) {
|
|
CustomMessageBox::selectable(this, tr("Selected file is not a valid skin"),
|
|
tr("Skin images must be 64x64 or 64x32 pixel PNG files."), QMessageBox::Critical)
|
|
->show();
|
|
return;
|
|
}
|
|
m_list.installSkin(raw_path, {});
|
|
}
|
|
|
|
QPixmap previewCape(QPixmap capeImage)
|
|
{
|
|
QPixmap preview = QPixmap(10, 16);
|
|
QPainter painter(&preview);
|
|
painter.drawPixmap(0, 0, capeImage.copy(1, 1, 10, 16));
|
|
return preview.scaled(80, 128, Qt::IgnoreAspectRatio, Qt::FastTransformation);
|
|
}
|
|
|
|
void SkinManageDialog::setupCapes()
|
|
{
|
|
// FIXME: add a model for this, download/refresh the capes on demand
|
|
auto& accountData = *m_acct->accountData();
|
|
int index = 0;
|
|
ui->capeCombo->addItem(tr("No Cape"), QVariant());
|
|
auto currentCape = accountData.minecraftProfile.currentCape;
|
|
if (currentCape.isEmpty()) {
|
|
ui->capeCombo->setCurrentIndex(index);
|
|
}
|
|
|
|
auto capesDir = FS::PathCombine(m_list.getDir(), "capes");
|
|
NetJob::Ptr job{ new NetJob(tr("Download capes"), APPLICATION->network()) };
|
|
bool needsToDownload = false;
|
|
for (auto& cape : accountData.minecraftProfile.capes) {
|
|
auto path = FS::PathCombine(capesDir, cape.id + ".png");
|
|
if (cape.data.size()) {
|
|
QPixmap capeImage;
|
|
if (capeImage.loadFromData(cape.data, "PNG") && capeImage.save(path)) {
|
|
m_capes[cape.id] = previewCape(capeImage);
|
|
continue;
|
|
}
|
|
}
|
|
if (QFileInfo(path).exists()) {
|
|
continue;
|
|
}
|
|
if (!cape.url.isEmpty()) {
|
|
needsToDownload = true;
|
|
job->addNetAction(Net::Download::makeFile(cape.url, path));
|
|
}
|
|
}
|
|
if (needsToDownload) {
|
|
ProgressDialog dlg(this);
|
|
dlg.execWithTask(job.get());
|
|
}
|
|
for (auto& cape : accountData.minecraftProfile.capes) {
|
|
index++;
|
|
QPixmap capeImage;
|
|
if (!m_capes.contains(cape.id)) {
|
|
auto path = FS::PathCombine(capesDir, cape.id + ".png");
|
|
if (QFileInfo(path).exists() && capeImage.load(path)) {
|
|
capeImage = previewCape(capeImage);
|
|
m_capes[cape.id] = capeImage;
|
|
}
|
|
}
|
|
if (!capeImage.isNull()) {
|
|
ui->capeCombo->addItem(capeImage, cape.alias, cape.id);
|
|
} else {
|
|
ui->capeCombo->addItem(cape.alias, cape.id);
|
|
}
|
|
|
|
m_capes_idx[cape.id] = index;
|
|
}
|
|
}
|
|
|
|
void SkinManageDialog::on_capeCombo_currentIndexChanged(int index)
|
|
{
|
|
auto id = ui->capeCombo->currentData();
|
|
ui->capeImage->setPixmap(m_capes.value(id.toString(), {}));
|
|
if (auto skin = m_list.skin(m_selected_skin); skin) {
|
|
skin->setCapeId(id.toString());
|
|
}
|
|
}
|
|
|
|
void SkinManageDialog::on_steveBtn_toggled(bool checked)
|
|
{
|
|
if (auto skin = m_list.skin(m_selected_skin); skin) {
|
|
skin->setModel(checked ? SkinModel::CLASSIC : SkinModel::SLIM);
|
|
}
|
|
}
|
|
|
|
void SkinManageDialog::accept()
|
|
{
|
|
auto skin = m_list.skin(m_selected_skin);
|
|
if (!skin)
|
|
reject();
|
|
auto path = skin->getPath();
|
|
|
|
ProgressDialog prog(this);
|
|
NetJob::Ptr skinUpload{ new NetJob(tr("Change skin"), APPLICATION->network(), 1) };
|
|
|
|
if (!QFile::exists(path)) {
|
|
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec();
|
|
reject();
|
|
return;
|
|
}
|
|
|
|
skinUpload->addNetAction(SkinUpload::make(m_acct->accessToken(), skin));
|
|
|
|
auto selectedCape = skin->getCapeId();
|
|
if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) {
|
|
skinUpload->addNetAction(CapeChange::make(m_acct->accessToken(), selectedCape));
|
|
}
|
|
|
|
skinUpload->addTask(m_acct->refresh().staticCast<Task>());
|
|
if (prog.execWithTask(skinUpload.get()) != QDialog::Accepted) {
|
|
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec();
|
|
reject();
|
|
return;
|
|
}
|
|
skin->setURL(m_acct->accountData()->minecraftProfile.skin.url);
|
|
QDialog::accept();
|
|
}
|
|
|
|
void SkinManageDialog::on_resetBtn_clicked()
|
|
{
|
|
ProgressDialog prog(this);
|
|
NetJob::Ptr skinReset{ new NetJob(tr("Reset skin"), APPLICATION->network(), 1) };
|
|
skinReset->addNetAction(SkinDelete::make(m_acct->accessToken()));
|
|
skinReset->addTask(m_acct->refresh().staticCast<Task>());
|
|
if (prog.execWithTask(skinReset.get()) != QDialog::Accepted) {
|
|
CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec();
|
|
reject();
|
|
return;
|
|
}
|
|
QDialog::accept();
|
|
}
|
|
|
|
void SkinManageDialog::show_context_menu(const QPoint& pos)
|
|
{
|
|
QMenu myMenu(tr("Context menu"), this);
|
|
myMenu.addAction(ui->action_Rename_Skin);
|
|
myMenu.addAction(ui->action_Delete_Skin);
|
|
|
|
myMenu.exec(ui->listView->mapToGlobal(pos));
|
|
}
|
|
|
|
bool SkinManageDialog::eventFilter(QObject* obj, QEvent* ev)
|
|
{
|
|
if (obj == ui->listView) {
|
|
if (ev->type() == QEvent::KeyPress) {
|
|
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(ev);
|
|
switch (keyEvent->key()) {
|
|
case Qt::Key_Delete:
|
|
on_action_Delete_Skin_triggered(false);
|
|
return true;
|
|
case Qt::Key_F2:
|
|
on_action_Rename_Skin_triggered(false);
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return QDialog::eventFilter(obj, ev);
|
|
}
|
|
|
|
void SkinManageDialog::on_action_Rename_Skin_triggered(bool checked)
|
|
{
|
|
if (!m_selected_skin.isEmpty()) {
|
|
ui->listView->edit(ui->listView->currentIndex());
|
|
}
|
|
}
|
|
|
|
void SkinManageDialog::on_action_Delete_Skin_triggered(bool checked)
|
|
{
|
|
if (m_selected_skin.isEmpty())
|
|
return;
|
|
|
|
if (m_list.getSkinIndex(m_selected_skin) == m_list.getSelectedAccountSkin()) {
|
|
CustomMessageBox::selectable(this, tr("Delete error"), tr("Can not delete skin that is in use."), QMessageBox::Warning);
|
|
return;
|
|
}
|
|
|
|
auto skin = m_list.skin(m_selected_skin);
|
|
if (!skin)
|
|
return;
|
|
|
|
auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"),
|
|
tr("You are about to delete \"%1\".\n"
|
|
"Are you sure?")
|
|
.arg(skin->name()),
|
|
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
|
->exec();
|
|
|
|
if (response == QMessageBox::Yes) {
|
|
if (!m_list.deleteSkin(m_selected_skin, true)) {
|
|
m_list.deleteSkin(m_selected_skin, false);
|
|
}
|
|
}
|
|
}
|