 511076d3ba
			
		
	
	511076d3ba
	
	
	
		
			
			- added array support - cleaned up logic - ran clang-format - added description & default value columns - added basic editing support (bools only) - no saving Co-authored-by: TheLastRar <TheLastRar@users.noreply.github.com> Signed-off-by: Tayou <tayou@gmx.net>
		
			
				
	
	
		
			401 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			401 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-3.0-only
 | |
| /*
 | |
|  *  Prism Launcher - Minecraft Launcher
 | |
|  *  Copyright (C) 2023 Tayou <tayou@gmx.net>
 | |
|  *  Copyright (C) 2023 TheLastRar <TheLastRar@users.noreply.github.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/>.
 | |
|  *
 | |
|  * This file incorporates work covered by the following copyright and
 | |
|  * permission notice:
 | |
|  *
 | |
|  *      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 <QDebug>
 | |
| #include <QSaveFile>
 | |
| #include "FileSystem.h"
 | |
| 
 | |
| #include "FileSystem.h"
 | |
| #include "GameOptions.h"
 | |
| 
 | |
| static Qt::CheckState boolToState(bool b)
 | |
| {
 | |
|     return b ? Qt::Checked : Qt::Unchecked;
 | |
| };
 | |
| 
 | |
| namespace {
 | |
| bool load(const QString& path, std::vector<GameOptionItem>& contents, int& version)
 | |
| {
 | |
|     contents.clear();
 | |
|     QFile file(path);
 | |
|     if (!file.open(QFile::ReadOnly)) {
 | |
|         qWarning() << "Failed to read options file.";
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     version = 0;
 | |
|     while (!file.atEnd()) {
 | |
|         // This should be handled by toml++ or some other toml parser rather than this manual parsing
 | |
|         QString line = QString::fromUtf8(file.readLine());
 | |
|         if (line.endsWith('\n')) {
 | |
|             line.chop(1);
 | |
|         }
 | |
|         if (line.endsWith('\r')) {
 | |
|             line.chop(1);
 | |
|         }
 | |
|         GameOptionItem item = GameOptionItem();
 | |
| 
 | |
|         auto parts = line.split(':');
 | |
| 
 | |
|         item.key = parts[0];
 | |
|         item.value = parts[1];
 | |
|         item.type = OptionType::String;
 | |
|         qDebug() << "Reading Game Options Key:" << item.key;
 | |
| 
 | |
|         if (item.key == "version") {
 | |
|             version = item.value.toInt();
 | |
|             continue;
 | |
|         };
 | |
| 
 | |
|         bool isInt = false;
 | |
|         bool isFloat = false;
 | |
|         item.intValue = item.value.toInt(&isInt);
 | |
|         item.floatValue = item.value.toFloat(&isFloat);
 | |
|         if (isInt) {
 | |
|             item.type = OptionType::Int;
 | |
|             // qDebug() << "The value" << value << "is a int";
 | |
|         } else if (isFloat) {
 | |
|             item.type = OptionType::Float;
 | |
|             // qDebug() << "The value" << value << "is a float";
 | |
|         } else if (item.value == "true" || item.value == "false") {
 | |
|             item.boolValue = item.value == "true" ? true : false;
 | |
|             item.type = OptionType::Bool;
 | |
|             qDebug() << "The value" << item.value << "is a bool";
 | |
|         } else if (item.value.endsWith("]") && item.value.startsWith("[")) {
 | |
|             qDebug() << "The value" << item.value << "is an array";
 | |
|             for (QString part : item.value.mid(1, item.value.size() - 2).split(",")) {
 | |
|                 GameOptionChildItem child{ part, static_cast<int>(contents.size()) };
 | |
|                 qDebug() << "Array has entry" << part;
 | |
|                 item.children.append(child);
 | |
|             }
 | |
|         }
 | |
|         contents.emplace_back(item);
 | |
|     }
 | |
|     qDebug() << "Loaded" << path << "with version:" << version;
 | |
|     return true;
 | |
| }
 | |
| bool save(const QString& path, std::vector<GameOptionItem>& mapping, int version)
 | |
| {
 | |
|     QSaveFile out(path);
 | |
|     if (!out.open(QIODevice::WriteOnly)) {
 | |
|         return false;
 | |
|     }
 | |
|     if (version != 0) {
 | |
|         QString versionLine = QString("version:%1\n").arg(version);
 | |
|         out.write(versionLine.toUtf8());
 | |
|     }
 | |
|     auto iter = mapping.begin();
 | |
|     while (iter != mapping.end()) {
 | |
|         out.write(iter->key.toUtf8());
 | |
|         out.write(":");
 | |
|         out.write(iter->value.toUtf8());
 | |
|         out.write("\n");
 | |
|         iter++;
 | |
|     }
 | |
|     return out.commit();
 | |
| }
 | |
| }  // namespace
 | |
| 
 | |
| GameOptions::GameOptions(const QString& path) : path(path)
 | |
| {
 | |
|     reload();
 | |
| }
 | |
| 
 | |
| QVariant GameOptions::headerData(int section, Qt::Orientation orientation, int role) const
 | |
| {
 | |
|     if (role != Qt::DisplayRole) {
 | |
|         return QAbstractItemModel::headerData(section, orientation, role);
 | |
|     }
 | |
|     switch (section) {
 | |
|         case 0:
 | |
|             return tr("Key");
 | |
|         case 1:
 | |
|             return tr("Description");
 | |
|         case 2:
 | |
|             return tr("Value");
 | |
|         case 3:
 | |
|             return tr("Default Value");
 | |
|         default:
 | |
|             return QVariant();
 | |
|     }
 | |
| }
 | |
| bool GameOptions::setData(const QModelIndex& index, const QVariant& value, int role)
 | |
| {
 | |
|     auto row = index.row();
 | |
|     auto column = (Column)index.column();
 | |
|     if (column == Column::Value) {
 | |
|         switch (contents[row].type) {
 | |
|             case OptionType::String: {
 | |
|                 contents[row].value = value.toString();
 | |
|             }
 | |
|             case OptionType::Int: {
 | |
|                 contents[row].intValue = value.toInt();
 | |
|             }
 | |
|             case OptionType::Bool: {
 | |
|                 contents[row].boolValue = value.toBool();
 | |
|             }
 | |
|             case OptionType::Float: {
 | |
|                 contents[row].floatValue = value.toFloat();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| Qt::ItemFlags GameOptions::flags(const QModelIndex& index) const
 | |
| {
 | |
|     Qt::ItemFlags flags = QAbstractItemModel::flags(index);
 | |
| 
 | |
|     if (!index.isValid())
 | |
|         return flags;
 | |
| 
 | |
|     Column column = (Column)index.column();
 | |
| 
 | |
|     if (column == Column::Key) {
 | |
|         return flags;
 | |
|     }
 | |
| 
 | |
|     if (index.parent().isValid()) {
 | |
|         return flags;
 | |
|     }
 | |
| 
 | |
|     if (contents[index.row()].children.count() > 0) {
 | |
|         return flags;
 | |
|     }
 | |
| 
 | |
|     flags = flags | Qt::ItemFlag::ItemIsEditable;
 | |
|     if (column == Column::Value || column == Column::DefaultValue) {
 | |
|         flags = flags | Qt::ItemFlag::ItemIsUserCheckable;
 | |
|     }
 | |
|     if (column == Column::DefaultValue) {
 | |
|         flags = flags & ~Qt::ItemFlag::ItemIsEnabled;
 | |
|     }
 | |
| 
 | |
|     return flags;
 | |
| }
 | |
| 
 | |
| QVariant GameOptions::data(const QModelIndex& index, int role) const
 | |
| {
 | |
|     if (!index.isValid())
 | |
|         return QVariant();
 | |
| 
 | |
|     int row = index.row();
 | |
|     Column column = (Column)index.column();
 | |
| 
 | |
|     if (row < 0 || row >= int(contents.size()))
 | |
|         return QVariant();
 | |
| 
 | |
|     if (index.parent().isValid()) {
 | |
|         switch (role) {
 | |
|             case Qt::DisplayRole: {
 | |
|                 if (column == Column::Value) {
 | |
|                     GameOptionChildItem* item = static_cast<GameOptionChildItem*>(index.internalPointer());
 | |
|                     return item->value;
 | |
|                 } else {
 | |
|                     return QVariant();
 | |
|                 }
 | |
|             }
 | |
|             default: {
 | |
|                 return QVariant();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     switch (role) {
 | |
|         case Qt::DisplayRole: {
 | |
|             switch (column) {
 | |
|                 case Column::Key: {
 | |
|                     return contents[row].key;
 | |
|                 }
 | |
|                 case Column::Description: {
 | |
|                     return "Description goes here!";
 | |
|                 }
 | |
|                 case Column::Value: {
 | |
|                     switch (contents[row].type) {
 | |
|                         case OptionType::String: {
 | |
|                             return contents[row].value;
 | |
|                         }
 | |
|                         case OptionType::Int: {
 | |
|                             return contents[row].intValue;
 | |
|                         }
 | |
|                         case OptionType::Bool: {
 | |
|                             return contents[row].boolValue;
 | |
|                         }
 | |
|                         case OptionType::Float: {
 | |
|                             return contents[row].floatValue;
 | |
|                         }
 | |
|                         case OptionType::KeyBind: {
 | |
|                             return contents[row].value;
 | |
|                         }
 | |
|                         default: {
 | |
|                             return QVariant();
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 case Column::DefaultValue: {
 | |
|                     switch (contents[row].type) {
 | |
|                         case OptionType::String: {
 | |
|                             return contents[row].value;
 | |
|                         }
 | |
|                         case OptionType::Int: {
 | |
|                             return contents[row].intValue;
 | |
|                         }
 | |
|                         case OptionType::Bool: {
 | |
|                             return contents[row].boolValue;
 | |
|                         }
 | |
|                         case OptionType::Float: {
 | |
|                             return contents[row].floatValue;
 | |
|                         }
 | |
|                         case OptionType::KeyBind: {
 | |
|                             return contents[row].value;
 | |
|                         }
 | |
|                         default: {
 | |
|                             return QVariant();
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 default: {
 | |
|                     return QVariant();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         case Qt::CheckStateRole: {
 | |
|             switch (column) {
 | |
|                 case Column::Value: {
 | |
|                     if (contents[row].type == OptionType::Bool) {
 | |
|                         return boolToState(contents[row].boolValue);
 | |
|                     } else {
 | |
|                         return QVariant();
 | |
|                     }
 | |
|                 }
 | |
|                 case Column::DefaultValue: {
 | |
|                         return boolToState(contents[row].boolValue);
 | |
|                     } else {
 | |
|                         return QVariant();
 | |
|                     }
 | |
|                 }
 | |
|                 default: {
 | |
|                     return QVariant();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         default: {
 | |
|             return QVariant();
 | |
|         }
 | |
|     }
 | |
|     return QVariant();
 | |
| }
 | |
| 
 | |
| QModelIndex GameOptions::index(int row, int column, const QModelIndex& parent) const
 | |
| {
 | |
|     if (!hasIndex(row, column, parent))
 | |
|         return QModelIndex();
 | |
| 
 | |
|     if (parent.isValid()) {
 | |
|         if (parent.parent().isValid())
 | |
|             return QModelIndex();
 | |
| 
 | |
|         GameOptionItem* item = static_cast<GameOptionItem*>(parent.internalPointer());
 | |
|         return createIndex(row, column, &item->children[row]);
 | |
|     } else {
 | |
|         return createIndex(row, column, &contents[row]);
 | |
|     }
 | |
| }
 | |
| 
 | |
| QModelIndex GameOptions::parent(const QModelIndex& index) const
 | |
| {
 | |
|     if (!index.isValid())
 | |
|         return QModelIndex();
 | |
| 
 | |
|     const void* childItem = index.internalPointer();
 | |
| 
 | |
|     // Determine where childItem points to
 | |
|     if (childItem >= &contents[0] && childItem <= &contents.back()) {
 | |
|         // Parent is root/contents
 | |
|         return QModelIndex();
 | |
|     } else {
 | |
|         GameOptionChildItem* child = static_cast<GameOptionChildItem*>(index.internalPointer());
 | |
|         return createIndex(child->parentRow, 0, &contents[child->parentRow]);
 | |
|     }
 | |
| }
 | |
| 
 | |
| int GameOptions::rowCount(const QModelIndex& parent) const
 | |
| {
 | |
|     if (!parent.isValid()) {
 | |
|         return static_cast<int>(contents.size());
 | |
|     } else {
 | |
|         if (parent.column() > 0)
 | |
|             return 0;
 | |
| 
 | |
|         // Our tree model is only one layer deep
 | |
|         // If we have parent, we can't go deeper
 | |
|         if (parent.parent().isValid())
 | |
|             return 0;
 | |
| 
 | |
|         GameOptionItem* item = static_cast<GameOptionItem*>(parent.internalPointer());
 | |
|         return item->children.count();
 | |
|     }
 | |
| }
 | |
| 
 | |
| int GameOptions::columnCount(const QModelIndex& parent) const
 | |
| {
 | |
|     // Our tree model is only one layer deep
 | |
|     // If we have parent, we can't go deeper
 | |
|     if (parent.parent().isValid())
 | |
|         return 0;
 | |
| 
 | |
|     return 4;
 | |
| }
 | |
| 
 | |
| bool GameOptions::isLoaded() const
 | |
| {
 | |
|     return loaded;
 | |
| }
 | |
| 
 | |
| bool GameOptions::reload()
 | |
| {
 | |
|     beginResetModel();
 | |
|     loaded = load(path, contents, version);
 | |
|     endResetModel();
 | |
|     return loaded;
 | |
| }
 | |
| 
 | |
| bool GameOptions::save()
 | |
| {
 | |
|     return ::save(path, contents, version);
 | |
| }
 |