diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/yuzu/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure.ui | 11 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_dialog.cpp | 17 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_profile_manager.cpp | 300 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_profile_manager.h | 57 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_profile_manager.ui | 172 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_system.cpp | 261 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_system.h | 27 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_system.ui | 144 |
9 files changed, 565 insertions, 427 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 17ecaafde..5446be6be 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt | |||
| @@ -37,6 +37,8 @@ add_executable(yuzu | |||
| 37 | configuration/configure_input_simple.h | 37 | configuration/configure_input_simple.h |
| 38 | configuration/configure_mouse_advanced.cpp | 38 | configuration/configure_mouse_advanced.cpp |
| 39 | configuration/configure_mouse_advanced.h | 39 | configuration/configure_mouse_advanced.h |
| 40 | configuration/configure_profile_manager.cpp | ||
| 41 | configuration/configure_profile_manager.h | ||
| 40 | configuration/configure_system.cpp | 42 | configuration/configure_system.cpp |
| 41 | configuration/configure_system.h | 43 | configuration/configure_system.h |
| 42 | configuration/configure_per_general.cpp | 44 | configuration/configure_per_general.cpp |
| @@ -94,6 +96,7 @@ set(UIS | |||
| 94 | configuration/configure_input_simple.ui | 96 | configuration/configure_input_simple.ui |
| 95 | configuration/configure_mouse_advanced.ui | 97 | configuration/configure_mouse_advanced.ui |
| 96 | configuration/configure_per_general.ui | 98 | configuration/configure_per_general.ui |
| 99 | configuration/configure_profile_manager.ui | ||
| 97 | configuration/configure_system.ui | 100 | configuration/configure_system.ui |
| 98 | configuration/configure_touchscreen_advanced.ui | 101 | configuration/configure_touchscreen_advanced.ui |
| 99 | configuration/configure_web.ui | 102 | configuration/configure_web.ui |
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index ce833b6c8..3f03f0b77 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui | |||
| @@ -52,6 +52,11 @@ | |||
| 52 | <string>System</string> | 52 | <string>System</string> |
| 53 | </attribute> | 53 | </attribute> |
| 54 | </widget> | 54 | </widget> |
| 55 | <widget class="ConfigureProfileManager" name="profileManagerTab"> | ||
| 56 | <attribute name="title"> | ||
| 57 | <string>Profiles</string> | ||
| 58 | </attribute> | ||
| 59 | </widget> | ||
| 55 | <widget class="ConfigureInputSimple" name="inputTab"> | 60 | <widget class="ConfigureInputSimple" name="inputTab"> |
| 56 | <attribute name="title"> | 61 | <attribute name="title"> |
| 57 | <string>Input</string> | 62 | <string>Input</string> |
| @@ -104,6 +109,12 @@ | |||
| 104 | <container>1</container> | 109 | <container>1</container> |
| 105 | </customwidget> | 110 | </customwidget> |
| 106 | <customwidget> | 111 | <customwidget> |
| 112 | <class>ConfigureProfileManager</class> | ||
| 113 | <extends>QWidget</extends> | ||
| 114 | <header>configuration/configure_profile_manager.h</header> | ||
| 115 | <container>1</container> | ||
| 116 | </customwidget> | ||
| 117 | <customwidget> | ||
| 107 | <class>ConfigureAudio</class> | 118 | <class>ConfigureAudio</class> |
| 108 | <extends>QWidget</extends> | 119 | <extends>QWidget</extends> |
| 109 | <header>configuration/configure_audio.h</header> | 120 | <header>configuration/configure_audio.h</header> |
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 90d7c6372..d802443d0 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp | |||
| @@ -32,6 +32,7 @@ void ConfigureDialog::applyConfiguration() { | |||
| 32 | ui->generalTab->applyConfiguration(); | 32 | ui->generalTab->applyConfiguration(); |
| 33 | ui->gameListTab->applyConfiguration(); | 33 | ui->gameListTab->applyConfiguration(); |
| 34 | ui->systemTab->applyConfiguration(); | 34 | ui->systemTab->applyConfiguration(); |
| 35 | ui->profileManagerTab->applyConfiguration(); | ||
| 35 | ui->inputTab->applyConfiguration(); | 36 | ui->inputTab->applyConfiguration(); |
| 36 | ui->graphicsTab->applyConfiguration(); | 37 | ui->graphicsTab->applyConfiguration(); |
| 37 | ui->audioTab->applyConfiguration(); | 38 | ui->audioTab->applyConfiguration(); |
| @@ -43,7 +44,7 @@ void ConfigureDialog::applyConfiguration() { | |||
| 43 | void ConfigureDialog::PopulateSelectionList() { | 44 | void ConfigureDialog::PopulateSelectionList() { |
| 44 | const std::array<std::pair<QString, QStringList>, 4> items{ | 45 | const std::array<std::pair<QString, QStringList>, 4> items{ |
| 45 | {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}}, | 46 | {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}}, |
| 46 | {tr("System"), {tr("System"), tr("Audio")}}, | 47 | {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}}, |
| 47 | {tr("Graphics"), {tr("Graphics")}}, | 48 | {tr("Graphics"), {tr("Graphics")}}, |
| 48 | {tr("Controls"), {tr("Input")}}}}; | 49 | {tr("Controls"), {tr("Input")}}}}; |
| 49 | 50 | ||
| @@ -60,11 +61,15 @@ void ConfigureDialog::UpdateVisibleTabs() { | |||
| 60 | if (items.isEmpty()) | 61 | if (items.isEmpty()) |
| 61 | return; | 62 | return; |
| 62 | 63 | ||
| 63 | const std::map<QString, QWidget*> widgets = { | 64 | const std::map<QString, QWidget*> widgets = {{tr("General"), ui->generalTab}, |
| 64 | {tr("General"), ui->generalTab}, {tr("System"), ui->systemTab}, | 65 | {tr("System"), ui->systemTab}, |
| 65 | {tr("Input"), ui->inputTab}, {tr("Graphics"), ui->graphicsTab}, | 66 | {tr("Profiles"), ui->profileManagerTab}, |
| 66 | {tr("Audio"), ui->audioTab}, {tr("Debug"), ui->debugTab}, | 67 | {tr("Input"), ui->inputTab}, |
| 67 | {tr("Web"), ui->webTab}, {tr("Game List"), ui->gameListTab}}; | 68 | {tr("Graphics"), ui->graphicsTab}, |
| 69 | {tr("Audio"), ui->audioTab}, | ||
| 70 | {tr("Debug"), ui->debugTab}, | ||
| 71 | {tr("Web"), ui->webTab}, | ||
| 72 | {tr("Game List"), ui->gameListTab}}; | ||
| 68 | 73 | ||
| 69 | ui->tabWidget->clear(); | 74 | ui->tabWidget->clear(); |
| 70 | 75 | ||
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp new file mode 100644 index 000000000..41663e39a --- /dev/null +++ b/src/yuzu/configuration/configure_profile_manager.cpp | |||
| @@ -0,0 +1,300 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <QFileDialog> | ||
| 7 | #include <QGraphicsItem> | ||
| 8 | #include <QGraphicsScene> | ||
| 9 | #include <QHeaderView> | ||
| 10 | #include <QMessageBox> | ||
| 11 | #include <QStandardItemModel> | ||
| 12 | #include <QTreeView> | ||
| 13 | #include <QVBoxLayout> | ||
| 14 | #include "common/assert.h" | ||
| 15 | #include "common/file_util.h" | ||
| 16 | #include "common/string_util.h" | ||
| 17 | #include "core/core.h" | ||
| 18 | #include "core/hle/service/acc/profile_manager.h" | ||
| 19 | #include "core/settings.h" | ||
| 20 | #include "ui_configure_profile_manager.h" | ||
| 21 | #include "yuzu/configuration/configure_profile_manager.h" | ||
| 22 | #include "yuzu/util/limitable_input_dialog.h" | ||
| 23 | |||
| 24 | namespace { | ||
| 25 | // Same backup JPEG used by acc IProfile::GetImage if no jpeg found | ||
| 26 | constexpr std::array<u8, 107> backup_jpeg{ | ||
| 27 | 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, | ||
| 28 | 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, | ||
| 29 | 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, | ||
| 30 | 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, | ||
| 31 | 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, | ||
| 32 | 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, | ||
| 33 | 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, | ||
| 34 | }; | ||
| 35 | |||
| 36 | QString GetImagePath(Service::Account::UUID uuid) { | ||
| 37 | const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + | ||
| 38 | "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; | ||
| 39 | return QString::fromStdString(path); | ||
| 40 | } | ||
| 41 | |||
| 42 | QString GetAccountUsername(const Service::Account::ProfileManager& manager, | ||
| 43 | Service::Account::UUID uuid) { | ||
| 44 | Service::Account::ProfileBase profile; | ||
| 45 | if (!manager.GetProfileBase(uuid, profile)) { | ||
| 46 | return {}; | ||
| 47 | } | ||
| 48 | |||
| 49 | const auto text = Common::StringFromFixedZeroTerminatedBuffer( | ||
| 50 | reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); | ||
| 51 | return QString::fromStdString(text); | ||
| 52 | } | ||
| 53 | |||
| 54 | QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) { | ||
| 55 | return ConfigureProfileManager::tr("%1\n%2", | ||
| 56 | "%1 is the profile username, %2 is the formatted UUID (e.g. " | ||
| 57 | "00112233-4455-6677-8899-AABBCCDDEEFF))") | ||
| 58 | .arg(username, QString::fromStdString(uuid.FormatSwitch())); | ||
| 59 | } | ||
| 60 | |||
| 61 | QPixmap GetIcon(Service::Account::UUID uuid) { | ||
| 62 | QPixmap icon{GetImagePath(uuid)}; | ||
| 63 | |||
| 64 | if (!icon) { | ||
| 65 | icon.fill(Qt::black); | ||
| 66 | icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size())); | ||
| 67 | } | ||
| 68 | |||
| 69 | return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); | ||
| 70 | } | ||
| 71 | |||
| 72 | QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) { | ||
| 73 | return LimitableInputDialog::GetText(parent, ConfigureProfileManager::tr("Enter Username"), | ||
| 74 | description_text, 1, | ||
| 75 | static_cast<int>(Service::Account::profile_username_size)); | ||
| 76 | } | ||
| 77 | } // Anonymous namespace | ||
| 78 | |||
| 79 | ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent) | ||
| 80 | : QWidget(parent), ui(new Ui::ConfigureProfileManager), | ||
| 81 | profile_manager(std::make_unique<Service::Account::ProfileManager>()) { | ||
| 82 | ui->setupUi(this); | ||
| 83 | |||
| 84 | layout = new QVBoxLayout; | ||
| 85 | tree_view = new QTreeView; | ||
| 86 | item_model = new QStandardItemModel(tree_view); | ||
| 87 | tree_view->setModel(item_model); | ||
| 88 | |||
| 89 | tree_view->setAlternatingRowColors(true); | ||
| 90 | tree_view->setSelectionMode(QHeaderView::SingleSelection); | ||
| 91 | tree_view->setSelectionBehavior(QHeaderView::SelectRows); | ||
| 92 | tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); | ||
| 93 | tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); | ||
| 94 | tree_view->setSortingEnabled(true); | ||
| 95 | tree_view->setEditTriggers(QHeaderView::NoEditTriggers); | ||
| 96 | tree_view->setUniformRowHeights(true); | ||
| 97 | tree_view->setIconSize({64, 64}); | ||
| 98 | tree_view->setContextMenuPolicy(Qt::NoContextMenu); | ||
| 99 | |||
| 100 | item_model->insertColumns(0, 1); | ||
| 101 | item_model->setHeaderData(0, Qt::Horizontal, "Users"); | ||
| 102 | |||
| 103 | // We must register all custom types with the Qt Automoc system so that we are able to use it | ||
| 104 | // with signals/slots. In this case, QList falls under the umbrells of custom types. | ||
| 105 | qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); | ||
| 106 | |||
| 107 | layout->setContentsMargins(0, 0, 0, 0); | ||
| 108 | layout->setSpacing(0); | ||
| 109 | layout->addWidget(tree_view); | ||
| 110 | |||
| 111 | ui->scrollArea->setLayout(layout); | ||
| 112 | |||
| 113 | connect(tree_view, &QTreeView::clicked, this, &ConfigureProfileManager::SelectUser); | ||
| 114 | |||
| 115 | connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureProfileManager::AddUser); | ||
| 116 | connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureProfileManager::RenameUser); | ||
| 117 | connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureProfileManager::DeleteUser); | ||
| 118 | connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureProfileManager::SetUserImage); | ||
| 119 | |||
| 120 | scene = new QGraphicsScene; | ||
| 121 | ui->current_user_icon->setScene(scene); | ||
| 122 | |||
| 123 | this->setConfiguration(); | ||
| 124 | } | ||
| 125 | |||
| 126 | ConfigureProfileManager::~ConfigureProfileManager() = default; | ||
| 127 | |||
| 128 | void ConfigureProfileManager::setConfiguration() { | ||
| 129 | enabled = !Core::System::GetInstance().IsPoweredOn(); | ||
| 130 | item_model->removeRows(0, item_model->rowCount()); | ||
| 131 | list_items.clear(); | ||
| 132 | |||
| 133 | PopulateUserList(); | ||
| 134 | UpdateCurrentUser(); | ||
| 135 | } | ||
| 136 | |||
| 137 | void ConfigureProfileManager::PopulateUserList() { | ||
| 138 | const auto& profiles = profile_manager->GetAllUsers(); | ||
| 139 | for (const auto& user : profiles) { | ||
| 140 | Service::Account::ProfileBase profile; | ||
| 141 | if (!profile_manager->GetProfileBase(user, profile)) | ||
| 142 | continue; | ||
| 143 | |||
| 144 | const auto username = Common::StringFromFixedZeroTerminatedBuffer( | ||
| 145 | reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); | ||
| 146 | |||
| 147 | list_items.push_back(QList<QStandardItem*>{new QStandardItem{ | ||
| 148 | GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}}); | ||
| 149 | } | ||
| 150 | |||
| 151 | for (const auto& item : list_items) | ||
| 152 | item_model->appendRow(item); | ||
| 153 | } | ||
| 154 | |||
| 155 | void ConfigureProfileManager::UpdateCurrentUser() { | ||
| 156 | ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS); | ||
| 157 | |||
| 158 | const auto& current_user = profile_manager->GetUser(Settings::values.current_user); | ||
| 159 | ASSERT(current_user); | ||
| 160 | const auto username = GetAccountUsername(*profile_manager, *current_user); | ||
| 161 | |||
| 162 | scene->clear(); | ||
| 163 | scene->addPixmap( | ||
| 164 | GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); | ||
| 165 | ui->current_user_username->setText(username); | ||
| 166 | } | ||
| 167 | |||
| 168 | void ConfigureProfileManager::applyConfiguration() { | ||
| 169 | if (!enabled) | ||
| 170 | return; | ||
| 171 | |||
| 172 | Settings::Apply(); | ||
| 173 | } | ||
| 174 | |||
| 175 | void ConfigureProfileManager::SelectUser(const QModelIndex& index) { | ||
| 176 | Settings::values.current_user = | ||
| 177 | std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1)); | ||
| 178 | |||
| 179 | UpdateCurrentUser(); | ||
| 180 | |||
| 181 | ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2); | ||
| 182 | ui->pm_rename->setEnabled(true); | ||
| 183 | ui->pm_set_image->setEnabled(true); | ||
| 184 | } | ||
| 185 | |||
| 186 | void ConfigureProfileManager::AddUser() { | ||
| 187 | const auto username = | ||
| 188 | GetProfileUsernameFromUser(this, tr("Enter a username for the new user:")); | ||
| 189 | if (username.isEmpty()) { | ||
| 190 | return; | ||
| 191 | } | ||
| 192 | |||
| 193 | const auto uuid = Service::Account::UUID::Generate(); | ||
| 194 | profile_manager->CreateNewUser(uuid, username.toStdString()); | ||
| 195 | |||
| 196 | item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)}); | ||
| 197 | } | ||
| 198 | |||
| 199 | void ConfigureProfileManager::RenameUser() { | ||
| 200 | const auto user = tree_view->currentIndex().row(); | ||
| 201 | const auto uuid = profile_manager->GetUser(user); | ||
| 202 | ASSERT(uuid); | ||
| 203 | |||
| 204 | Service::Account::ProfileBase profile; | ||
| 205 | if (!profile_manager->GetProfileBase(*uuid, profile)) | ||
| 206 | return; | ||
| 207 | |||
| 208 | const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:")); | ||
| 209 | if (new_username.isEmpty()) { | ||
| 210 | return; | ||
| 211 | } | ||
| 212 | |||
| 213 | const auto username_std = new_username.toStdString(); | ||
| 214 | std::fill(profile.username.begin(), profile.username.end(), '\0'); | ||
| 215 | std::copy(username_std.begin(), username_std.end(), profile.username.begin()); | ||
| 216 | |||
| 217 | profile_manager->SetProfileBase(*uuid, profile); | ||
| 218 | |||
| 219 | item_model->setItem( | ||
| 220 | user, 0, | ||
| 221 | new QStandardItem{GetIcon(*uuid), | ||
| 222 | FormatUserEntryText(QString::fromStdString(username_std), *uuid)}); | ||
| 223 | UpdateCurrentUser(); | ||
| 224 | } | ||
| 225 | |||
| 226 | void ConfigureProfileManager::DeleteUser() { | ||
| 227 | const auto index = tree_view->currentIndex().row(); | ||
| 228 | const auto uuid = profile_manager->GetUser(index); | ||
| 229 | ASSERT(uuid); | ||
| 230 | const auto username = GetAccountUsername(*profile_manager, *uuid); | ||
| 231 | |||
| 232 | const auto confirm = QMessageBox::question( | ||
| 233 | this, tr("Confirm Delete"), | ||
| 234 | tr("You are about to delete user with name \"%1\". Are you sure?").arg(username)); | ||
| 235 | |||
| 236 | if (confirm == QMessageBox::No) | ||
| 237 | return; | ||
| 238 | |||
| 239 | if (Settings::values.current_user == tree_view->currentIndex().row()) | ||
| 240 | Settings::values.current_user = 0; | ||
| 241 | UpdateCurrentUser(); | ||
| 242 | |||
| 243 | if (!profile_manager->RemoveUser(*uuid)) | ||
| 244 | return; | ||
| 245 | |||
| 246 | item_model->removeRows(tree_view->currentIndex().row(), 1); | ||
| 247 | tree_view->clearSelection(); | ||
| 248 | |||
| 249 | ui->pm_remove->setEnabled(false); | ||
| 250 | ui->pm_rename->setEnabled(false); | ||
| 251 | } | ||
| 252 | |||
| 253 | void ConfigureProfileManager::SetUserImage() { | ||
| 254 | const auto index = tree_view->currentIndex().row(); | ||
| 255 | const auto uuid = profile_manager->GetUser(index); | ||
| 256 | ASSERT(uuid); | ||
| 257 | |||
| 258 | const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(), | ||
| 259 | tr("JPEG Images (*.jpg *.jpeg)")); | ||
| 260 | |||
| 261 | if (file.isEmpty()) { | ||
| 262 | return; | ||
| 263 | } | ||
| 264 | |||
| 265 | const auto image_path = GetImagePath(*uuid); | ||
| 266 | if (QFile::exists(image_path) && !QFile::remove(image_path)) { | ||
| 267 | QMessageBox::warning( | ||
| 268 | this, tr("Error deleting image"), | ||
| 269 | tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path)); | ||
| 270 | return; | ||
| 271 | } | ||
| 272 | |||
| 273 | const auto raw_path = QString::fromStdString( | ||
| 274 | FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010"); | ||
| 275 | const QFileInfo raw_info{raw_path}; | ||
| 276 | if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) { | ||
| 277 | QMessageBox::warning(this, tr("Error deleting file"), | ||
| 278 | tr("Unable to delete existing file: %1.").arg(raw_path)); | ||
| 279 | return; | ||
| 280 | } | ||
| 281 | |||
| 282 | const QString absolute_dst_path = QFileInfo{image_path}.absolutePath(); | ||
| 283 | if (!QDir{raw_path}.mkpath(absolute_dst_path)) { | ||
| 284 | QMessageBox::warning( | ||
| 285 | this, tr("Error creating user image directory"), | ||
| 286 | tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path)); | ||
| 287 | return; | ||
| 288 | } | ||
| 289 | |||
| 290 | if (!QFile::copy(file, image_path)) { | ||
| 291 | QMessageBox::warning(this, tr("Error copying user image"), | ||
| 292 | tr("Unable to copy image from %1 to %2").arg(file, image_path)); | ||
| 293 | return; | ||
| 294 | } | ||
| 295 | |||
| 296 | const auto username = GetAccountUsername(*profile_manager, *uuid); | ||
| 297 | item_model->setItem(index, 0, | ||
| 298 | new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)}); | ||
| 299 | UpdateCurrentUser(); | ||
| 300 | } | ||
diff --git a/src/yuzu/configuration/configure_profile_manager.h b/src/yuzu/configuration/configure_profile_manager.h new file mode 100644 index 000000000..7fe95a2a8 --- /dev/null +++ b/src/yuzu/configuration/configure_profile_manager.h | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <memory> | ||
| 8 | |||
| 9 | #include <QList> | ||
| 10 | #include <QWidget> | ||
| 11 | |||
| 12 | class QGraphicsScene; | ||
| 13 | class QStandardItem; | ||
| 14 | class QStandardItemModel; | ||
| 15 | class QTreeView; | ||
| 16 | class QVBoxLayout; | ||
| 17 | |||
| 18 | namespace Service::Account { | ||
| 19 | class ProfileManager; | ||
| 20 | } | ||
| 21 | |||
| 22 | namespace Ui { | ||
| 23 | class ConfigureProfileManager; | ||
| 24 | } | ||
| 25 | |||
| 26 | class ConfigureProfileManager : public QWidget { | ||
| 27 | Q_OBJECT | ||
| 28 | |||
| 29 | public: | ||
| 30 | explicit ConfigureProfileManager(QWidget* parent = nullptr); | ||
| 31 | ~ConfigureProfileManager() override; | ||
| 32 | |||
| 33 | void applyConfiguration(); | ||
| 34 | void setConfiguration(); | ||
| 35 | |||
| 36 | private: | ||
| 37 | void PopulateUserList(); | ||
| 38 | void UpdateCurrentUser(); | ||
| 39 | |||
| 40 | void SelectUser(const QModelIndex& index); | ||
| 41 | void AddUser(); | ||
| 42 | void RenameUser(); | ||
| 43 | void DeleteUser(); | ||
| 44 | void SetUserImage(); | ||
| 45 | |||
| 46 | QVBoxLayout* layout; | ||
| 47 | QTreeView* tree_view; | ||
| 48 | QStandardItemModel* item_model; | ||
| 49 | QGraphicsScene* scene; | ||
| 50 | |||
| 51 | std::vector<QList<QStandardItem*>> list_items; | ||
| 52 | |||
| 53 | std::unique_ptr<Ui::ConfigureProfileManager> ui; | ||
| 54 | bool enabled = false; | ||
| 55 | |||
| 56 | std::unique_ptr<Service::Account::ProfileManager> profile_manager; | ||
| 57 | }; | ||
diff --git a/src/yuzu/configuration/configure_profile_manager.ui b/src/yuzu/configuration/configure_profile_manager.ui new file mode 100644 index 000000000..dedba4998 --- /dev/null +++ b/src/yuzu/configuration/configure_profile_manager.ui | |||
| @@ -0,0 +1,172 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | <ui version="4.0"> | ||
| 3 | <class>ConfigureProfileManager</class> | ||
| 4 | <widget class="QWidget" name="ConfigureProfileManager"> | ||
| 5 | <property name="geometry"> | ||
| 6 | <rect> | ||
| 7 | <x>0</x> | ||
| 8 | <y>0</y> | ||
| 9 | <width>366</width> | ||
| 10 | <height>483</height> | ||
| 11 | </rect> | ||
| 12 | </property> | ||
| 13 | <property name="windowTitle"> | ||
| 14 | <string>Form</string> | ||
| 15 | </property> | ||
| 16 | <layout class="QHBoxLayout" name="horizontalLayout"> | ||
| 17 | <item> | ||
| 18 | <layout class="QVBoxLayout" name="verticalLayout"> | ||
| 19 | <item> | ||
| 20 | <widget class="QGroupBox" name="gridGroupBox"> | ||
| 21 | <property name="title"> | ||
| 22 | <string>Profile Manager</string> | ||
| 23 | </property> | ||
| 24 | <layout class="QGridLayout" name="gridLayout_2"> | ||
| 25 | <property name="sizeConstraint"> | ||
| 26 | <enum>QLayout::SetNoConstraint</enum> | ||
| 27 | </property> | ||
| 28 | <item row="0" column="0"> | ||
| 29 | <layout class="QHBoxLayout" name="horizontalLayout_2"> | ||
| 30 | <item> | ||
| 31 | <widget class="QLabel" name="label"> | ||
| 32 | <property name="sizePolicy"> | ||
| 33 | <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> | ||
| 34 | <horstretch>0</horstretch> | ||
| 35 | <verstretch>0</verstretch> | ||
| 36 | </sizepolicy> | ||
| 37 | </property> | ||
| 38 | <property name="text"> | ||
| 39 | <string>Current User</string> | ||
| 40 | </property> | ||
| 41 | </widget> | ||
| 42 | </item> | ||
| 43 | <item> | ||
| 44 | <widget class="QGraphicsView" name="current_user_icon"> | ||
| 45 | <property name="minimumSize"> | ||
| 46 | <size> | ||
| 47 | <width>48</width> | ||
| 48 | <height>48</height> | ||
| 49 | </size> | ||
| 50 | </property> | ||
| 51 | <property name="maximumSize"> | ||
| 52 | <size> | ||
| 53 | <width>48</width> | ||
| 54 | <height>48</height> | ||
| 55 | </size> | ||
| 56 | </property> | ||
| 57 | <property name="verticalScrollBarPolicy"> | ||
| 58 | <enum>Qt::ScrollBarAlwaysOff</enum> | ||
| 59 | </property> | ||
| 60 | <property name="horizontalScrollBarPolicy"> | ||
| 61 | <enum>Qt::ScrollBarAlwaysOff</enum> | ||
| 62 | </property> | ||
| 63 | <property name="interactive"> | ||
| 64 | <bool>false</bool> | ||
| 65 | </property> | ||
| 66 | </widget> | ||
| 67 | </item> | ||
| 68 | <item> | ||
| 69 | <widget class="QLabel" name="current_user_username"> | ||
| 70 | <property name="sizePolicy"> | ||
| 71 | <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> | ||
| 72 | <horstretch>0</horstretch> | ||
| 73 | <verstretch>0</verstretch> | ||
| 74 | </sizepolicy> | ||
| 75 | </property> | ||
| 76 | <property name="text"> | ||
| 77 | <string>Username</string> | ||
| 78 | </property> | ||
| 79 | </widget> | ||
| 80 | </item> | ||
| 81 | </layout> | ||
| 82 | </item> | ||
| 83 | <item row="1" column="0"> | ||
| 84 | <widget class="QScrollArea" name="scrollArea"> | ||
| 85 | <property name="sizePolicy"> | ||
| 86 | <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> | ||
| 87 | <horstretch>0</horstretch> | ||
| 88 | <verstretch>0</verstretch> | ||
| 89 | </sizepolicy> | ||
| 90 | </property> | ||
| 91 | <property name="frameShape"> | ||
| 92 | <enum>QFrame::StyledPanel</enum> | ||
| 93 | </property> | ||
| 94 | <property name="widgetResizable"> | ||
| 95 | <bool>false</bool> | ||
| 96 | </property> | ||
| 97 | </widget> | ||
| 98 | </item> | ||
| 99 | <item row="2" column="0"> | ||
| 100 | <layout class="QHBoxLayout" name="horizontalLayout_3"> | ||
| 101 | <item> | ||
| 102 | <widget class="QPushButton" name="pm_set_image"> | ||
| 103 | <property name="enabled"> | ||
| 104 | <bool>false</bool> | ||
| 105 | </property> | ||
| 106 | <property name="text"> | ||
| 107 | <string>Set Image</string> | ||
| 108 | </property> | ||
| 109 | </widget> | ||
| 110 | </item> | ||
| 111 | <item> | ||
| 112 | <spacer name="horizontalSpacer"> | ||
| 113 | <property name="orientation"> | ||
| 114 | <enum>Qt::Horizontal</enum> | ||
| 115 | </property> | ||
| 116 | <property name="sizeHint" stdset="0"> | ||
| 117 | <size> | ||
| 118 | <width>40</width> | ||
| 119 | <height>20</height> | ||
| 120 | </size> | ||
| 121 | </property> | ||
| 122 | </spacer> | ||
| 123 | </item> | ||
| 124 | <item> | ||
| 125 | <widget class="QPushButton" name="pm_add"> | ||
| 126 | <property name="text"> | ||
| 127 | <string>Add</string> | ||
| 128 | </property> | ||
| 129 | </widget> | ||
| 130 | </item> | ||
| 131 | <item> | ||
| 132 | <widget class="QPushButton" name="pm_rename"> | ||
| 133 | <property name="enabled"> | ||
| 134 | <bool>false</bool> | ||
| 135 | </property> | ||
| 136 | <property name="text"> | ||
| 137 | <string>Rename</string> | ||
| 138 | </property> | ||
| 139 | </widget> | ||
| 140 | </item> | ||
| 141 | <item> | ||
| 142 | <widget class="QPushButton" name="pm_remove"> | ||
| 143 | <property name="enabled"> | ||
| 144 | <bool>false</bool> | ||
| 145 | </property> | ||
| 146 | <property name="text"> | ||
| 147 | <string>Remove</string> | ||
| 148 | </property> | ||
| 149 | </widget> | ||
| 150 | </item> | ||
| 151 | </layout> | ||
| 152 | </item> | ||
| 153 | </layout> | ||
| 154 | </widget> | ||
| 155 | </item> | ||
| 156 | <item> | ||
| 157 | <widget class="QLabel" name="label_disable_info"> | ||
| 158 | <property name="text"> | ||
| 159 | <string>Profile management is available only when game is not running.</string> | ||
| 160 | </property> | ||
| 161 | <property name="wordWrap"> | ||
| 162 | <bool>true</bool> | ||
| 163 | </property> | ||
| 164 | </widget> | ||
| 165 | </item> | ||
| 166 | </layout> | ||
| 167 | </item> | ||
| 168 | </layout> | ||
| 169 | </widget> | ||
| 170 | <resources/> | ||
| 171 | <connections/> | ||
| 172 | </ui> | ||
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index ab5d46492..445d01ca0 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp | |||
| @@ -15,7 +15,6 @@ | |||
| 15 | #include "common/file_util.h" | 15 | #include "common/file_util.h" |
| 16 | #include "common/string_util.h" | 16 | #include "common/string_util.h" |
| 17 | #include "core/core.h" | 17 | #include "core/core.h" |
| 18 | #include "core/hle/service/acc/profile_manager.h" | ||
| 19 | #include "core/settings.h" | 18 | #include "core/settings.h" |
| 20 | #include "ui_configure_system.h" | 19 | #include "ui_configure_system.h" |
| 21 | #include "yuzu/configuration/configure_system.h" | 20 | #include "yuzu/configuration/configure_system.h" |
| @@ -36,64 +35,9 @@ constexpr std::array<int, 12> days_in_month = {{ | |||
| 36 | 30, | 35 | 30, |
| 37 | 31, | 36 | 31, |
| 38 | }}; | 37 | }}; |
| 39 | |||
| 40 | // Same backup JPEG used by acc IProfile::GetImage if no jpeg found | ||
| 41 | constexpr std::array<u8, 107> backup_jpeg{ | ||
| 42 | 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, | ||
| 43 | 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, | ||
| 44 | 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, | ||
| 45 | 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, | ||
| 46 | 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, | ||
| 47 | 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, | ||
| 48 | 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, | ||
| 49 | }; | ||
| 50 | |||
| 51 | QString GetImagePath(Service::Account::UUID uuid) { | ||
| 52 | const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + | ||
| 53 | "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; | ||
| 54 | return QString::fromStdString(path); | ||
| 55 | } | ||
| 56 | |||
| 57 | QString GetAccountUsername(const Service::Account::ProfileManager& manager, | ||
| 58 | Service::Account::UUID uuid) { | ||
| 59 | Service::Account::ProfileBase profile; | ||
| 60 | if (!manager.GetProfileBase(uuid, profile)) { | ||
| 61 | return {}; | ||
| 62 | } | ||
| 63 | |||
| 64 | const auto text = Common::StringFromFixedZeroTerminatedBuffer( | ||
| 65 | reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); | ||
| 66 | return QString::fromStdString(text); | ||
| 67 | } | ||
| 68 | |||
| 69 | QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) { | ||
| 70 | return ConfigureSystem::tr("%1\n%2", | ||
| 71 | "%1 is the profile username, %2 is the formatted UUID (e.g. " | ||
| 72 | "00112233-4455-6677-8899-AABBCCDDEEFF))") | ||
| 73 | .arg(username, QString::fromStdString(uuid.FormatSwitch())); | ||
| 74 | } | ||
| 75 | |||
| 76 | QPixmap GetIcon(Service::Account::UUID uuid) { | ||
| 77 | QPixmap icon{GetImagePath(uuid)}; | ||
| 78 | |||
| 79 | if (!icon) { | ||
| 80 | icon.fill(Qt::black); | ||
| 81 | icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size())); | ||
| 82 | } | ||
| 83 | |||
| 84 | return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); | ||
| 85 | } | ||
| 86 | |||
| 87 | QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) { | ||
| 88 | return LimitableInputDialog::GetText(parent, ConfigureSystem::tr("Enter Username"), | ||
| 89 | description_text, 1, | ||
| 90 | static_cast<int>(Service::Account::profile_username_size)); | ||
| 91 | } | ||
| 92 | } // Anonymous namespace | 38 | } // Anonymous namespace |
| 93 | 39 | ||
| 94 | ConfigureSystem::ConfigureSystem(QWidget* parent) | 40 | ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) { |
| 95 | : QWidget(parent), ui(new Ui::ConfigureSystem), | ||
| 96 | profile_manager(std::make_unique<Service::Account::ProfileManager>()) { | ||
| 97 | ui->setupUi(this); | 41 | ui->setupUi(this); |
| 98 | connect(ui->combo_birthmonth, | 42 | connect(ui->combo_birthmonth, |
| 99 | static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, | 43 | static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, |
| @@ -101,51 +45,12 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) | |||
| 101 | connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, | 45 | connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, |
| 102 | &ConfigureSystem::RefreshConsoleID); | 46 | &ConfigureSystem::RefreshConsoleID); |
| 103 | 47 | ||
| 104 | layout = new QVBoxLayout; | ||
| 105 | tree_view = new QTreeView; | ||
| 106 | item_model = new QStandardItemModel(tree_view); | ||
| 107 | tree_view->setModel(item_model); | ||
| 108 | |||
| 109 | tree_view->setAlternatingRowColors(true); | ||
| 110 | tree_view->setSelectionMode(QHeaderView::SingleSelection); | ||
| 111 | tree_view->setSelectionBehavior(QHeaderView::SelectRows); | ||
| 112 | tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); | ||
| 113 | tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); | ||
| 114 | tree_view->setSortingEnabled(true); | ||
| 115 | tree_view->setEditTriggers(QHeaderView::NoEditTriggers); | ||
| 116 | tree_view->setUniformRowHeights(true); | ||
| 117 | tree_view->setIconSize({64, 64}); | ||
| 118 | tree_view->setContextMenuPolicy(Qt::NoContextMenu); | ||
| 119 | |||
| 120 | item_model->insertColumns(0, 1); | ||
| 121 | item_model->setHeaderData(0, Qt::Horizontal, "Users"); | ||
| 122 | |||
| 123 | // We must register all custom types with the Qt Automoc system so that we are able to use it | ||
| 124 | // with signals/slots. In this case, QList falls under the umbrells of custom types. | ||
| 125 | qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); | ||
| 126 | |||
| 127 | layout->setContentsMargins(0, 0, 0, 0); | ||
| 128 | layout->setSpacing(0); | ||
| 129 | layout->addWidget(tree_view); | ||
| 130 | |||
| 131 | ui->scrollArea->setLayout(layout); | ||
| 132 | |||
| 133 | connect(tree_view, &QTreeView::clicked, this, &ConfigureSystem::SelectUser); | ||
| 134 | |||
| 135 | connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureSystem::AddUser); | ||
| 136 | connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureSystem::RenameUser); | ||
| 137 | connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureSystem::DeleteUser); | ||
| 138 | connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureSystem::SetUserImage); | ||
| 139 | |||
| 140 | connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { | 48 | connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { |
| 141 | ui->rng_seed_edit->setEnabled(checked); | 49 | ui->rng_seed_edit->setEnabled(checked); |
| 142 | if (!checked) | 50 | if (!checked) |
| 143 | ui->rng_seed_edit->setText(QStringLiteral("00000000")); | 51 | ui->rng_seed_edit->setText(QStringLiteral("00000000")); |
| 144 | }); | 52 | }); |
| 145 | 53 | ||
| 146 | scene = new QGraphicsScene; | ||
| 147 | ui->current_user_icon->setScene(scene); | ||
| 148 | |||
| 149 | this->setConfiguration(); | 54 | this->setConfiguration(); |
| 150 | } | 55 | } |
| 151 | 56 | ||
| @@ -156,12 +61,6 @@ void ConfigureSystem::setConfiguration() { | |||
| 156 | 61 | ||
| 157 | ui->combo_language->setCurrentIndex(Settings::values.language_index); | 62 | ui->combo_language->setCurrentIndex(Settings::values.language_index); |
| 158 | 63 | ||
| 159 | item_model->removeRows(0, item_model->rowCount()); | ||
| 160 | list_items.clear(); | ||
| 161 | |||
| 162 | PopulateUserList(); | ||
| 163 | UpdateCurrentUser(); | ||
| 164 | |||
| 165 | ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value()); | 64 | ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value()); |
| 166 | ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value()); | 65 | ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value()); |
| 167 | 66 | ||
| @@ -170,37 +69,6 @@ void ConfigureSystem::setConfiguration() { | |||
| 170 | ui->rng_seed_edit->setText(rng_seed); | 69 | ui->rng_seed_edit->setText(rng_seed); |
| 171 | } | 70 | } |
| 172 | 71 | ||
| 173 | void ConfigureSystem::PopulateUserList() { | ||
| 174 | const auto& profiles = profile_manager->GetAllUsers(); | ||
| 175 | for (const auto& user : profiles) { | ||
| 176 | Service::Account::ProfileBase profile; | ||
| 177 | if (!profile_manager->GetProfileBase(user, profile)) | ||
| 178 | continue; | ||
| 179 | |||
| 180 | const auto username = Common::StringFromFixedZeroTerminatedBuffer( | ||
| 181 | reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); | ||
| 182 | |||
| 183 | list_items.push_back(QList<QStandardItem*>{new QStandardItem{ | ||
| 184 | GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}}); | ||
| 185 | } | ||
| 186 | |||
| 187 | for (const auto& item : list_items) | ||
| 188 | item_model->appendRow(item); | ||
| 189 | } | ||
| 190 | |||
| 191 | void ConfigureSystem::UpdateCurrentUser() { | ||
| 192 | ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS); | ||
| 193 | |||
| 194 | const auto& current_user = profile_manager->GetUser(Settings::values.current_user); | ||
| 195 | ASSERT(current_user); | ||
| 196 | const auto username = GetAccountUsername(*profile_manager, *current_user); | ||
| 197 | |||
| 198 | scene->clear(); | ||
| 199 | scene->addPixmap( | ||
| 200 | GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); | ||
| 201 | ui->current_user_username->setText(username); | ||
| 202 | } | ||
| 203 | |||
| 204 | void ConfigureSystem::ReadSystemSettings() {} | 72 | void ConfigureSystem::ReadSystemSettings() {} |
| 205 | 73 | ||
| 206 | void ConfigureSystem::applyConfiguration() { | 74 | void ConfigureSystem::applyConfiguration() { |
| @@ -256,130 +124,3 @@ void ConfigureSystem::RefreshConsoleID() { | |||
| 256 | ui->label_console_id->setText( | 124 | ui->label_console_id->setText( |
| 257 | tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); | 125 | tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); |
| 258 | } | 126 | } |
| 259 | |||
| 260 | void ConfigureSystem::SelectUser(const QModelIndex& index) { | ||
| 261 | Settings::values.current_user = | ||
| 262 | std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1)); | ||
| 263 | |||
| 264 | UpdateCurrentUser(); | ||
| 265 | |||
| 266 | ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2); | ||
| 267 | ui->pm_rename->setEnabled(true); | ||
| 268 | ui->pm_set_image->setEnabled(true); | ||
| 269 | } | ||
| 270 | |||
| 271 | void ConfigureSystem::AddUser() { | ||
| 272 | const auto username = | ||
| 273 | GetProfileUsernameFromUser(this, tr("Enter a username for the new user:")); | ||
| 274 | if (username.isEmpty()) { | ||
| 275 | return; | ||
| 276 | } | ||
| 277 | |||
| 278 | const auto uuid = Service::Account::UUID::Generate(); | ||
| 279 | profile_manager->CreateNewUser(uuid, username.toStdString()); | ||
| 280 | |||
| 281 | item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)}); | ||
| 282 | } | ||
| 283 | |||
| 284 | void ConfigureSystem::RenameUser() { | ||
| 285 | const auto user = tree_view->currentIndex().row(); | ||
| 286 | const auto uuid = profile_manager->GetUser(user); | ||
| 287 | ASSERT(uuid); | ||
| 288 | |||
| 289 | Service::Account::ProfileBase profile; | ||
| 290 | if (!profile_manager->GetProfileBase(*uuid, profile)) | ||
| 291 | return; | ||
| 292 | |||
| 293 | const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:")); | ||
| 294 | if (new_username.isEmpty()) { | ||
| 295 | return; | ||
| 296 | } | ||
| 297 | |||
| 298 | const auto username_std = new_username.toStdString(); | ||
| 299 | std::fill(profile.username.begin(), profile.username.end(), '\0'); | ||
| 300 | std::copy(username_std.begin(), username_std.end(), profile.username.begin()); | ||
| 301 | |||
| 302 | profile_manager->SetProfileBase(*uuid, profile); | ||
| 303 | |||
| 304 | item_model->setItem( | ||
| 305 | user, 0, | ||
| 306 | new QStandardItem{GetIcon(*uuid), | ||
| 307 | FormatUserEntryText(QString::fromStdString(username_std), *uuid)}); | ||
| 308 | UpdateCurrentUser(); | ||
| 309 | } | ||
| 310 | |||
| 311 | void ConfigureSystem::DeleteUser() { | ||
| 312 | const auto index = tree_view->currentIndex().row(); | ||
| 313 | const auto uuid = profile_manager->GetUser(index); | ||
| 314 | ASSERT(uuid); | ||
| 315 | const auto username = GetAccountUsername(*profile_manager, *uuid); | ||
| 316 | |||
| 317 | const auto confirm = QMessageBox::question( | ||
| 318 | this, tr("Confirm Delete"), | ||
| 319 | tr("You are about to delete user with name \"%1\". Are you sure?").arg(username)); | ||
| 320 | |||
| 321 | if (confirm == QMessageBox::No) | ||
| 322 | return; | ||
| 323 | |||
| 324 | if (Settings::values.current_user == tree_view->currentIndex().row()) | ||
| 325 | Settings::values.current_user = 0; | ||
| 326 | UpdateCurrentUser(); | ||
| 327 | |||
| 328 | if (!profile_manager->RemoveUser(*uuid)) | ||
| 329 | return; | ||
| 330 | |||
| 331 | item_model->removeRows(tree_view->currentIndex().row(), 1); | ||
| 332 | tree_view->clearSelection(); | ||
| 333 | |||
| 334 | ui->pm_remove->setEnabled(false); | ||
| 335 | ui->pm_rename->setEnabled(false); | ||
| 336 | } | ||
| 337 | |||
| 338 | void ConfigureSystem::SetUserImage() { | ||
| 339 | const auto index = tree_view->currentIndex().row(); | ||
| 340 | const auto uuid = profile_manager->GetUser(index); | ||
| 341 | ASSERT(uuid); | ||
| 342 | |||
| 343 | const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(), | ||
| 344 | tr("JPEG Images (*.jpg *.jpeg)")); | ||
| 345 | |||
| 346 | if (file.isEmpty()) { | ||
| 347 | return; | ||
| 348 | } | ||
| 349 | |||
| 350 | const auto image_path = GetImagePath(*uuid); | ||
| 351 | if (QFile::exists(image_path) && !QFile::remove(image_path)) { | ||
| 352 | QMessageBox::warning( | ||
| 353 | this, tr("Error deleting image"), | ||
| 354 | tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path)); | ||
| 355 | return; | ||
| 356 | } | ||
| 357 | |||
| 358 | const auto raw_path = QString::fromStdString( | ||
| 359 | FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010"); | ||
| 360 | const QFileInfo raw_info{raw_path}; | ||
| 361 | if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) { | ||
| 362 | QMessageBox::warning(this, tr("Error deleting file"), | ||
| 363 | tr("Unable to delete existing file: %1.").arg(raw_path)); | ||
| 364 | return; | ||
| 365 | } | ||
| 366 | |||
| 367 | const QString absolute_dst_path = QFileInfo{image_path}.absolutePath(); | ||
| 368 | if (!QDir{raw_path}.mkpath(absolute_dst_path)) { | ||
| 369 | QMessageBox::warning( | ||
| 370 | this, tr("Error creating user image directory"), | ||
| 371 | tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path)); | ||
| 372 | return; | ||
| 373 | } | ||
| 374 | |||
| 375 | if (!QFile::copy(file, image_path)) { | ||
| 376 | QMessageBox::warning(this, tr("Error copying user image"), | ||
| 377 | tr("Unable to copy image from %1 to %2").arg(file, image_path)); | ||
| 378 | return; | ||
| 379 | } | ||
| 380 | |||
| 381 | const auto username = GetAccountUsername(*profile_manager, *uuid); | ||
| 382 | item_model->setItem(index, 0, | ||
| 383 | new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)}); | ||
| 384 | UpdateCurrentUser(); | ||
| 385 | } | ||
diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index 07764e1f7..cf1e54de5 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h | |||
| @@ -9,16 +9,6 @@ | |||
| 9 | #include <QList> | 9 | #include <QList> |
| 10 | #include <QWidget> | 10 | #include <QWidget> |
| 11 | 11 | ||
| 12 | class QGraphicsScene; | ||
| 13 | class QStandardItem; | ||
| 14 | class QStandardItemModel; | ||
| 15 | class QTreeView; | ||
| 16 | class QVBoxLayout; | ||
| 17 | |||
| 18 | namespace Service::Account { | ||
| 19 | class ProfileManager; | ||
| 20 | } | ||
| 21 | |||
| 22 | namespace Ui { | 12 | namespace Ui { |
| 23 | class ConfigureSystem; | 13 | class ConfigureSystem; |
| 24 | } | 14 | } |
| @@ -39,21 +29,6 @@ private: | |||
| 39 | void UpdateBirthdayComboBox(int birthmonth_index); | 29 | void UpdateBirthdayComboBox(int birthmonth_index); |
| 40 | void RefreshConsoleID(); | 30 | void RefreshConsoleID(); |
| 41 | 31 | ||
| 42 | void PopulateUserList(); | ||
| 43 | void UpdateCurrentUser(); | ||
| 44 | void SelectUser(const QModelIndex& index); | ||
| 45 | void AddUser(); | ||
| 46 | void RenameUser(); | ||
| 47 | void DeleteUser(); | ||
| 48 | void SetUserImage(); | ||
| 49 | |||
| 50 | QVBoxLayout* layout; | ||
| 51 | QTreeView* tree_view; | ||
| 52 | QStandardItemModel* item_model; | ||
| 53 | QGraphicsScene* scene; | ||
| 54 | |||
| 55 | std::vector<QList<QStandardItem*>> list_items; | ||
| 56 | |||
| 57 | std::unique_ptr<Ui::ConfigureSystem> ui; | 32 | std::unique_ptr<Ui::ConfigureSystem> ui; |
| 58 | bool enabled = false; | 33 | bool enabled = false; |
| 59 | 34 | ||
| @@ -61,6 +36,4 @@ private: | |||
| 61 | int birthday = 0; | 36 | int birthday = 0; |
| 62 | int language_index = 0; | 37 | int language_index = 0; |
| 63 | int sound_index = 0; | 38 | int sound_index = 0; |
| 64 | |||
| 65 | std::unique_ptr<Service::Account::ProfileManager> profile_manager; | ||
| 66 | }; | 39 | }; |
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index a91580893..74e800c2a 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui | |||
| @@ -280,141 +280,17 @@ | |||
| 280 | </widget> | 280 | </widget> |
| 281 | </item> | 281 | </item> |
| 282 | <item> | 282 | <item> |
| 283 | <widget class="QGroupBox" name="gridGroupBox"> | 283 | <spacer name="verticalSpacer"> |
| 284 | <property name="title"> | 284 | <property name="orientation"> |
| 285 | <string>Profile Manager</string> | 285 | <enum>Qt::Vertical</enum> |
| 286 | </property> | 286 | </property> |
| 287 | <layout class="QGridLayout" name="gridLayout_2"> | 287 | <property name="sizeHint" stdset="0"> |
| 288 | <property name="sizeConstraint"> | 288 | <size> |
| 289 | <enum>QLayout::SetNoConstraint</enum> | 289 | <width>20</width> |
| 290 | </property> | 290 | <height>40</height> |
| 291 | <item row="0" column="0"> | 291 | </size> |
| 292 | <layout class="QHBoxLayout" name="horizontalLayout_2"> | 292 | </property> |
| 293 | <item> | 293 | </spacer> |
| 294 | <widget class="QLabel" name="label"> | ||
| 295 | <property name="sizePolicy"> | ||
| 296 | <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> | ||
| 297 | <horstretch>0</horstretch> | ||
| 298 | <verstretch>0</verstretch> | ||
| 299 | </sizepolicy> | ||
| 300 | </property> | ||
| 301 | <property name="text"> | ||
| 302 | <string>Current User</string> | ||
| 303 | </property> | ||
| 304 | </widget> | ||
| 305 | </item> | ||
| 306 | <item> | ||
| 307 | <widget class="QGraphicsView" name="current_user_icon"> | ||
| 308 | <property name="minimumSize"> | ||
| 309 | <size> | ||
| 310 | <width>48</width> | ||
| 311 | <height>48</height> | ||
| 312 | </size> | ||
| 313 | </property> | ||
| 314 | <property name="maximumSize"> | ||
| 315 | <size> | ||
| 316 | <width>48</width> | ||
| 317 | <height>48</height> | ||
| 318 | </size> | ||
| 319 | </property> | ||
| 320 | <property name="verticalScrollBarPolicy"> | ||
| 321 | <enum>Qt::ScrollBarAlwaysOff</enum> | ||
| 322 | </property> | ||
| 323 | <property name="horizontalScrollBarPolicy"> | ||
| 324 | <enum>Qt::ScrollBarAlwaysOff</enum> | ||
| 325 | </property> | ||
| 326 | <property name="interactive"> | ||
| 327 | <bool>false</bool> | ||
| 328 | </property> | ||
| 329 | </widget> | ||
| 330 | </item> | ||
| 331 | <item> | ||
| 332 | <widget class="QLabel" name="current_user_username"> | ||
| 333 | <property name="sizePolicy"> | ||
| 334 | <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> | ||
| 335 | <horstretch>0</horstretch> | ||
| 336 | <verstretch>0</verstretch> | ||
| 337 | </sizepolicy> | ||
| 338 | </property> | ||
| 339 | <property name="text"> | ||
| 340 | <string>Username</string> | ||
| 341 | </property> | ||
| 342 | </widget> | ||
| 343 | </item> | ||
| 344 | </layout> | ||
| 345 | </item> | ||
| 346 | <item row="1" column="0"> | ||
| 347 | <widget class="QScrollArea" name="scrollArea"> | ||
| 348 | <property name="sizePolicy"> | ||
| 349 | <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> | ||
| 350 | <horstretch>0</horstretch> | ||
| 351 | <verstretch>0</verstretch> | ||
| 352 | </sizepolicy> | ||
| 353 | </property> | ||
| 354 | <property name="frameShape"> | ||
| 355 | <enum>QFrame::StyledPanel</enum> | ||
| 356 | </property> | ||
| 357 | <property name="widgetResizable"> | ||
| 358 | <bool>false</bool> | ||
| 359 | </property> | ||
| 360 | </widget> | ||
| 361 | </item> | ||
| 362 | <item row="2" column="0"> | ||
| 363 | <layout class="QHBoxLayout" name="horizontalLayout_3"> | ||
| 364 | <item> | ||
| 365 | <widget class="QPushButton" name="pm_set_image"> | ||
| 366 | <property name="enabled"> | ||
| 367 | <bool>false</bool> | ||
| 368 | </property> | ||
| 369 | <property name="text"> | ||
| 370 | <string>Set Image</string> | ||
| 371 | </property> | ||
| 372 | </widget> | ||
| 373 | </item> | ||
| 374 | <item> | ||
| 375 | <spacer name="horizontalSpacer"> | ||
| 376 | <property name="orientation"> | ||
| 377 | <enum>Qt::Horizontal</enum> | ||
| 378 | </property> | ||
| 379 | <property name="sizeHint" stdset="0"> | ||
| 380 | <size> | ||
| 381 | <width>40</width> | ||
| 382 | <height>20</height> | ||
| 383 | </size> | ||
| 384 | </property> | ||
| 385 | </spacer> | ||
| 386 | </item> | ||
| 387 | <item> | ||
| 388 | <widget class="QPushButton" name="pm_add"> | ||
| 389 | <property name="text"> | ||
| 390 | <string>Add</string> | ||
| 391 | </property> | ||
| 392 | </widget> | ||
| 393 | </item> | ||
| 394 | <item> | ||
| 395 | <widget class="QPushButton" name="pm_rename"> | ||
| 396 | <property name="enabled"> | ||
| 397 | <bool>false</bool> | ||
| 398 | </property> | ||
| 399 | <property name="text"> | ||
| 400 | <string>Rename</string> | ||
| 401 | </property> | ||
| 402 | </widget> | ||
| 403 | </item> | ||
| 404 | <item> | ||
| 405 | <widget class="QPushButton" name="pm_remove"> | ||
| 406 | <property name="enabled"> | ||
| 407 | <bool>false</bool> | ||
| 408 | </property> | ||
| 409 | <property name="text"> | ||
| 410 | <string>Remove</string> | ||
| 411 | </property> | ||
| 412 | </widget> | ||
| 413 | </item> | ||
| 414 | </layout> | ||
| 415 | </item> | ||
| 416 | </layout> | ||
| 417 | </widget> | ||
| 418 | </item> | 294 | </item> |
| 419 | <item> | 295 | <item> |
| 420 | <widget class="QLabel" name="label_disable_info"> | 296 | <widget class="QLabel" name="label_disable_info"> |