From 5f569aeb1807f268288ff65c78a7634d19e1ce87 Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Tue, 26 May 2026 20:21:27 -0400 Subject: [PATCH 01/20] initial code for multi world list --- launcher/minecraft/MultiWorldList.cpp | 454 ++++++++++++++++++++++++ launcher/minecraft/MultiWorldList.h | 100 ++++++ launcher/ui/MultiWorldListPage.cpp | 477 ++++++++++++++++++++++++++ launcher/ui/MultiWorldListPage.h | 107 ++++++ launcher/ui/MultiWorldListPage.ui | 170 +++++++++ 5 files changed, 1308 insertions(+) create mode 100644 launcher/minecraft/MultiWorldList.cpp create mode 100644 launcher/minecraft/MultiWorldList.h create mode 100644 launcher/ui/MultiWorldListPage.cpp create mode 100644 launcher/ui/MultiWorldListPage.h create mode 100644 launcher/ui/MultiWorldListPage.ui diff --git a/launcher/minecraft/MultiWorldList.cpp b/launcher/minecraft/MultiWorldList.cpp new file mode 100644 index 000000000..48d3a3c18 --- /dev/null +++ b/launcher/minecraft/MultiWorldList.cpp @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * 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 "MultiWorldList.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MultiWorldList::MultiWorldList(const QList dirs, QList instances) : QAbstractListModel(), m_instances(instances) +{ + for (int i = 0; i < dirs.length(); i++) { // better way to do this? iy + m_dirs[i] = dirs[i]; + } + + for (QDir dir : m_dirs) { + FS::ensureFolderPathExists(dir.absolutePath()); + dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); + } + + m_watcher = new QFileSystemWatcher(this); + m_isWatching = false; + connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &MultiWorldList::directoryChanged); +} + +void MultiWorldList::startWatching() //figure out what watchers do / if necessary and do all paths iy +{ + if (m_isWatching) { + return; + } + update(); + m_isWatching = m_watcher->addPath(m_dirs[0].absolutePath()); + if (m_isWatching) { + qDebug() << "Started watching" << m_dirs[0].absolutePath(); + } else { + qDebug() << "Failed to start watching" << m_dirs[0].absolutePath(); + } +} + +void MultiWorldList::stopWatching() //same as above function iy +{ + if (!m_isWatching) { + return; + } + m_isWatching = !m_watcher->removePath(m_dirs[0].absolutePath()); + if (!m_isWatching) { + qDebug() << "Stopped watching" << m_dirs[0].absolutePath(); + } else { + qDebug() << "Failed to stop watching" << m_dirs[0].absolutePath(); + } +} + +bool MultiWorldList::update() +{ + if (!isValid()) + return false; + + QList newWorlds; + + for (QDir dir : m_dirs) { + dir.refresh(); + auto folderContents = dir.entryInfoList(); + // if there are any untracked files... + for (QFileInfo entry : folderContents) { + if (!entry.isDir()) + continue; + + World w(entry); + if (w.isValid()) { + newWorlds.append(w); + } + } + } + + beginResetModel(); + m_worlds.swap(newWorlds); + endResetModel(); + loadWorldsAsync(); + return true; +} + +void MultiWorldList::directoryChanged(QString) +{ + update(); +} + +bool MultiWorldList::isValid() //account for all directories +{ + return m_dirs[0].exists() && m_dirs[0].isReadable(); +} + +QList MultiWorldList::instDirPaths() const +{ + QList dirList; + + for (BaseInstance* instance : m_instances) { + dirList.append(QFileInfo(instance->instanceRoot()).absoluteFilePath()); + } + + return dirList; +} + +bool MultiWorldList::deleteWorld(int index) +{ + if (index >= m_worlds.size() || index < 0) + return false; + World& m = m_worlds[index]; + if (m.destroy()) { + beginRemoveRows(QModelIndex(), index, index); + m_worlds.removeAt(index); + endRemoveRows(); + emit changed(); + return true; + } + return false; +} + +bool MultiWorldList::deleteWorlds(int first, int last) +{ + for (int i = first; i <= last; i++) { + World& m = m_worlds[i]; + m.destroy(); + } + beginRemoveRows(QModelIndex(), first, last); + m_worlds.erase(m_worlds.begin() + first, m_worlds.begin() + last + 1); + endRemoveRows(); + emit changed(); + return true; +} + +bool MultiWorldList::resetIcon(int row) +{ + if (row >= m_worlds.size() || row < 0) + return false; + World& m = m_worlds[row]; + if (m.resetIcon()) { + QModelIndex modelIndex = index(row, NameColumn); + emit dataChanged(modelIndex, modelIndex, { MultiWorldList::IconFileRole }); + return true; + } + return false; +} + +int MultiWorldList::columnCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : 5; +} + +QVariant MultiWorldList::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= m_worlds.size()) + return QVariant(); + + QLocale locale; + + auto& world = m_worlds[row]; + switch (role) { + case Qt::DisplayRole: + switch (column) { + case NameColumn: + return world.name(); + + case GameModeColumn: + return world.gameType().toTranslatedString(); + + case LastPlayedColumn: + return world.lastPlayed(); + + case SizeColumn: + return locale.formattedDataSize(world.bytes()); + + case InfoColumn: + if (world.isSymLinkUnder(instDirPaths()[0])) { //FIX THIS--NO INDEX 0 iy + return tr("This world is symbolically linked from elsewhere."); + } + if (world.isMoreThanOneHardLink()) { + return tr("\nThis world is hard linked elsewhere."); + } + return ""; + default: + return QVariant(); + } + + case Qt::UserRole: + if (column == SizeColumn) + return QVariant::fromValue(world.bytes()); + return data(index, Qt::DisplayRole); + + case Qt::ToolTipRole: { + if (column == InfoColumn) { + if (world.isSymLinkUnder(instDirPaths()[0])) { //SAME HERE iy + return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1") + .arg(world.canonicalFilePath()); + } + if (world.isMoreThanOneHardLink()) { + return tr("Warning: This world is hard linked elsewhere. Editing it will also change the original."); + } + } + return world.folderName(); + } + case ObjectRole: { + return QVariant::fromValue((void*)&world); + } + case FolderRole: { + return QDir::toNativeSeparators(dirs()[0].absoluteFilePath(world.folderName())); //SAME HERE iy + } + case SeedRole: { + return QVariant::fromValue(world.seed()); + } + case NameRole: { + return world.name(); + } + case LastPlayedRole: { + return world.lastPlayed(); + } + case SizeRole: { + return QVariant::fromValue(world.bytes()); + } + case IconFileRole: { + return world.iconFile(); + } + default: + return QVariant(); + } +} + +QVariant MultiWorldList::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const +{ + switch (role) { + case Qt::DisplayRole: + switch (section) { + case NameColumn: + return tr("Name"); + case GameModeColumn: + return tr("Game Mode"); + case LastPlayedColumn: + return tr("Last Played"); + case SizeColumn: + //: World size on disk + return tr("Size"); + case InfoColumn: + //: special warnings? + return tr("Info"); + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) { + case NameColumn: + return tr("The name of the world."); + case GameModeColumn: + return tr("Game mode of the world."); + case LastPlayedColumn: + return tr("Date and time the world was last played."); + case SizeColumn: + return tr("Size of the world on disk."); + case InfoColumn: + return tr("Information and warnings about the world."); + default: + return QVariant(); + } + default: + return QVariant(); + } +} + +QStringList MultiWorldList::mimeTypes() const +{ + QStringList types; + types << "text/uri-list"; + return types; +} + +QMimeData* MultiWorldList::mimeData(const QModelIndexList& indexes) const +{ + QList urls; + + for (auto idx : indexes) { + if (idx.column() != 0) + continue; + + int row = idx.row(); + if (row < 0 || row >= this->m_worlds.size()) + continue; + + const World& world = m_worlds[row]; + + if (!world.isValid() || !world.isOnFS()) + continue; + + QString worldPath = world.container().absoluteFilePath(); + qDebug() << worldPath; + urls.append(QUrl::fromLocalFile(worldPath)); + } + + auto result = new QMimeData(); + result->setUrls(urls); + return result; +} + +Qt::ItemFlags MultiWorldList::flags(const QModelIndex& index) const +{ + Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); + if (index.isValid()) + return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; + else + return Qt::ItemIsDropEnabled | defaultFlags; +} + +Qt::DropActions MultiWorldList::supportedDragActions() const +{ + // move to other mod lists or VOID + return Qt::MoveAction; +} + +Qt::DropActions MultiWorldList::supportedDropActions() const +{ + // copy from outside, move from within and other mod lists + return Qt::CopyAction | Qt::MoveAction; +} + +void MultiWorldList::installWorld(QFileInfo filename) +{ + qDebug() << "installing:" << filename.absoluteFilePath(); + World w(filename); + if (!w.isValid()) { + return; + } + w.install(m_dirs[0].absolutePath()); //more directory stuff iy +} + +bool MultiWorldList::dropMimeData(const QMimeData* data, + Qt::DropAction action, + [[maybe_unused]] int row, + [[maybe_unused]] int column, + [[maybe_unused]] const QModelIndex& parent) +{ + if (action == Qt::IgnoreAction) + return true; + // check if the action is supported + if (!data || !(action & supportedDropActions())) + return false; + // files dropped from outside? + if (data->hasUrls()) { + bool was_watching = m_isWatching; + if (was_watching) + stopWatching(); + auto urls = data->urls(); + for (auto url : urls) { + // only local files may be dropped... + if (!url.isLocalFile()) + continue; + QString filename = url.toLocalFile(); + + QFileInfo worldInfo(filename); + + if (!m_dirs[0].entryInfoList().contains(worldInfo)) { //more stuff to fix iy + installWorld(worldInfo); + } + } + if (was_watching) + startWatching(); + return true; + } + return false; +} + +int64_t calculateWorldSize(const QFileInfo& file) +{ + if (file.isFile() && file.suffix() == "zip") { + return file.size(); + } else if (file.isDir()) { + QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories); + int64_t total = 0; + while (it.hasNext()) { + it.next(); + total += it.fileInfo().size(); + } + return total; + } + return -1; +} + +void MultiWorldList::loadWorldsAsync() +{ + for (int i = 0; i < m_worlds.size(); ++i) { + auto file = m_worlds.at(i).container(); + int row = i; + QThreadPool::globalInstance()->start([this, file, row]() mutable { + auto size = calculateWorldSize(file); + + QMetaObject::invokeMethod( + this, + [this, size, row, file]() { + if (row < m_worlds.size() && m_worlds[row].container() == file) { + m_worlds[row].setSize(size); + + // Notify views + QModelIndex modelIndex = index(row, SizeColumn); + emit dataChanged(modelIndex, modelIndex, { SizeRole }); + } + }, + Qt::QueuedConnection); + }); + } +} diff --git a/launcher/minecraft/MultiWorldList.h b/launcher/minecraft/MultiWorldList.h new file mode 100644 index 000000000..eff3bfe22 --- /dev/null +++ b/launcher/minecraft/MultiWorldList.h @@ -0,0 +1,100 @@ +/* Copyright 2015-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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "BaseInstance.h" +#include "minecraft/World.h" + +class QFileSystemWatcher; + +class MultiWorldList : public QAbstractListModel { + Q_OBJECT + public: + enum Columns { NameColumn, GameModeColumn, LastPlayedColumn, SizeColumn, InfoColumn }; + + enum Roles { ObjectRole = Qt::UserRole + 1, FolderRole, SeedRole, NameRole, GameModeRole, LastPlayedRole, SizeRole, IconFileRole }; + + MultiWorldList(const QList dirs, QList instances); + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const { return parent.isValid() ? 0 : static_cast(size()); }; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + virtual int columnCount(const QModelIndex& parent) const; + + size_t size() const { return m_worlds.size(); }; + bool empty() const { return size() == 0; } + World& operator[](size_t index) { return m_worlds[index]; } + + /// Reloads the mod list and returns true if the list changed. + virtual bool update(); + + /// Install a world from location + void installWorld(QFileInfo filename); + + /// Deletes the mod at the given index. + virtual bool deleteWorld(int index); + + /// Removes the world icon, if any + virtual bool resetIcon(int index); + + /// Deletes all the selected mods + virtual bool deleteWorlds(int first, int last); + + /// flags, mostly to support drag&drop + virtual Qt::ItemFlags flags(const QModelIndex& index) const; + /// get data for drag action + virtual QMimeData* mimeData(const QModelIndexList& indexes) const; + /// get the supported mime types + virtual QStringList mimeTypes() const; + /// process data from drop action + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + /// what drag actions do we support? + virtual Qt::DropActions supportedDragActions() const; + + /// what drop actions do we support? + virtual Qt::DropActions supportedDropActions() const; + + void startWatching(); + void stopWatching(); + + virtual bool isValid(); + + QList dirs() const { return m_dirs; } + + QList instDirPaths() const; + + const QList& allWorlds() const { return m_worlds; } + + private slots: + void directoryChanged(QString path); + void loadWorldsAsync(); + + signals: + void changed(); + + protected: + QList m_instances; + QFileSystemWatcher* m_watcher; + bool m_isWatching; + QList m_dirs; + QList m_worlds; +}; diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp new file mode 100644 index 000000000..3698e9356 --- /dev/null +++ b/launcher/ui/MultiWorldListPage.cpp @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad + * + * 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 . + * + * 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 "MultiWorldListPage.h" +#include "minecraft/MultiWorldList.h" +#include "ui/dialogs/CustomMessageBox.h" +#include "ui_WorldListPage.h" //fix this iy + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FileSystem.h" +#include "tools/MCEditTool.h" + +#include "DesktopServices.h" +#include "ui/GuiUtil.h" + +#include "Application.h" +#include "pages/instance/DataPackPage.h" + +class WorldListProxyModel : public QSortFilterProxyModel { + Q_OBJECT + + public: + WorldListProxyModel(QObject* parent) : QSortFilterProxyModel(parent) {} + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const + { + QModelIndex sourceIndex = mapToSource(index); + + if (index.column() == 0 && role == Qt::DecorationRole) { + MultiWorldList* worlds = qobject_cast(sourceModel()); + auto iconFile = worlds->data(sourceIndex, MultiWorldList::IconFileRole).toString(); + if (iconFile.isNull()) { + // NOTE: Minecraft uses the same placeholder for servers AND worlds + return QIcon::fromTheme("unknown_server"); + } + return QIcon(iconFile); + } + + return sourceIndex.data(role); + } +}; + +MultiWorldListPage::MultiWorldListPage(MinecraftInstance* inst, MultiWorldList* worlds, QWidget* parent) + : QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds) //use MultiWorldListPage.ui to have separate ui from normal world list page iy +{ + ui->setupUi(this); + + ui->toolBar->insertSpacer(ui->actionRefresh); + + WorldListProxyModel* proxy = new WorldListProxyModel(this); + proxy->setSortCaseSensitivity(Qt::CaseInsensitive); + proxy->setSourceModel(m_worlds); + proxy->setSortRole(Qt::UserRole); + ui->worldTreeView->setSortingEnabled(true); + ui->worldTreeView->setModel(proxy); + ui->worldTreeView->installEventFilter(this); + ui->worldTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + ui->worldTreeView->setIconSize(QSize(64, 64)); + connect(ui->worldTreeView, &QTreeView::customContextMenuRequested, this, &MultiWorldListPage::ShowContextMenu); + + auto head = ui->worldTreeView->header(); + head->setSectionResizeMode(0, QHeaderView::Stretch); + head->setSectionResizeMode(1, QHeaderView::ResizeToContents); + head->setSectionResizeMode(4, QHeaderView::ResizeToContents); + + connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &MultiWorldListPage::worldChanged); + worldChanged(QModelIndex(), QModelIndex()); +} + +void MultiWorldListPage::openedImpl() +{ + m_worlds->startWatching(); + + if (!m_inst || !m_inst->traits().contains("feature:is_quick_play_singleplayer")) { + ui->toolBar->removeAction(ui->actionJoin); + } + + auto const setting_name = QString("WideBarVisibility_%1").arg(id()); + m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); + + ui->toolBar->setVisibilityState(QByteArray::fromBase64(m_wide_bar_setting->get().toString().toUtf8())); +} + +void MultiWorldListPage::closedImpl() +{ + m_worlds->stopWatching(); + + m_wide_bar_setting->set(QString::fromUtf8(ui->toolBar->getVisibilityState().toBase64())); +} + +MultiWorldListPage::~MultiWorldListPage() +{ + m_worlds->stopWatching(); + delete ui; +} + +void MultiWorldListPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->worldTreeView->mapToGlobal(pos)); + delete menu; +} + +QMenu* MultiWorldListPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction(ui->toolBar->toggleViewAction()); + return filteredMenu; +} + +bool MultiWorldListPage::shouldDisplay() const +{ + return true; +} + +void MultiWorldListPage::retranslate() +{ + ui->retranslateUi(this); +} + +bool MultiWorldListPage::worldListFilter(QKeyEvent* keyEvent) +{ + if (keyEvent->key() == Qt::Key_Delete) { + on_actionRemove_triggered(); + return true; + } + return QWidget::eventFilter(ui->worldTreeView, keyEvent); +} + +bool MultiWorldListPage::eventFilter(QObject* obj, QEvent* ev) +{ + if (ev->type() != QEvent::KeyPress) { + return QWidget::eventFilter(obj, ev); + } + QKeyEvent* keyEvent = static_cast(ev); + if (obj == ui->worldTreeView) + return worldListFilter(keyEvent); + return QWidget::eventFilter(obj, ev); +} + +void MultiWorldListPage::on_actionRemove_triggered() +{ + auto proxiedIndex = getSelectedWorld(); + + if (!proxiedIndex.isValid()) + return; + + auto result = CustomMessageBox::selectable(this, tr("Confirm Deletion"), + tr("You are about to delete \"%1\".\n" + "The world may be gone forever (A LONG TIME).\n\n" + "Are you sure?") + .arg(m_worlds->allWorlds().at(proxiedIndex.row()).name()), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (result != QMessageBox::Yes) { + return; + } + m_worlds->stopWatching(); + m_worlds->deleteWorld(proxiedIndex.row()); + m_worlds->startWatching(); +} + +void MultiWorldListPage::on_actionView_Folder_triggered() +{ + DesktopServices::openPath(m_worlds->dirs()[0].absolutePath(), true); //change this iy +} + +void MultiWorldListPage::on_actionData_Packs_triggered() +{ + QModelIndex index = getSelectedWorld(); + + if (!index.isValid()) { + return; + } + + if (!worldSafetyNagQuestion(tr("Manage Data Packs"))) + return; + + const QString fullPath = m_worlds->data(index, MultiWorldList::FolderRole).toString(); + const QString folder = FS::PathCombine(fullPath, "datapacks"); + + auto dialog = new QDialog(this); + dialog->setWindowTitle(tr("Data packs for %1").arg(m_worlds->data(index, MultiWorldList::NameRole).toString())); + dialog->setWindowModality(Qt::WindowModal); + + dialog->resize(static_cast(std::max(0.5 * window()->width(), 400.0)), + static_cast(std::max(0.75 * window()->height(), 400.0))); + dialog->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("DataPackDownloadGeometry").toByteArray())); + + GenericPageProvider provider(dialog->windowTitle()); + + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_datapackModel.reset(new DataPackFolderModel(folder, m_inst, isIndexed, true)); + + provider.addPageCreator([this] { return new DataPackPage(m_inst, m_datapackModel.get(), this); }); + + auto layout = new QVBoxLayout(dialog); + + auto focusStealer = new QPushButton(dialog); + layout->addWidget(focusStealer); + focusStealer->setDefault(true); + focusStealer->hide(); + + auto pageContainer = new PageContainer(&provider, {}, dialog); + pageContainer->hidePageList(); + layout->addWidget(pageContainer); + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close | QDialogButtonBox::Help); + connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); + connect(buttonBox, &QDialogButtonBox::helpRequested, pageContainer, &PageContainer::help); + layout->addWidget(buttonBox); + + dialog->setLayout(layout); + + dialog->exec(); + + APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); +} + +void MultiWorldListPage::on_actionReset_Icon_triggered() +{ + auto proxiedIndex = getSelectedWorld(); + + if (!proxiedIndex.isValid()) + return; + + if (m_worlds->resetIcon(proxiedIndex.row())) { + ui->actionReset_Icon->setEnabled(false); + } +} + +QModelIndex MultiWorldListPage::getSelectedWorld() +{ + auto index = ui->worldTreeView->selectionModel()->currentIndex(); + + auto proxy = (QSortFilterProxyModel*)ui->worldTreeView->model(); + return proxy->mapToSource(index); +} + +void MultiWorldListPage::on_actionCopy_Seed_triggered() +{ + QModelIndex index = getSelectedWorld(); + + if (!index.isValid()) { + return; + } + int64_t seed = m_worlds->data(index, MultiWorldList::SeedRole).toLongLong(); + APPLICATION->clipboard()->setText(QString::number(seed)); +} + +void MultiWorldListPage::on_actionMCEdit_triggered() +{ + if (m_mceditStarting) + return; + + auto mcedit = APPLICATION->mcedit(); + + const QString mceditPath = mcedit->path(); + + QModelIndex index = getSelectedWorld(); + + if (!index.isValid()) { + return; + } + + if (!worldSafetyNagQuestion(tr("Open World in MCEdit"))) + return; + + auto fullPath = m_worlds->data(index, MultiWorldList::FolderRole).toString(); + + auto program = mcedit->getProgramPath(); + if (program.size()) { +#ifdef Q_OS_WIN32 + if (!QProcess::startDetached(program, { fullPath }, mceditPath)) { + mceditError(); + } +#else + m_mceditProcess.reset(new LoggedProcess()); + m_mceditProcess->setDetachable(true); + connect(m_mceditProcess.get(), &LoggedProcess::stateChanged, this, &MultiWorldListPage::mceditState); + m_mceditProcess->start(program, { fullPath }); + m_mceditProcess->setWorkingDirectory(mceditPath); + m_mceditStarting = true; +#endif + } else { + QMessageBox::warning(this->parentWidget(), tr("No MCEdit found or set up!"), + tr("You do not have MCEdit set up or it was moved.\nYou can set it up in the global settings.")); + } +} + +void MultiWorldListPage::mceditError() +{ + QMessageBox::warning(this->parentWidget(), tr("MCEdit failed to start!"), + tr("MCEdit failed to start.\nIt may be necessary to reinstall it.")); +} + +void MultiWorldListPage::mceditState(LoggedProcess::State state) +{ + bool failed = false; + switch (state) { + case LoggedProcess::NotRunning: + case LoggedProcess::Starting: + return; + case LoggedProcess::FailedToStart: + case LoggedProcess::Crashed: + case LoggedProcess::Aborted: { + failed = true; + } + /* fallthrough */ + case LoggedProcess::Running: + case LoggedProcess::Finished: { + m_mceditStarting = false; + break; + } + } + if (failed) { + mceditError(); + } +} + +void MultiWorldListPage::worldChanged([[maybe_unused]] const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) +{ + QModelIndex index = getSelectedWorld(); + bool enable = index.isValid(); + ui->actionCopy_Seed->setEnabled(enable); + ui->actionMCEdit->setEnabled(enable); + ui->actionRemove->setEnabled(enable); + ui->actionCopy->setEnabled(enable); + ui->actionRename->setEnabled(enable); + ui->actionData_Packs->setEnabled(enable); + bool hasIcon = !index.data(MultiWorldList::IconFileRole).isNull(); + ui->actionReset_Icon->setEnabled(enable && hasIcon); + + auto supportsJoin = m_inst && m_inst->traits().contains("feature:is_quick_play_singleplayer"); + ui->actionJoin->setEnabled(enable && supportsJoin); + + if (!supportsJoin) { + ui->toolBar->removeAction(ui->actionJoin); + } +} + +void MultiWorldListPage::on_actionAdd_triggered() +{ + auto list = GuiUtil::BrowseForFiles(displayName(), tr("Select a Minecraft world zip"), tr("Minecraft World Zip File") + " (*.zip)", + QString(), this->parentWidget()); + if (!list.empty()) { + m_worlds->stopWatching(); + for (auto filename : list) { + m_worlds->installWorld(QFileInfo(filename)); + } + m_worlds->startWatching(); + } +} + +bool MultiWorldListPage::isWorldSafe(QModelIndex) +{ + return !m_inst->isRunning(); +} + +bool MultiWorldListPage::worldSafetyNagQuestion(const QString& actionType) +{ + if (!isWorldSafe(getSelectedWorld())) { + auto result = QMessageBox::question( + this, actionType, tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?")); + if (result == QMessageBox::No) { + return false; + } + } + return true; +} + +void MultiWorldListPage::on_actionCopy_triggered() +{ + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) { + return; + } + + if (!worldSafetyNagQuestion(tr("Copy World"))) + return; + + auto worldVariant = m_worlds->data(index, MultiWorldList::ObjectRole); + auto world = (World*)worldVariant.value(); + bool ok = false; + QString name = + QInputDialog::getText(this, tr("World name"), tr("Enter a new name for the copy."), QLineEdit::Normal, world->name(), &ok); + + if (ok && name.length() > 0) { + world->install(m_worlds->dirs()[0].absolutePath(), name); //you know what to do iy + } +} + +void MultiWorldListPage::on_actionRename_triggered() +{ + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) { + return; + } + + if (!worldSafetyNagQuestion(tr("Rename World"))) + return; + + auto worldVariant = m_worlds->data(index, MultiWorldList::ObjectRole); + auto world = (World*)worldVariant.value(); + + bool ok = false; + QString name = QInputDialog::getText(this, tr("World name"), tr("Enter a new world name."), QLineEdit::Normal, world->name(), &ok); + + if (ok && name.length() > 0) { + world->rename(name); + } +} + +void MultiWorldListPage::on_actionRefresh_triggered() +{ + m_worlds->update(); +} + +void MultiWorldListPage::on_actionJoin_triggered() +{ + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) { + return; + } + auto worldVariant = m_worlds->data(index, MultiWorldList::ObjectRole); + auto world = (World*)worldVariant.value(); + APPLICATION->launch(m_inst, LaunchMode::Normal, std::make_shared(MinecraftTarget::parse(world->folderName(), true))); +} + +#include "WorldListPage.moc" //change this iy diff --git a/launcher/ui/MultiWorldListPage.h b/launcher/ui/MultiWorldListPage.h new file mode 100644 index 000000000..e9c9dd530 --- /dev/null +++ b/launcher/ui/MultiWorldListPage.h @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield + * + * 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 . + * + * 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. + */ + +#pragma once + +#include + +#include +#include "minecraft/MinecraftInstance.h" +#include "ui/pages/BasePage.h" + +#include "settings/Setting.h" + +class MultiWorldList; +namespace Ui { +class MultiWorldListPage; +} + +class MultiWorldListPage : public QMainWindow, public BasePage { + Q_OBJECT + + public: + explicit MultiWorldListPage(MinecraftInstance* inst, MultiWorldList* worlds, QWidget* parent = 0); + virtual ~MultiWorldListPage(); + + virtual QString displayName() const override { return tr("Worlds"); } + virtual QIcon icon() const override { return QIcon::fromTheme("worlds"); } + virtual QString id() const override { return "worlds"; } + virtual QString helpPage() const override { return "Worlds"; } + virtual bool shouldDisplay() const override; + void retranslate() override; + + virtual void openedImpl() override; + virtual void closedImpl() override; + + protected: + bool eventFilter(QObject* obj, QEvent* ev) override; + bool worldListFilter(QKeyEvent* ev); + QMenu* createPopupMenu() override; + + protected: + MinecraftInstance* m_inst; + + private: + QModelIndex getSelectedWorld(); + bool isWorldSafe(QModelIndex index); + bool worldSafetyNagQuestion(const QString& actionType); + void mceditError(); + + private: + Ui::WorldListPage* ui; + MultiWorldList* m_worlds; + unique_qobject_ptr m_mceditProcess; + bool m_mceditStarting = false; + + std::shared_ptr m_wide_bar_setting = nullptr; + std::unique_ptr m_datapackModel; + + private slots: + void on_actionCopy_Seed_triggered(); + void on_actionMCEdit_triggered(); + void on_actionRemove_triggered(); + void on_actionAdd_triggered(); + void on_actionCopy_triggered(); + void on_actionRename_triggered(); + void on_actionRefresh_triggered(); + void on_actionView_Folder_triggered(); + void on_actionData_Packs_triggered(); + void on_actionReset_Icon_triggered(); + void worldChanged(const QModelIndex& current, const QModelIndex& previous); + void mceditState(LoggedProcess::State state); + void on_actionJoin_triggered(); + + void ShowContextMenu(const QPoint& pos); +}; diff --git a/launcher/ui/MultiWorldListPage.ui b/launcher/ui/MultiWorldListPage.ui new file mode 100644 index 000000000..874721eee --- /dev/null +++ b/launcher/ui/MultiWorldListPage.ui @@ -0,0 +1,170 @@ + + + MultiWorldListPage + + + + 0 + 0 + 800 + 600 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + true + + + QAbstractItemView::DragDrop + + + true + + + false + + + false + + + true + + + true + + + QAbstractItemView::ScrollPerPixel + + + false + + + + + + + + Actions + + + Qt::LeftToolBarArea|Qt::RightToolBarArea + + + Qt::ToolButtonTextOnly + + + false + + + RightToolBarArea + + + false + + + + + + + + + + + + + + + + + + Add + + + + + Join + + + + + Rename + + + + + Copy + + + + + Delete + + + + + MCEdit + + + + + Copy Seed + + + + + Refresh + + + + + View Folder + + + + + Reset Icon + + + Remove world icon to make the game re-generate it on next load. + + + + + Data Packs + + + Manage data packs inside the world. + + + + + + WideBar + QToolBar +
ui/widgets/WideBar.h
+
+
+ + +
From e4c48e5e60fde0a683bab891c292156c1c120f2e Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Tue, 26 May 2026 20:22:23 -0400 Subject: [PATCH 02/20] commit CMakeLists --- launcher/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 7d4430fd2..f72445f61 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -298,6 +298,8 @@ set(MINECRAFT_SOURCES minecraft/MinecraftLoadAndCheck.cpp minecraft/MojangVersionFormat.cpp minecraft/MojangVersionFormat.h + minecraft/MultiWorldList.cpp + minecraft/MultiWorldList.h minecraft/Rule.cpp minecraft/Rule.h minecraft/OneSixVersionFormat.cpp @@ -832,6 +834,8 @@ SET(LAUNCHER_SOURCES ui/GuiUtil.cpp ui/MainWindow.h ui/MainWindow.cpp + ui/MultiWorldListPage.cpp + ui/MultiWorldListPage.h ui/InstanceWindow.h ui/InstanceWindow.cpp ui/ViewLogWindow.h From 66b20c990fc354576c52784b09fa1e2357e98d4e Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Tue, 26 May 2026 21:31:34 -0400 Subject: [PATCH 03/20] fix ui/miscellanous issues --- launcher/minecraft/MultiWorldList.cpp | 4 ++-- launcher/minecraft/MultiWorldList.h | 3 ++- launcher/ui/MultiWorldListPage.cpp | 12 ++++++------ launcher/ui/MultiWorldListPage.h | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/launcher/minecraft/MultiWorldList.cpp b/launcher/minecraft/MultiWorldList.cpp index 48d3a3c18..4851f63b7 100644 --- a/launcher/minecraft/MultiWorldList.cpp +++ b/launcher/minecraft/MultiWorldList.cpp @@ -46,7 +46,7 @@ #include #include -MultiWorldList::MultiWorldList(const QList dirs, QList instances) : QAbstractListModel(), m_instances(instances) +MultiWorldList::MultiWorldList(const QList& dirs, QList instances) : QAbstractListModel(), m_instances(instances) { for (int i = 0; i < dirs.length(); i++) { // better way to do this? iy m_dirs[i] = dirs[i]; @@ -413,7 +413,7 @@ bool MultiWorldList::dropMimeData(const QMimeData* data, return false; } -int64_t calculateWorldSize(const QFileInfo& file) +int64_t MultiWorldList::calculateWorldSize(const QFileInfo& file) { if (file.isFile() && file.suffix() == "zip") { return file.size(); diff --git a/launcher/minecraft/MultiWorldList.h b/launcher/minecraft/MultiWorldList.h index eff3bfe22..1e0a7e60e 100644 --- a/launcher/minecraft/MultiWorldList.h +++ b/launcher/minecraft/MultiWorldList.h @@ -32,7 +32,7 @@ class MultiWorldList : public QAbstractListModel { enum Roles { ObjectRole = Qt::UserRole + 1, FolderRole, SeedRole, NameRole, GameModeRole, LastPlayedRole, SizeRole, IconFileRole }; - MultiWorldList(const QList dirs, QList instances); + MultiWorldList(const QList& dirs, QList instances); virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; @@ -68,6 +68,7 @@ class MultiWorldList : public QAbstractListModel { /// process data from drop action virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); /// what drag actions do we support? + int64_t calculateWorldSize(const QFileInfo& file); virtual Qt::DropActions supportedDragActions() const; /// what drop actions do we support? diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index 3698e9356..768ab4a08 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -38,7 +38,7 @@ #include "MultiWorldListPage.h" #include "minecraft/MultiWorldList.h" #include "ui/dialogs/CustomMessageBox.h" -#include "ui_WorldListPage.h" //fix this iy +#include "ui_MultiWorldListPage.h" //fix this iy #include #include @@ -62,11 +62,11 @@ #include "Application.h" #include "pages/instance/DataPackPage.h" -class WorldListProxyModel : public QSortFilterProxyModel { +class MultiWorldListProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - WorldListProxyModel(QObject* parent) : QSortFilterProxyModel(parent) {} + MultiWorldListProxyModel(QObject* parent) : QSortFilterProxyModel(parent) {} virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const { @@ -87,13 +87,13 @@ class WorldListProxyModel : public QSortFilterProxyModel { }; MultiWorldListPage::MultiWorldListPage(MinecraftInstance* inst, MultiWorldList* worlds, QWidget* parent) - : QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds) //use MultiWorldListPage.ui to have separate ui from normal world list page iy + : QMainWindow(parent), m_inst(inst), ui(new Ui::MultiWorldListPage), m_worlds(worlds) //use MultiWorldListPage.ui to have separate ui from normal world list page iy { ui->setupUi(this); ui->toolBar->insertSpacer(ui->actionRefresh); - WorldListProxyModel* proxy = new WorldListProxyModel(this); + MultiWorldListProxyModel* proxy = new MultiWorldListProxyModel(this); proxy->setSortCaseSensitivity(Qt::CaseInsensitive); proxy->setSourceModel(m_worlds); proxy->setSortRole(Qt::UserRole); @@ -474,4 +474,4 @@ void MultiWorldListPage::on_actionJoin_triggered() APPLICATION->launch(m_inst, LaunchMode::Normal, std::make_shared(MinecraftTarget::parse(world->folderName(), true))); } -#include "WorldListPage.moc" //change this iy +#include "MultiWorldListPage.moc" //change this iy diff --git a/launcher/ui/MultiWorldListPage.h b/launcher/ui/MultiWorldListPage.h index e9c9dd530..91cc088cf 100644 --- a/launcher/ui/MultiWorldListPage.h +++ b/launcher/ui/MultiWorldListPage.h @@ -80,7 +80,7 @@ class MultiWorldListPage : public QMainWindow, public BasePage { void mceditError(); private: - Ui::WorldListPage* ui; + Ui::MultiWorldListPage* ui; MultiWorldList* m_worlds; unique_qobject_ptr m_mceditProcess; bool m_mceditStarting = false; From c2950552794a1cb93eee9cb7e2bdb4f8d81b301b Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Wed, 27 May 2026 16:48:14 -0400 Subject: [PATCH 04/20] clean up watchers --- launcher/minecraft/MultiWorldList.cpp | 56 +++++++++++++++++---------- launcher/ui/MultiWorldListPage.cpp | 10 ++--- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/launcher/minecraft/MultiWorldList.cpp b/launcher/minecraft/MultiWorldList.cpp index 4851f63b7..c32f8cabc 100644 --- a/launcher/minecraft/MultiWorldList.cpp +++ b/launcher/minecraft/MultiWorldList.cpp @@ -48,7 +48,7 @@ MultiWorldList::MultiWorldList(const QList& dirs, QList instances) : QAbstractListModel(), m_instances(instances) { - for (int i = 0; i < dirs.length(); i++) { // better way to do this? iy + for (int i = 0; i < dirs.length(); i++) { m_dirs[i] = dirs[i]; } @@ -63,30 +63,40 @@ MultiWorldList::MultiWorldList(const QList& dirs, QList connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &MultiWorldList::directoryChanged); } -void MultiWorldList::startWatching() //figure out what watchers do / if necessary and do all paths iy +void MultiWorldList::startWatching() { if (m_isWatching) { return; } update(); - m_isWatching = m_watcher->addPath(m_dirs[0].absolutePath()); - if (m_isWatching) { - qDebug() << "Started watching" << m_dirs[0].absolutePath(); - } else { - qDebug() << "Failed to start watching" << m_dirs[0].absolutePath(); + + m_isWatching = true; + + for (QDir dir : m_dirs) { + if (m_watcher->addPath(dir.absolutePath())) { + qDebug() << "Started watching" << dir.absolutePath(); + } else { + m_isWatching = false; + qDebug() << "Failed to start watching" << dir.absolutePath(); + } } } -void MultiWorldList::stopWatching() //same as above function iy +void MultiWorldList::stopWatching() { if (!m_isWatching) { return; } - m_isWatching = !m_watcher->removePath(m_dirs[0].absolutePath()); - if (!m_isWatching) { - qDebug() << "Stopped watching" << m_dirs[0].absolutePath(); - } else { - qDebug() << "Failed to stop watching" << m_dirs[0].absolutePath(); + + m_isWatching = false; + + for (QDir dir : m_dirs) { + if (m_watcher->removePath(dir.absolutePath())) { + qDebug() << "Stopped watching" << dir.absolutePath(); + } else { + m_isWatching = true; + qDebug() << "Failed to stop watching" << dir.absolutePath(); + } } } @@ -216,8 +226,10 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const return locale.formattedDataSize(world.bytes()); case InfoColumn: - if (world.isSymLinkUnder(instDirPaths()[0])) { //FIX THIS--NO INDEX 0 iy - return tr("This world is symbolically linked from elsewhere."); + for (QString path : instDirPaths()) { //use canonical paths instead of for loops? iy + if (world.isSymLinkUnder(path)) { + return tr("This world is symbolically linked from elsewhere."); + } } if (world.isMoreThanOneHardLink()) { return tr("\nThis world is hard linked elsewhere."); @@ -234,10 +246,12 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const case Qt::ToolTipRole: { if (column == InfoColumn) { - if (world.isSymLinkUnder(instDirPaths()[0])) { //SAME HERE iy - return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original." + for (QString path : instDirPaths()) { //use canonical paths instead of for loops? iy + if (world.isSymLinkUnder(path)) { + return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original." "\nCanonical Path: %1") - .arg(world.canonicalFilePath()); + .arg(world.canonicalFilePath()); + } } if (world.isMoreThanOneHardLink()) { return tr("Warning: This world is hard linked elsewhere. Editing it will also change the original."); @@ -249,7 +263,7 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const return QVariant::fromValue((void*)&world); } case FolderRole: { - return QDir::toNativeSeparators(dirs()[0].absoluteFilePath(world.folderName())); //SAME HERE iy + return QDir::toNativeSeparators(QDir(world.canonicalFilePath()).absoluteFilePath(world.folderName())); //test if canonical file path works iy } case SeedRole: { return QVariant::fromValue(world.seed()); @@ -374,7 +388,7 @@ void MultiWorldList::installWorld(QFileInfo filename) if (!w.isValid()) { return; } - w.install(m_dirs[0].absolutePath()); //more directory stuff iy + w.install(m_dirs[0].absolutePath()); //add option for which instance to install to iy } bool MultiWorldList::dropMimeData(const QMimeData* data, @@ -402,7 +416,7 @@ bool MultiWorldList::dropMimeData(const QMimeData* data, QFileInfo worldInfo(filename); - if (!m_dirs[0].entryInfoList().contains(worldInfo)) { //more stuff to fix iy + if (!m_dirs[0].entryInfoList().contains(worldInfo)) { //same as above, add option for which instance to install to iy installWorld(worldInfo); } } diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index 768ab4a08..da527cc4f 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -38,7 +38,7 @@ #include "MultiWorldListPage.h" #include "minecraft/MultiWorldList.h" #include "ui/dialogs/CustomMessageBox.h" -#include "ui_MultiWorldListPage.h" //fix this iy +#include "ui_MultiWorldListPage.h" #include #include @@ -87,7 +87,7 @@ class MultiWorldListProxyModel : public QSortFilterProxyModel { }; MultiWorldListPage::MultiWorldListPage(MinecraftInstance* inst, MultiWorldList* worlds, QWidget* parent) - : QMainWindow(parent), m_inst(inst), ui(new Ui::MultiWorldListPage), m_worlds(worlds) //use MultiWorldListPage.ui to have separate ui from normal world list page iy + : QMainWindow(parent), m_inst(inst), ui(new Ui::MultiWorldListPage), m_worlds(worlds) { ui->setupUi(this); @@ -209,7 +209,7 @@ void MultiWorldListPage::on_actionRemove_triggered() void MultiWorldListPage::on_actionView_Folder_triggered() { - DesktopServices::openPath(m_worlds->dirs()[0].absolutePath(), true); //change this iy + DesktopServices::openPath(m_worlds->dirs()[0].absolutePath(), true); //remove view folder option for all worlds (make it world specific) iy } void MultiWorldListPage::on_actionData_Packs_triggered() @@ -433,7 +433,7 @@ void MultiWorldListPage::on_actionCopy_triggered() QInputDialog::getText(this, tr("World name"), tr("Enter a new name for the copy."), QLineEdit::Normal, world->name(), &ok); if (ok && name.length() > 0) { - world->install(m_worlds->dirs()[0].absolutePath(), name); //you know what to do iy + world->install(m_worlds->dirs()[0].absolutePath(), name); //ask which instance iy } } @@ -474,4 +474,4 @@ void MultiWorldListPage::on_actionJoin_triggered() APPLICATION->launch(m_inst, LaunchMode::Normal, std::make_shared(MinecraftTarget::parse(world->folderName(), true))); } -#include "MultiWorldListPage.moc" //change this iy +#include "MultiWorldListPage.moc" From 4d1141f274fd2013e1341d39463dc957c954a8d6 Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Wed, 27 May 2026 20:27:27 -0400 Subject: [PATCH 05/20] start work on ui --- launcher/InstanceList.cpp | 39 +++++++++++++++++---------- launcher/InstanceList.h | 7 ++--- launcher/minecraft/MultiWorldList.cpp | 4 +-- launcher/ui/MainWindow.cpp | 33 +++++++++++++++++++++++ launcher/ui/MainWindow.h | 4 +++ launcher/ui/MultiWorldListPage.cpp | 19 ++++++------- 6 files changed, 78 insertions(+), 28 deletions(-) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 1339499c7..8eb43d5da 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -138,7 +138,7 @@ QMimeData* InstanceList::mimeData(const QModelIndexList& indexes) const QStringList InstanceList::getLinkedInstancesById(const QString& id) const { QStringList linkedInstances; - for (auto& inst : m_instances) { + for (auto& inst : instances) { if (inst->isLinkedToInstanceId(id)) linkedInstances.append(inst->id()); } @@ -156,7 +156,7 @@ QModelIndex InstanceList::index(int row, int column, const QModelIndex& parent) Q_UNUSED(parent); if (row < 0 || row >= count()) return QModelIndex(); - return createIndex(row, column, m_instances.at(row).get()); + return createIndex(row, column, instances.at(row).get()); } QVariant InstanceList::data(const QModelIndex& index, int role) const @@ -279,7 +279,7 @@ void InstanceList::deleteGroup(const GroupId& name) bool removed = false; qDebug() << "Delete group" << name; - for (auto& instance : m_instances) { + for (auto& instance : instances) { const QString& instID = instance->id(); const QString instGroupName = getInstanceGroup(instID); if (instGroupName == name) { @@ -303,7 +303,7 @@ void InstanceList::renameGroup(const QString& src, const QString& dst) bool modified = false; qDebug() << "Rename group" << src << "to" << dst; - for (auto& instance : m_instances) { + for (auto& instance : instances) { const QString& instID = instance->id(); const QString instGroupName = getInstanceGroup(instID); if (instGroupName == src) { @@ -497,7 +497,7 @@ QList InstanceList::discoverInstances() InstanceList::InstListError InstanceList::loadList() { - auto existingIds = getIdMapping(m_instances); + auto existingIds = getIdMapping(instances); std::vector> newList; @@ -525,7 +525,7 @@ InstanceList::InstListError InstanceList::loadList() int currentItem = -1; auto removeNow = [this, &front_bookmark, &back_bookmark, ¤tItem]() { beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark); - m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1); + instances.erase(instances.begin() + front_bookmark, instances.begin() + back_bookmark + 1); endRemoveRows(); front_bookmark = -1; back_bookmark = currentItem; @@ -560,14 +560,14 @@ InstanceList::InstListError InstanceList::loadList() void InstanceList::updateTotalPlayTime() { totalPlayTime = 0; - for (const auto& itr : m_instances) { + for (const auto& itr : instances) { totalPlayTime += itr->totalTimePlayed(); } } void InstanceList::saveNow() { - for (auto& item : m_instances) { + for (auto& item : instances) { item->saveNow(); } } @@ -576,8 +576,8 @@ void InstanceList::add(std::vector>& t) { beginInsertRows(QModelIndex(), count(), static_cast(count() + t.size() - 1)); for (auto& ptr : t) { - m_instances.push_back(std::move(ptr)); - connect(m_instances.back().get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged); + instances.push_back(std::move(ptr)); + connect(instances.back().get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged); } endInsertRows(); } @@ -607,11 +607,22 @@ void InstanceList::providerUpdated() } } +QList InstanceList::getAllInstances() +{ + QList instanceList; + + for (auto& inst : instances) { + instanceList.append(inst.get()); + } + + return instanceList; +} + BaseInstance* InstanceList::getInstanceById(QString instId) const { if (instId.isEmpty()) return nullptr; - for (auto& inst : m_instances) { + for (auto& inst : instances) { if (inst->id() == instId) { return inst.get(); } @@ -624,7 +635,7 @@ BaseInstance* InstanceList::getInstanceByManagedName(const QString& managed_name if (managed_name.isEmpty()) return {}; - for (auto& instance : m_instances) { + for (auto& instance : instances) { if (instance->getManagedPackName() == managed_name) return instance.get(); } @@ -641,7 +652,7 @@ int InstanceList::getInstIndex(BaseInstance* inst) const { int count = this->count(); for (int i = 0; i < count; i++) { - if (inst == m_instances.at(i).get()) { + if (inst == instances.at(i).get()) { return i; } } @@ -882,7 +893,7 @@ void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting, m_instDir = newInstDir; m_groupsLoaded = false; beginRemoveRows(QModelIndex(), 0, count()); - m_instances.erase(m_instances.begin(), m_instances.end()); + instances.erase(instances.begin(), instances.end()); endRemoveRows(); emit instancesChanged(); } diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index f0a92d273..76ac01bc3 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -96,13 +96,14 @@ class InstanceList : public QAbstractListModel { */ enum InstListError { NoError = 0, UnknownError }; - BaseInstance* at(int i) const { return m_instances.at(i).get(); } + BaseInstance* at(int i) const { return instances.at(i).get(); } - int count() const { return static_cast(m_instances.size()); } + int count() const { return static_cast(instances.size()); } InstListError loadList(); void saveNow(); + QList getAllInstances(); /* O(n) */ BaseInstance* getInstanceById(QString id) const; /* O(n) */ @@ -192,7 +193,7 @@ class InstanceList : public QAbstractListModel { int m_watchLevel = 0; int totalPlayTime = 0; bool m_dirty = false; - std::vector> m_instances; + std::vector> instances; // id -> refs QMap m_groupNameCache; diff --git a/launcher/minecraft/MultiWorldList.cpp b/launcher/minecraft/MultiWorldList.cpp index c32f8cabc..258768c18 100644 --- a/launcher/minecraft/MultiWorldList.cpp +++ b/launcher/minecraft/MultiWorldList.cpp @@ -48,8 +48,8 @@ MultiWorldList::MultiWorldList(const QList& dirs, QList instances) : QAbstractListModel(), m_instances(instances) { - for (int i = 0; i < dirs.length(); i++) { - m_dirs[i] = dirs[i]; + for (QString dir : dirs) { + m_dirs.append(dir); } for (QDir dir : m_dirs) { diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 1bdcd3f68..42611ab88 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -93,6 +93,7 @@ #include "InstanceWindow.h" #include "ui/GuiUtil.h" +#include "ui/MultiWorldListPage.h" #include "ui/ViewLogWindow.h" #include "ui/dialogs/AboutDialog.h" #include "ui/dialogs/CopyInstanceDialog.h" @@ -113,6 +114,7 @@ #include "ui/themes/ThemeManager.h" #include "ui/widgets/LabeledToolButton.h" +#include "minecraft/MultiWorldList.h" #include "minecraft/PackProfile.h" #include "minecraft/VersionFile.h" #include "minecraft/WorldList.h" @@ -334,6 +336,22 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi connect(view, &InstanceView::groupStateChanged, APPLICATION->instances(), &InstanceList::on_GroupStateChanged); ui->horizontalLayout->addWidget(view); } + // Create the all worlds widget + { + QList allInstances = APPLICATION->instances()->getAllInstances(); + qDebug() << "iy initially" << allInstances.length(); + QList dirs; + for (BaseInstance* inst : allInstances) { + dirs.append(dynamic_cast(inst)->worldDir()); + } + + allWorlds = new MultiWorldList(dirs, allInstances); + allWorlds->update(); + allWorldsPage = new MultiWorldListPage(dynamic_cast(allInstances[0]), allWorlds); //shouldnt be only one instance iy + + ui->horizontalLayout->addWidget(allWorldsPage); + } + // The cat background { // set the cat action priority here so you can still see the action in qt designer @@ -926,6 +944,21 @@ void MainWindow::addInstance(const QString& url, const QMap& e if (creationTask) { instanceFromInstanceTask(creationTask); } + + //clean this up iy - fix ghosting issue and only joining one world despite which one you click + + QList allInstances = APPLICATION->instances()->getAllInstances(); + qDebug() << "iy finally " << allInstances.length(); + QList dirs; + for (BaseInstance* inst : allInstances) { + dirs.append(dynamic_cast(inst)->worldDir()); + } + + allWorlds = new MultiWorldList(dirs, allInstances); + allWorlds->update(); + auto newAllWorldsPage = new MultiWorldListPage(dynamic_cast(allInstances[0]), allWorlds); //shouldnt be only one instance iy + ui->horizontalLayout->replaceWidget(allWorldsPage, newAllWorldsPage); + allWorldsPage = newAllWorldsPage; } void MainWindow::on_actionAddInstance_triggered() diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 80860deef..adc13cfe1 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -58,6 +58,8 @@ class QLabel; class MinecraftLauncher; class BaseProfilerFactory; class InstanceView; +class MultiWorldList; +class MultiWorldListPage; class KonamiCode; class InstanceTask; class LabeledToolButton; @@ -236,6 +238,7 @@ class MainWindow : public QMainWindow { Ui::MainWindow* ui; // these are managed by Qt's memory management model! InstanceView* view = nullptr; + MultiWorldListPage* allWorldsPage = nullptr; InstanceProxyModel* proxymodel = nullptr; QToolButton* newsLabel = nullptr; QLabel* m_statusLeft = nullptr; @@ -244,6 +247,7 @@ class MainWindow : public QMainWindow { LabeledToolButton* renameButton = nullptr; QToolButton* helpMenuButton = nullptr; KonamiCode* secretEventFilter = nullptr; + MultiWorldList* allWorlds = nullptr; std::shared_ptr instanceToolbarSetting = nullptr; diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index da527cc4f..16dc6a5c5 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -86,7 +86,7 @@ class MultiWorldListProxyModel : public QSortFilterProxyModel { } }; -MultiWorldListPage::MultiWorldListPage(MinecraftInstance* inst, MultiWorldList* worlds, QWidget* parent) +MultiWorldListPage::MultiWorldListPage(MinecraftInstance* inst, MultiWorldList* worlds, QWidget* parent) //require all instances instead of just one iy : QMainWindow(parent), m_inst(inst), ui(new Ui::MultiWorldListPage), m_worlds(worlds) { ui->setupUi(this); @@ -117,9 +117,9 @@ void MultiWorldListPage::openedImpl() { m_worlds->startWatching(); - if (!m_inst || !m_inst->traits().contains("feature:is_quick_play_singleplayer")) { - ui->toolBar->removeAction(ui->actionJoin); - } + // if (!m_inst || !m_inst->traits().contains("feature:is_quick_play_singleplayer")) { //get rid of this??? iy + // ui->toolBar->removeAction(ui->actionJoin); + // } auto const setting_name = QString("WideBarVisibility_%1").arg(id()); m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); @@ -378,12 +378,13 @@ void MultiWorldListPage::worldChanged([[maybe_unused]] const QModelIndex& curren bool hasIcon = !index.data(MultiWorldList::IconFileRole).isNull(); ui->actionReset_Icon->setEnabled(enable && hasIcon); - auto supportsJoin = m_inst && m_inst->traits().contains("feature:is_quick_play_singleplayer"); - ui->actionJoin->setEnabled(enable && supportsJoin); + // auto supportsJoin = m_inst && m_inst->traits().contains("feature:is_quick_play_singleplayer"); //change this to be on instance by instance basis iy + // ui->actionJoin->setEnabled(enable && supportsJoin); + ui->actionJoin->setEnabled(true); - if (!supportsJoin) { - ui->toolBar->removeAction(ui->actionJoin); - } + // if (!supportsJoin) { + // ui->toolBar->removeAction(ui->actionJoin); + // } } void MultiWorldListPage::on_actionAdd_triggered() From 3a6ae78bc64e91dcebd1360b449c551cdbbbcdab Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Sun, 31 May 2026 13:48:31 -0400 Subject: [PATCH 06/20] all worlds list will now join correct instance for each world --- launcher/minecraft/MultiWorldList.cpp | 77 +++++++++++++++------------ launcher/minecraft/MultiWorldList.h | 13 +++-- launcher/ui/MainWindow.cpp | 9 ++-- launcher/ui/MultiWorldListPage.cpp | 18 +++---- launcher/ui/MultiWorldListPage.h | 5 +- 5 files changed, 66 insertions(+), 56 deletions(-) diff --git a/launcher/minecraft/MultiWorldList.cpp b/launcher/minecraft/MultiWorldList.cpp index 258768c18..c43f84054 100644 --- a/launcher/minecraft/MultiWorldList.cpp +++ b/launcher/minecraft/MultiWorldList.cpp @@ -46,7 +46,9 @@ #include #include -MultiWorldList::MultiWorldList(const QList& dirs, QList instances) : QAbstractListModel(), m_instances(instances) +#include "MinecraftInstance.h" + +MultiWorldList::MultiWorldList(const QList& dirs, const QList& instances) : QAbstractListModel(), m_instances(instances) { for (QString dir : dirs) { m_dirs.append(dir); @@ -105,9 +107,10 @@ bool MultiWorldList::update() if (!isValid()) return false; - QList newWorlds; + QList newWorlds; - for (QDir dir : m_dirs) { + for (BaseInstance* inst : m_instances) { + QDir dir = dynamic_cast(inst)->worldDir(); dir.refresh(); auto folderContents = dir.entryInfoList(); // if there are any untracked files... @@ -117,7 +120,7 @@ bool MultiWorldList::update() World w(entry); if (w.isValid()) { - newWorlds.append(w); + newWorlds.append(InstanceWorld(w, inst)); } } } @@ -134,16 +137,24 @@ void MultiWorldList::directoryChanged(QString) update(); } -bool MultiWorldList::isValid() //account for all directories +bool MultiWorldList::isValid() { - return m_dirs[0].exists() && m_dirs[0].isReadable(); + bool valid = true; + + for (const QDir& dir : m_dirs) { + if (!(dir.exists() && dir.isReadable())) { + valid = false; + } + } + + return valid; } QList MultiWorldList::instDirPaths() const { QList dirList; - for (BaseInstance* instance : m_instances) { + for (BaseInstance* instance : m_instances) { //use m_dirs??? iy dirList.append(QFileInfo(instance->instanceRoot()).absoluteFilePath()); } @@ -154,7 +165,7 @@ bool MultiWorldList::deleteWorld(int index) { if (index >= m_worlds.size() || index < 0) return false; - World& m = m_worlds[index]; + World& m = m_worlds[index].world; if (m.destroy()) { beginRemoveRows(QModelIndex(), index, index); m_worlds.removeAt(index); @@ -168,7 +179,7 @@ bool MultiWorldList::deleteWorld(int index) bool MultiWorldList::deleteWorlds(int first, int last) { for (int i = first; i <= last; i++) { - World& m = m_worlds[i]; + World& m = m_worlds[i].world; m.destroy(); } beginRemoveRows(QModelIndex(), first, last); @@ -182,7 +193,7 @@ bool MultiWorldList::resetIcon(int row) { if (row >= m_worlds.size() || row < 0) return false; - World& m = m_worlds[row]; + World& m = m_worlds[row].world; if (m.resetIcon()) { QModelIndex modelIndex = index(row, NameColumn); emit dataChanged(modelIndex, modelIndex, { MultiWorldList::IconFileRole }); @@ -209,29 +220,29 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const QLocale locale; - auto& world = m_worlds[row]; + auto& instanceWorld = m_worlds[row]; switch (role) { case Qt::DisplayRole: switch (column) { case NameColumn: - return world.name(); + return instanceWorld.world.name(); case GameModeColumn: - return world.gameType().toTranslatedString(); + return instanceWorld.world.gameType().toTranslatedString(); case LastPlayedColumn: - return world.lastPlayed(); + return instanceWorld.world.lastPlayed(); case SizeColumn: - return locale.formattedDataSize(world.bytes()); + return locale.formattedDataSize(instanceWorld.world.bytes()); case InfoColumn: for (QString path : instDirPaths()) { //use canonical paths instead of for loops? iy - if (world.isSymLinkUnder(path)) { + if (instanceWorld.world.isSymLinkUnder(path)) { return tr("This world is symbolically linked from elsewhere."); } } - if (world.isMoreThanOneHardLink()) { + if (instanceWorld.world.isMoreThanOneHardLink()) { return tr("\nThis world is hard linked elsewhere."); } return ""; @@ -241,44 +252,44 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const case Qt::UserRole: if (column == SizeColumn) - return QVariant::fromValue(world.bytes()); + return QVariant::fromValue(instanceWorld.world.bytes()); return data(index, Qt::DisplayRole); case Qt::ToolTipRole: { if (column == InfoColumn) { for (QString path : instDirPaths()) { //use canonical paths instead of for loops? iy - if (world.isSymLinkUnder(path)) { + if (instanceWorld.world.isSymLinkUnder(path)) { return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original." "\nCanonical Path: %1") - .arg(world.canonicalFilePath()); + .arg(instanceWorld.world.canonicalFilePath()); } } - if (world.isMoreThanOneHardLink()) { + if (instanceWorld.world.isMoreThanOneHardLink()) { return tr("Warning: This world is hard linked elsewhere. Editing it will also change the original."); } } - return world.folderName(); + return instanceWorld.world.folderName(); } case ObjectRole: { - return QVariant::fromValue((void*)&world); + return QVariant::fromValue((void*)&instanceWorld); } case FolderRole: { - return QDir::toNativeSeparators(QDir(world.canonicalFilePath()).absoluteFilePath(world.folderName())); //test if canonical file path works iy + return QDir::toNativeSeparators(QDir(instanceWorld.world.canonicalFilePath()).absoluteFilePath(instanceWorld.world.folderName())); //test if canonical file path works iy } case SeedRole: { - return QVariant::fromValue(world.seed()); + return QVariant::fromValue(instanceWorld.world.seed()); } case NameRole: { - return world.name(); + return instanceWorld.world.name(); } case LastPlayedRole: { - return world.lastPlayed(); + return instanceWorld.world.lastPlayed(); } case SizeRole: { - return QVariant::fromValue(world.bytes()); + return QVariant::fromValue(instanceWorld.world.bytes()); } case IconFileRole: { - return world.iconFile(); + return instanceWorld.world.iconFile(); } default: return QVariant(); @@ -345,7 +356,7 @@ QMimeData* MultiWorldList::mimeData(const QModelIndexList& indexes) const if (row < 0 || row >= this->m_worlds.size()) continue; - const World& world = m_worlds[row]; + const World& world = m_worlds[row].world; if (!world.isValid() || !world.isOnFS()) continue; @@ -446,7 +457,7 @@ int64_t MultiWorldList::calculateWorldSize(const QFileInfo& file) void MultiWorldList::loadWorldsAsync() { for (int i = 0; i < m_worlds.size(); ++i) { - auto file = m_worlds.at(i).container(); + auto file = m_worlds.at(i).world.container(); int row = i; QThreadPool::globalInstance()->start([this, file, row]() mutable { auto size = calculateWorldSize(file); @@ -454,8 +465,8 @@ void MultiWorldList::loadWorldsAsync() QMetaObject::invokeMethod( this, [this, size, row, file]() { - if (row < m_worlds.size() && m_worlds[row].container() == file) { - m_worlds[row].setSize(size); + if (row < m_worlds.size() && m_worlds[row].world.container() == file) { + m_worlds[row].world.setSize(size); // Notify views QModelIndex modelIndex = index(row, SizeColumn); diff --git a/launcher/minecraft/MultiWorldList.h b/launcher/minecraft/MultiWorldList.h index 1e0a7e60e..147f5e8f8 100644 --- a/launcher/minecraft/MultiWorldList.h +++ b/launcher/minecraft/MultiWorldList.h @@ -25,6 +25,11 @@ class QFileSystemWatcher; +struct InstanceWorld { + World world; + BaseInstance* instance; +}; + class MultiWorldList : public QAbstractListModel { Q_OBJECT public: @@ -32,7 +37,7 @@ class MultiWorldList : public QAbstractListModel { enum Roles { ObjectRole = Qt::UserRole + 1, FolderRole, SeedRole, NameRole, GameModeRole, LastPlayedRole, SizeRole, IconFileRole }; - MultiWorldList(const QList& dirs, QList instances); + MultiWorldList(const QList& dirs, const QList& instances); virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; @@ -42,7 +47,7 @@ class MultiWorldList : public QAbstractListModel { size_t size() const { return m_worlds.size(); }; bool empty() const { return size() == 0; } - World& operator[](size_t index) { return m_worlds[index]; } + World& operator[](size_t index) { return m_worlds[index].world; } /// Reloads the mod list and returns true if the list changed. virtual bool update(); @@ -83,7 +88,7 @@ class MultiWorldList : public QAbstractListModel { QList instDirPaths() const; - const QList& allWorlds() const { return m_worlds; } + const QList& allWorlds() const { return m_worlds; } private slots: void directoryChanged(QString path); @@ -97,5 +102,5 @@ class MultiWorldList : public QAbstractListModel { QFileSystemWatcher* m_watcher; bool m_isWatching; QList m_dirs; - QList m_worlds; + QList m_worlds; }; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 42611ab88..8fd3ee8aa 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -339,7 +339,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi // Create the all worlds widget { QList allInstances = APPLICATION->instances()->getAllInstances(); - qDebug() << "iy initially" << allInstances.length(); QList dirs; for (BaseInstance* inst : allInstances) { dirs.append(dynamic_cast(inst)->worldDir()); @@ -347,7 +346,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi allWorlds = new MultiWorldList(dirs, allInstances); allWorlds->update(); - allWorldsPage = new MultiWorldListPage(dynamic_cast(allInstances[0]), allWorlds); //shouldnt be only one instance iy + allWorldsPage = new MultiWorldListPage(allWorlds); ui->horizontalLayout->addWidget(allWorldsPage); } @@ -945,10 +944,7 @@ void MainWindow::addInstance(const QString& url, const QMap& e instanceFromInstanceTask(creationTask); } - //clean this up iy - fix ghosting issue and only joining one world despite which one you click - QList allInstances = APPLICATION->instances()->getAllInstances(); - qDebug() << "iy finally " << allInstances.length(); QList dirs; for (BaseInstance* inst : allInstances) { dirs.append(dynamic_cast(inst)->worldDir()); @@ -956,8 +952,9 @@ void MainWindow::addInstance(const QString& url, const QMap& e allWorlds = new MultiWorldList(dirs, allInstances); allWorlds->update(); - auto newAllWorldsPage = new MultiWorldListPage(dynamic_cast(allInstances[0]), allWorlds); //shouldnt be only one instance iy + auto newAllWorldsPage = new MultiWorldListPage(allWorlds); ui->horizontalLayout->replaceWidget(allWorldsPage, newAllWorldsPage); + delete allWorldsPage; allWorldsPage = newAllWorldsPage; } diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index 16dc6a5c5..3075bf840 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -86,14 +86,14 @@ class MultiWorldListProxyModel : public QSortFilterProxyModel { } }; -MultiWorldListPage::MultiWorldListPage(MinecraftInstance* inst, MultiWorldList* worlds, QWidget* parent) //require all instances instead of just one iy - : QMainWindow(parent), m_inst(inst), ui(new Ui::MultiWorldListPage), m_worlds(worlds) +MultiWorldListPage::MultiWorldListPage(MultiWorldList* worlds, QWidget* parent) + : QMainWindow(parent), ui(new Ui::MultiWorldListPage), m_worlds(worlds) { ui->setupUi(this); ui->toolBar->insertSpacer(ui->actionRefresh); - MultiWorldListProxyModel* proxy = new MultiWorldListProxyModel(this); + auto* proxy = new MultiWorldListProxyModel(this); proxy->setSortCaseSensitivity(Qt::CaseInsensitive); proxy->setSourceModel(m_worlds); proxy->setSortRole(Qt::UserRole); @@ -195,7 +195,7 @@ void MultiWorldListPage::on_actionRemove_triggered() tr("You are about to delete \"%1\".\n" "The world may be gone forever (A LONG TIME).\n\n" "Are you sure?") - .arg(m_worlds->allWorlds().at(proxiedIndex.row()).name()), + .arg(m_worlds->allWorlds().at(proxiedIndex.row()).world.name()), QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); @@ -237,9 +237,9 @@ void MultiWorldListPage::on_actionData_Packs_triggered() GenericPageProvider provider(dialog->windowTitle()); bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_datapackModel.reset(new DataPackFolderModel(folder, m_inst, isIndexed, true)); + m_datapackModel.reset(new DataPackFolderModel(folder, m_worlds->allWorlds()[0].instance, isIndexed, true)); //don't use instance 0 iy - provider.addPageCreator([this] { return new DataPackPage(m_inst, m_datapackModel.get(), this); }); + provider.addPageCreator([this] { return new DataPackPage(m_worlds->allWorlds()[0].instance, m_datapackModel.get(), this); }); //no instance 0 iy auto layout = new QVBoxLayout(dialog); @@ -402,7 +402,7 @@ void MultiWorldListPage::on_actionAdd_triggered() bool MultiWorldListPage::isWorldSafe(QModelIndex) { - return !m_inst->isRunning(); + return !m_worlds->allWorlds()[0].instance->isRunning(); //don't use instance 0 iy } bool MultiWorldListPage::worldSafetyNagQuestion(const QString& actionType) @@ -471,8 +471,8 @@ void MultiWorldListPage::on_actionJoin_triggered() return; } auto worldVariant = m_worlds->data(index, MultiWorldList::ObjectRole); - auto world = (World*)worldVariant.value(); - APPLICATION->launch(m_inst, LaunchMode::Normal, std::make_shared(MinecraftTarget::parse(world->folderName(), true))); + auto world = (InstanceWorld*)worldVariant.value(); + APPLICATION->launch(world->instance, LaunchMode::Normal, std::make_shared(MinecraftTarget::parse(world->world.folderName(), true))); } #include "MultiWorldListPage.moc" diff --git a/launcher/ui/MultiWorldListPage.h b/launcher/ui/MultiWorldListPage.h index 91cc088cf..5531017a7 100644 --- a/launcher/ui/MultiWorldListPage.h +++ b/launcher/ui/MultiWorldListPage.h @@ -52,7 +52,7 @@ class MultiWorldListPage : public QMainWindow, public BasePage { Q_OBJECT public: - explicit MultiWorldListPage(MinecraftInstance* inst, MultiWorldList* worlds, QWidget* parent = 0); + explicit MultiWorldListPage(MultiWorldList* worlds, QWidget* parent = 0); virtual ~MultiWorldListPage(); virtual QString displayName() const override { return tr("Worlds"); } @@ -70,9 +70,6 @@ class MultiWorldListPage : public QMainWindow, public BasePage { bool worldListFilter(QKeyEvent* ev); QMenu* createPopupMenu() override; - protected: - MinecraftInstance* m_inst; - private: QModelIndex getSelectedWorld(); bool isWorldSafe(QModelIndex index); From e88b293f4a65b6147bc3ee36a4934648de878980 Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Sun, 31 May 2026 21:38:58 -0400 Subject: [PATCH 07/20] initial toggle button ui --- launcher/minecraft/MultiWorldList.cpp | 2 +- launcher/ui/MainWindow.cpp | 44 +++++++++++++++++++-------- launcher/ui/MainWindow.h | 2 ++ launcher/ui/MainWindow.ui | 16 ++++++++++ 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/launcher/minecraft/MultiWorldList.cpp b/launcher/minecraft/MultiWorldList.cpp index c43f84054..923f1751b 100644 --- a/launcher/minecraft/MultiWorldList.cpp +++ b/launcher/minecraft/MultiWorldList.cpp @@ -454,7 +454,7 @@ int64_t MultiWorldList::calculateWorldSize(const QFileInfo& file) return -1; } -void MultiWorldList::loadWorldsAsync() +void MultiWorldList::loadWorldsAsync() //this causes problems when deleting instances iy - only load this screen when switch to all worlds page? { for (int i = 0; i < m_worlds.size(); ++i) { auto file = m_worlds.at(i).world.container(); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 8fd3ee8aa..d18db22e1 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -349,6 +349,11 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi allWorldsPage = new MultiWorldListPage(allWorlds); ui->horizontalLayout->addWidget(allWorldsPage); + + allWorldsPage->setVisible(false); + ui->instanceToolBar->setVisibilityState(QByteArray::fromBase64(instanceToolbarSetting->get().toString().toUtf8())); //fix instance toolbar checkbox independent of all worlds screen showing iy + + connect(ui->actionAllWorlds, &QAction::toggled, this, &MainWindow::onAllWorldsToggled); } // The cat background @@ -859,6 +864,32 @@ QString intListToString(const QList& list) return slist.join(','); } +void MainWindow::onAllWorldsToggled(bool toggled) +{ + if (toggled) { + QList allInstances = APPLICATION->instances()->getAllInstances(); + QList dirs; + for (BaseInstance* inst : allInstances) { + dirs.append(dynamic_cast(inst)->worldDir()); + } + + allWorlds = new MultiWorldList(dirs, allInstances); + allWorlds->update(); + auto newAllWorldsPage = new MultiWorldListPage(allWorlds); + ui->horizontalLayout->replaceWidget(allWorldsPage, newAllWorldsPage); + delete allWorldsPage; + allWorldsPage = newAllWorldsPage; + + view->setVisible(false); + ui->instanceToolBar->setVisible(false); + allWorldsPage->setVisible(true); + } else { + allWorldsPage->setVisible(false); + view->setVisible(true); + ui->instanceToolBar->setVisibilityState(QByteArray::fromBase64(instanceToolbarSetting->get().toString().toUtf8())); + } +} + void MainWindow::onCatToggled(bool state) { setCatBackground(state); @@ -943,19 +974,6 @@ void MainWindow::addInstance(const QString& url, const QMap& e if (creationTask) { instanceFromInstanceTask(creationTask); } - - QList allInstances = APPLICATION->instances()->getAllInstances(); - QList dirs; - for (BaseInstance* inst : allInstances) { - dirs.append(dynamic_cast(inst)->worldDir()); - } - - allWorlds = new MultiWorldList(dirs, allInstances); - allWorlds->update(); - auto newAllWorldsPage = new MultiWorldListPage(allWorlds); - ui->horizontalLayout->replaceWidget(allWorldsPage, newAllWorldsPage); - delete allWorldsPage; - allWorldsPage = newAllWorldsPage; } void MainWindow::on_actionAddInstance_triggered() diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index adc13cfe1..636e00617 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -90,6 +90,8 @@ class MainWindow : public QMainWindow { QMenu* createPopupMenu() override; private slots: + void onAllWorldsToggled(bool); + void onCatToggled(bool); void onCatChanged(int); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index e9b9aa442..2e7d210dd 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -56,6 +56,7 @@ + @@ -175,6 +176,7 @@ + @@ -779,6 +781,20 @@ Open the Java folder in a file browser. Only available if the built-in Java downloader is used. + + + true + + + + + + &All Worlds + + + Toggle All Worlds Screen + + From 66d661fd294a82b1d064a825e10227c8d8033346 Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Wed, 3 Jun 2026 18:53:02 -0400 Subject: [PATCH 08/20] instance tool bar is independent of all worlds screen being visible --- launcher/ui/MainWindow.cpp | 5 ++++- launcher/ui/MainWindow.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index d18db22e1..c9ec6bd23 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -192,6 +192,8 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi ui->instanceToolBar->addContextMenuAction(ui->instanceToolBar->toggleViewAction()); ui->instanceToolBar->addContextMenuAction(ui->actionToggleStatusBar); ui->instanceToolBar->addContextMenuAction(ui->actionLockToolbars); + + m_oldInstanceToolbarSetting = ui->instanceToolBar->isVisible(); } // set the menu for the folders help, accounts, and export tool buttons @@ -881,12 +883,13 @@ void MainWindow::onAllWorldsToggled(bool toggled) allWorldsPage = newAllWorldsPage; view->setVisible(false); + m_oldInstanceToolbarSetting = ui->instanceToolBar->isVisible(); //won't work if starts on all worlds screen iy ui->instanceToolBar->setVisible(false); allWorldsPage->setVisible(true); } else { allWorldsPage->setVisible(false); view->setVisible(true); - ui->instanceToolBar->setVisibilityState(QByteArray::fromBase64(instanceToolbarSetting->get().toString().toUtf8())); + ui->instanceToolBar->setVisible(m_oldInstanceToolbarSetting); } } diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 636e00617..3e80da15b 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -252,6 +252,7 @@ class MainWindow : public QMainWindow { MultiWorldList* allWorlds = nullptr; std::shared_ptr instanceToolbarSetting = nullptr; + bool m_oldInstanceToolbarSetting; unique_qobject_ptr m_newsChecker; From bd9bea70818e133319acc0e62814e709e76b7b93 Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:29:37 -0400 Subject: [PATCH 09/20] ui fixes/additions --- launcher/minecraft/MultiWorldList.cpp | 9 ++++++- launcher/minecraft/MultiWorldList.h | 4 +-- launcher/ui/MultiWorldListPage.cpp | 35 +++++++++++++-------------- launcher/ui/MultiWorldListPage.ui | 2 +- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/launcher/minecraft/MultiWorldList.cpp b/launcher/minecraft/MultiWorldList.cpp index 923f1751b..cff1c298c 100644 --- a/launcher/minecraft/MultiWorldList.cpp +++ b/launcher/minecraft/MultiWorldList.cpp @@ -227,6 +227,9 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const case NameColumn: return instanceWorld.world.name(); + case InstanceColumn: + return instanceWorld.instance->name(); + case GameModeColumn: return instanceWorld.world.gameType().toTranslatedString(); @@ -303,6 +306,8 @@ QVariant MultiWorldList::headerData(int section, [[maybe_unused]] Qt::Orientatio switch (section) { case NameColumn: return tr("Name"); + case InstanceColumn: + return tr("Instance"); case GameModeColumn: return tr("Game Mode"); case LastPlayedColumn: @@ -321,6 +326,8 @@ QVariant MultiWorldList::headerData(int section, [[maybe_unused]] Qt::Orientatio switch (section) { case NameColumn: return tr("The name of the world."); + case InstanceColumn: + return tr("The instance the world belongs to."); case GameModeColumn: return tr("Game mode of the world."); case LastPlayedColumn: @@ -454,7 +461,7 @@ int64_t MultiWorldList::calculateWorldSize(const QFileInfo& file) return -1; } -void MultiWorldList::loadWorldsAsync() //this causes problems when deleting instances iy - only load this screen when switch to all worlds page? +void MultiWorldList::loadWorldsAsync() { for (int i = 0; i < m_worlds.size(); ++i) { auto file = m_worlds.at(i).world.container(); diff --git a/launcher/minecraft/MultiWorldList.h b/launcher/minecraft/MultiWorldList.h index 147f5e8f8..75d140aea 100644 --- a/launcher/minecraft/MultiWorldList.h +++ b/launcher/minecraft/MultiWorldList.h @@ -33,9 +33,9 @@ struct InstanceWorld { class MultiWorldList : public QAbstractListModel { Q_OBJECT public: - enum Columns { NameColumn, GameModeColumn, LastPlayedColumn, SizeColumn, InfoColumn }; + enum Columns { NameColumn, InstanceColumn, GameModeColumn, LastPlayedColumn, SizeColumn, InfoColumn }; - enum Roles { ObjectRole = Qt::UserRole + 1, FolderRole, SeedRole, NameRole, GameModeRole, LastPlayedRole, SizeRole, IconFileRole }; + enum Roles { ObjectRole = Qt::UserRole + 1, FolderRole, SeedRole, NameRole, InstanceRole, GameModeRole, LastPlayedRole, SizeRole, IconFileRole }; MultiWorldList(const QList& dirs, const QList& instances); diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index 3075bf840..417a37eda 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -92,6 +92,7 @@ MultiWorldListPage::MultiWorldListPage(MultiWorldList* worlds, QWidget* parent) ui->setupUi(this); ui->toolBar->insertSpacer(ui->actionRefresh); + ui->actionJoin->setEnabled(true); auto* proxy = new MultiWorldListProxyModel(this); proxy->setSortCaseSensitivity(Qt::CaseInsensitive); @@ -117,10 +118,6 @@ void MultiWorldListPage::openedImpl() { m_worlds->startWatching(); - // if (!m_inst || !m_inst->traits().contains("feature:is_quick_play_singleplayer")) { //get rid of this??? iy - // ui->toolBar->removeAction(ui->actionJoin); - // } - auto const setting_name = QString("WideBarVisibility_%1").arg(id()); m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); @@ -209,7 +206,15 @@ void MultiWorldListPage::on_actionRemove_triggered() void MultiWorldListPage::on_actionView_Folder_triggered() { - DesktopServices::openPath(m_worlds->dirs()[0].absolutePath(), true); //remove view folder option for all worlds (make it world specific) iy + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) { + return; + } + + auto worldVariant = m_worlds->data(index, MultiWorldList::ObjectRole); + auto world = (World*)worldVariant.value(); + + DesktopServices::openPath(world->canonicalFilePath(), true); } void MultiWorldListPage::on_actionData_Packs_triggered() @@ -237,9 +242,9 @@ void MultiWorldListPage::on_actionData_Packs_triggered() GenericPageProvider provider(dialog->windowTitle()); bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_datapackModel.reset(new DataPackFolderModel(folder, m_worlds->allWorlds()[0].instance, isIndexed, true)); //don't use instance 0 iy + m_datapackModel.reset(new DataPackFolderModel(folder, static_cast(m_worlds->data(index, MultiWorldList::ObjectRole).value())->instance, isIndexed, true)); //these cause crashes sometimes iy with null error SIGSEGV (probably due to watchers) after every first time launched -> using index 0 for instances doesnt fix it - provider.addPageCreator([this] { return new DataPackPage(m_worlds->allWorlds()[0].instance, m_datapackModel.get(), this); }); //no instance 0 iy + provider.addPageCreator([this, index] { return new DataPackPage(static_cast(m_worlds->data(index, MultiWorldList::ObjectRole).value())->instance, m_datapackModel.get(), this); }); //iy auto layout = new QVBoxLayout(dialog); @@ -375,16 +380,10 @@ void MultiWorldListPage::worldChanged([[maybe_unused]] const QModelIndex& curren ui->actionCopy->setEnabled(enable); ui->actionRename->setEnabled(enable); ui->actionData_Packs->setEnabled(enable); + ui->actionView_Folder->setEnabled(enable); + ui->actionJoin->setEnabled(enable); bool hasIcon = !index.data(MultiWorldList::IconFileRole).isNull(); ui->actionReset_Icon->setEnabled(enable && hasIcon); - - // auto supportsJoin = m_inst && m_inst->traits().contains("feature:is_quick_play_singleplayer"); //change this to be on instance by instance basis iy - // ui->actionJoin->setEnabled(enable && supportsJoin); - ui->actionJoin->setEnabled(true); - - // if (!supportsJoin) { - // ui->toolBar->removeAction(ui->actionJoin); - // } } void MultiWorldListPage::on_actionAdd_triggered() @@ -400,9 +399,9 @@ void MultiWorldListPage::on_actionAdd_triggered() } } -bool MultiWorldListPage::isWorldSafe(QModelIndex) +bool MultiWorldListPage::isWorldSafe(QModelIndex index) { - return !m_worlds->allWorlds()[0].instance->isRunning(); //don't use instance 0 iy + return !static_cast(m_worlds->data(index, MultiWorldList::ObjectRole).value())->instance->isRunning(); } bool MultiWorldListPage::worldSafetyNagQuestion(const QString& actionType) @@ -471,7 +470,7 @@ void MultiWorldListPage::on_actionJoin_triggered() return; } auto worldVariant = m_worlds->data(index, MultiWorldList::ObjectRole); - auto world = (InstanceWorld*)worldVariant.value(); + auto *world = static_cast(worldVariant.value()); APPLICATION->launch(world->instance, LaunchMode::Normal, std::make_shared(MinecraftTarget::parse(world->world.folderName(), true))); } diff --git a/launcher/ui/MultiWorldListPage.ui b/launcher/ui/MultiWorldListPage.ui index 874721eee..4b4acfd70 100644 --- a/launcher/ui/MultiWorldListPage.ui +++ b/launcher/ui/MultiWorldListPage.ui @@ -91,10 +91,10 @@ + - From 547d8709307d122c04a4463b2e29653e9647411e Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:28:42 -0400 Subject: [PATCH 10/20] view instance button and double click to join world on all worlds screen --- launcher/ui/MultiWorldListPage.cpp | 32 ++++++++++++++++++++++++++++++ launcher/ui/MultiWorldListPage.h | 3 +++ launcher/ui/MultiWorldListPage.ui | 9 +++++++++ 3 files changed, 44 insertions(+) diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index 417a37eda..7064df357 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -111,6 +111,7 @@ MultiWorldListPage::MultiWorldListPage(MultiWorldList* worlds, QWidget* parent) head->setSectionResizeMode(4, QHeaderView::ResizeToContents); connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &MultiWorldListPage::worldChanged); + connect(ui->worldTreeView, &QAbstractItemView::doubleClicked, this, &MultiWorldListPage::worldDoubleClicked); worldChanged(QModelIndex(), QModelIndex()); } @@ -382,6 +383,7 @@ void MultiWorldListPage::worldChanged([[maybe_unused]] const QModelIndex& curren ui->actionData_Packs->setEnabled(enable); ui->actionView_Folder->setEnabled(enable); ui->actionJoin->setEnabled(enable); + ui->actionInstance_Settings->setEnabled(enable); bool hasIcon = !index.data(MultiWorldList::IconFileRole).isNull(); ui->actionReset_Icon->setEnabled(enable && hasIcon); } @@ -458,14 +460,44 @@ void MultiWorldListPage::on_actionRename_triggered() } } +void MultiWorldListPage::on_actionInstance_Settings_triggered() +{ + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) { + return; + } + auto worldVariant = m_worlds->data(index, MultiWorldList::ObjectRole); + auto *world = static_cast(worldVariant.value()); + + if (world->instance->canEdit()) { + APPLICATION->showInstanceWindow(world->instance); + } else { + CustomMessageBox::selectable(this, tr("Instance not editable"), + tr("This instance is not editable. It may be broken, invalid, or too old. Check logs for details."), + QMessageBox::Critical) + ->show(); + } +} + void MultiWorldListPage::on_actionRefresh_triggered() { m_worlds->update(); } +void MultiWorldListPage::worldDoubleClicked(const QModelIndex& index) +{ + auto proxy = (QSortFilterProxyModel*)ui->worldTreeView->model(); + join(proxy->mapToSource(index)); +} + void MultiWorldListPage::on_actionJoin_triggered() { QModelIndex index = getSelectedWorld(); + join(index); +} + +void MultiWorldListPage::join(QModelIndex index) +{ if (!index.isValid()) { return; } diff --git a/launcher/ui/MultiWorldListPage.h b/launcher/ui/MultiWorldListPage.h index 5531017a7..1440a9657 100644 --- a/launcher/ui/MultiWorldListPage.h +++ b/launcher/ui/MultiWorldListPage.h @@ -75,6 +75,7 @@ class MultiWorldListPage : public QMainWindow, public BasePage { bool isWorldSafe(QModelIndex index); bool worldSafetyNagQuestion(const QString& actionType); void mceditError(); + void join(QModelIndex index); private: Ui::MultiWorldListPage* ui; @@ -92,6 +93,7 @@ class MultiWorldListPage : public QMainWindow, public BasePage { void on_actionAdd_triggered(); void on_actionCopy_triggered(); void on_actionRename_triggered(); + void on_actionInstance_Settings_triggered(); void on_actionRefresh_triggered(); void on_actionView_Folder_triggered(); void on_actionData_Packs_triggered(); @@ -99,6 +101,7 @@ class MultiWorldListPage : public QMainWindow, public BasePage { void worldChanged(const QModelIndex& current, const QModelIndex& previous); void mceditState(LoggedProcess::State state); void on_actionJoin_triggered(); + void worldDoubleClicked(const QModelIndex& index); void ShowContextMenu(const QPoint& pos); }; diff --git a/launcher/ui/MultiWorldListPage.ui b/launcher/ui/MultiWorldListPage.ui index 4b4acfd70..aa89840aa 100644 --- a/launcher/ui/MultiWorldListPage.ui +++ b/launcher/ui/MultiWorldListPage.ui @@ -88,6 +88,7 @@ + @@ -157,6 +158,14 @@ Manage data packs inside the world. + + + View Instance + + + View the instance this world belongs to. + + From b0a600b7201335b88acbd5d8a6f3cdbc87b24a27 Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Sat, 6 Jun 2026 11:12:32 -0400 Subject: [PATCH 11/20] joining world switches to main screen + fix issues with instance toolbar visibility --- launcher/ui/MainWindow.cpp | 22 +++++++++++++++++++--- launcher/ui/MainWindow.h | 4 ++++ launcher/ui/MultiWorldListPage.cpp | 1 + launcher/ui/MultiWorldListPage.h | 3 +++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index c9ec6bd23..e7af59a6b 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -192,8 +192,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi ui->instanceToolBar->addContextMenuAction(ui->instanceToolBar->toggleViewAction()); ui->instanceToolBar->addContextMenuAction(ui->actionToggleStatusBar); ui->instanceToolBar->addContextMenuAction(ui->actionLockToolbars); - - m_oldInstanceToolbarSetting = ui->instanceToolBar->isVisible(); } // set the menu for the folders help, accounts, and export tool buttons @@ -356,6 +354,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi ui->instanceToolBar->setVisibilityState(QByteArray::fromBase64(instanceToolbarSetting->get().toString().toUtf8())); //fix instance toolbar checkbox independent of all worlds screen showing iy connect(ui->actionAllWorlds, &QAction::toggled, this, &MainWindow::onAllWorldsToggled); + connect(allWorldsPage, &MultiWorldListPage::worldJoined, this, &MainWindow::worldJoined); } // The cat background @@ -867,6 +866,17 @@ QString intListToString(const QList& list) } void MainWindow::onAllWorldsToggled(bool toggled) +{ + toggleAllWorldsScreen(toggled); +} + +void MainWindow::worldJoined() +{ + ui->actionAllWorlds->setChecked(false); + toggleAllWorldsScreen(false); +} + +void MainWindow::toggleAllWorldsScreen(bool toggled) { if (toggled) { QList allInstances = APPLICATION->instances()->getAllInstances(); @@ -883,9 +893,11 @@ void MainWindow::onAllWorldsToggled(bool toggled) allWorldsPage = newAllWorldsPage; view->setVisible(false); - m_oldInstanceToolbarSetting = ui->instanceToolBar->isVisible(); //won't work if starts on all worlds screen iy + m_oldInstanceToolbarSetting = ui->instanceToolBar->isVisible(); ui->instanceToolBar->setVisible(false); allWorldsPage->setVisible(true); + + connect(allWorldsPage, &MultiWorldListPage::worldJoined, this, &MainWindow::worldJoined); } else { allWorldsPage->setVisible(false); view->setVisible(true); @@ -1631,6 +1643,10 @@ void MainWindow::on_actionViewSelectedInstFolder_triggered() void MainWindow::closeEvent(QCloseEvent* event) { + if (view->isVisible()) { + m_oldInstanceToolbarSetting = ui->instanceToolBar->isVisible(); + } + toggleAllWorldsScreen(false); // Save the window state and geometry. APPLICATION->settings()->set("MainWindowState", QString::fromUtf8(saveState().toBase64())); APPLICATION->settings()->set("MainWindowGeometry", QString::fromUtf8(saveGeometry().toBase64())); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 3e80da15b..ee4a53a69 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -175,6 +175,8 @@ class MainWindow : public QMainWindow { void taskEnd(); + void worldJoined(); + /** * called when an icon is changed in the icon model. */ @@ -236,6 +238,8 @@ class MainWindow : public QMainWindow { void runModalTask(Task* task); void instanceFromInstanceTask(InstanceTask* task); + void toggleAllWorldsScreen(bool toggled); + private: Ui::MainWindow* ui; // these are managed by Qt's memory management model! diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index 7064df357..9dd38a5a1 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -504,6 +504,7 @@ void MultiWorldListPage::join(QModelIndex index) auto worldVariant = m_worlds->data(index, MultiWorldList::ObjectRole); auto *world = static_cast(worldVariant.value()); APPLICATION->launch(world->instance, LaunchMode::Normal, std::make_shared(MinecraftTarget::parse(world->world.folderName(), true))); + emit worldJoined(); } #include "MultiWorldListPage.moc" diff --git a/launcher/ui/MultiWorldListPage.h b/launcher/ui/MultiWorldListPage.h index 1440a9657..3114172e8 100644 --- a/launcher/ui/MultiWorldListPage.h +++ b/launcher/ui/MultiWorldListPage.h @@ -65,6 +65,9 @@ class MultiWorldListPage : public QMainWindow, public BasePage { virtual void openedImpl() override; virtual void closedImpl() override; + signals: + void worldJoined(); + protected: bool eventFilter(QObject* obj, QEvent* ev) override; bool worldListFilter(QKeyEvent* ev); From b13cfd9d6a74588b1db4e7c9d4703a0456d34242 Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Sat, 6 Jun 2026 13:13:27 -0400 Subject: [PATCH 12/20] made MultiWorldList not require dirs --- launcher/minecraft/MultiWorldList.cpp | 8 ++++---- launcher/minecraft/MultiWorldList.h | 2 +- launcher/ui/MainWindow.cpp | 14 +++----------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/launcher/minecraft/MultiWorldList.cpp b/launcher/minecraft/MultiWorldList.cpp index cff1c298c..a01633b6c 100644 --- a/launcher/minecraft/MultiWorldList.cpp +++ b/launcher/minecraft/MultiWorldList.cpp @@ -48,10 +48,10 @@ #include "MinecraftInstance.h" -MultiWorldList::MultiWorldList(const QList& dirs, const QList& instances) : QAbstractListModel(), m_instances(instances) +MultiWorldList::MultiWorldList(const QList& instances) : QAbstractListModel(), m_instances(instances) { - for (QString dir : dirs) { - m_dirs.append(dir); + for (BaseInstance* inst : m_instances) { + m_dirs.append(dynamic_cast(inst)->worldDir()); } for (QDir dir : m_dirs) { @@ -277,7 +277,7 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const return QVariant::fromValue((void*)&instanceWorld); } case FolderRole: { - return QDir::toNativeSeparators(QDir(instanceWorld.world.canonicalFilePath()).absoluteFilePath(instanceWorld.world.folderName())); //test if canonical file path works iy + return QDir::toNativeSeparators(QDir(dynamic_cast(instanceWorld.instance)->worldDir()).absoluteFilePath(instanceWorld.world.folderName())); //test if canonical file path works iy } case SeedRole: { return QVariant::fromValue(instanceWorld.world.seed()); diff --git a/launcher/minecraft/MultiWorldList.h b/launcher/minecraft/MultiWorldList.h index 75d140aea..a8b8ae85a 100644 --- a/launcher/minecraft/MultiWorldList.h +++ b/launcher/minecraft/MultiWorldList.h @@ -37,7 +37,7 @@ class MultiWorldList : public QAbstractListModel { enum Roles { ObjectRole = Qt::UserRole + 1, FolderRole, SeedRole, NameRole, InstanceRole, GameModeRole, LastPlayedRole, SizeRole, IconFileRole }; - MultiWorldList(const QList& dirs, const QList& instances); + MultiWorldList(const QList& instances); virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index e7af59a6b..7d2709a00 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -339,19 +339,15 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi // Create the all worlds widget { QList allInstances = APPLICATION->instances()->getAllInstances(); - QList dirs; - for (BaseInstance* inst : allInstances) { - dirs.append(dynamic_cast(inst)->worldDir()); - } - allWorlds = new MultiWorldList(dirs, allInstances); + allWorlds = new MultiWorldList(allInstances); allWorlds->update(); allWorldsPage = new MultiWorldListPage(allWorlds); ui->horizontalLayout->addWidget(allWorldsPage); allWorldsPage->setVisible(false); - ui->instanceToolBar->setVisibilityState(QByteArray::fromBase64(instanceToolbarSetting->get().toString().toUtf8())); //fix instance toolbar checkbox independent of all worlds screen showing iy + ui->instanceToolBar->setVisibilityState(QByteArray::fromBase64(instanceToolbarSetting->get().toString().toUtf8())); connect(ui->actionAllWorlds, &QAction::toggled, this, &MainWindow::onAllWorldsToggled); connect(allWorldsPage, &MultiWorldListPage::worldJoined, this, &MainWindow::worldJoined); @@ -880,12 +876,8 @@ void MainWindow::toggleAllWorldsScreen(bool toggled) { if (toggled) { QList allInstances = APPLICATION->instances()->getAllInstances(); - QList dirs; - for (BaseInstance* inst : allInstances) { - dirs.append(dynamic_cast(inst)->worldDir()); - } - allWorlds = new MultiWorldList(dirs, allInstances); + allWorlds = new MultiWorldList(allInstances); allWorlds->update(); auto newAllWorldsPage = new MultiWorldListPage(allWorlds); ui->horizontalLayout->replaceWidget(allWorldsPage, newAllWorldsPage); From 3458830d425152a61efb93ae5dba0eaa80d49021 Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Wed, 10 Jun 2026 13:14:52 -0400 Subject: [PATCH 13/20] fixed datapack screen causing crash --- launcher/minecraft/MultiWorldList.cpp | 6 +++--- launcher/ui/MultiWorldListPage.cpp | 15 ++++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/launcher/minecraft/MultiWorldList.cpp b/launcher/minecraft/MultiWorldList.cpp index a01633b6c..8f892f495 100644 --- a/launcher/minecraft/MultiWorldList.cpp +++ b/launcher/minecraft/MultiWorldList.cpp @@ -240,7 +240,7 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const return locale.formattedDataSize(instanceWorld.world.bytes()); case InfoColumn: - for (QString path : instDirPaths()) { //use canonical paths instead of for loops? iy + for (QString path : instDirPaths()) { if (instanceWorld.world.isSymLinkUnder(path)) { return tr("This world is symbolically linked from elsewhere."); } @@ -260,7 +260,7 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const case Qt::ToolTipRole: { if (column == InfoColumn) { - for (QString path : instDirPaths()) { //use canonical paths instead of for loops? iy + for (QString path : instDirPaths()) { if (instanceWorld.world.isSymLinkUnder(path)) { return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original." "\nCanonical Path: %1") @@ -277,7 +277,7 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const return QVariant::fromValue((void*)&instanceWorld); } case FolderRole: { - return QDir::toNativeSeparators(QDir(dynamic_cast(instanceWorld.instance)->worldDir()).absoluteFilePath(instanceWorld.world.folderName())); //test if canonical file path works iy + return QDir::toNativeSeparators(QDir(dynamic_cast(instanceWorld.instance)->worldDir()).absoluteFilePath(instanceWorld.world.folderName())); } case SeedRole: { return QVariant::fromValue(instanceWorld.world.seed()); diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index 9dd38a5a1..fbf11f777 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -242,10 +242,12 @@ void MultiWorldListPage::on_actionData_Packs_triggered() GenericPageProvider provider(dialog->windowTitle()); - bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_datapackModel.reset(new DataPackFolderModel(folder, static_cast(m_worlds->data(index, MultiWorldList::ObjectRole).value())->instance, isIndexed, true)); //these cause crashes sometimes iy with null error SIGSEGV (probably due to watchers) after every first time launched -> using index 0 for instances doesnt fix it + auto instance = (static_cast(m_worlds->data(index, MultiWorldList::ObjectRole).value()))->instance; - provider.addPageCreator([this, index] { return new DataPackPage(static_cast(m_worlds->data(index, MultiWorldList::ObjectRole).value())->instance, m_datapackModel.get(), this); }); //iy + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_datapackModel.reset(new DataPackFolderModel(folder, instance, isIndexed, true)); + + provider.addPageCreator([this, instance] { return new DataPackPage(instance, m_datapackModel.get(), this); }); auto layout = new QVBoxLayout(dialog); @@ -265,9 +267,12 @@ void MultiWorldListPage::on_actionData_Packs_triggered() dialog->setLayout(layout); - dialog->exec(); + dialog->setAttribute(Qt::WA_DeleteOnClose); - APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); + connect(dialog, &QDialog::finished, this, + [dialog]() { APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); }); + + dialog->open(); } void MultiWorldListPage::on_actionReset_Icon_triggered() From 7e7de65fb426b9ba68d9461aed1cd56705c88896 Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Thu, 11 Jun 2026 21:10:16 -0400 Subject: [PATCH 14/20] added select instance dialog for copying/adding worlds --- launcher/Application.cpp | 1 + launcher/minecraft/MultiWorldList.cpp | 17 +++--- launcher/minecraft/MultiWorldList.h | 9 +++- launcher/ui/MultiWorldListPage.cpp | 78 +++++++++++++++++++++++++-- launcher/ui/MultiWorldListPage.h | 2 + 5 files changed, 92 insertions(+), 15 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ddeb30588..0e5f3cb3b 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -825,6 +825,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("TPDownloadGeometry", ""); m_settings->registerSetting("ShaderDownloadGeometry", ""); m_settings->registerSetting("DataPackDownloadGeometry", ""); + m_settings->registerSetting("SelectInstanceGeometry", ""); // data pack window // in future, more pages may be added - so this name is chosen to avoid needing migration diff --git a/launcher/minecraft/MultiWorldList.cpp b/launcher/minecraft/MultiWorldList.cpp index 8f892f495..cb00d7b05 100644 --- a/launcher/minecraft/MultiWorldList.cpp +++ b/launcher/minecraft/MultiWorldList.cpp @@ -48,9 +48,9 @@ #include "MinecraftInstance.h" -MultiWorldList::MultiWorldList(const QList& instances) : QAbstractListModel(), m_instances(instances) +MultiWorldList::MultiWorldList(const QList& instances) : QAbstractListModel(), allInstances(instances) { - for (BaseInstance* inst : m_instances) { + for (BaseInstance* inst : allInstances) { m_dirs.append(dynamic_cast(inst)->worldDir()); } @@ -109,7 +109,7 @@ bool MultiWorldList::update() QList newWorlds; - for (BaseInstance* inst : m_instances) { + for (BaseInstance* inst : allInstances) { QDir dir = dynamic_cast(inst)->worldDir(); dir.refresh(); auto folderContents = dir.entryInfoList(); @@ -154,7 +154,7 @@ QList MultiWorldList::instDirPaths() const { QList dirList; - for (BaseInstance* instance : m_instances) { //use m_dirs??? iy + for (BaseInstance* instance : allInstances) { dirList.append(QFileInfo(instance->instanceRoot()).absoluteFilePath()); } @@ -399,14 +399,15 @@ Qt::DropActions MultiWorldList::supportedDropActions() const return Qt::CopyAction | Qt::MoveAction; } -void MultiWorldList::installWorld(QFileInfo filename) +void MultiWorldList::installWorld(BaseInstance* instance, QFileInfo filename) { qDebug() << "installing:" << filename.absoluteFilePath(); World w(filename); if (!w.isValid()) { return; } - w.install(m_dirs[0].absolutePath()); //add option for which instance to install to iy + w.install(QDir(dynamic_cast(instance)->worldDir()).absolutePath()); + update(); } bool MultiWorldList::dropMimeData(const QMimeData* data, @@ -434,9 +435,7 @@ bool MultiWorldList::dropMimeData(const QMimeData* data, QFileInfo worldInfo(filename); - if (!m_dirs[0].entryInfoList().contains(worldInfo)) { //same as above, add option for which instance to install to iy - installWorld(worldInfo); - } + emit fileDropped(worldInfo); } if (was_watching) startWatching(); diff --git a/launcher/minecraft/MultiWorldList.h b/launcher/minecraft/MultiWorldList.h index a8b8ae85a..ddd936892 100644 --- a/launcher/minecraft/MultiWorldList.h +++ b/launcher/minecraft/MultiWorldList.h @@ -53,7 +53,7 @@ class MultiWorldList : public QAbstractListModel { virtual bool update(); /// Install a world from location - void installWorld(QFileInfo filename); + void installWorld(BaseInstance* instance, QFileInfo filename); /// Deletes the mod at the given index. virtual bool deleteWorld(int index); @@ -90,17 +90,22 @@ class MultiWorldList : public QAbstractListModel { const QList& allWorlds() const { return m_worlds; } + QList getInstances() const { return allInstances; } + private slots: void directoryChanged(QString path); void loadWorldsAsync(); signals: void changed(); + void fileDropped(QFileInfo worldInfo); protected: - QList m_instances; QFileSystemWatcher* m_watcher; bool m_isWatching; QList m_dirs; QList m_worlds; + + private: + QList allInstances; }; diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index fbf11f777..7c0c41062 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -60,6 +60,7 @@ #include "ui/GuiUtil.h" #include "Application.h" +#include "icons/IconList.h" #include "pages/instance/DataPackPage.h" class MultiWorldListProxyModel : public QSortFilterProxyModel { @@ -113,6 +114,8 @@ MultiWorldListPage::MultiWorldListPage(MultiWorldList* worlds, QWidget* parent) connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &MultiWorldListPage::worldChanged); connect(ui->worldTreeView, &QAbstractItemView::doubleClicked, this, &MultiWorldListPage::worldDoubleClicked); worldChanged(QModelIndex(), QModelIndex()); + + connect(m_worlds, &MultiWorldList::fileDropped, this, &MultiWorldListPage::fileDropped); } void MultiWorldListPage::openedImpl() @@ -400,7 +403,11 @@ void MultiWorldListPage::on_actionAdd_triggered() if (!list.empty()) { m_worlds->stopWatching(); for (auto filename : list) { - m_worlds->installWorld(QFileInfo(filename)); + auto *instance = selectInstance(tr("Select instance to add world '%1' to.").arg(QFileInfo(filename).fileName())); + + if (instance != nullptr) { + m_worlds->installWorld(instance, QFileInfo(filename)); + } } m_worlds->startWatching(); } @@ -434,16 +441,68 @@ void MultiWorldListPage::on_actionCopy_triggered() return; auto worldVariant = m_worlds->data(index, MultiWorldList::ObjectRole); - auto world = (World*)worldVariant.value(); + auto *world = static_cast(worldVariant.value()); + bool ok = false; QString name = - QInputDialog::getText(this, tr("World name"), tr("Enter a new name for the copy."), QLineEdit::Normal, world->name(), &ok); + QInputDialog::getText(this, tr("World name"), tr("Enter a new name for the copy."), QLineEdit::Normal, world->world.name(), &ok); if (ok && name.length() > 0) { - world->install(m_worlds->dirs()[0].absolutePath(), name); //ask which instance iy + auto *instance = selectInstance(tr("Select instance to copy world to."), world->instance); + + if (instance != nullptr) { + world->world.install((QDir(instance->worldDir())).absolutePath(), name); + m_worlds->update(); + } } } +Q_DECLARE_METATYPE(BaseInstance*); + +MinecraftInstance* MultiWorldListPage::selectInstance(const QString& message, BaseInstance* preselectedInstance) +{ + auto *dialog = new QDialog(this); + dialog->setWindowTitle(tr("Select Instance")); + + dialog->resize(static_cast(std::max(0.5 * window()->width(), 400.0)), + static_cast(std::max(0.75 * window()->height(), 400.0))); + dialog->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("SelectInstanceGeometry").toByteArray())); + + auto layout = new QVBoxLayout(dialog); + + layout->addWidget(new QLabel(message)); + + auto instanceList = new QListWidget(dialog); + + for (auto instance : m_worlds->getInstances()) { + auto *item = new QListWidgetItem(instanceList); + item->setText(instance->name()); + item->setIcon(APPLICATION->icons()->getIcon(instance->iconKey())); + item->setData(Qt::UserRole, QVariant::fromValue(instance)); + if (instance == preselectedInstance) { + instanceList->setCurrentItem(item); + } + } + + layout->addWidget(instanceList); + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); + connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); + layout->addWidget(buttonBox); + + dialog->setLayout(layout); + + connect(dialog, &QDialog::finished, this, + [dialog]() { APPLICATION->settings()->set("SelectInstanceGeometry", dialog->saveGeometry().toBase64()); }); + + if (dialog->exec() == QDialog::Accepted) { + return static_cast(instanceList->currentItem()->data(Qt::UserRole).value()); + } + + return nullptr; +} + void MultiWorldListPage::on_actionRename_triggered() { QModelIndex index = getSelectedWorld(); @@ -495,6 +554,17 @@ void MultiWorldListPage::worldDoubleClicked(const QModelIndex& index) join(proxy->mapToSource(index)); } +void MultiWorldListPage::fileDropped(const QFileInfo& worldInfo) +{ + auto *instance = selectInstance(tr("Select instance to add world '%1' to.").arg(worldInfo.fileName())); + + if (instance != nullptr) { + if (!QDir(instance->worldDir()).entryInfoList().contains(worldInfo)) { + m_worlds->installWorld(instance, worldInfo); + } + } +} + void MultiWorldListPage::on_actionJoin_triggered() { QModelIndex index = getSelectedWorld(); diff --git a/launcher/ui/MultiWorldListPage.h b/launcher/ui/MultiWorldListPage.h index 3114172e8..848bd5a41 100644 --- a/launcher/ui/MultiWorldListPage.h +++ b/launcher/ui/MultiWorldListPage.h @@ -79,6 +79,7 @@ class MultiWorldListPage : public QMainWindow, public BasePage { bool worldSafetyNagQuestion(const QString& actionType); void mceditError(); void join(QModelIndex index); + MinecraftInstance* selectInstance(const QString& message, BaseInstance* instance = nullptr); private: Ui::MultiWorldListPage* ui; @@ -105,6 +106,7 @@ class MultiWorldListPage : public QMainWindow, public BasePage { void mceditState(LoggedProcess::State state); void on_actionJoin_triggered(); void worldDoubleClicked(const QModelIndex& index); + void fileDropped(const QFileInfo& worldInfo); void ShowContextMenu(const QPoint& pos); }; From 2e656c247ba14bd143e10b3d2f6e261b1de5fd10 Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:09:33 -0400 Subject: [PATCH 15/20] instance icons and minecraft version --- launcher/minecraft/MultiWorldList.cpp | 30 ++++++++++++++++++--------- launcher/minecraft/MultiWorldList.h | 4 ++-- launcher/ui/MainWindow.cpp | 4 ++++ launcher/ui/MultiWorldListPage.cpp | 8 +++++++ 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/launcher/minecraft/MultiWorldList.cpp b/launcher/minecraft/MultiWorldList.cpp index cb00d7b05..12b8529cb 100644 --- a/launcher/minecraft/MultiWorldList.cpp +++ b/launcher/minecraft/MultiWorldList.cpp @@ -46,7 +46,10 @@ #include #include +#include "Application.h" #include "MinecraftInstance.h" +#include "PackProfile.h" +#include "icons/IconList.h" MultiWorldList::MultiWorldList(const QList& instances) : QAbstractListModel(), allInstances(instances) { @@ -230,6 +233,10 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const case InstanceColumn: return instanceWorld.instance->name(); + case VersionColumn: + static_cast(instanceWorld.instance)->getPackProfile()->reload(Net::Mode::Online); + return static_cast(instanceWorld.instance)->getPackProfile()->getComponentVersion("net.minecraft"); + case GameModeColumn: return instanceWorld.world.gameType().toTranslatedString(); @@ -240,10 +247,8 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const return locale.formattedDataSize(instanceWorld.world.bytes()); case InfoColumn: - for (QString path : instDirPaths()) { - if (instanceWorld.world.isSymLinkUnder(path)) { - return tr("This world is symbolically linked from elsewhere."); - } + if (instanceWorld.world.isSymLinkUnder(QFileInfo(instanceWorld.instance->instanceRoot()).absoluteFilePath())) { + return tr("This world is symbolically linked from elsewhere."); } if (instanceWorld.world.isMoreThanOneHardLink()) { return tr("\nThis world is hard linked elsewhere."); @@ -260,12 +265,10 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const case Qt::ToolTipRole: { if (column == InfoColumn) { - for (QString path : instDirPaths()) { - if (instanceWorld.world.isSymLinkUnder(path)) { - return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original." - "\nCanonical Path: %1") - .arg(instanceWorld.world.canonicalFilePath()); - } + if (instanceWorld.world.isSymLinkUnder(QFileInfo(instanceWorld.instance->instanceRoot()).absoluteFilePath())) { + return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1") + .arg(instanceWorld.world.canonicalFilePath()); } if (instanceWorld.world.isMoreThanOneHardLink()) { return tr("Warning: This world is hard linked elsewhere. Editing it will also change the original."); @@ -294,6 +297,9 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const case IconFileRole: { return instanceWorld.world.iconFile(); } + case InstanceIconFileRole: { + return APPLICATION->icons()->getIcon(instanceWorld.instance->iconKey()); + } default: return QVariant(); } @@ -308,6 +314,8 @@ QVariant MultiWorldList::headerData(int section, [[maybe_unused]] Qt::Orientatio return tr("Name"); case InstanceColumn: return tr("Instance"); + case VersionColumn: + return tr("Version"); case GameModeColumn: return tr("Game Mode"); case LastPlayedColumn: @@ -328,6 +336,8 @@ QVariant MultiWorldList::headerData(int section, [[maybe_unused]] Qt::Orientatio return tr("The name of the world."); case InstanceColumn: return tr("The instance the world belongs to."); + case VersionColumn: + return tr("The Minecraft version of the world."); case GameModeColumn: return tr("Game mode of the world."); case LastPlayedColumn: diff --git a/launcher/minecraft/MultiWorldList.h b/launcher/minecraft/MultiWorldList.h index ddd936892..be9103ba3 100644 --- a/launcher/minecraft/MultiWorldList.h +++ b/launcher/minecraft/MultiWorldList.h @@ -33,9 +33,9 @@ struct InstanceWorld { class MultiWorldList : public QAbstractListModel { Q_OBJECT public: - enum Columns { NameColumn, InstanceColumn, GameModeColumn, LastPlayedColumn, SizeColumn, InfoColumn }; + enum Columns { NameColumn, InstanceColumn, VersionColumn, GameModeColumn, LastPlayedColumn, SizeColumn, InfoColumn }; - enum Roles { ObjectRole = Qt::UserRole + 1, FolderRole, SeedRole, NameRole, InstanceRole, GameModeRole, LastPlayedRole, SizeRole, IconFileRole }; + enum Roles { ObjectRole = Qt::UserRole + 1, FolderRole, SeedRole, NameRole, InstanceRole, VersionRole, GameModeRole, LastPlayedRole, SizeRole, IconFileRole, InstanceIconFileRole }; MultiWorldList(const QList& instances); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 7d2709a00..4ce7a61ec 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -889,11 +889,15 @@ void MainWindow::toggleAllWorldsScreen(bool toggled) ui->instanceToolBar->setVisible(false); allWorldsPage->setVisible(true); + allWorlds->startWatching(); + connect(allWorldsPage, &MultiWorldListPage::worldJoined, this, &MainWindow::worldJoined); } else { allWorldsPage->setVisible(false); view->setVisible(true); ui->instanceToolBar->setVisible(m_oldInstanceToolbarSetting); + + allWorlds->stopWatching(); } } diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index 7c0c41062..324aedf38 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -83,6 +83,12 @@ class MultiWorldListProxyModel : public QSortFilterProxyModel { return QIcon(iconFile); } + if (index.column() == 1 && role == Qt::DecorationRole) { + MultiWorldList* worlds = qobject_cast(sourceModel()); + auto icon = worlds->data(sourceIndex, MultiWorldList::InstanceIconFileRole).value(); + return icon.pixmap(24, 24); + } + return sourceIndex.data(role); } }; @@ -484,6 +490,8 @@ MinecraftInstance* MultiWorldListPage::selectInstance(const QString& message, Ba } } + instanceList->sortItems(Qt::AscendingOrder); + layout->addWidget(instanceList); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); From 1c1717a8ea7641fce182b6141407a5e7cf849cb3 Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Fri, 12 Jun 2026 13:27:22 -0400 Subject: [PATCH 16/20] select instance after joining world from all worlds screen --- launcher/ui/MainWindow.cpp | 9 +++++++-- launcher/ui/MainWindow.h | 4 +++- launcher/ui/MultiWorldListPage.cpp | 2 +- launcher/ui/MultiWorldListPage.h | 2 +- launcher/ui/instanceview/InstanceView.cpp | 18 ++++++++++++++++++ launcher/ui/instanceview/InstanceView.h | 3 +++ 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 4ce7a61ec..5d4c00f38 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -335,6 +335,8 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi [](const QString& groupName) -> bool { return APPLICATION->instances()->isGroupCollapsed(groupName); }); connect(view, &InstanceView::groupStateChanged, APPLICATION->instances(), &InstanceList::on_GroupStateChanged); ui->horizontalLayout->addWidget(view); + + connect(this, &MainWindow::selectInstance, view, &InstanceView::selectInstance); } // Create the all worlds widget { @@ -866,10 +868,11 @@ void MainWindow::onAllWorldsToggled(bool toggled) toggleAllWorldsScreen(toggled); } -void MainWindow::worldJoined() +void MainWindow::worldJoined(BaseInstance* instance) { ui->actionAllWorlds->setChecked(false); toggleAllWorldsScreen(false); + emit selectInstance(instance); } void MainWindow::toggleAllWorldsScreen(bool toggled) @@ -877,7 +880,7 @@ void MainWindow::toggleAllWorldsScreen(bool toggled) if (toggled) { QList allInstances = APPLICATION->instances()->getAllInstances(); - allWorlds = new MultiWorldList(allInstances); + allWorlds = new MultiWorldList(allInstances); //make this be unique pointer or whatever instead of awkward replace and delete iy allWorlds->update(); auto newAllWorldsPage = new MultiWorldListPage(allWorlds); ui->horizontalLayout->replaceWidget(allWorldsPage, newAllWorldsPage); @@ -888,6 +891,7 @@ void MainWindow::toggleAllWorldsScreen(bool toggled) m_oldInstanceToolbarSetting = ui->instanceToolBar->isVisible(); ui->instanceToolBar->setVisible(false); allWorldsPage->setVisible(true); + statusBar()->setVisible(false); allWorlds->startWatching(); @@ -896,6 +900,7 @@ void MainWindow::toggleAllWorldsScreen(bool toggled) allWorldsPage->setVisible(false); view->setVisible(true); ui->instanceToolBar->setVisible(m_oldInstanceToolbarSetting); + statusBar()->setVisible(APPLICATION->settings()->get("StatusBarVisible").toBool()); allWorlds->stopWatching(); } diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index ee4a53a69..493d9c8e2 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -86,6 +86,8 @@ class MainWindow : public QMainWindow { signals: void isClosing(); + void selectInstance(BaseInstance* instance); + protected: QMenu* createPopupMenu() override; @@ -175,7 +177,7 @@ class MainWindow : public QMainWindow { void taskEnd(); - void worldJoined(); + void worldJoined(BaseInstance* instance); /** * called when an icon is changed in the icon model. diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index 324aedf38..4f0d41614 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -587,7 +587,7 @@ void MultiWorldListPage::join(QModelIndex index) auto worldVariant = m_worlds->data(index, MultiWorldList::ObjectRole); auto *world = static_cast(worldVariant.value()); APPLICATION->launch(world->instance, LaunchMode::Normal, std::make_shared(MinecraftTarget::parse(world->world.folderName(), true))); - emit worldJoined(); + emit worldJoined(world->instance); } #include "MultiWorldListPage.moc" diff --git a/launcher/ui/MultiWorldListPage.h b/launcher/ui/MultiWorldListPage.h index 848bd5a41..4af09503b 100644 --- a/launcher/ui/MultiWorldListPage.h +++ b/launcher/ui/MultiWorldListPage.h @@ -66,7 +66,7 @@ class MultiWorldListPage : public QMainWindow, public BasePage { virtual void closedImpl() override; signals: - void worldJoined(); + void worldJoined(BaseInstance* instance); protected: bool eventFilter(QObject* obj, QEvent* ev) override; diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index 9a24b7990..1619443e0 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -207,6 +207,24 @@ void InstanceView::updateGeometries() viewport()->update(); } +void InstanceView::selectInstance(BaseInstance* instance) +{ + QModelIndex index; + + for (int row = 0; row < model()->rowCount(); row++) { + for (int j = 0; j < model()->columnCount(); j++) { + auto testIndex = model()->index(row, j); + if (testIndex.data(InstanceList::InstanceIDRole).toString() == instance->id()) { + index = testIndex; + } + } + } + + if (index.isValid()) { + selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + } +} + bool InstanceView::isIndexHidden(const QModelIndex& index) const { VisualGroup* cat = category(index); diff --git a/launcher/ui/instanceview/InstanceView.h b/launcher/ui/instanceview/InstanceView.h index 5d9dbf729..db5d01dd8 100644 --- a/launcher/ui/instanceview/InstanceView.h +++ b/launcher/ui/instanceview/InstanceView.h @@ -40,6 +40,8 @@ #include #include #include + +#include "BaseInstance.h" #include "VisualGroup.h" #include "ui/themes/CatPainter.h" @@ -82,6 +84,7 @@ class InstanceView : public QAbstractItemView { public slots: virtual void updateGeometries() override; + void selectInstance(BaseInstance* instance); protected slots: virtual void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList& roles) override; From cb105b720b130e5341983ce03918309301e5335c Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> Date: Sun, 14 Jun 2026 15:47:04 -0400 Subject: [PATCH 17/20] join offline + icon for all worlds toggle --- launcher/ui/MainWindow.ui | 2 +- launcher/ui/MultiWorldListPage.cpp | 17 ++++++++++++----- launcher/ui/MultiWorldListPage.h | 3 ++- launcher/ui/MultiWorldListPage.ui | 6 ++++++ 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 2e7d210dd..d2ac79f2f 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -786,7 +786,7 @@ true - + &All Worlds diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index 4f0d41614..7a08d046c 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -397,6 +397,7 @@ void MultiWorldListPage::worldChanged([[maybe_unused]] const QModelIndex& curren ui->actionData_Packs->setEnabled(enable); ui->actionView_Folder->setEnabled(enable); ui->actionJoin->setEnabled(enable); + ui->actionJoin_Offline->setEnabled(enable); ui->actionInstance_Settings->setEnabled(enable); bool hasIcon = !index.data(MultiWorldList::IconFileRole).isNull(); ui->actionReset_Icon->setEnabled(enable && hasIcon); @@ -463,8 +464,8 @@ void MultiWorldListPage::on_actionCopy_triggered() } } +// TODO: Make this a separate dialog class Q_DECLARE_METATYPE(BaseInstance*); - MinecraftInstance* MultiWorldListPage::selectInstance(const QString& message, BaseInstance* preselectedInstance) { auto *dialog = new QDialog(this); @@ -559,7 +560,7 @@ void MultiWorldListPage::on_actionRefresh_triggered() void MultiWorldListPage::worldDoubleClicked(const QModelIndex& index) { auto proxy = (QSortFilterProxyModel*)ui->worldTreeView->model(); - join(proxy->mapToSource(index)); + join(proxy->mapToSource(index), LaunchMode::Normal); } void MultiWorldListPage::fileDropped(const QFileInfo& worldInfo) @@ -576,17 +577,23 @@ void MultiWorldListPage::fileDropped(const QFileInfo& worldInfo) void MultiWorldListPage::on_actionJoin_triggered() { QModelIndex index = getSelectedWorld(); - join(index); + join(index, LaunchMode::Normal); } -void MultiWorldListPage::join(QModelIndex index) +void MultiWorldListPage::on_actionJoin_Offline_triggered() +{ + QModelIndex index = getSelectedWorld(); + join(index, LaunchMode::Offline); +} + +void MultiWorldListPage::join(const QModelIndex& index, const LaunchMode launchMode) { if (!index.isValid()) { return; } auto worldVariant = m_worlds->data(index, MultiWorldList::ObjectRole); auto *world = static_cast(worldVariant.value()); - APPLICATION->launch(world->instance, LaunchMode::Normal, std::make_shared(MinecraftTarget::parse(world->world.folderName(), true))); + APPLICATION->launch(world->instance, launchMode, std::make_shared(MinecraftTarget::parse(world->world.folderName(), true))); emit worldJoined(world->instance); } diff --git a/launcher/ui/MultiWorldListPage.h b/launcher/ui/MultiWorldListPage.h index 4af09503b..b2ef3e080 100644 --- a/launcher/ui/MultiWorldListPage.h +++ b/launcher/ui/MultiWorldListPage.h @@ -78,7 +78,7 @@ class MultiWorldListPage : public QMainWindow, public BasePage { bool isWorldSafe(QModelIndex index); bool worldSafetyNagQuestion(const QString& actionType); void mceditError(); - void join(QModelIndex index); + void join(const QModelIndex& index, LaunchMode launchMode); MinecraftInstance* selectInstance(const QString& message, BaseInstance* instance = nullptr); private: @@ -105,6 +105,7 @@ class MultiWorldListPage : public QMainWindow, public BasePage { void worldChanged(const QModelIndex& current, const QModelIndex& previous); void mceditState(LoggedProcess::State state); void on_actionJoin_triggered(); + void on_actionJoin_Offline_triggered(); void worldDoubleClicked(const QModelIndex& index); void fileDropped(const QFileInfo& worldInfo); diff --git a/launcher/ui/MultiWorldListPage.ui b/launcher/ui/MultiWorldListPage.ui index aa89840aa..5c1a86b75 100644 --- a/launcher/ui/MultiWorldListPage.ui +++ b/launcher/ui/MultiWorldListPage.ui @@ -85,6 +85,7 @@ + @@ -107,6 +108,11 @@ Join + + + Join Offline + + Rename From 08d9e57d96f95a356c8e1b8dd348fd58a03d7a83 Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+iceyetiwins@users.noreply.github.com> Date: Sun, 14 Jun 2026 17:23:10 -0400 Subject: [PATCH 18/20] Final clean up for pull request Signed-off-by: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> --- launcher/InstanceList.cpp | 4 +- launcher/InstanceList.h | 2 +- launcher/minecraft/MultiWorldList.cpp | 6 +-- launcher/minecraft/MultiWorldList.h | 2 +- launcher/ui/MainWindow.cpp | 50 +++++++++++------------ launcher/ui/MainWindow.h | 2 +- launcher/ui/MultiWorldListPage.cpp | 12 +++--- launcher/ui/MultiWorldListPage.h | 2 +- launcher/ui/instanceview/InstanceView.cpp | 6 +-- launcher/ui/instanceview/InstanceView.h | 2 +- 10 files changed, 44 insertions(+), 44 deletions(-) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 8eb43d5da..27b2da7cf 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -607,11 +607,11 @@ void InstanceList::providerUpdated() } } -QList InstanceList::getAllInstances() +QList InstanceList::getAllInstances() const { QList instanceList; - for (auto& inst : instances) { + for (const auto& inst : instances) { instanceList.append(inst.get()); } diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index 76ac01bc3..c0265c486 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -103,7 +103,7 @@ class InstanceList : public QAbstractListModel { InstListError loadList(); void saveNow(); - QList getAllInstances(); + QList getAllInstances() const; /* O(n) */ BaseInstance* getInstanceById(QString id) const; /* O(n) */ diff --git a/launcher/minecraft/MultiWorldList.cpp b/launcher/minecraft/MultiWorldList.cpp index 12b8529cb..280f84215 100644 --- a/launcher/minecraft/MultiWorldList.cpp +++ b/launcher/minecraft/MultiWorldList.cpp @@ -77,7 +77,7 @@ void MultiWorldList::startWatching() m_isWatching = true; - for (QDir dir : m_dirs) { + for (const QDir& dir : m_dirs) { if (m_watcher->addPath(dir.absolutePath())) { qDebug() << "Started watching" << dir.absolutePath(); } else { @@ -157,7 +157,7 @@ QList MultiWorldList::instDirPaths() const { QList dirList; - for (BaseInstance* instance : allInstances) { + for (BaseInstance const* instance : allInstances) { dirList.append(QFileInfo(instance->instanceRoot()).absoluteFilePath()); } @@ -223,7 +223,7 @@ QVariant MultiWorldList::data(const QModelIndex& index, int role) const QLocale locale; - auto& instanceWorld = m_worlds[row]; + const auto& instanceWorld = m_worlds[row]; switch (role) { case Qt::DisplayRole: switch (column) { diff --git a/launcher/minecraft/MultiWorldList.h b/launcher/minecraft/MultiWorldList.h index be9103ba3..dc0829349 100644 --- a/launcher/minecraft/MultiWorldList.h +++ b/launcher/minecraft/MultiWorldList.h @@ -73,7 +73,7 @@ class MultiWorldList : public QAbstractListModel { /// process data from drop action virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); /// what drag actions do we support? - int64_t calculateWorldSize(const QFileInfo& file); + static int64_t calculateWorldSize(const QFileInfo& file); virtual Qt::DropActions supportedDragActions() const; /// what drop actions do we support? diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 5d4c00f38..b80b197f2 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -338,19 +338,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi connect(this, &MainWindow::selectInstance, view, &InstanceView::selectInstance); } - // Create the all worlds widget + + // All worlds toggle { - QList allInstances = APPLICATION->instances()->getAllInstances(); - - allWorlds = new MultiWorldList(allInstances); - allWorlds->update(); - allWorldsPage = new MultiWorldListPage(allWorlds); - - ui->horizontalLayout->addWidget(allWorldsPage); - - allWorldsPage->setVisible(false); - ui->instanceToolBar->setVisibilityState(QByteArray::fromBase64(instanceToolbarSetting->get().toString().toUtf8())); - connect(ui->actionAllWorlds, &QAction::toggled, this, &MainWindow::onAllWorldsToggled); connect(allWorldsPage, &MultiWorldListPage::worldJoined, this, &MainWindow::worldJoined); } @@ -878,14 +868,13 @@ void MainWindow::worldJoined(BaseInstance* instance) void MainWindow::toggleAllWorldsScreen(bool toggled) { if (toggled) { - QList allInstances = APPLICATION->instances()->getAllInstances(); + QList const allInstances = APPLICATION->instances()->getAllInstances(); - allWorlds = new MultiWorldList(allInstances); //make this be unique pointer or whatever instead of awkward replace and delete iy - allWorlds->update(); - auto newAllWorldsPage = new MultiWorldListPage(allWorlds); - ui->horizontalLayout->replaceWidget(allWorldsPage, newAllWorldsPage); - delete allWorldsPage; - allWorldsPage = newAllWorldsPage; + allWorldsList = new MultiWorldList(allInstances); + allWorldsList->update(); + + allWorldsPage = new MultiWorldListPage(allWorldsList); + ui->horizontalLayout->addWidget(allWorldsPage); view->setVisible(false); m_oldInstanceToolbarSetting = ui->instanceToolBar->isVisible(); @@ -893,16 +882,27 @@ void MainWindow::toggleAllWorldsScreen(bool toggled) allWorldsPage->setVisible(true); statusBar()->setVisible(false); - allWorlds->startWatching(); + allWorldsList->startWatching(); connect(allWorldsPage, &MultiWorldListPage::worldJoined, this, &MainWindow::worldJoined); } else { - allWorldsPage->setVisible(false); - view->setVisible(true); - ui->instanceToolBar->setVisible(m_oldInstanceToolbarSetting); - statusBar()->setVisible(APPLICATION->settings()->get("StatusBarVisible").toBool()); + if (allWorldsList == nullptr || allWorldsPage == nullptr) { + view->setVisible(true); + ui->instanceToolBar->setVisible(m_oldInstanceToolbarSetting); + statusBar()->setVisible(APPLICATION->settings()->get("StatusBarVisible").toBool()); + } else { + allWorldsPage->setVisible(false); + view->setVisible(true); + ui->instanceToolBar->setVisible(m_oldInstanceToolbarSetting); + statusBar()->setVisible(APPLICATION->settings()->get("StatusBarVisible").toBool()); - allWorlds->stopWatching(); + allWorldsList->stopWatching(); + + delete allWorldsList; + allWorldsList = nullptr; + delete allWorldsPage; + allWorldsPage = nullptr; + } } } diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 493d9c8e2..601a2e5f0 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -255,7 +255,7 @@ class MainWindow : public QMainWindow { LabeledToolButton* renameButton = nullptr; QToolButton* helpMenuButton = nullptr; KonamiCode* secretEventFilter = nullptr; - MultiWorldList* allWorlds = nullptr; + MultiWorldList* allWorldsList = nullptr; std::shared_ptr instanceToolbarSetting = nullptr; bool m_oldInstanceToolbarSetting; diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index 7a08d046c..4e597fb60 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -466,7 +466,7 @@ void MultiWorldListPage::on_actionCopy_triggered() // TODO: Make this a separate dialog class Q_DECLARE_METATYPE(BaseInstance*); -MinecraftInstance* MultiWorldListPage::selectInstance(const QString& message, BaseInstance* preselectedInstance) +MinecraftInstance* MultiWorldListPage::selectInstance(const QString& message, const BaseInstance* preselectedInstance) { auto *dialog = new QDialog(this); dialog->setWindowTitle(tr("Select Instance")); @@ -475,13 +475,13 @@ MinecraftInstance* MultiWorldListPage::selectInstance(const QString& message, Ba static_cast(std::max(0.75 * window()->height(), 400.0))); dialog->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("SelectInstanceGeometry").toByteArray())); - auto layout = new QVBoxLayout(dialog); + auto *layout = new QVBoxLayout(dialog); layout->addWidget(new QLabel(message)); - auto instanceList = new QListWidget(dialog); + auto *instanceList = new QListWidget(dialog); - for (auto instance : m_worlds->getInstances()) { + for (auto *instance : m_worlds->getInstances()) { auto *item = new QListWidgetItem(instanceList); item->setText(instance->name()); item->setIcon(APPLICATION->icons()->getIcon(instance->iconKey())); @@ -495,7 +495,7 @@ MinecraftInstance* MultiWorldListPage::selectInstance(const QString& message, Ba layout->addWidget(instanceList); - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); layout->addWidget(buttonBox); @@ -559,7 +559,7 @@ void MultiWorldListPage::on_actionRefresh_triggered() void MultiWorldListPage::worldDoubleClicked(const QModelIndex& index) { - auto proxy = (QSortFilterProxyModel*)ui->worldTreeView->model(); + auto *proxy = static_cast(ui->worldTreeView->model()); join(proxy->mapToSource(index), LaunchMode::Normal); } diff --git a/launcher/ui/MultiWorldListPage.h b/launcher/ui/MultiWorldListPage.h index b2ef3e080..4e3e4b466 100644 --- a/launcher/ui/MultiWorldListPage.h +++ b/launcher/ui/MultiWorldListPage.h @@ -79,7 +79,7 @@ class MultiWorldListPage : public QMainWindow, public BasePage { bool worldSafetyNagQuestion(const QString& actionType); void mceditError(); void join(const QModelIndex& index, LaunchMode launchMode); - MinecraftInstance* selectInstance(const QString& message, BaseInstance* instance = nullptr); + MinecraftInstance* selectInstance(const QString& message, const BaseInstance* preselectedInstance = nullptr); private: Ui::MultiWorldListPage* ui; diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index 1619443e0..90fe7606f 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -207,13 +207,13 @@ void InstanceView::updateGeometries() viewport()->update(); } -void InstanceView::selectInstance(BaseInstance* instance) +void InstanceView::selectInstance(const BaseInstance* instance) const { QModelIndex index; for (int row = 0; row < model()->rowCount(); row++) { - for (int j = 0; j < model()->columnCount(); j++) { - auto testIndex = model()->index(row, j); + for (int col = 0; col < model()->columnCount(); col++) { + auto testIndex = model()->index(row, col); if (testIndex.data(InstanceList::InstanceIDRole).toString() == instance->id()) { index = testIndex; } diff --git a/launcher/ui/instanceview/InstanceView.h b/launcher/ui/instanceview/InstanceView.h index db5d01dd8..3fca0c4fa 100644 --- a/launcher/ui/instanceview/InstanceView.h +++ b/launcher/ui/instanceview/InstanceView.h @@ -84,7 +84,7 @@ class InstanceView : public QAbstractItemView { public slots: virtual void updateGeometries() override; - void selectInstance(BaseInstance* instance); + void selectInstance(const BaseInstance* instance) const; protected slots: virtual void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList& roles) override; From a31be08606fd593f04c987b75b5e3e7eec33bff1 Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+iceyetiwins@users.noreply.github.com> Date: Mon, 15 Jun 2026 22:38:13 -0400 Subject: [PATCH 19/20] fix crash Signed-off-by: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index b80b197f2..5b2ff9d8e 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -898,10 +898,10 @@ void MainWindow::toggleAllWorldsScreen(bool toggled) allWorldsList->stopWatching(); - delete allWorldsList; - allWorldsList = nullptr; - delete allWorldsPage; + allWorldsPage->deleteLater(); allWorldsPage = nullptr; + allWorldsList->deleteLater(); + allWorldsList = nullptr; } } } From b1344f89c537c6bdf997ed38759fef866e373f2d Mon Sep 17 00:00:00 2001 From: Ice Yeti <101294194+iceyetiwins@users.noreply.github.com> Date: Tue, 16 Jun 2026 16:54:14 -0400 Subject: [PATCH 20/20] DCO Remediation Commit for Ice Yeti <101294194+iceyetiwins@users.noreply.github.com> I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: cb105b7 I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 1c1717a I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 2e656c2 I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 7e7de65 I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 3458830 I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: b13cfd9 I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: b0a600b I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 547d870 I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: bd9bea7 I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 66d661f I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: e88b293 I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 3a6ae78 I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 4d1141f I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: c295055 I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 66b20c9 I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: e4c48e5 I, Ice Yeti <101294194+iceyetiwins@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 5f569ae Signed-off-by: Ice Yeti <101294194+IceYetiWins@users.noreply.github.com> --- launcher/ui/MultiWorldListPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/MultiWorldListPage.cpp b/launcher/ui/MultiWorldListPage.cpp index 4e597fb60..76756f4ba 100644 --- a/launcher/ui/MultiWorldListPage.cpp +++ b/launcher/ui/MultiWorldListPage.cpp @@ -464,7 +464,7 @@ void MultiWorldListPage::on_actionCopy_triggered() } } -// TODO: Make this a separate dialog class +// TODO: Make this a separate dialog class in launcher/ui/dialogs Q_DECLARE_METATYPE(BaseInstance*); MinecraftInstance* MultiWorldListPage::selectInstance(const QString& message, const BaseInstance* preselectedInstance) {