/*
Copyright (C) 2012 Sebastian Herbord. All rights reserved.
This file is part of Mod Organizer.
Mod Organizer 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 3 of the License, or
(at your option) any later version.
Mod Organizer 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 Mod Organizer. If not, see .
*/
#include "profilesdialog.h"
#include "ui_profilesdialog.h"
#include "bsainvalidation.h"
#include "filesystemutilities.h"
#include "game_features.h"
#include "iplugingame.h"
#include "localsavegames.h"
#include "organizercore.h"
#include "profile.h"
#include "profileinputdialog.h"
#include "report.h"
#include "settings.h"
#include "shared/appconfig.h"
#include "transfersavesdialog.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace MOBase;
using namespace MOShared;
Q_DECLARE_METATYPE(Profile::Ptr)
ProfilesDialog::ProfilesDialog(const QString& profileName, OrganizerCore& organizer,
QWidget* parent)
: TutorableDialog("Profiles", parent), ui(new Ui::ProfilesDialog),
m_GameFeatures(organizer.gameFeatures()), m_FailState(false),
m_Game(organizer.managedGame()), m_ActiveProfileName("")
{
ui->setupUi(this);
QDir profilesDir(Settings::instance().paths().profiles());
profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
QDirIterator profileIter(profilesDir);
while (profileIter.hasNext()) {
profileIter.next();
QListWidgetItem* item = addItem(profileIter.filePath());
if (profileName == profileIter.fileName()) {
ui->profilesList->setCurrentItem(item);
m_ActiveProfileName = profileName;
}
}
auto invalidation = m_GameFeatures.gameFeature();
if (invalidation == nullptr) {
ui->invalidationBox->setToolTip(
tr("Archive invalidation isn't required for this game."));
ui->invalidationBox->setEnabled(false);
}
if (!m_GameFeatures.gameFeature()) {
ui->localSavesBox->setToolTip(
tr("This game does not support profile-specific game saves."));
ui->localSavesBox->setEnabled(false);
}
connect(this, &ProfilesDialog::profileCreated, &organizer,
&OrganizerCore::profileCreated);
connect(this, &ProfilesDialog::profileRenamed, &organizer,
&OrganizerCore::profileRenamed);
connect(this, &ProfilesDialog::profileRemoved, &organizer,
&OrganizerCore::profileRemoved);
}
ProfilesDialog::~ProfilesDialog()
{
delete ui;
}
int ProfilesDialog::exec()
{
GeometrySaver gs(Settings::instance(), this);
return QDialog::exec();
}
void ProfilesDialog::showEvent(QShowEvent* event)
{
TutorableDialog::showEvent(event);
if (ui->profilesList->count() == 0) {
QPoint pos = ui->profilesList->mapToGlobal(QPoint(0, 0));
pos.rx() += ui->profilesList->width() / 2;
pos.ry() += (ui->profilesList->height() / 2) - 20;
QWhatsThis::showText(
pos,
QObject::tr(
"Before you can use ModOrganizer, you need to create at least one profile. "
"ATTENTION: Run the game at least once before creating a profile!"),
ui->profilesList);
}
}
void ProfilesDialog::on_close_clicked()
{
close();
}
void ProfilesDialog::on_select_clicked()
{
const Profile::Ptr currentProfile =
ui->profilesList->currentItem()->data(Qt::UserRole).value();
if (!currentProfile) {
return;
}
m_Selected = currentProfile->name();
close();
}
std::optional ProfilesDialog::selectedProfile() const
{
return m_Selected;
}
QListWidgetItem* ProfilesDialog::addItem(const QString& name)
{
QDir profileDir(name);
QListWidgetItem* newItem =
new QListWidgetItem(profileDir.dirName(), ui->profilesList);
try {
newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(
profileDir, m_Game, m_GameFeatures))));
m_FailState = false;
} catch (const std::exception& e) {
reportError(tr("failed to create profile: %1").arg(e.what()));
}
return newItem;
}
void ProfilesDialog::createProfile(const QString& name, bool useDefaultSettings)
{
try {
auto profile =
Profile::Ptr(new Profile(name, m_Game, m_GameFeatures, useDefaultSettings));
QListWidgetItem* newItem = new QListWidgetItem(name, ui->profilesList);
newItem->setData(Qt::UserRole, QVariant::fromValue(profile));
ui->profilesList->addItem(newItem);
m_FailState = false;
ui->profilesList->setCurrentItem(newItem);
emit profileCreated(profile.get());
} catch (const std::exception&) {
m_FailState = true;
throw;
}
}
void ProfilesDialog::createProfile(const QString& name, const Profile& reference)
{
try {
auto profile = Profile::Ptr(Profile::createPtrFrom(name, reference, m_Game));
QListWidgetItem* newItem = new QListWidgetItem(name, ui->profilesList);
newItem->setData(Qt::UserRole, QVariant::fromValue(profile));
ui->profilesList->addItem(newItem);
m_FailState = false;
ui->profilesList->setCurrentItem(newItem);
emit profileCreated(profile.get());
} catch (const std::exception&) {
m_FailState = true;
throw;
}
}
void ProfilesDialog::on_addProfileButton_clicked()
{
ProfileInputDialog dialog(this);
bool okClicked = dialog.exec();
QString name = dialog.getName();
if (okClicked && (name.size() > 0)) {
try {
createProfile(name, dialog.getPreferDefaultSettings());
} catch (const std::exception& e) {
reportError(tr("failed to create profile: %1").arg(e.what()));
}
}
}
void ProfilesDialog::on_copyProfileButton_clicked()
{
bool okClicked;
QString name = QInputDialog::getText(this, tr("Name"),
tr("Please enter a name for the new profile"),
QLineEdit::Normal, QString(), &okClicked);
if (okClicked) {
if (fixDirectoryName(name)) {
try {
const Profile::Ptr currentProfile =
ui->profilesList->currentItem()->data(Qt::UserRole).value();
createProfile(name, *currentProfile);
} catch (const std::exception& e) {
reportError(tr("failed to copy profile: %1").arg(e.what()));
}
} else {
QMessageBox::warning(this, tr("Invalid name"), tr("Invalid profile name"));
}
}
}
void ProfilesDialog::on_removeProfileButton_clicked()
{
Profile::Ptr profileToDelete =
ui->profilesList->currentItem()->data(Qt::UserRole).value();
if (profileToDelete->name() == m_ActiveProfileName) {
QMessageBox::warning(this, tr("Deleting active profile"),
tr("Unable to delete active profile. Please change to a "
"different profile first."));
return;
}
QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"),
tr("Are you sure you want to remove this profile (including "
"profile-specific save games, if any)?"),
QMessageBox::Yes | QMessageBox::No, this);
if (confirmBox.exec() == QMessageBox::Yes) {
QString profilePath;
if (profileToDelete.get() == nullptr) {
profilePath = Settings::instance().paths().profiles() + "/" +
ui->profilesList->currentItem()->text();
if (QMessageBox::question(
this, tr("Profile broken"),
tr("This profile you're about to delete seems to be broken or the path "
"is invalid. "
"I'm about to delete the following folder: \"%1\". Proceed?")
.arg(profilePath),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
return;
}
} else {
// on destruction, the profile object would write the profile.ini file again, so
// we have to get rid of the it before deleting the directory
profilePath = profileToDelete->absolutePath();
}
QListWidgetItem* item = ui->profilesList->takeItem(ui->profilesList->currentRow());
if (item != nullptr) {
delete item;
}
if (!shellDelete(QStringList(profilePath))) {
log::warn("Failed to shell-delete \"{}\" (errorcode {}), trying regular delete",
profilePath, ::GetLastError());
if (!removeDir(profilePath)) {
log::warn("regular delete failed too");
}
}
emit profileRemoved(profileToDelete->name());
}
}
void ProfilesDialog::on_renameButton_clicked()
{
Profile::Ptr currentProfile =
ui->profilesList->currentItem()->data(Qt::UserRole).value();
if (currentProfile->name() == m_ActiveProfileName) {
QMessageBox::warning(this, tr("Renaming active profile"),
tr("The active profile cannot be renamed. Please change to a "
"different profile first."));
return;
}
bool valid = false;
QString name;
while (!valid) {
bool ok = false;
name = QInputDialog::getText(this, tr("Rename Profile"), tr("New Name"),
QLineEdit::Normal, currentProfile->name(), &ok);
valid = fixDirectoryName(name);
if (!ok) {
return;
}
}
ui->profilesList->currentItem()->setText(name);
QString oldName = currentProfile->name();
currentProfile->rename(name);
emit profileRenamed(currentProfile.get(), oldName, name);
}
void ProfilesDialog::on_invalidationBox_stateChanged(int state)
{
QListWidgetItem* currentItem = ui->profilesList->currentItem();
if (currentItem == nullptr) {
return;
}
if (!ui->invalidationBox->isEnabled()) {
return;
}
try {
QVariant currentProfileVariant = currentItem->data(Qt::UserRole);
if (!currentProfileVariant.isValid() || currentProfileVariant.isNull()) {
return;
}
const Profile::Ptr currentProfile =
currentItem->data(Qt::UserRole).value();
if (state == Qt::Unchecked) {
currentProfile->deactivateInvalidation();
} else {
currentProfile->activateInvalidation();
}
} catch (const std::exception& e) {
reportError(tr("failed to change archive invalidation state: %1").arg(e.what()));
}
}
void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem* current,
QListWidgetItem*)
{
if (current != nullptr) {
if (!current->data(Qt::UserRole).isValid())
return;
const Profile::Ptr currentProfile =
current->data(Qt::UserRole).value();
try {
bool invalidationSupported = false;
ui->invalidationBox->blockSignals(true);
ui->invalidationBox->setChecked(
currentProfile->invalidationActive(&invalidationSupported));
ui->invalidationBox->setEnabled(invalidationSupported);
ui->invalidationBox->blockSignals(false);
bool localSaves = currentProfile->localSavesEnabled();
ui->transferButton->setEnabled(localSaves);
// prevent the stateChanged-event for the saves-box from triggering, otherwise it
// may think local saves were disabled and delete the files/rename the dir
ui->localSavesBox->blockSignals(true);
ui->localSavesBox->setChecked(localSaves);
ui->localSavesBox->blockSignals(false);
ui->copyProfileButton->setEnabled(true);
ui->removeProfileButton->setEnabled(true);
ui->renameButton->setEnabled(true);
ui->localIniFilesBox->blockSignals(true);
ui->localIniFilesBox->setChecked(currentProfile->localSettingsEnabled());
ui->localIniFilesBox->blockSignals(false);
} catch (const std::exception& E) {
reportError(
tr("failed to determine if invalidation is active: %1").arg(E.what()));
ui->copyProfileButton->setEnabled(false);
ui->removeProfileButton->setEnabled(false);
ui->renameButton->setEnabled(false);
ui->invalidationBox->setChecked(false);
}
} else {
ui->invalidationBox->setChecked(false);
ui->copyProfileButton->setEnabled(false);
ui->removeProfileButton->setEnabled(false);
ui->renameButton->setEnabled(false);
}
}
void ProfilesDialog::on_profilesList_itemActivated(QListWidgetItem* item)
{
on_select_clicked();
}
void ProfilesDialog::on_localSavesBox_stateChanged(int state)
{
Profile::Ptr currentProfile =
ui->profilesList->currentItem()->data(Qt::UserRole).value();
if (currentProfile->enableLocalSaves(state == Qt::Checked)) {
ui->transferButton->setEnabled(state == Qt::Checked);
} else {
// revert checkbox-state
ui->localSavesBox->setChecked(state != Qt::Checked);
}
}
void ProfilesDialog::on_transferButton_clicked()
{
const Profile::Ptr currentProfile =
ui->profilesList->currentItem()->data(Qt::UserRole).value();
TransferSavesDialog transferDialog(*currentProfile, m_Game, this);
transferDialog.exec();
}
void ProfilesDialog::on_localIniFilesBox_stateChanged(int state)
{
Profile::Ptr currentProfile =
ui->profilesList->currentItem()->data(Qt::UserRole).value();
if (!currentProfile->enableLocalSettings(state == Qt::Checked)) {
// revert checkbox-state
ui->localIniFilesBox->setChecked(state != Qt::Checked);
}
}