From 81435ee082d4cca1a017fa89b38020281b0d3481 Mon Sep 17 00:00:00 2001 From: Tayou Date: Mon, 27 Feb 2023 23:33:59 +0100 Subject: [PATCH] Added Edit Delegate Signed-off-by: Tayou --- launcher/CMakeLists.txt | 2 + .../gameoptions/GameOptionDelegate.cpp | 199 ++++++++++++++++++ .../gameoptions/GameOptionDelegate.h | 44 ++++ .../minecraft/gameoptions/GameOptions.cpp | 7 +- launcher/minecraft/gameoptions/GameOptions.h | 2 + .../ui/pages/instance/GameOptionsPage.cpp | 4 +- 6 files changed, 255 insertions(+), 3 deletions(-) create mode 100644 launcher/minecraft/gameoptions/GameOptionDelegate.cpp create mode 100644 launcher/minecraft/gameoptions/GameOptionDelegate.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 3a614751d..1998addf0 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -248,6 +248,8 @@ set(MINECRAFT_SOURCES minecraft/gameoptions/GameOptions.cpp minecraft/gameoptions/GameOptionsSchema.h minecraft/gameoptions/GameOptionsSchema.cpp + minecraft/gameoptions/GameOptionDelegate.h + minecraft/gameoptions/GameOptionDelegate.cpp minecraft/update/AssetUpdateTask.h minecraft/update/AssetUpdateTask.cpp diff --git a/launcher/minecraft/gameoptions/GameOptionDelegate.cpp b/launcher/minecraft/gameoptions/GameOptionDelegate.cpp new file mode 100644 index 000000000..033aed321 --- /dev/null +++ b/launcher/minecraft/gameoptions/GameOptionDelegate.cpp @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 Tayou + * + * 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 . + */ +#include "GameOptionDelegate.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +QWidget* GameOptionDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { + std::shared_ptr knownOption = contents->at(index.row()).knownOption; + + switch (contents->at(index.row()).type) { + case OptionType::String: { + if (knownOption != nullptr && !knownOption->validValues.isEmpty()) { + QComboBox* comboBox = new QComboBox(parent); + for (auto value : knownOption->validValues) { + comboBox->addItem(value); + } + comboBox->setGeometry(option.rect); + return comboBox; + } else { + QLineEdit* textField = new QLineEdit(parent); + textField->setGeometry(option.rect); + return textField; + } + } + case OptionType::Int: { + if (knownOption != nullptr && (knownOption->getIntRange().max != 0 || knownOption->getIntRange().min != 0)) { + QSlider* slider = new QSlider(parent); + slider->setMinimum(knownOption->getIntRange().min); + slider->setMaximum(knownOption->getIntRange().max); + slider->setOrientation(Qt::Horizontal); + slider->setGeometry(option.rect); + return slider; + } else { + QSpinBox* intInput = new QSpinBox(parent); + intInput->setGeometry(option.rect); + return intInput; + } + } + case OptionType::Bool: { + QCheckBox* checkBox = new QCheckBox(parent); + checkBox->setGeometry(option.rect); + return checkBox; + } + case OptionType::Float: { + if (knownOption != nullptr && (knownOption->getFloatRange().max != 0 || knownOption->getFloatRange().min != 0)) { + QSlider* slider = new QSlider(parent); + slider->setMinimum(knownOption->getFloatRange().min*100); + slider->setMaximum(knownOption->getFloatRange().max*100); + slider->setOrientation(Qt::Horizontal); + slider->setGeometry(option.rect); + return slider; + } else { + QDoubleSpinBox* floatInput = new QDoubleSpinBox(parent); + floatInput->setGeometry(option.rect); + return floatInput; + } + } + case OptionType::KeyBind: { + QKeySequenceEdit* keySequenceEdit = new QKeySequenceEdit(parent); + keySequenceEdit->setGeometry(option.rect); + return keySequenceEdit; + } + default: + break; + } +}; +void GameOptionDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + std::shared_ptr knownOption = contents->at(index.row()).knownOption; + + switch (contents->at(index.row()).type) { + case OptionType::String: { + QComboBox* comboBox; + if ((comboBox = dynamic_cast(editor)) != nullptr) { + comboBox->setCurrentIndex(comboBox->findText(contents->at(index.row()).value)); + } else { + ((QLineEdit*)editor)->setText(contents->at(index.row()).value); + } + return; + } + case OptionType::Int: { + QSlider* slider; + if ((slider = dynamic_cast(editor)) != nullptr) { + slider->setValue(contents->at(index.row()).intValue); + } else { + ((QSpinBox*)editor)->setValue(contents->at(index.row()).intValue); + } + return; + } + case OptionType::Bool: { + ((QCheckBox*)editor)->setText(contents->at(index.row()).value); + ((QCheckBox*)editor)->setChecked(contents->at(index.row()).boolValue); + return; + } + case OptionType::Float: { + QSlider* slider; + if ((slider = dynamic_cast(editor)) != nullptr) { + slider->setValue(contents->at(index.row()).floatValue*100); + } else { + ((QDoubleSpinBox*)editor)->setValue(contents->at(index.row()).floatValue); + } + return; + } + case OptionType::KeyBind: { + // TODO: fall back to QLineEdit if keybind can't be represented, like some mods do (by using another key input API) + // TODO: mouse binding? If not possible, fall back as well maybe? + Qt::Key key; + for (auto& keyBinding : *keybindingOptions) { + // this could become a std::find_if eventually, if someone wants to bother making it that. + if (keyBinding->minecraftKeyCode == contents->at(index.row()).value) { + key = keyBinding->qtKeyCode.keyboardKey; + } + } + ((QKeySequenceEdit*)editor)->setKeySequence(QKeySequence(key)); + return; + } + default: + return; + } +}; +void GameOptionDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const { + editor->setGeometry(option.rect); +}; +void GameOptionDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { + std::shared_ptr knownOption = contents->at(index.row()).knownOption; + + switch (contents->at(index.row()).type) { + case OptionType::String: { + QComboBox* comboBox; + if ((comboBox = dynamic_cast(editor)) != nullptr) { + contents->at(index.row()).value = comboBox->currentText(); + } else { + contents->at(index.row()).value = ((QLineEdit*)editor)->text(); + } + return; + } + case OptionType::Int: { + QSlider* slider; + if ((slider = dynamic_cast(editor)) != nullptr) { + contents->at(index.row()).intValue = slider->value(); + } else { + contents->at(index.row()).intValue = ((QSpinBox*)editor)->value(); + } + return; + } + case OptionType::Bool: { + ((QCheckBox*)editor)->setText(contents->at(index.row()).value); + contents->at(index.row()).boolValue = ((QCheckBox*)editor)->isChecked(); + return; + } + case OptionType::Float: { + QSlider* slider; + if ((slider = dynamic_cast(editor)) != nullptr) { + contents->at(index.row()).floatValue = slider->value()/100.0f; + } else { + contents->at(index.row()).floatValue = ((QDoubleSpinBox*)editor)->value(); + } + return; + } + case OptionType::KeyBind: { + QKeySequenceEdit* keySequenceEdit = (QKeySequenceEdit*)editor; + + QString minecraftKeyCode; + for (auto& keyBinding : *keybindingOptions) { + // this could become a std::find_if eventually, if someone wants to bother making it that. + if (keyBinding->qtKeyCode.keyboardKey == keySequenceEdit->keySequence()[0].key() || + keyBinding->qtKeyCode.mouseButton == keySequenceEdit->keySequence()[0].key()) { + minecraftKeyCode = keyBinding->minecraftKeyCode; + } + } + contents->at(index.row()).value = minecraftKeyCode; + return; + } + default: + return; + } +}; diff --git a/launcher/minecraft/gameoptions/GameOptionDelegate.h b/launcher/minecraft/gameoptions/GameOptionDelegate.h new file mode 100644 index 000000000..61e59fe34 --- /dev/null +++ b/launcher/minecraft/gameoptions/GameOptionDelegate.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 Tayou + * + * 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 . + */ +#pragma once + +#include +#include +#include + +#include "GameOptions.h" + +class GameOptionDelegate : public QStyledItemDelegate { + public: + GameOptionDelegate(QObject* parent, std::vector* contents) : contents(contents), QStyledItemDelegate(parent) + { + keybindingOptions = GameOptionsSchema::getKeyBindOptions(); + }; + QWidget* createEditor(QWidget* parent, + const QStyleOptionViewItem& option, + const QModelIndex& index) const override; + void setEditorData(QWidget* editor, const QModelIndex& index) const override; + void updateEditorGeometry(QWidget* editor, + const QStyleOptionViewItem& option, + const QModelIndex& index) const override; + void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; + + private: + std::vector* contents; + QList>* keybindingOptions; +}; \ No newline at end of file diff --git a/launcher/minecraft/gameoptions/GameOptions.cpp b/launcher/minecraft/gameoptions/GameOptions.cpp index 6457f78f5..d11fbbd27 100644 --- a/launcher/minecraft/gameoptions/GameOptions.cpp +++ b/launcher/minecraft/gameoptions/GameOptions.cpp @@ -110,6 +110,9 @@ bool load(const QString& path, } } else if (item.key.startsWith("key_")) { item.type = OptionType::KeyBind; + } else { + // this is really ugly, please suggest how to truncate the start and end + item.value = item.value.remove(item.value.length()-1, 1).remove(0, 1); } // adds reference to known option from gameOptionsSchema if avaiable to get display name and other metadata @@ -210,9 +213,9 @@ Qt::ItemFlags GameOptions::flags(const QModelIndex& index) const } flags = flags | Qt::ItemFlag::ItemIsEditable; - if (column == Column::Value || column == Column::DefaultValue) { + /*if (column == Column::Value || column == Column::DefaultValue) { flags = flags | Qt::ItemFlag::ItemIsUserCheckable; - } + }*/ if (column == Column::DefaultValue) { flags = flags & ~Qt::ItemFlag::ItemIsEnabled; } diff --git a/launcher/minecraft/gameoptions/GameOptions.h b/launcher/minecraft/gameoptions/GameOptions.h index 5601b0016..a4c8e56dc 100644 --- a/launcher/minecraft/gameoptions/GameOptions.h +++ b/launcher/minecraft/gameoptions/GameOptions.h @@ -78,6 +78,8 @@ class GameOptions : public QAbstractItemModel { bool reload(); bool save(); + std::vector* getContents(){ return &contents; }; + private: std::vector contents; bool loaded = false; diff --git a/launcher/ui/pages/instance/GameOptionsPage.cpp b/launcher/ui/pages/instance/GameOptionsPage.cpp index 71cf3d8f0..d46e1b252 100644 --- a/launcher/ui/pages/instance/GameOptionsPage.cpp +++ b/launcher/ui/pages/instance/GameOptionsPage.cpp @@ -38,7 +38,7 @@ #include "GameOptionsPage.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/gameoptions/GameOptions.h" -#include "ui_GameOptionsPage.h" +#include "minecraft/gameoptions/GameOptionDelegate.h" GameOptionsPage::GameOptionsPage(MinecraftInstance* inst, QWidget* parent) : QWidget(parent), ui(new Ui::GameOptionsPage) { @@ -46,6 +46,8 @@ GameOptionsPage::GameOptionsPage(MinecraftInstance* inst, QWidget* parent) : QWi ui->tabWidget->tabBar()->hide(); m_model = inst->gameOptionsModel(); ui->optionsView->setModel(m_model.get()); + ui->optionsView->setItemDelegateForColumn(2, new GameOptionDelegate(ui->optionsView, m_model->getContents())); + ui->optionsView->setEditTriggers(QAbstractItemView::AllEditTriggers); auto head = ui->optionsView->header(); head->setDefaultSectionSize(250); if (head->count()) {