diff options
-rw-r--r-- | CMakeLists.txt | 13 | ||||
-rw-r--r-- | gui/MainWindow.cpp | 9 | ||||
-rw-r--r-- | gui/pagedialog/PageDialog.cpp | 154 | ||||
-rw-r--r-- | gui/pagedialog/PageDialog.h | 50 | ||||
-rw-r--r-- | gui/pagedialog/PageDialog_p.h | 106 | ||||
-rw-r--r-- | gui/pages/BasePage.h | 35 | ||||
-rw-r--r-- | gui/pages/BasePageProvider.h | 28 | ||||
-rw-r--r-- | gui/pages/ModFolderPage.cpp | 140 | ||||
-rw-r--r-- | gui/pages/ModFolderPage.h | 60 | ||||
-rw-r--r-- | gui/pages/ModFolderPage.ui | 114 | ||||
-rw-r--r-- | gui/pages/VersionPage.cpp | 365 | ||||
-rw-r--r-- | gui/pages/VersionPage.h | 70 | ||||
-rw-r--r-- | gui/pages/VersionPage.ui | 169 | ||||
-rw-r--r-- | logic/BaseInstance.h | 3 | ||||
-rw-r--r-- | logic/LegacyInstance.cpp | 5 | ||||
-rw-r--r-- | logic/LegacyInstance.h | 1 | ||||
-rw-r--r-- | logic/ModList.cpp | 1 | ||||
-rw-r--r-- | logic/OneSixInstance.cpp | 35 | ||||
-rw-r--r-- | logic/OneSixInstance.h | 13 | ||||
-rw-r--r-- | logic/OneSixInstance_p.h | 1 | ||||
-rw-r--r-- | logic/minecraft/VersionBuilder.cpp | 2 |
21 files changed, 1357 insertions, 17 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index e5508f44..12089613 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -291,6 +291,15 @@ SET(MULTIMC_SOURCES gui/ConsoleWindow.h gui/ConsoleWindow.cpp + # GUI - page dialog and pages + gui/pagedialog/PageDialog.cpp + gui/pagedialog/PageDialog.h + gui/pagedialog/PageDialog_p.h + gui/pages/VersionPage.cpp + gui/pages/VersionPage.h + gui/pages/ModFolderPage.cpp + gui/pages/ModFolderPage.h + # GUI - dialogs gui/dialogs/AboutDialog.cpp gui/dialogs/AboutDialog.h @@ -598,6 +607,10 @@ SET(MULTIMC_UIS gui/MainWindow.ui gui/ConsoleWindow.ui + # Option pages + gui/pages/VersionPage.ui + gui/pages/ModFolderPage.ui + # Dialogs gui/dialogs/SettingsDialog.ui gui/dialogs/CopyInstanceDialog.ui diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index 568607a7..830a4fb4 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -68,6 +68,7 @@ #include "dialogs/ScreenshotDialog.h" #include "gui/ConsoleWindow.h" +#include "pagedialog/PageDialog.h" #include "logic/InstanceList.h" #include "logic/minecraft/MinecraftVersionList.h" @@ -1043,7 +1044,13 @@ void MainWindow::on_actionEditInstance_triggered() { if (m_selectedInstance) { - auto dialog = m_selectedInstance->createModEditDialog(this); + auto provider = std::dynamic_pointer_cast<BasePageProvider>(m_selectedInstance); + if(!provider) + { + QLOG_ERROR() << "Instance can't be converted to BasePageProvider (NYI)"; + return; + } + auto dialog = new PageDialog(provider, this); if (dialog) dialog->exec(); dialog->deleteLater(); diff --git a/gui/pagedialog/PageDialog.cpp b/gui/pagedialog/PageDialog.cpp new file mode 100644 index 00000000..0dda7c1f --- /dev/null +++ b/gui/pagedialog/PageDialog.cpp @@ -0,0 +1,154 @@ +/* Copyright 2014 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 "PageDialog.h" +#include "gui/Platform.h" +#include <QStackedLayout> +#include <QPushButton> +#include <QSortFilterProxyModel> +#include "MultiMC.h" +#include <QStyledItemDelegate> +#include <QListView> +#include <QLineEdit> +#include <QLabel> +#include <QDialogButtonBox> +#include <QGridLayout> + +#include "PageDialog_p.h" +#include <gui/widgets/IconLabel.h> + +class PageEntryFilterModel : public QSortFilterProxyModel +{ +public: + explicit PageEntryFilterModel(QObject *parent = 0) : QSortFilterProxyModel(parent) + { + } + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const + { + // Regular contents check, then check page-filter. + if (QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent)) + return true; + + const QString pattern = filterRegExp().pattern(); + const auto model = static_cast<PageModel *>(sourceModel()); + const auto page = model->pages().at(sourceRow); + return page->shouldDisplay(); + } +}; + +PageDialog::PageDialog(BasePageProviderPtr pageProvider, QWidget *parent) : QDialog(parent) +{ + MultiMCPlatform::fixWM_CLASS(this); + createUI(); + setWindowTitle(pageProvider->dialogTitle()); + + m_model = new PageModel(this); + m_proxyModel = new PageEntryFilterModel(this); + int firstIndex = -1; + auto pages = pageProvider->getPages(); + for(auto page: pages) + { + page->index = m_pageStack->addWidget(dynamic_cast<QWidget *>(page)); + if(firstIndex == -1) + { + firstIndex = page->index; + } + } + m_model->setPages(pages); + + m_proxyModel->setSourceModel(m_model); + m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + + m_pageList->setIconSize(QSize(pageIconSize, pageIconSize)); + m_pageList->setSelectionMode(QAbstractItemView::SingleSelection); + m_pageList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + m_pageList->setModel(m_proxyModel); + connect(m_pageList->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + this, SLOT(currentChanged(QModelIndex))); + + m_pageStack->setStackingMode(QStackedLayout::StackOne); + m_pageList->setFocus(); +} + +void PageDialog::createUI() +{ + m_pageStack = new QStackedLayout; + m_filter = new QLineEdit; + m_pageList = new PageView; + m_header = new QLabel(); + m_iconHeader = new IconLabel(this, QIcon::fromTheme("bug"), QSize(24,24)); + + QFont headerLabelFont = m_header->font(); + headerLabelFont.setBold(true); + const int pointSize = headerLabelFont.pointSize(); + if (pointSize > 0) + headerLabelFont.setPointSize(pointSize + 2); + m_header->setFont(headerLabelFont); + + QHBoxLayout *headerHLayout = new QHBoxLayout; + const int leftMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutLeftMargin); + headerHLayout->addSpacerItem( + new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); + headerHLayout->addWidget(m_header); + headerHLayout->addSpacerItem( + new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); + headerHLayout->addWidget(m_iconHeader); + + m_pageStack->setMargin(0); + m_pageStack->addWidget(new QWidget(this)); + + QDialogButtonBox *buttons = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel); + buttons->button(QDialogButtonBox::Ok)->setDefault(true); + connect(buttons->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(apply())); + connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); + + QGridLayout *mainGridLayout = new QGridLayout; + mainGridLayout->addLayout(headerHLayout, 0, 1, 1, 1); + mainGridLayout->addWidget(m_pageList, 0, 0, 2, 1); + mainGridLayout->addLayout(m_pageStack, 1, 1, 1, 1); + mainGridLayout->addWidget(buttons, 2, 0, 1, 2); + mainGridLayout->setColumnStretch(1, 4); + setLayout(mainGridLayout); +} + +void PageDialog::showPage(int row) +{ + auto page = m_model->pages().at(row); + m_pageStack->setCurrentIndex(page->index); + m_header->setText(page->displayName()); + m_iconHeader->setIcon(page->icon()); +} + +void PageDialog::currentChanged(const QModelIndex ¤t) +{ + if (current.isValid()) + { + showPage(m_proxyModel->mapToSource(current).row()); + } + else + { + m_pageStack->setCurrentIndex(0); + m_header->setText(QString()); + m_iconHeader->setIcon(QIcon::fromTheme("bug")); + } +} + +void PageDialog::apply() +{ +} diff --git a/gui/pagedialog/PageDialog.h b/gui/pagedialog/PageDialog.h new file mode 100644 index 00000000..880b7df4 --- /dev/null +++ b/gui/pagedialog/PageDialog.h @@ -0,0 +1,50 @@ +/* Copyright 2014 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. + */ + +#pragma once +#include <QDialog> +#include <QModelIndex> +#include <gui/pages/BasePageProvider.h> + +class IconLabel; +class QSortFilterProxyModel; +class PageModel; +class QLabel; +class QListView; +class QLineEdit; +class QStackedLayout; + +class PageDialog : public QDialog +{ + Q_OBJECT +public: + explicit PageDialog(BasePageProviderPtr pageProvider, QWidget *parent = 0); + virtual ~PageDialog() {}; +private: + void createUI(); +private slots: + void apply(); + void currentChanged(const QModelIndex ¤t); + void showPage(int row); + +private: + QSortFilterProxyModel *m_proxyModel; + PageModel *m_model; + QStackedLayout *m_pageStack; + QLineEdit *m_filter; + QListView *m_pageList; + QLabel *m_header; + IconLabel *m_iconHeader; +}; diff --git a/gui/pagedialog/PageDialog_p.h b/gui/pagedialog/PageDialog_p.h new file mode 100644 index 00000000..36e641e3 --- /dev/null +++ b/gui/pagedialog/PageDialog_p.h @@ -0,0 +1,106 @@ +#pragma once +#include <QListView> +#include <QStyledItemDelegate> +#include <QEvent> +#include <QScrollBar> + +const int pageIconSize = 24; + +class PageViewDelegate : public QStyledItemDelegate +{ +public: + PageViewDelegate(QObject *parent) : QStyledItemDelegate(parent) + { + } + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QSize size = QStyledItemDelegate::sizeHint(option, index); + size.setHeight(qMax(size.height(), 32)); + return size; + } +}; + +class PageModel : public QAbstractListModel +{ +public: + PageModel(QObject *parent = 0) : QAbstractListModel(parent) + { + QPixmap empty(pageIconSize, pageIconSize); + empty.fill(Qt::transparent); + m_emptyIcon = QIcon(empty); + } + virtual ~PageModel() {}; + + int rowCount(const QModelIndex &parent = QModelIndex()) const + { + return parent.isValid() ? 0 : m_pages.size(); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const + { + switch (role) + { + case Qt::DisplayRole: + return m_pages.at(index.row())->displayName(); + case Qt::DecorationRole: + { + QIcon icon = m_pages.at(index.row())->icon(); + if (icon.isNull()) + icon = m_emptyIcon; + return icon; + } + } + return QVariant(); + } + + void setPages(const QList<BasePage *> &pages) + { + beginResetModel(); + m_pages = pages; + endResetModel(); + } + const QList<BasePage *> &pages() const + { + return m_pages; + } + +private: + BasePage * findPageEntryById(QString id) + { + for(auto page: m_pages) + { + if (page->id() == id) + return page; + } + return nullptr; + } + + QList<BasePage *> m_pages; + QIcon m_emptyIcon; +}; + +class PageView : public QListView +{ +public: + PageView(QWidget *parent = 0) : QListView(parent) + { + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); + setItemDelegate(new PageViewDelegate(this)); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } + + virtual QSize sizeHint() const + { + int width = sizeHintForColumn(0) + frameWidth() * 2 + 5; + if (verticalScrollBar()->isVisible()) + width += verticalScrollBar()->width(); + return QSize(width, 100); + } + + virtual bool eventFilter(QObject *obj, QEvent *event) + { + if (obj == verticalScrollBar() && + (event->type() == QEvent::Show || event->type() == QEvent::Hide)) + updateGeometry(); + return QListView::eventFilter(obj, event); + } +}; diff --git a/gui/pages/BasePage.h b/gui/pages/BasePage.h new file mode 100644 index 00000000..90e27d6f --- /dev/null +++ b/gui/pages/BasePage.h @@ -0,0 +1,35 @@ +/* Copyright 2014 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. + */ + +#pragma once +#include <QString> +#include <QIcon> +#include <memory> + +class BasePage +{ +public: + virtual ~BasePage(){}; + virtual QString id() = 0; + virtual QString displayName() = 0; + virtual QIcon icon() = 0; + virtual bool shouldDisplay() + { + return true; + } + int index = -1; +}; + +typedef std::shared_ptr<BasePage> BasePagePtr;
\ No newline at end of file diff --git a/gui/pages/BasePageProvider.h b/gui/pages/BasePageProvider.h new file mode 100644 index 00000000..cff9c8e7 --- /dev/null +++ b/gui/pages/BasePageProvider.h @@ -0,0 +1,28 @@ +/* Copyright 2014 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. + */ + +#pragma once + +#include "BasePage.h" +#include <memory> + +class BasePageProvider +{ +public: + virtual QList<BasePage *> getPages() = 0; + virtual QString dialogTitle() = 0; +}; + +typedef std::shared_ptr<BasePageProvider> BasePageProviderPtr; diff --git a/gui/pages/ModFolderPage.cpp b/gui/pages/ModFolderPage.cpp new file mode 100644 index 00000000..de51edfa --- /dev/null +++ b/gui/pages/ModFolderPage.cpp @@ -0,0 +1,140 @@ +/* Copyright 2014 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 "MultiMC.h" + +#include <pathutils.h> +#include <QFileDialog> +#include <QMessageBox> +#include <QDebug> +#include <QEvent> +#include <QKeyEvent> +#include <QDesktopServices> +#include <QAbstractItemModel> + +#include "ModFolderPage.h" +#include "ui_ModFolderPage.h" + +#include "gui/dialogs/CustomMessageBox.h" +#include "gui/dialogs/ModEditDialogCommon.h" + +#include "logic/ModList.h" +#include "logic/Mod.h" + +QString ModFolderPage::displayName() +{ + return m_displayName; +} + +QIcon ModFolderPage::icon() +{ + return QIcon::fromTheme(m_iconName); +} + +QString ModFolderPage::id() +{ + return m_id; +} + +ModFolderPage::ModFolderPage(std::shared_ptr<ModList> mods, QString id, QString iconName, + QString displayName, QWidget *parent) + : QWidget(parent), ui(new Ui::ModFolderPage) +{ + ui->setupUi(this); + m_mods = mods; + m_id = id; + m_displayName = displayName; + m_iconName = iconName; + ui->modTreeView->setModel(m_mods.get()); + ui->modTreeView->installEventFilter(this); + m_mods->startWatching(); + auto smodel = ui->modTreeView->selectionModel(); + connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), + SLOT(modCurrent(QModelIndex, QModelIndex))); +} + +ModFolderPage::~ModFolderPage() +{ + m_mods->stopWatching(); + delete ui; +} + +bool ModFolderPage::modListFilter(QKeyEvent *keyEvent) +{ + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_rmModBtn_clicked(); + return true; + case Qt::Key_Plus: + on_addModBtn_clicked(); + return true; + default: + break; + } + return QWidget::eventFilter(ui->modTreeView, keyEvent); +} + +bool ModFolderPage::eventFilter(QObject *obj, QEvent *ev) +{ + if (ev->type() != QEvent::KeyPress) + { + return QWidget::eventFilter(obj, ev); + } + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); + if (obj == ui->modTreeView) + return modListFilter(keyEvent); + return QWidget::eventFilter(obj, ev); +} + +void ModFolderPage::on_addModBtn_clicked() +{ + QStringList fileNames = QFileDialog::getOpenFileNames( + this, QApplication::translate("ModFolderPage", "Select Loader Mods")); + for (auto filename : fileNames) + { + m_mods->stopWatching(); + m_mods->installMod(QFileInfo(filename)); + m_mods->startWatching(); + } +} +void ModFolderPage::on_rmModBtn_clicked() +{ + int first, last; + auto list = ui->modTreeView->selectionModel()->selectedRows(); + + if (!lastfirst(list, first, last)) + return; + m_mods->stopWatching(); + m_mods->deleteMods(first, last); + m_mods->startWatching(); +} + +void ModFolderPage::on_viewModBtn_clicked() +{ + openDirInDefaultProgram(m_mods->dir().absolutePath(), true); +} + +void ModFolderPage::modCurrent(const QModelIndex ¤t, const QModelIndex &previous) +{ + if (!current.isValid()) + { + ui->frame->clear(); + return; + } + int row = current.row(); + Mod &m = m_mods->operator[](row); + ui->frame->updateWithMod(m); +} diff --git a/gui/pages/ModFolderPage.h b/gui/pages/ModFolderPage.h new file mode 100644 index 00000000..276e23d5 --- /dev/null +++ b/gui/pages/ModFolderPage.h @@ -0,0 +1,60 @@ +/* Copyright 2014 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. + */ + +#pragma once +#include <QWidget> + +#include <logic/OneSixInstance.h> +#include <logic/net/NetJob.h> +#include "BasePage.h" + +class EnabledItemFilter; +class ModList; +namespace Ui +{ +class ModFolderPage; +} + +class ModFolderPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit ModFolderPage(std::shared_ptr<ModList> mods, QString id, QString iconName, + QString displayName, QWidget *parent = 0); + virtual ~ModFolderPage(); + virtual QString displayName() override; + virtual QIcon icon() override; + virtual QString id() override; + +protected: + bool eventFilter(QObject *obj, QEvent *ev); + bool modListFilter(QKeyEvent *ev); + +private: + Ui::ModFolderPage *ui; + std::shared_ptr<ModList> m_mods; + QString m_iconName; + QString m_id; + QString m_displayName; + +public slots: + void modCurrent(const QModelIndex ¤t, const QModelIndex &previous); + +private slots: + void on_addModBtn_clicked(); + void on_rmModBtn_clicked(); + void on_viewModBtn_clicked(); +}; diff --git a/gui/pages/ModFolderPage.ui b/gui/pages/ModFolderPage.ui new file mode 100644 index 00000000..05125be3 --- /dev/null +++ b/gui/pages/ModFolderPage.ui @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ModFolderPage</class> + <widget class="QWidget" name="ModFolderPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>540</width> + <height>350</height> + </rect> + </property> + <property name="windowTitle"> + <string>Mods</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="ModListView" name="modTreeView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="acceptDrops"> + <bool>true</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::DropOnly</enum> + </property> + </widget> + </item> + <item> + <widget class="MCModInfoFrame" name="frame"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QPushButton" name="addModBtn"> + <property name="text"> + <string>&Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="rmModBtn"> + <property name="text"> + <string>&Remove</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="viewModBtn"> + <property name="text"> + <string>&View Folder</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ModListView</class> + <extends>QTreeView</extends> + <header>gui/widgets/ModListView.h</header> + </customwidget> + <customwidget> + <class>MCModInfoFrame</class> + <extends>QFrame</extends> + <header>gui/widgets/MCModInfoFrame.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/gui/pages/VersionPage.cpp b/gui/pages/VersionPage.cpp new file mode 100644 index 00000000..34b959e0 --- /dev/null +++ b/gui/pages/VersionPage.cpp @@ -0,0 +1,365 @@ +/* Copyright 2014 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 "MultiMC.h" + +#include <pathutils.h> +#include <QFileDialog> +#include <QMessageBox> +#include <QDebug> +#include <QEvent> +#include <QKeyEvent> + +#include "VersionPage.h" +#include "ui_VersionPage.h" + +#include "gui/Platform.h" +#include "gui/dialogs/CustomMessageBox.h" +#include "gui/dialogs/VersionSelectDialog.h" +#include "gui/dialogs/ModEditDialogCommon.h" + +#include "gui/dialogs/ProgressDialog.h" + +#include "logic/ModList.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/EnabledItemFilter.h" +#include "logic/forge/ForgeVersionList.h" +#include "logic/forge/ForgeInstaller.h" +#include "logic/liteloader/LiteLoaderVersionList.h" +#include "logic/liteloader/LiteLoaderInstaller.h" +#include "logic/minecraft/VersionBuilder.h" +#include "logic/auth/MojangAccountList.h" + +#include <QAbstractItemModel> +#include <logic/Mod.h> + +#include <QMessageBox> +#include <QListView> +#include <QString> +#include <QUrl> + +QString VersionPage::displayName() +{ + return tr("Version"); +} + +QIcon VersionPage::icon() +{ + return QIcon::fromTheme("settings"); +} + +QString VersionPage::id() +{ + return "version"; +} + +VersionPage::VersionPage(OneSixInstance *inst, QWidget *parent) + : QWidget(parent), ui(new Ui::VersionPage), m_inst(inst) +{ + ui->setupUi(this); + // libraries! + + m_version = m_inst->getFullVersion(); + if (m_version) + { + main_model = new EnabledItemFilter(this); + main_model->setActive(true); + main_model->setSourceModel(m_version.get()); + ui->libraryTreeView->setModel(main_model); + ui->libraryTreeView->installEventFilter(this); + connect(ui->libraryTreeView->selectionModel(), &QItemSelectionModel::currentChanged, + this, &VersionPage::versionCurrent); + updateVersionControls(); + } + else + { + disableVersionControls(); + } + connect(m_inst, &OneSixInstance::versionReloaded, this, + &VersionPage::updateVersionControls); +} + +VersionPage::~VersionPage() +{ + delete ui; +} + +void VersionPage::updateVersionControls() +{ + ui->forgeBtn->setEnabled(true); + ui->liteloaderBtn->setEnabled(true); +} + +void VersionPage::disableVersionControls() +{ + ui->forgeBtn->setEnabled(false); + ui->liteloaderBtn->setEnabled(false); + ui->reloadLibrariesBtn->setEnabled(false); + ui->removeLibraryBtn->setEnabled(false); +} + +bool VersionPage::reloadInstanceVersion() +{ + try + { + m_inst->reloadVersion(); + return true; + } + catch (MMCError &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + return false; + } + catch (...) + { + QMessageBox::critical( + this, tr("Error"), + tr("Failed to load the version description file for reasons unknown.")); + return false; + } +} + +void VersionPage::on_reloadLibrariesBtn_clicked() +{ + reloadInstanceVersion(); +} + +void VersionPage::on_removeLibraryBtn_clicked() +{ + if (ui->libraryTreeView->currentIndex().isValid()) + { + // FIXME: use actual model, not reloading. + if (!m_version->remove(ui->libraryTreeView->currentIndex().row())) + { + QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file")); + } + } +} + +void VersionPage::on_jarmodBtn_clicked() +{ + QFileDialog w; + QSet<QString> locations; + QString modsFolder = MMC->settings()->get("CentralModsDir").toString(); + auto f = [&](QStandardPaths::StandardLocation l) + { + QString location = QStandardPaths::writableLocation(l); + if(!QFileInfo::exists(location)) + return; + locations.insert(location); + }; + f(QStandardPaths::DesktopLocation); + f(QStandardPaths::DocumentsLocation); + f(QStandardPaths::DownloadLocation); + f(QStandardPaths::HomeLocation); + QList<QUrl> urls; + for(auto location: locations) + { + urls.append(QUrl::fromLocalFile(location)); + } + urls.append(QUrl::fromLocalFile(modsFolder)); + + w.setFileMode(QFileDialog::ExistingFiles); + w.setAcceptMode(QFileDialog::AcceptOpen); + w.setNameFilter(tr("Minecraft jar mods (*.zip *.jar)")); + w.setDirectory(modsFolder); + w.setSidebarUrls(urls); + + if(w.exec()); + m_version->installJarMods(w.selectedFiles()); +} + +void VersionPage::on_resetLibraryOrderBtn_clicked() +{ + try + { + m_version->resetOrder(); + } + catch (MMCError &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + } +} + +void VersionPage::on_moveLibraryUpBtn_clicked() +{ + if (ui->libraryTreeView->selectionModel()->selectedRows().isEmpty()) + { + return; + } + try + { + const int row = ui->libraryTreeView->selectionModel()->selectedRows().first().row(); + const int newRow = 0; + m_version->move(row, InstanceVersion::MoveUp); + // ui->libraryTreeView->selectionModel()->setCurrentIndex(m_version->index(newRow), + // QItemSelectionModel::ClearAndSelect); + } + catch (MMCError &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + } +} + +void VersionPage::on_moveLibraryDownBtn_clicked() +{ + if (ui->libraryTreeView->selectionModel()->selectedRows().isEmpty()) + { + return; + } + try + { + const int row = ui->libraryTreeView->selectionModel()->selectedRows().first().row(); + const int newRow = 0; + m_version->move(row, InstanceVersion::MoveDown); + // ui->libraryTreeView->selectionModel()->setCurrentIndex(m_version->index(newRow), + // QItemSelectionModel::ClearAndSelect); + } + catch (MMCError &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + } +} + +void VersionPage::on_changeMCVersionBtn_clicked() +{ + VersionSelectDialog vselect(m_inst->versionList().get(), tr("Change Minecraft version"), + this); + if (!vselect.exec() || !vselect.selectedVersion()) + return; + + if (!MMC->accounts()->anyAccountIsValid()) + { + CustomMessageBox::selectable( + this, tr("Error"), + tr("MultiMC cannot download Minecraft or update instances unless you have at least " + "one account added.\nPlease add your Mojang or Minecraft account."), + QMessageBox::Warning)->show(); + return; + } + + if (m_inst->versionIsCustom()) + { + auto result = CustomMessageBox::selectable( + this, tr("Are you sure?"), + tr("This will remove any library/version customization you did previously. " + "This includes things like Forge install and similar."), + QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Abort, + QMessageBox::Abort)->exec(); + + if (result != QMessageBox::Ok) + return; + m_version->revertToVanilla(); + reloadInstanceVersion(); + } + m_inst->setIntendedVersionId(vselect.selectedVersion()->descriptor()); + + auto updateTask = m_inst->doUpdate(); + if (!updateTask) + { + return; + } + ProgressDialog tDialog(this); + connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); + tDialog.exec(updateTask.get()); +} + +void VersionPage::on_forgeBtn_clicked() +{ + // FIXME: use actual model, not reloading. Move logic to model. + if (m_version->hasFtbPack()) + { + if (QMessageBox::question( + this, tr("Revert?"), + tr("This action will remove the FTB pack version patch. Continue?")) != + QMessageBox::Yes) + { + return; + } + m_version->removeFtbPack(); + reloadInstanceVersion(); + } + if (m_version->usesLegacyCustomJson()) + { + if (QMessageBox::question(this, tr("Revert?"), + tr("This action will remove your custom.json. Continue?")) != + QMessageBox::Yes) + { + return; + } + m_version->revertToVanilla(); + reloadInstanceVersion(); + } + VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); + vselect.setExactFilter(1, m_inst->currentVersionId()); + vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + + m_inst->currentVersionId()); + if (vselect.exec() && vselect.selectedVersion()) + { + ProgressDialog dialog(this); + dialog.exec( + ForgeInstaller().createInstallTask(m_inst, vselect.selectedVersion(), this)); + } +} + +void VersionPage::on_liteloaderBtn_clicked() +{ + if (m_version->hasFtbPack()) + { + if (QMessageBox::question( + this, tr("Revert?"), + tr("This action will remove the FTB pack version patch. Continue?")) != + QMessageBox::Yes) + { + return; + } + m_version->removeFtbPack(); + reloadInstanceVersion(); + } + if (m_version->usesLegacyCustomJson()) + { + if (QMessageBox::question(this, tr("Revert?"), + tr("This action will remove your custom.json. Continue?")) != + QMessageBox::Yes) + { + return; + } + m_version->revertToVanilla(); + reloadInstanceVersion(); + } + VersionSelectDialog vselect(MMC->liteloaderlist().get(), tr("Select LiteLoader version"), + this); + vselect.setExactFilter(1, m_inst->currentVersionId()); + vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + + m_inst->currentVersionId()); + if (vselect.exec() && vselect.selectedVersion()) + { + ProgressDialog dialog(this); + dialog.exec( + LiteLoaderInstaller().createInstallTask(m_inst, vselect.selectedVersion(), this)); + } +} + +void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex &previous) +{ + if (!current.isValid()) + { + ui->removeLibraryBtn->setDisabled(true); + } + else + { + ui->removeLibraryBtn->setEnabled(m_version->canRemove(current.row())); + } +} diff --git a/gui/pages/VersionPage.h b/gui/pages/VersionPage.h new file mode 100644 index 00000000..55e244b3 --- /dev/null +++ b/gui/pages/VersionPage.h @@ -0,0 +1,70 @@ +/* Copyright 2014 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. + */ + +#pragma once +#include <QWidget> + +#include <logic/OneSixInstance.h> +#include <logic/net/NetJob.h> +#include "BasePage.h" + +class EnabledItemFilter; +namespace Ui +{ +class VersionPage; +} + +class VersionPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit VersionPage(OneSixInstance *inst, QWidget *parent = 0); + virtual ~VersionPage(); + virtual QString displayName() override; + virtual QIcon icon() override; + virtual QString id() override; +private +slots: + + // version tab + void on_forgeBtn_clicked(); + void on_liteloaderBtn_clicked(); + void on_reloadLibrariesBtn_clicked(); + void on_removeLibraryBtn_clicked(); + void on_resetLibraryOrderBtn_clicked(); + void on_moveLibraryUpBtn_clicked(); + void on_moveLibraryDownBtn_clicked(); + void on_jarmodBtn_clicked(); + + void updateVersionControls(); + void disableVersionControls(); + void on_changeMCVersionBtn_clicked(); + +protected: + /// FIXME: this shouldn't be necessary! + bool reloadInstanceVersion(); + +private: + Ui::VersionPage *ui; + std::shared_ptr<InstanceVersion> m_version; + EnabledItemFilter *main_model; + OneSixInstance *m_inst; + NetJobPtr forgeJob; + +public +slots: + void versionCurrent(const QModelIndex ¤t, const QModelIndex &previous); +}; diff --git a/gui/pages/VersionPage.ui b/gui/pages/VersionPage.ui new file mode 100644 index 00000000..036295f0 --- /dev/null +++ b/gui/pages/VersionPage.ui @@ -0,0 +1,169 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>VersionPage</class> + <widget class="QWidget" name="VersionPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>326</height> + </rect> + </property> + <property name="windowTitle"> + <string>Version</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QVBoxLayout" name="verticalLayout_10"> + <item> + <widget class="ModListView" name="libraryTreeView"> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOn</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="headerHidden"> + <bool>false</bool> + </property> + <attribute name="headerVisible"> + <bool>true</bool> + </attribute> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QPushButton" name="changeMCVersionBtn"> + <property name="text"> + <string>Change version</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="forgeBtn"> + <property name="toolTip"> + <string>Replace any current custom version with Minecraft Forge</string> + </property> + <property name="text"> + <string>Install Forge</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="liteloaderBtn"> + <property name="text"> + <string>Install LiteLoader</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="jarmodBtn"> + <property name="text"> + <string>Add jar mod</string> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="reloadLibrariesBtn"> + <property name="text"> + <string>Reload</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="removeLibraryBtn"> + <property name="text"> + <string>Remove</string> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="moveLibraryUpBtn"> + <property name="toolTip"> + <string>This isn't implemented yet.</string> + </property> + <property name="text"> + <string>Move up</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="moveLibraryDownBtn"> + <property name="toolTip"> + <string>This isn't implemented yet.</string> + </property> + <property name="text"> + <string>Move down</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="resetLibraryOrderBtn"> + <property name="toolTip"> + <string>This isn't implemented yet.</string> + </property> + <property name="text"> + <string>Reset order</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_7"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ModListView</class> + <extends>QTreeView</extends> + <header>gui/widgets/ModListView.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index d272dc71..bdd2f1be 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -169,9 +169,6 @@ public: /// 'prepareForLaunch' virtual void cleanupAfterRun() = 0; - /// create a mod edit dialog for the instance - virtual QDialog *createModEditDialog(QWidget *parent) = 0; - /// is a particular action enabled with this instance selected? virtual bool menuActionEnabled(QString action_name) const = 0; diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index 279282e3..5bc8bc34 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -135,11 +135,6 @@ std::shared_ptr<ModList> LegacyInstance::texturePackList() return d->texture_pack_list; } -QDialog *LegacyInstance::createModEditDialog(QWidget *parent) -{ - return new LegacyModEditDialog(this, parent); -} - QString LegacyInstance::jarModsDir() const { return PathCombine(instanceRoot(), "instMods"); diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h index aa80968e..3d426601 100644 --- a/logic/LegacyInstance.h +++ b/logic/LegacyInstance.h @@ -81,7 +81,6 @@ public: virtual bool prepareForLaunch(AuthSessionPtr account, QString & launchScript) override; virtual void cleanupAfterRun() override; - virtual QDialog *createModEditDialog(QWidget *parent) override; virtual QString defaultBaseJar() const override; virtual QString defaultCustomBaseJar() const override; diff --git a/logic/ModList.cpp b/logic/ModList.cpp index 79b56986..f7770920 100644 --- a/logic/ModList.cpp +++ b/logic/ModList.cpp @@ -26,6 +26,7 @@ ModList::ModList(const QString &dir, const QString &list_file) : QAbstractListModel(), m_dir(dir), m_list_file(list_file) { + ensureFolderPathExists(m_dir.absolutePath()); m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index e2d5ef0a..4fd7999a 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -30,6 +30,9 @@ #include "icons/IconList.h" #include "logic/MinecraftProcess.h" #include "gui/dialogs/InstanceEditDialog.h" +#include "gui/pagedialog/PageDialog.h" +#include "gui/pages/VersionPage.h" +#include <gui/pages/ModFolderPage.h> OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) @@ -52,6 +55,22 @@ void OneSixInstance::init() } } +QList<BasePage *> OneSixInstance::getPages() +{ + QList<BasePage *> values; + values.append(new VersionPage(this)); + values.append(new ModFolderPage(loaderModList(), "mods", "centralmods", tr("Mods"))); + values.append(new ModFolderPage(coreModList(), "coremods", "viewfolder", tr("Core Mods"))); + values.append(new ModFolderPage(resourcePackList(), "resourcepacks", "viewfolder", tr("Resource Packs"))); + values.append(new ModFolderPage(texturePackList(), "texturepacks", "viewfolder", tr("Texture Packs"))); + return values; +} + +QString OneSixInstance::dialogTitle() +{ + return tr("Edit Instance (%1)").arg(name()); +} + std::shared_ptr<Task> OneSixInstance::doUpdate() { return std::shared_ptr<Task>(new OneSixUpdate(this)); @@ -310,9 +329,15 @@ std::shared_ptr<ModList> OneSixInstance::resourcePackList() return d->resource_pack_list; } -QDialog *OneSixInstance::createModEditDialog(QWidget *parent) +std::shared_ptr<ModList> OneSixInstance::texturePackList() { - return new InstanceEditDialog(this, parent); + I_D(OneSixInstance); + if (!d->texture_pack_list) + { + d->texture_pack_list.reset(new ModList(texturePacksDir())); + } + d->texture_pack_list->update(); + return d->texture_pack_list; } bool OneSixInstance::setIntendedVersionId(QString version) @@ -503,6 +528,12 @@ QString OneSixInstance::resourcePacksDir() const return PathCombine(minecraftRoot(), "resourcepacks"); } +QString OneSixInstance::texturePacksDir() const +{ + return PathCombine(minecraftRoot(), "texturepacks"); +} + + QString OneSixInstance::instanceConfigFolder() const { return PathCombine(minecraftRoot(), "config"); diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index 98ce1ce8..fae38095 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -19,8 +19,9 @@ #include "logic/minecraft/InstanceVersion.h" #include "logic/ModList.h" +#include "gui/pages/BasePageProvider.h" -class OneSixInstance : public BaseInstance +class OneSixInstance : public BaseInstance, public BasePageProvider { Q_OBJECT public: @@ -29,15 +30,21 @@ public: virtual ~OneSixInstance(){}; virtual void init() override; + + ////// Edit Instance Dialog stuff ////// + virtual QList<BasePage *> getPages(); + virtual QString dialogTitle(); ////// Mod Lists ////// std::shared_ptr<ModList> loaderModList(); std::shared_ptr<ModList> coreModList(); std::shared_ptr<ModList> resourcePackList(); + std::shared_ptr<ModList> texturePackList(); ////// Directories and files ////// QString jarModsDir() const; QString resourcePacksDir() const; + QString texturePacksDir() const; QString loaderModsDir() const; QString coreModsDir() const; QString libDir() const; @@ -56,10 +63,8 @@ public: virtual bool shouldUpdate() const override; virtual void setShouldUpdate(bool val) override; - virtual QDialog *createModEditDialog(QWidget *parent) override; - /** - * reload the full version json files. return true on success! + * reload the full version json files. * * throws various exceptions :3 */ diff --git a/logic/OneSixInstance_p.h b/logic/OneSixInstance_p.h index 2797cc45..3c4ef324 100644 --- a/logic/OneSixInstance_p.h +++ b/logic/OneSixInstance_p.h @@ -29,4 +29,5 @@ public: std::shared_ptr<ModList> loader_mod_list; std::shared_ptr<ModList> core_mod_list; std::shared_ptr<ModList> resource_pack_list; + std::shared_ptr<ModList> texture_pack_list; }; diff --git a/logic/minecraft/VersionBuilder.cpp b/logic/minecraft/VersionBuilder.cpp index 6e96191f..29418fe3 100644 --- a/logic/minecraft/VersionBuilder.cpp +++ b/logic/minecraft/VersionBuilder.cpp @@ -185,7 +185,7 @@ void VersionBuilder::buildFromMultilayer() minecraftPatch->setOrder(-2); m_version->VersionPatches.append(minecraftPatch); - QResource LWJGL(":/versions/LWJGL/2.9.1.json"); + QResource LWJGL(":/versions/LWJGL/2.9.0.json"); auto lwjgl = parseJsonFile(LWJGL.absoluteFilePath(), false, false); auto lwjglPatch = std::dynamic_pointer_cast<VersionPatch>(lwjgl); if (!lwjglPatch) |