summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt39
-rw-r--r--changelog.yaml2
-rw-r--r--depends/groupview/CMakeLists.txt46
-rw-r--r--depends/groupview/include/categorizedsortfilterproxymodel.h175
-rw-r--r--depends/groupview/include/categorizedview.h332
-rw-r--r--depends/groupview/include/categorydrawer.h179
-rw-r--r--depends/groupview/src/categorizedsortfilterproxymodel.cpp168
-rw-r--r--depends/groupview/src/categorizedsortfilterproxymodel_p.h48
-rw-r--r--depends/groupview/src/categorizedview.cpp1692
-rw-r--r--depends/groupview/src/categorizedview_p.h160
-rw-r--r--depends/groupview/src/categorydrawer.cpp231
-rw-r--r--depends/util/CMakeLists.txt3
-rw-r--r--depends/util/include/modutils.h32
-rw-r--r--depends/util/src/modutils.cpp216
-rw-r--r--gui/ConsoleWindow.cpp18
-rw-r--r--gui/ConsoleWindow.h2
-rw-r--r--gui/MainWindow.cpp236
-rw-r--r--gui/MainWindow.h21
-rw-r--r--gui/MainWindow.ui60
-rw-r--r--gui/dialogs/AccountListDialog.cpp2
-rw-r--r--gui/dialogs/IconPickerDialog.cpp2
-rw-r--r--gui/dialogs/OneSixModEditDialog.cpp256
-rw-r--r--gui/dialogs/OneSixModEditDialog.h12
-rw-r--r--gui/dialogs/OneSixModEditDialog.ui65
-rw-r--r--gui/dialogs/VersionSelectDialog.cpp5
-rw-r--r--gui/dialogs/VersionSelectDialog.h1
-rw-r--r--gui/dialogs/VersionSelectDialog.ui9
-rw-r--r--gui/groupview/Group.cpp269
-rw-r--r--gui/groupview/Group.h69
-rw-r--r--gui/groupview/GroupView.cpp931
-rw-r--r--gui/groupview/GroupView.h143
-rw-r--r--gui/groupview/GroupedProxyModel.cpp26
-rw-r--r--gui/groupview/GroupedProxyModel.h15
-rw-r--r--gui/groupview/InstanceDelegate.cpp (renamed from gui/widgets/InstanceDelegate.cpp)32
-rw-r--r--gui/groupview/InstanceDelegate.h (renamed from gui/widgets/InstanceDelegate.h)8
-rw-r--r--gui/widgets/Common.cpp27
-rw-r--r--gui/widgets/Common.h6
-rw-r--r--gui/widgets/VersionListView.cpp150
-rw-r--r--gui/widgets/VersionListView.h43
-rw-r--r--logic/BaseInstaller.cpp66
-rw-r--r--logic/BaseInstaller.h (renamed from depends/groupview/include/groupview_config.h)31
-rw-r--r--logic/BaseInstance.h4
-rw-r--r--logic/ForgeInstaller.cpp95
-rw-r--r--logic/ForgeInstaller.h10
-rw-r--r--logic/InstanceFactory.cpp10
-rw-r--r--logic/LegacyInstance.cpp15
-rw-r--r--logic/LegacyInstance.h4
-rw-r--r--logic/LegacyUpdate.cpp12
-rw-r--r--logic/LegacyUpdate.h3
-rw-r--r--logic/LiteLoaderInstaller.cpp101
-rw-r--r--logic/LiteLoaderInstaller.h20
-rw-r--r--logic/MinecraftProcess.cpp28
-rw-r--r--logic/MinecraftProcess.h6
-rw-r--r--logic/Mod.cpp21
-rw-r--r--logic/ModList.cpp27
-rw-r--r--logic/ModList.h1
-rw-r--r--logic/OneSixFTBInstance.cpp12
-rw-r--r--logic/OneSixFTBInstance.h2
-rw-r--r--logic/OneSixInstance.cpp157
-rw-r--r--logic/OneSixInstance.h33
-rw-r--r--logic/OneSixInstance_p.h12
-rw-r--r--logic/OneSixLibrary.cpp38
-rw-r--r--logic/OneSixLibrary.h41
-rw-r--r--logic/OneSixRule.cpp4
-rw-r--r--logic/OneSixRule.h2
-rw-r--r--logic/OneSixUpdate.cpp56
-rw-r--r--logic/OneSixUpdate.h6
-rw-r--r--logic/OneSixVersion.cpp355
-rw-r--r--logic/OneSixVersion.h63
-rw-r--r--logic/OneSixVersionBuilder.cpp1077
-rw-r--r--logic/OneSixVersionBuilder.h47
-rw-r--r--logic/auth/AuthSession.cpp30
-rw-r--r--logic/auth/AuthSession.h49
-rw-r--r--logic/auth/MojangAccount.cpp82
-rw-r--r--logic/auth/MojangAccount.h38
-rw-r--r--logic/auth/YggdrasilTask.h17
-rw-r--r--logic/auth/flows/RefreshTask.cpp4
-rw-r--r--logic/auth/flows/RefreshTask.h3
-rw-r--r--logic/lists/InstanceList.cpp116
-rw-r--r--logic/lists/InstanceList.h20
-rw-r--r--logic/lists/MinecraftVersionList.cpp10
-rw-r--r--logic/lists/MinecraftVersionList.h2
-rw-r--r--logic/net/NetJob.cpp2
-rw-r--r--logic/net/NetJob.h2
84 files changed, 4442 insertions, 4023 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0b276cf8..4e7dea67 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -93,7 +93,7 @@ SET(MultiMC_NEWS_RSS_URL "http://multimc.org/rss.xml" CACHE STRING "URL to fetch
######## Set version numbers ########
SET(MultiMC_VERSION_MAJOR 0)
SET(MultiMC_VERSION_MINOR 2)
-SET(MultiMC_VERSION_HOTFIX 0)
+SET(MultiMC_VERSION_HOTFIX 1)
# Build number
SET(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
@@ -186,7 +186,6 @@ configure_file("${PROJECT_SOURCE_DIR}/config.h.in" "${PROJECT_BINARY_DIR}/includ
ADD_DEFINITIONS(-DQUAZIP_STATIC)
ADD_DEFINITIONS(-DLIBSETTINGS_STATIC)
ADD_DEFINITIONS(-DLIBUTIL_STATIC)
-ADD_DEFINITIONS(-DLIBGROUPVIEW_STATIC)
######## Packaging/install paths setup ########
@@ -247,10 +246,6 @@ include_directories(${LIBUTIL_INCLUDE_DIR})
add_subdirectory(depends/settings)
include_directories(${LIBSETTINGS_INCLUDE_DIR})
-# Add the group view library.
-add_subdirectory(depends/groupview)
-include_directories(${LIBGROUPVIEW_INCLUDE_DIR})
-
# Add the updater
add_subdirectory(mmc_updater)
@@ -315,15 +310,27 @@ gui/dialogs/UpdateDialog.h
gui/dialogs/UpdateDialog.cpp
# GUI - widgets
-gui/widgets/InstanceDelegate.h
-gui/widgets/InstanceDelegate.cpp
+gui/widgets/Common.h
+gui/widgets/Common.cpp
gui/widgets/ModListView.h
gui/widgets/ModListView.cpp
+gui/widgets/VersionListView.h
+gui/widgets/VersionListView.cpp
gui/widgets/LabeledToolButton.h
gui/widgets/LabeledToolButton.cpp
gui/widgets/MCModInfoFrame.h
gui/widgets/MCModInfoFrame.cpp
+# GUI - instance group view
+gui/groupview/Group.cpp
+gui/groupview/Group.h
+gui/groupview/GroupedProxyModel.cpp
+gui/groupview/GroupedProxyModel.h
+gui/groupview/GroupView.cpp
+gui/groupview/GroupView.h
+gui/groupview/InstanceDelegate.cpp
+gui/groupview/InstanceDelegate.h
+
# Base classes and infrastructure
logic/BaseVersion.h
logic/MinecraftVersion.h
@@ -365,6 +372,8 @@ logic/net/PasteUpload.cpp
logic/net/URLConstants.h
# Yggdrasil login stuff
+logic/auth/AuthSession.h
+logic/auth/AuthSession.cpp
logic/auth/MojangAccountList.h
logic/auth/MojangAccountList.cpp
logic/auth/MojangAccount.h
@@ -405,10 +414,7 @@ logic/LegacyUpdate.cpp
logic/LegacyForge.h
logic/LegacyForge.cpp
-# 1.6 instances
-logic/OneSixInstance.h
-logic/OneSixInstance.cpp
-logic/OneSixInstance_p.h
+# OneSix instances
logic/OneSixUpdate.h
logic/OneSixUpdate.cpp
logic/OneSixVersion.h
@@ -419,10 +425,17 @@ logic/OneSixRule.h
logic/OneSixRule.cpp
logic/OpSys.h
logic/OpSys.cpp
+logic/BaseInstaller.h
+logic/BaseInstaller.cpp
logic/ForgeInstaller.h
logic/ForgeInstaller.cpp
logic/LiteLoaderInstaller.h
logic/LiteLoaderInstaller.cpp
+logic/OneSixInstance.h
+logic/OneSixInstance.cpp
+logic/OneSixInstance_p.h
+logic/OneSixVersionBuilder.h
+logic/OneSixVersionBuilder.cpp
# Nostalgia
logic/NostalgiaInstance.h
@@ -578,7 +591,7 @@ ADD_EXECUTABLE(MultiMC MACOSX_BUNDLE WIN32 main.cpp ${MULTIMC_RCS})
# Link
TARGET_LINK_LIBRARIES(MultiMC MultiMC_common)
-TARGET_LINK_LIBRARIES(MultiMC_common xz-embedded unpack200 quazip libUtil libSettings libGroupView ${MultiMC_LINK_ADDITIONAL_LIBS})
+TARGET_LINK_LIBRARIES(MultiMC_common xz-embedded unpack200 quazip libUtil libSettings ${MultiMC_LINK_ADDITIONAL_LIBS})
QT5_USE_MODULES(MultiMC Core Widgets Network Xml Concurrent ${MultiMC_QT_ADDITIONAL_MODULES})
QT5_USE_MODULES(MultiMC_common Core Widgets Network Xml Concurrent ${MultiMC_QT_ADDITIONAL_MODULES})
diff --git a/changelog.yaml b/changelog.yaml
index 01e11f52..8b96b76e 100644
--- a/changelog.yaml
+++ b/changelog.yaml
@@ -29,3 +29,5 @@
- Started using icon themes for the application icons, fixing many OSX graphical glitches.
- Icon sources have been located, along with icon licenses.
- Update to the German translation.
+0.2.1:
+ - Hotfix - move the native library extraction into the onesix launcher part.
diff --git a/depends/groupview/CMakeLists.txt b/depends/groupview/CMakeLists.txt
deleted file mode 100644
index 417b1d3a..00000000
--- a/depends/groupview/CMakeLists.txt
+++ /dev/null
@@ -1,46 +0,0 @@
-project(libGroupView)
-
-set(CMAKE_AUTOMOC ON)
-
-# Find Qt
-find_package(Qt5Core REQUIRED)
-find_package(Qt5Widgets REQUIRED)
-
-# Include Qt headers.
-include_directories(${Qt5Base_INCLUDE_DIRS})
-
-SET(LIBGROUPVIEW_HEADERS
-include/groupview_config.h
-
-# Public headers
-include/categorizedsortfilterproxymodel.h
-include/categorizedview.h
-include/categorydrawer.h
-
-# Private headers
-src/categorizedsortfilterproxymodel_p.h
-src/categorizedview_p.h
-)
-
-SET(LIBGROUPVIEW_SOURCES
-src/categorizedsortfilterproxymodel.cpp
-src/categorizedview.cpp
-src/categorydrawer.cpp
-)
-
-# Set the include dir path.
-SET(LIBGROUPVIEW_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE)
-
-# Include self.
-include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
-include_directories(${CMAKE_BINARY_DIR}/include)
-
-# Static link!
-ADD_DEFINITIONS(-DLIBGROUPVIEW_STATIC)
-
-add_definitions(-DLIBGROUPVIEW_LIBRARY)
-
-set(CMAKE_POSITION_INDEPENDENT_CODE ON)
-
-add_library(libGroupView STATIC ${LIBGROUPVIEW_SOURCES} ${LIBGROUPVIEW_HEADERS})
-qt5_use_modules(libGroupView Core Widgets)
diff --git a/depends/groupview/include/categorizedsortfilterproxymodel.h b/depends/groupview/include/categorizedsortfilterproxymodel.h
deleted file mode 100644
index d90fb254..00000000
--- a/depends/groupview/include/categorizedsortfilterproxymodel.h
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * This file is part of the KDE project
- * Copyright (C) 2007 Rafael Fernández López <ereslibre@kde.org>
- * Copyright (C) 2007 John Tapsell <tapsell@kde.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#ifndef KCATEGORIZEDSORTFILTERPROXYMODEL_H
-#define KCATEGORIZEDSORTFILTERPROXYMODEL_H
-
-#include <QSortFilterProxyModel>
-
-#include <groupview_config.h>
-
-class QItemSelection;
-
-
-/**
- * This class lets you categorize a view. It is meant to be used along with
- * KCategorizedView class.
- *
- * In general terms all you need to do is to reimplement subSortLessThan() and
- * compareCategories() methods. In order to make categorization work, you need
- * to also call setCategorizedModel() class to enable it, since the categorization
- * is disabled by default.
- *
- * @see KCategorizedView
- *
- * @author Rafael Fernández López <ereslibre@kde.org>
- */
-class LIBGROUPVIEW_EXPORT KCategorizedSortFilterProxyModel
- : public QSortFilterProxyModel
-{
-public:
- enum AdditionalRoles
- {
- // Note: use printf "0x%08X\n" $(($RANDOM*$RANDOM))
- // to define additional roles.
- CategoryDisplayRole = 0x17CE990A, ///< This role is used for asking the category to a given index
-
- CategorySortRole = 0x27857E60 ///< This role is used for sorting categories. You can return a
- ///< string or a long long value. Strings will be sorted alphabetically
- ///< while long long will be sorted by their value. Please note that this
- ///< value won't be shown on the view, is only for sorting purposes. What will
- ///< be shown as "Category" on the view will be asked with the role
- ///< CategoryDisplayRole.
- };
-
- KCategorizedSortFilterProxyModel ( QObject *parent = 0 );
- virtual ~KCategorizedSortFilterProxyModel();
-
- /**
- * Overridden from QSortFilterProxyModel. Sorts the source model using
- * @p column for the given @p order.
- */
- virtual void sort ( int column, Qt::SortOrder order = Qt::AscendingOrder );
-
- /**
- * @return whether the model is categorized or not. Disabled by default.
- */
- bool isCategorizedModel() const;
-
- /**
- * Enables or disables the categorization feature.
- *
- * @param categorizedModel whether to enable or disable the categorization feature.
- */
- void setCategorizedModel ( bool categorizedModel );
-
- /**
- * @return the column being used for sorting.
- */
- int sortColumn() const;
-
- /**
- * @return the sort order being used for sorting.
- */
- Qt::SortOrder sortOrder() const;
-
- /**
- * Set if the sorting using CategorySortRole will use a natural comparison
- * in the case that strings were returned. If enabled, QString::localeAwareCompare
- * will be used for sorting.
- *
- * @param sortCategoriesByNaturalComparison whether to sort using a natural comparison or not.
- */
- void setSortCategoriesByNaturalComparison ( bool sortCategoriesByNaturalComparison );
-
- /**
- * @return whether it is being used a natural comparison for sorting. Enabled by default.
- */
- bool sortCategoriesByNaturalComparison() const;
-
-protected:
- /**
- * Overridden from QSortFilterProxyModel. If you are subclassing
- * KCategorizedSortFilterProxyModel, you will probably not need to reimplement this
- * method.
- *
- * It calls compareCategories() to sort by category. If the both items are in the
- * same category (i.e. compareCategories returns 0), then subSortLessThan is called.
- *
- * @return Returns true if the item @p left is less than the item @p right when sorting.
- *
- * @warning You usually won't need to reimplement this method when subclassing
- * from KCategorizedSortFilterProxyModel.
- */
- virtual bool lessThan ( const QModelIndex &left, const QModelIndex &right ) const;
-
- /**
- * This method has a similar purpose as lessThan() has on QSortFilterProxyModel.
- * It is used for sorting items that are in the same category.
- *
- * @return Returns true if the item @p left is less than the item @p right when sorting.
- */
- virtual bool subSortLessThan ( const QModelIndex &left, const QModelIndex &right ) const;
-
- /**
- * This method compares the category of the @p left index with the category
- * of the @p right index.
- *
- * Internally and if not reimplemented, this method will ask for @p left and
- * @p right models for role CategorySortRole. In order to correctly sort
- * categories, the data() metod of the model should return a qlonglong (or numeric) value, or
- * a QString object. QString objects will be sorted with QString::localeAwareCompare if
- * sortCategoriesByNaturalComparison() is true.
- *
- * @note Please have present that:
- * QString(QChar(QChar::ObjectReplacementCharacter)) >
- * QString(QChar(QChar::ReplacementCharacter)) >
- * [ all possible strings ] >
- * QString();
- *
- * This means that QString() will be sorted the first one, while
- * QString(QChar(QChar::ObjectReplacementCharacter)) and
- * QString(QChar(QChar::ReplacementCharacter)) will be sorted in last
- * position.
- *
- * @warning Please note that data() method of the model should return always
- * information of the same type. If you return a QString for an index,
- * you should return always QStrings for all indexes for role CategorySortRole
- * in order to correctly sort categories. You can't mix by returning
- * a QString for one index, and a qlonglong for other.
- *
- * @note If you need a more complex layout, you will have to reimplement this
- * method.
- *
- * @return A negative value if the category of @p left should be placed before the
- * category of @p right. 0 if @p left and @p right are on the same category, and
- * a positive value if the category of @p left should be placed after the
- * category of @p right.
- */
- virtual int compareCategories ( const QModelIndex &left, const QModelIndex &right ) const;
-
-private:
- class Private;
- Private *const d;
-};
-
-
-#endif // KCATEGORIZEDSORTFILTERPROXYMODEL_H
diff --git a/depends/groupview/include/categorizedview.h b/depends/groupview/include/categorizedview.h
deleted file mode 100644
index 81b1dbb1..00000000
--- a/depends/groupview/include/categorizedview.h
+++ /dev/null
@@ -1,332 +0,0 @@
-/**
- * This file is part of the KDE project
- * Copyright (C) 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#ifndef KCATEGORIZEDVIEW_H
-#define KCATEGORIZEDVIEW_H
-
-#include <QListView>
-
-#include <groupview_config.h>
-
-class KCategoryDrawer;
-
-/**
- * @short Item view for listing items in a categorized fashion optionally
- *
- * KCategorizedView basically has the same functionality as QListView, only that it also lets you
- * layout items in a way that they are categorized visually.
- *
- * For it to work you will need to set a KCategorizedSortFilterProxyModel and a KCategoryDrawer
- * with methods setModel() and setCategoryDrawer() respectively. Also, the model will need to be
- * flagged as categorized with KCategorizedSortFilterProxyModel::setCategorizedModel(true).
- *
- * The way it works (if categorization enabled):
- *
- * - When sorting, it does more things than QListView does. It will ask the model for the
- * special role CategorySortRole (@see KCategorizedSortFilterProxyModel). This can return
- * a QString or an int in order to tell the view the order of categories. In this sense, for
- * instance, if we are sorting by name ascending, "A" would be before than "B". If we are
- * sorting by size ascending, 512 bytes would be before 1024 bytes. This way categories are
- * also sorted.
- *
- * - When the view has to paint, it will ask the model with the role CategoryDisplayRole
- * (@see KCategorizedSortFilterProxyModel). It will for instance return "F" for "foo.pdf" if
- * we are sorting by name ascending, or "Small" if a certain item has 100 bytes, for example.
- *
- * For drawing categories, KCategoryDrawer will be used. You can inherit this class to do your own
- * drawing.
- *
- * @note All examples cited before talk about filesystems and such, but have present that this
- * is a completely generic class, and it can be used for whatever your purpose is. For
- * instance when talking about animals, you can separate them by "Mammal" and "Oviparous". In
- * this very case, for example, the CategorySortRole and the CategoryDisplayRole could be the
- * same ("Mammal" and "Oviparous").
- *
- * @note There is a really performance boost if CategorySortRole returns an int instead of a QString.
- * Have present that this role is asked (n * log n) times when sorting and compared. Comparing
- * ints is always faster than comparing strings, whithout mattering how fast the string
- * comparison is. Consider thinking of a way of returning ints instead of QStrings if your
- * model can contain a high number of items.
- *
- * @warning Note that for really drawing items in blocks you will need some things to be done:
- * - The model set to this view has to be (or inherit if you want to do special stuff
- * in it) KCategorizedSortFilterProxyModel.
- * - This model needs to be set setCategorizedModel to true.
- * - Set a category drawer by calling setCategoryDrawer.
- *
- * @see KCategorizedSortFilterProxyModel, KCategoryDrawer
- *
- * @author Rafael Fernández López <ereslibre@kde.org>
- */
-class LIBGROUPVIEW_EXPORT KCategorizedView
- : public QListView
-{
- Q_OBJECT
- Q_PROPERTY ( int categorySpacing READ categorySpacing WRITE setCategorySpacing )
- Q_PROPERTY ( bool alternatingBlockColors READ alternatingBlockColors WRITE setAlternatingBlockColors )
- Q_PROPERTY ( bool collapsibleBlocks READ collapsibleBlocks WRITE setCollapsibleBlocks )
-
-public:
- KCategorizedView ( QWidget *parent = 0 );
-
- ~KCategorizedView();
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual void setModel ( QAbstractItemModel *model );
-
- /**
- * Calls to setGridSizeOwn().
- */
- void setGridSize ( const QSize &size );
-
- /**
- * @warning note that setGridSize is not virtual in the base class (QListView), so if you are
- * calling to this method, make sure you have a KCategorizedView pointer around. This
- * means that something like:
- * @code
- * QListView *lv = new KCategorizedView();
- * lv->setGridSize(mySize);
- * @endcode
- *
- * will not call to the expected setGridSize method. Instead do something like this:
- *
- * @code
- * QListView *lv;
- * ...
- * KCategorizedView *cv = qobject_cast<KCategorizedView*>(lv);
- * if (cv) {
- * cv->setGridSizeOwn(mySize);
- * } else {
- * lv->setGridSize(mySize);
- * }
- * @endcode
- *
- * @note this method will call to QListView::setGridSize among other operations.
- *
- * @since 4.4
- */
- void setGridSizeOwn ( const QSize &size );
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual QRect visualRect ( const QModelIndex &index ) const;
-
- /**
- * Returns the current category drawer.
- */
- KCategoryDrawer *categoryDrawer() const;
-
- /**
- * The category drawer that will be used for drawing categories.
- */
- void setCategoryDrawer ( KCategoryDrawer *categoryDrawer );
-
- /**
- * @return Category spacing. The spacing between categories.
- *
- * @since 4.4
- */
- int categorySpacing() const;
-
- /**
- * Stablishes the category spacing. This is the spacing between categories.
- *
- * @since 4.4
- */
- void setCategorySpacing ( int categorySpacing );
-
- /**
- * @return Whether blocks should be drawn with alternating colors.
- *
- * @since 4.4
- */
- bool alternatingBlockColors() const;
-
- /**
- * Sets whether blocks should be drawn with alternating colors.
- *
- * @since 4.4
- */
- void setAlternatingBlockColors ( bool enable );
-
- /**
- * @return Whether blocks can be collapsed or not.
- *
- * @since 4.4
- */
- bool collapsibleBlocks() const;
-
- /**
- * Sets whether blocks can be collapsed or not.
- *
- * @since 4.4
- */
- void setCollapsibleBlocks ( bool enable );
-
- /**
- * @return Block of indexes that are into @p category.
- *
- * @since 4.5
- */
- QModelIndexList block ( const QString &category );
-
- /**
- * @return Block of indexes that are represented by @p representative.
- *
- * @since 4.5
- */
- QModelIndexList block ( const QModelIndex &representative );
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual QModelIndex indexAt ( const QPoint &point ) const;
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual void reset();
-
- /**
- * Signify that all item delegates size hints return the same fixed size
- */
- void setUniformItemWidths(bool enable);
-
- /**
- * Do all item delegate size hints return the same fixed size?
- */
- bool uniformItemWidths() const;
-
-protected:
- /**
- * Reimplemented from QWidget.
- */
- virtual void paintEvent ( QPaintEvent *event );
-
- /**
- * Reimplemented from QWidget.
- */
- virtual void resizeEvent ( QResizeEvent *event );
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual void setSelection ( const QRect &rect,
- QItemSelectionModel::SelectionFlags flags );
-
- /**
- * Reimplemented from QWidget.
- */
- virtual void mouseMoveEvent ( QMouseEvent *event );
-
- /**
- * Reimplemented from QWidget.
- */
- virtual void mousePressEvent ( QMouseEvent *event );
-
- /**
- * Reimplemented from QWidget.
- */
- virtual void mouseReleaseEvent ( QMouseEvent *event );
-
- /**
- * Reimplemented from QWidget.
- */
- virtual void leaveEvent ( QEvent *event );
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual void startDrag ( Qt::DropActions supportedActions );
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual void dragMoveEvent ( QDragMoveEvent *event );
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual void dragEnterEvent ( QDragEnterEvent *event );
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual void dragLeaveEvent ( QDragLeaveEvent *event );
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual void dropEvent ( QDropEvent *event );
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual QModelIndex moveCursor ( CursorAction cursorAction,
- Qt::KeyboardModifiers modifiers );
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual void rowsAboutToBeRemoved ( const QModelIndex &parent,
- int start,
- int end );
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual void updateGeometries();
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual void currentChanged ( const QModelIndex &current,
- const QModelIndex &previous );
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual void dataChanged ( const QModelIndex &topLeft,
- const QModelIndex &bottomRight );
-
- /**
- * Reimplemented from QAbstractItemView.
- */
- virtual void rowsInserted ( const QModelIndex &parent,
- int start,
- int end );
-
-protected Q_SLOTS:
- /**
- * @internal
- * Reposition items as needed.
- */
- virtual void slotLayoutChanged();
- virtual void slotCollapseOrExpandClicked ( QModelIndex );
-
-private:
- class Private;
- Private *const d;
-};
-
-#endif // KCATEGORIZEDVIEW_H
diff --git a/depends/groupview/include/categorydrawer.h b/depends/groupview/include/categorydrawer.h
deleted file mode 100644
index f37422ec..00000000
--- a/depends/groupview/include/categorydrawer.h
+++ /dev/null
@@ -1,179 +0,0 @@
-/**
- * This file is part of the KDE project
- * Copyright (C) 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#ifndef KCATEGORYDRAWER_H
-#define KCATEGORYDRAWER_H
-
-#include <groupview_config.h>
-
-#include <QtCore/QObject>
-#include <QtGui/QMouseEvent>
-
-class QPainter;
-class QModelIndex;
-class QStyleOption;
-class KCategorizedView;
-
-
-/**
- * @since 4.5
- */
-class LIBGROUPVIEW_EXPORT KCategoryDrawer
- : public QObject
-{
- friend class KCategorizedView;
- Q_OBJECT
-
-
-public:
- KCategoryDrawer ( KCategorizedView *view );
- virtual ~KCategoryDrawer();
-
- /**
- * @return The view this category drawer is associated with.
- */
- KCategorizedView *view() const;
-
- /**
- * This method purpose is to draw a category represented by the given
- * @param index with the given @param sortRole sorting role
- *
- * @note This method will be called one time per category, always with the
- * first element in that category
- */
- virtual void drawCategory ( const QModelIndex &index,
- int sortRole,
- const QStyleOption &option,
- QPainter *painter ) const;
-
- /**
- * @return The category height for the category representated by index @p index with
- * style options @p option.
- */
- virtual int categoryHeight ( const QModelIndex &index, const QStyleOption &option ) const;
-
- //TODO KDE5: make virtual as leftMargin
- /**
- * @note 0 by default
- *
- * @since 4.4
- */
- int leftMargin() const;
-
- /**
- * @note call to this method on the KCategoryDrawer constructor to set the left margin
- *
- * @since 4.4
- */
- void setLeftMargin ( int leftMargin );
-
- //TODO KDE5: make virtual as rightMargin
- /**
- * @note 0 by default
- *
- * @since 4.4
- */
- int rightMargin() const;
-
- /**
- * @note call to this method on the KCategoryDrawer constructor to set the right margin
- *
- * @since 4.4
- */
- void setRightMargin ( int rightMargin );
-
- KCategoryDrawer &operator= ( const KCategoryDrawer &cd );
-protected:
- /**
- * Method called when the mouse button has been pressed.
- *
- * @param index The representative index of the block of items.
- * @param blockRect The rect occupied by the block of items.
- * @param event The mouse event.
- *
- * @warning You explicitly have to determine whether the event has been accepted or not. You
- * have to call event->accept() or event->ignore() at all possible case branches in
- * your code.
- */
- virtual void mouseButtonPressed ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event );
-
- /**
- * Method called when the mouse button has been released.
- *
- * @param index The representative index of the block of items.
- * @param blockRect The rect occupied by the block of items.
- * @param event The mouse event.
- *
- * @warning You explicitly have to determine whether the event has been accepted or not. You
- * have to call event->accept() or event->ignore() at all possible case branches in
- * your code.
- */
- virtual void mouseButtonReleased ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event );
-
- /**
- * Method called when the mouse has been moved.
- *
- * @param index The representative index of the block of items.
- * @param blockRect The rect occupied by the block of items.
- * @param event The mouse event.
- */
- virtual void mouseMoved ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event );
-
- /**
- * Method called when the mouse button has been double clicked.
- *
- * @param index The representative index of the block of items.
- * @param blockRect The rect occupied by the block of items.
- * @param event The mouse event.
- *
- * @warning You explicitly have to determine whether the event has been accepted or not. You
- * have to call event->accept() or event->ignore() at all possible case branches in
- * your code.
- */
- virtual void mouseButtonDoubleClicked ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event );
-
- /**
- * Method called when the mouse button has left this block.
- *
- * @param index The representative index of the block of items.
- * @param blockRect The rect occupied by the block of items.
- */
- virtual void mouseLeft ( const QModelIndex &index, const QRect &blockRect );
-
-private:
- class Private;
- Private *const d;
-Q_SIGNALS:
- /**
- * This signal becomes emitted when collapse or expand has been clicked.
- */
- void collapseOrExpandClicked ( const QModelIndex &index );
-
- /**
- * Emit this signal on your subclass implementation to notify that something happened. Usually
- * this will be triggered when you have received an event, and its position matched some "hot spot".
- *
- * You give this action the integer you want, and having connected this signal to your code,
- * the connected slot can perform the needed changes (view, model, selection model, delegate...)
- */
- void actionRequested ( int action, const QModelIndex &index );
-};
-
-#endif // KCATEGORYDRAWER_H
diff --git a/depends/groupview/src/categorizedsortfilterproxymodel.cpp b/depends/groupview/src/categorizedsortfilterproxymodel.cpp
deleted file mode 100644
index 09da9dd3..00000000
--- a/depends/groupview/src/categorizedsortfilterproxymodel.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/**
- * This file is part of the KDE project
- * Copyright (C) 2007 Rafael Fernández López <ereslibre@kde.org>
- * Copyright (C) 2007 John Tapsell <tapsell@kde.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include "categorizedsortfilterproxymodel.h"
-#include "categorizedsortfilterproxymodel_p.h"
-
-#include <limits.h>
-
-#include <QItemSelection>
-#include <QStringList>
-#include <QSize>
-
-KCategorizedSortFilterProxyModel::KCategorizedSortFilterProxyModel ( QObject *parent )
- : QSortFilterProxyModel ( parent )
- , d ( new Private() )
-{
-}
-
-KCategorizedSortFilterProxyModel::~KCategorizedSortFilterProxyModel()
-{
- delete d;
-}
-
-void KCategorizedSortFilterProxyModel::sort ( int column, Qt::SortOrder order )
-{
- d->sortColumn = column;
- d->sortOrder = order;
-
- QSortFilterProxyModel::sort ( column, order );
-}
-
-bool KCategorizedSortFilterProxyModel::isCategorizedModel() const
-{
- return d->categorizedModel;
-}
-
-void KCategorizedSortFilterProxyModel::setCategorizedModel ( bool categorizedModel )
-{
- if ( categorizedModel == d->categorizedModel )
- {
- return;
- }
-
- d->categorizedModel = categorizedModel;
-
- invalidate();
-}
-
-int KCategorizedSortFilterProxyModel::sortColumn() const
-{
- return d->sortColumn;
-}
-
-Qt::SortOrder KCategorizedSortFilterProxyModel::sortOrder() const
-{
- return d->sortOrder;
-}
-
-void KCategorizedSortFilterProxyModel::setSortCategoriesByNaturalComparison ( bool sortCategoriesByNaturalComparison )
-{
- if ( sortCategoriesByNaturalComparison == d->sortCategoriesByNaturalComparison )
- {
- return;
- }
-
- d->sortCategoriesByNaturalComparison = sortCategoriesByNaturalComparison;
-
- invalidate();
-}
-
-bool KCategorizedSortFilterProxyModel::sortCategoriesByNaturalComparison() const
-{
- return d->sortCategoriesByNaturalComparison;
-}
-
-bool KCategorizedSortFilterProxyModel::lessThan ( const QModelIndex &left, const QModelIndex &right ) const
-{
- if ( d->categorizedModel )
- {
- int compare = compareCategories ( left, right );
-
- if ( compare > 0 ) // left is greater than right
- {
- return false;
- }
- else if ( compare < 0 ) // left is less than right
- {
- return true;
- }
- }
-
- return subSortLessThan ( left, right );
-}
-
-bool KCategorizedSortFilterProxyModel::subSortLessThan ( const QModelIndex &left, const QModelIndex &right ) const
-{
- return QSortFilterProxyModel::lessThan ( left, right );
-}
-
-int KCategorizedSortFilterProxyModel::compareCategories ( const QModelIndex &left, const QModelIndex &right ) const
-{
- QVariant l = ( left.model() ? left.model()->data ( left, CategorySortRole ) : QVariant() );
- QVariant r = ( right.model() ? right.model()->data ( right, CategorySortRole ) : QVariant() );
-
- Q_ASSERT ( l.isValid() );
- Q_ASSERT ( r.isValid() );
- Q_ASSERT ( l.type() == r.type() );
-
- if ( l.type() == QVariant::String )
- {
- QString lstr = l.toString();
- QString rstr = r.toString();
-
- /*
- if ( d->sortCategoriesByNaturalComparison )
- {
- return KStringHandler::naturalCompare ( lstr, rstr );
- }
- else
- {
- */
- if ( lstr < rstr )
- {
- return -1;
- }
-
- if ( lstr > rstr )
- {
- return 1;
- }
-
- return 0;
- //}
- }
-
- qlonglong lint = l.toLongLong();
- qlonglong rint = r.toLongLong();
-
- if ( lint < rint )
- {
- return -1;
- }
-
- if ( lint > rint )
- {
- return 1;
- }
-
- return 0;
-}
diff --git a/depends/groupview/src/categorizedsortfilterproxymodel_p.h b/depends/groupview/src/categorizedsortfilterproxymodel_p.h
deleted file mode 100644
index d7e7c9a0..00000000
--- a/depends/groupview/src/categorizedsortfilterproxymodel_p.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * This file is part of the KDE project
- * Copyright (C) 2007 Rafael Fernández López <ereslibre@kde.org>
- * Copyright (C) 2007 John Tapsell <tapsell@kde.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#ifndef KCATEGORIZEDSORTFILTERPROXYMODEL_P_H
-#define KCATEGORIZEDSORTFILTERPROXYMODEL_P_H
-
-class KCategorizedSortFilterProxyModel;
-
-class KCategorizedSortFilterProxyModel::Private
-{
-public:
- Private()
- : sortColumn ( 0 )
- , sortOrder ( Qt::AscendingOrder )
- , categorizedModel ( false )
- , sortCategoriesByNaturalComparison ( true )
- {
- }
-
- ~Private()
- {
- }
-
- int sortColumn;
- Qt::SortOrder sortOrder;
- bool categorizedModel;
- bool sortCategoriesByNaturalComparison;
-};
-
-#endif
diff --git a/depends/groupview/src/categorizedview.cpp b/depends/groupview/src/categorizedview.cpp
deleted file mode 100644
index 1345205c..00000000
--- a/depends/groupview/src/categorizedview.cpp
+++ /dev/null
@@ -1,1692 +0,0 @@
-/**
- * This file is part of the KDE project
- * Copyright (C) 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-/**
- * IMPLEMENTATION NOTES:
- *
- * QListView::setRowHidden() and QListView::isRowHidden() are not taken into
- * account. This methods should actually not exist. This effect should be handled
- * by an hypothetical QSortFilterProxyModel which filters out the desired rows.
- *
- * In case this needs to be implemented, contact me, but I consider this a faulty
- * design.
- */
-
-#include "categorizedview.h"
-#include "categorizedview_p.h"
-
-#include <math.h> // trunc on C99 compliant systems
-//#include <kdefakes.h> // trunc for not C99 compliant systems
-
-#include <QPainter>
-#include <QScrollBar>
-#include <QPaintEvent>
-
-#include "categorydrawer.h"
-#include "categorizedsortfilterproxymodel.h"
-
-//BEGIN: Private part
-
-struct KCategorizedView::Private::Item
-{
- Item()
- : topLeft ( QPoint() )
- , size ( QSize() )
- {
- }
-
- QPoint topLeft;
- QSize size;
-};
-
-struct KCategorizedView::Private::Block
-{
- bool operator!= ( const Block &rhs ) const
- {
- return firstIndex != rhs.firstIndex;
- }
-
- static bool lessThan ( const Block &left, const Block &right )
- {
- Q_ASSERT ( left.firstIndex.isValid() );
- Q_ASSERT ( right.firstIndex.isValid() );
- return left.firstIndex.row() < right.firstIndex.row();
- }
-
- QPoint topLeft;
- int height = -1;
- QPersistentModelIndex firstIndex;
- // if we have n elements on this block, and we inserted an element at position i. The quarantine
- // will start at index (i, column, parent). This means that for all elements j where i <= j <= n, the
- // visual rect position of item j will have to be recomputed (cannot use the cached point). The quarantine
- // will only affect the current block, since the rest of blocks can be affected only in the way
- // that the whole block will have different offset, but items will keep the same relative position
- // in terms of their parent blocks.
- QPersistentModelIndex quarantineStart;
- QList<Item> items;
-
- // this affects the whole block, not items separately. items contain the topLeft point relative
- // to the block. Because of insertions or removals a whole block can be moved, so the whole block
- // will enter in quarantine, what is faster than moving all items in absolute terms.
- bool outOfQuarantine = false;
-
- // should we alternate its color ? is just a hint, could not be used
- bool alternate = false;
- bool collapsed = false;
-};
-
-KCategorizedView::Private::Private ( KCategorizedView *q )
- : q ( q )
- , hoveredBlock ( new Block() )
-{
-}
-
-KCategorizedView::Private::~Private()
-{
- delete hoveredBlock;
-}
-
-bool KCategorizedView::Private::isCategorized() const
-{
- return proxyModel && categoryDrawer && proxyModel->isCategorizedModel();
-}
-
-QStyleOptionViewItemV4 KCategorizedView::Private::blockRect ( const QModelIndex &representative )
-{
- QStyleOptionViewItemV4 option ( q->viewOptions() );
- const int height = categoryDrawer->categoryHeight ( representative, option );
- const QString categoryDisplay = representative.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString();
- QPoint pos = blockPosition ( categoryDisplay );
- pos.ry() -= height;
- option.rect.setTopLeft ( pos );
- option.rect.setWidth ( viewportWidth() + categoryDrawer->leftMargin() + categoryDrawer->rightMargin() );
- option.rect.setHeight ( height + blockHeight ( categoryDisplay ) );
- option.rect = mapToViewport ( option.rect );
-
- return option;
-}
-
-QPair<QModelIndex, QModelIndex> KCategorizedView::Private::intersectingIndexesWithRect ( const QRect &_rect ) const
-{
- const int rowCount = proxyModel->rowCount();
-
- const QRect rect = _rect.normalized();
-
- // binary search to find out the top border
- int bottom = 0;
- int top = rowCount - 1;
- while ( bottom <= top )
- {
- const int middle = ( bottom + top ) / 2;
- const QModelIndex index = proxyModel->index ( middle, q->modelColumn(), q->rootIndex() );
- QRect itemRect = q->visualRect ( index );
- const int verticalOff = q->verticalOffset();
- const int horizontalOff = q->horizontalOffset();
- itemRect.topLeft().ry() += verticalOff;
- itemRect.topLeft().rx() += horizontalOff;
- itemRect.bottomRight().ry() += verticalOff;
- itemRect.bottomRight().rx() += horizontalOff;
- if ( itemRect.bottomRight().y() <= rect.topLeft().y() )
- {
- bottom = middle + 1;
- }
- else
- {
- top = middle - 1;
- }
- }
-
- const QModelIndex bottomIndex = proxyModel->index ( bottom, q->modelColumn(), q->rootIndex() );
-
- // binary search to find out the bottom border
- bottom = 0;
- top = rowCount - 1;
- while ( bottom <= top )
- {
- const int middle = ( bottom + top ) / 2;
- const QModelIndex index = proxyModel->index ( middle, q->modelColumn(), q->rootIndex() );
- QRect itemRect = q->visualRect ( index );
- const int verticalOff = q->verticalOffset();
- const int horizontalOff = q->horizontalOffset();
- itemRect.topLeft().ry() += verticalOff;
- itemRect.topLeft().rx() += horizontalOff;
- itemRect.bottomRight().ry() += verticalOff;
- itemRect.bottomRight().rx() += horizontalOff;
- if ( itemRect.topLeft().y() <= rect.bottomRight().y() )
- {
- bottom = middle + 1;
- }
- else
- {
- top = middle - 1;
- }
- }
-
- const QModelIndex topIndex = proxyModel->index ( top, q->modelColumn(), q->rootIndex() );
-
- return qMakePair ( bottomIndex, topIndex );
-}
-
-QPoint KCategorizedView::Private::blockPosition ( const QString &category )
-{
- Block &block = blocks[category];
-
- if ( block.outOfQuarantine && !block.topLeft.isNull() )
- {
- return block.topLeft;
- }
-
- QPoint res ( categorySpacing, 0 );
-
- const QModelIndex index = block.firstIndex;
-
- for ( QHash<QString, Private::Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it )
- {
- Block &block = *it;
- const QModelIndex categoryIndex = block.firstIndex;
- if ( index.row() < categoryIndex.row() )
- {
- continue;
- }
- res.ry() += categoryDrawer->categoryHeight ( categoryIndex, q->viewOptions() ) + categorySpacing;
- if ( index.row() == categoryIndex.row() )
- {
- continue;
- }
- res.ry() += blockHeight ( it.key() );
- }
-
- block.outOfQuarantine = true;
- block.topLeft = res;
-
- return res;
-}
-
-int KCategorizedView::Private::blockHeight ( const QString &category )
-{
- Block &block = blocks[category];
-
- if ( block.collapsed )
- {
- return 0;
- }
-
- if ( block.height > -1 )
- {
- return block.height;
- }
-
- const QModelIndex firstIndex = block.firstIndex;
- const QModelIndex lastIndex = proxyModel->index ( firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex() );
- const QRect topLeft = q->visualRect ( firstIndex );
- QRect bottomRight = q->visualRect ( lastIndex );
-
- if ( hasGrid() )
- {
- bottomRight.setHeight ( qMax ( bottomRight.height(), q->gridSize().height() ) );
- }
- else
- {
- if ( !q->uniformItemSizes() )
- {
- bottomRight.setHeight ( highestElementInLastRow ( block ) + q->spacing() * 2 );
- }
- }
-
- const int height = bottomRight.bottomRight().y() - topLeft.topLeft().y() + 1;
- block.height = height;
-
- return height;
-}
-
-int KCategorizedView::Private::viewportWidth() const
-{
- return q->viewport()->width() - categorySpacing * 2 - categoryDrawer->leftMargin() - categoryDrawer->rightMargin();
-}
-
-void KCategorizedView::Private::regenerateAllElements()
-{
- for ( QHash<QString, Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it )
- {
- Block &block = *it;
- block.outOfQuarantine = false;
- block.quarantineStart = block.firstIndex;
- block.height = -1;
- }
-}
-
-void KCategorizedView::Private::rowsInserted ( const QModelIndex &parent, int start, int end )
-{
- if ( !isCategorized() )
- {
- return;
- }
-
- for ( int i = start; i <= end; ++i )
- {
- const QModelIndex index = proxyModel->index ( i, q->modelColumn(), parent );
-
- Q_ASSERT ( index.isValid() );
-
- const QString category = categoryForIndex ( index );
-
- Block &block = blocks[category];
-
- //BEGIN: update firstIndex
- // save as firstIndex in block if
- // - it forced the category creation (first element on this category)
- // - it is before the first row on that category
- const QModelIndex firstIndex = block.firstIndex;
- if ( !firstIndex.isValid() || index.row() < firstIndex.row() )
- {
- block.firstIndex = index;
- }
- //END: update firstIndex
-
- Q_ASSERT ( block.firstIndex.isValid() );
-
- const int firstIndexRow = block.firstIndex.row();
-
- block.items.insert ( index.row() - firstIndexRow, Private::Item() );
- block.height = -1;
-
- q->visualRect ( index );
- q->viewport()->update();
- }
-
- //BEGIN: update the items that are in quarantine in affected categories
- {
- const QModelIndex lastIndex = proxyModel->index ( end, q->modelColumn(), parent );
- const QString category = categoryForIndex ( lastIndex );
- Private::Block &block = blocks[category];
- block.quarantineStart = block.firstIndex;
- }
- //END: update the items that are in quarantine in affected categories
-
- //BEGIN: mark as in quarantine those categories that are under the affected ones
- {
- const QModelIndex firstIndex = proxyModel->index ( start, q->modelColumn(), parent );
- const QString category = categoryForIndex ( firstIndex );
- const QModelIndex firstAffectedCategory = blocks[category].firstIndex;
- //BEGIN: order for marking as alternate those blocks that are alternate
- QList<Block> blockList = blocks.values();
- qSort ( blockList.begin(), blockList.end(), Block::lessThan );
- QList<int> firstIndexesRows;
- foreach ( const Block &block, blockList )
- {
- firstIndexesRows << block.firstIndex.row();
- }
- //END: order for marking as alternate those blocks that are alternate
- for ( QHash<QString, Private::Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it )
- {
- Private::Block &block = *it;
- if ( block.firstIndex.row() > firstAffectedCategory.row() )
- {
- block.outOfQuarantine = false;
- block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2;
- }
- else if ( block.firstIndex.row() == firstAffectedCategory.row() )
- {
- block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2;
- }
- }
- }
- //END: mark as in quarantine those categories that are under the affected ones
-}
-
-QRect KCategorizedView::Private::mapToViewport ( const QRect &rect ) const
-{
- const int dx = -q->horizontalOffset();
- const int dy = -q->verticalOffset();
- return rect.adjusted ( dx, dy, dx, dy );
-}
-
-QRect KCategorizedView::Private::mapFromViewport ( const QRect &rect ) const
-{
- const int dx = q->horizontalOffset();
- const int dy = q->verticalOffset();
- return rect.adjusted ( dx, dy, dx, dy );
-}
-
-int KCategorizedView::Private::highestElementInLastRow ( const Block &block ) const
-{
- //Find the highest element in the last row
- const QModelIndex lastIndex = proxyModel->index ( block.firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex() );
- const QRect prevRect = q->visualRect ( lastIndex );
- int res = prevRect.height();
- QModelIndex prevIndex = proxyModel->index ( lastIndex.row() - 1, q->modelColumn(), q->rootIndex() );
- if ( !prevIndex.isValid() )
- {
- return res;
- }
- Q_FOREVER
- {
- const QRect tempRect = q->visualRect ( prevIndex );
- if ( tempRect.topLeft().y() < prevRect.topLeft().y() )
- {
- break;
- }
- res = qMax ( res, tempRect.height() );
- if ( prevIndex == block.firstIndex )
- {
- break;
- }
- prevIndex = proxyModel->index ( prevIndex.row() - 1, q->modelColumn(), q->rootIndex() );
- }
-
- return res;
-}
-
-bool KCategorizedView::Private::hasGrid() const
-{
- const QSize gridSize = q->gridSize();
- return gridSize.isValid() && !gridSize.isNull();
-}
-
-QString KCategorizedView::Private::categoryForIndex ( const QModelIndex &index ) const
-{
- const QModelIndex categoryIndex = index.model()->index ( index.row(), proxyModel->sortColumn(), index.parent() );
- return categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString();
-}
-
-void KCategorizedView::Private::leftToRightVisualRect ( const QModelIndex &index, Item &item,
- const Block &block, const QPoint &blockPos ) const
-{
- const int firstIndexRow = block.firstIndex.row();
-
- if ( hasGrid() )
- {
- const int relativeRow = index.row() - firstIndexRow;
- const int maxItemsPerRow = qMax ( viewportWidth() / q->gridSize().width(), 1 );
- if ( q->layoutDirection() == Qt::LeftToRight )
- {
- item.topLeft.rx() = ( relativeRow % maxItemsPerRow ) * q->gridSize().width() + blockPos.x() + categoryDrawer->leftMargin();
- }
- else
- {
- item.topLeft.rx() = viewportWidth() - ( ( relativeRow % maxItemsPerRow ) + 1 ) * q->gridSize().width() + categoryDrawer->leftMargin() + categorySpacing;
- }
- item.topLeft.ry() = ( relativeRow / maxItemsPerRow ) * q->gridSize().height();
- }
- else
- {
- if ( q->uniformItemSizes() /*|| q->uniformItemWidths()*/ )
- {
- const int relativeRow = index.row() - firstIndexRow;
- const QSize itemSize = q->sizeHintForIndex ( index );
- //HACK: Why is the -2 needed?
- const int maxItemsPerRow = qMax ( ( viewportWidth() - q->spacing() - 2 ) / ( itemSize.width() + q->spacing() ), 1 );
- if ( q->layoutDirection() == Qt::LeftToRight )
- {
- item.topLeft.rx() = ( relativeRow % maxItemsPerRow ) * itemSize.width() + blockPos.x() + categoryDrawer->leftMargin();
- }
- else
- {
- item.topLeft.rx() = viewportWidth() - ( relativeRow % maxItemsPerRow ) * itemSize.width() + categoryDrawer->leftMargin() + categorySpacing;
- }
- item.topLeft.ry() = ( relativeRow / maxItemsPerRow ) * itemSize.height();
- }
- else
- {
- const QSize currSize = q->sizeHintForIndex ( index );
- if ( index != block.firstIndex )
- {
- const int viewportW = viewportWidth() - q->spacing();
- QModelIndex prevIndex = proxyModel->index ( index.row() - 1, q->modelColumn(), q->rootIndex() );
- QRect prevRect = q->visualRect ( prevIndex );
- prevRect = mapFromViewport ( prevRect );
- if ( ( prevRect.bottomRight().x() + 1 ) + currSize.width() - blockPos.x() + q->spacing() > viewportW )
- {
- // we have to check the whole previous row, and see which one was the
- // highest.
- Q_FOREVER
- {
- prevIndex = proxyModel->index ( prevIndex.row() - 1, q->modelColumn(), q->rootIndex() );
- QRect tempRect = q->visualRect ( prevIndex );
- tempRect = mapFromViewport ( tempRect );
- if ( tempRect.topLeft().y() < prevRect.topLeft().y() )
- {
- break;
- }
- if ( tempRect.bottomRight().y() > prevRect.bottomRight().y() )
- {
- prevRect = tempRect;
- }
- if ( prevIndex == block.firstIndex )
- {
- break;
- }
- }
- if ( q->layoutDirection() == Qt::LeftToRight )
- {
- item.topLeft.rx() = categoryDrawer->leftMargin() + blockPos.x() + q->spacing();
- }
- else
- {
- item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing;
- }
- item.topLeft.ry() = ( prevRect.bottomRight().y() + 1 ) + q->spacing() - blockPos.y();
- }
- else
- {
- if ( q->layoutDirection() == Qt::LeftToRight )
- {
- item.topLeft.rx() = ( prevRect.bottomRight().x() + 1 ) + q->spacing();
- }
- else
- {
- item.topLeft.rx() = ( prevRect.bottomLeft().x() - 1 ) - q->spacing() - item.size.width() + categoryDrawer->leftMargin() + categorySpacing;
- }
- item.topLeft.ry() = prevRect.topLeft().y() - blockPos.y();
- }
- }
- else
- {
- if ( q->layoutDirection() == Qt::LeftToRight )
- {
- item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
- }
- else
- {
- item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing;
- }
- item.topLeft.ry() = q->spacing();
- }
- }
- }
- item.size = q->sizeHintForIndex ( index );
-}
-
-void KCategorizedView::Private::topToBottomVisualRect ( const QModelIndex &index, Item &item,
- const Block &block, const QPoint &blockPos ) const
-{
- const int firstIndexRow = block.firstIndex.row();
-
- if ( hasGrid() )
- {
- const int relativeRow = index.row() - firstIndexRow;
- item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin();
- item.topLeft.ry() = relativeRow * q->gridSize().height();
- }
- else
- {
- if ( q->uniformItemSizes() )
- {
- const int relativeRow = index.row() - firstIndexRow;
- const QSize itemSize = q->sizeHintForIndex ( index );
- item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin();
- item.topLeft.ry() = relativeRow * itemSize.height();
- }
- else
- {
- if ( index != block.firstIndex )
- {
- QModelIndex prevIndex = proxyModel->index ( index.row() - 1, q->modelColumn(), q->rootIndex() );
- QRect prevRect = q->visualRect ( prevIndex );
- prevRect = mapFromViewport ( prevRect );
- item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
- item.topLeft.ry() = ( prevRect.bottomRight().y() + 1 ) + q->spacing() - blockPos.y();
- }
- else
- {
- item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
- item.topLeft.ry() = q->spacing();
- }
- }
- }
- item.size = q->sizeHintForIndex ( index );
- item.size.setWidth ( viewportWidth() );
-}
-
-void KCategorizedView::Private::_k_slotCollapseOrExpandClicked ( QModelIndex )
-{
-}
-
-//END: Private part
-
-//BEGIN: Public part
-
-KCategorizedView::KCategorizedView ( QWidget *parent )
- : QListView ( parent )
- , d ( new Private ( this ) )
-{
-}
-
-KCategorizedView::~KCategorizedView()
-{
- delete d;
-}
-
-void KCategorizedView::setModel ( QAbstractItemModel *model )
-{
- if ( d->proxyModel == model )
- {
- return;
- }
-
- d->blocks.clear();
-
- if ( d->proxyModel )
- {
- disconnect ( d->proxyModel, SIGNAL ( layoutChanged() ), this, SLOT ( slotLayoutChanged() ) );
- }
-
- d->proxyModel = dynamic_cast<KCategorizedSortFilterProxyModel*> ( model );
-
- if ( d->proxyModel )
- {
- connect ( d->proxyModel, SIGNAL ( layoutChanged() ), this, SLOT ( slotLayoutChanged() ) );
- }
-
- QListView::setModel ( model );
-
- // if the model already had information inserted, update our data structures to it
- if ( model->rowCount() )
- {
- slotLayoutChanged();
- }
-}
-
-
-void KCategorizedView::setUniformItemWidths(bool enable)
-{
- d->constantItemWidth = enable;
-}
-
-
-bool KCategorizedView::uniformItemWidths() const
-{
- return d->constantItemWidth;
-}
-
-void KCategorizedView::setGridSize ( const QSize &size )
-{
- setGridSizeOwn ( size );
-}
-
-void KCategorizedView::setGridSizeOwn ( const QSize &size )
-{
- d->regenerateAllElements();
- QListView::setGridSize ( size );
-}
-
-QRect KCategorizedView::visualRect ( const QModelIndex &index ) const
-{
- if ( !d->isCategorized() )
- {
- return QListView::visualRect ( index );
- }
-
- if ( !index.isValid() )
- {
- return QRect();
- }
-
- const QString category = d->categoryForIndex ( index );
-
- if ( !d->blocks.contains ( category ) )
- {
- return QRect();
- }
-
- Private::Block &block = d->blocks[category];
- const int firstIndexRow = block.firstIndex.row();
-
- Q_ASSERT ( block.firstIndex.isValid() );
-
- if ( index.row() - firstIndexRow < 0 || index.row() - firstIndexRow >= block.items.count() )
- {
- return QRect();
- }
-
- const QPoint blockPos = d->blockPosition ( category );
-
- Private::Item &ritem = block.items[index.row() - firstIndexRow];
-
- if ( ritem.topLeft.isNull() || ( block.quarantineStart.isValid() &&
- index.row() >= block.quarantineStart.row() ) )
- {
- if ( flow() == LeftToRight )
- {
- d->leftToRightVisualRect ( index, ritem, block, blockPos );
- }
- else
- {
- d->topToBottomVisualRect ( index, ritem, block, blockPos );
- }
-
- //BEGIN: update the quarantine start
- const bool wasLastIndex = ( index.row() == ( block.firstIndex.row() + block.items.count() - 1 ) );
- if ( index.row() == block.quarantineStart.row() )
- {
- if ( wasLastIndex )
- {
- block.quarantineStart = QModelIndex();
- }
- else
- {
- const QModelIndex nextIndex = d->proxyModel->index ( index.row() + 1, modelColumn(), rootIndex() );
- block.quarantineStart = nextIndex;
- }
- }
- //END: update the quarantine start
- }
-
- // we get now the absolute position through the relative position of the parent block. do not
- // save this on ritem, since this would override the item relative position in block terms.
- Private::Item item ( ritem );
- item.topLeft.ry() += blockPos.y();
-
- const QSize sizeHint = item.size;
-
- if ( d->hasGrid() )
- {
- const QSize sizeGrid = gridSize();
- const QSize resultingSize = sizeHint.boundedTo ( sizeGrid );
- QRect res ( item.topLeft.x() + ( ( sizeGrid.width() - resultingSize.width() ) / 2 ),
- item.topLeft.y(), resultingSize.width(), resultingSize.height() );
- if ( block.collapsed )
- {
- // we can still do binary search, while we "hide" items. We move those items in collapsed
- // blocks to the left and set a 0 height.
- res.setLeft ( -resultingSize.width() );
- res.setHeight ( 0 );
- }
- return d->mapToViewport ( res );
- }
-
- QRect res ( item.topLeft.x(), item.topLeft.y(), sizeHint.width(), sizeHint.height() );
- if ( block.collapsed )
- {
- // we can still do binary search, while we "hide" items. We move those items in collapsed
- // blocks to the left and set a 0 height.
- res.setLeft ( -sizeHint.width() );
- res.setHeight ( 0 );
- }
- return d->mapToViewport ( res );
-}
-
-KCategoryDrawer *KCategorizedView::categoryDrawer() const
-{
- return d->categoryDrawer;
-}
-
-void KCategorizedView::setCategoryDrawer ( KCategoryDrawer *categoryDrawer )
-{
- disconnect ( d->categoryDrawer, SIGNAL ( collapseOrExpandClicked ( QModelIndex ) ),
- this, SLOT ( slotCollapseOrExpandClicked ( QModelIndex ) ) );
- d->categoryDrawer = categoryDrawer;
-
- connect ( d->categoryDrawer, SIGNAL ( collapseOrExpandClicked ( QModelIndex ) ),
- this, SLOT ( slotCollapseOrExpandClicked ( QModelIndex ) ) );
-}
-
-int KCategorizedView::categorySpacing() const
-{
- return d->categorySpacing;
-}
-
-void KCategorizedView::setCategorySpacing ( int categorySpacing )
-{
- if ( d->categorySpacing == categorySpacing )
- {
- return;
- }
-
- d->categorySpacing = categorySpacing;
-
- for ( QHash<QString, Private::Block>::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it )
- {
- Private::Block &block = *it;
- block.outOfQuarantine = false;
- }
-}
-
-bool KCategorizedView::alternatingBlockColors() const
-{
- return d->alternatingBlockColors;
-}
-
-void KCategorizedView::setAlternatingBlockColors ( bool enable )
-{
- d->alternatingBlockColors = enable;
-}
-
-bool KCategorizedView::collapsibleBlocks() const
-{
- return d->collapsibleBlocks;
-}
-
-void KCategorizedView::setCollapsibleBlocks ( bool enable )
-{
- d->collapsibleBlocks = enable;
-}
-
-QModelIndexList KCategorizedView::block ( const QString &category )
-{
- QModelIndexList res;
- const Private::Block &block = d->blocks[category];
- if ( block.height == -1 )
- {
- return res;
- }
- QModelIndex current = block.firstIndex;
- const int first = current.row();
- for ( int i = 1; i <= block.items.count(); ++i )
- {
- if ( current.isValid() )
- {
- res << current;
- }
- current = d->proxyModel->index ( first + i, modelColumn(), rootIndex() );
- }
- return res;
-}
-
-QModelIndexList KCategorizedView::block ( const QModelIndex &representative )
-{
- return block ( representative.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString() );
-}
-
-QModelIndex KCategorizedView::indexAt ( const QPoint &point ) const
-{
- if ( !d->isCategorized() )
- {
- return QListView::indexAt ( point );
- }
-
- const int rowCount = d->proxyModel->rowCount();
- if ( !rowCount )
- {
- return QModelIndex();
- }
-
- // Binary search that will try to spot if there is an index under point
- int bottom = 0;
- int top = rowCount - 1;
- while ( bottom <= top )
- {
- const int middle = ( bottom + top ) / 2;
- const QModelIndex index = d->proxyModel->index ( middle, modelColumn(), rootIndex() );
- QRect rect = visualRect ( index );
- const int verticalOff = verticalOffset();
- int horizontalOff = horizontalOffset();
- if ( layoutDirection() == Qt::RightToLeft )
- {
- horizontalOff *= -1;
- }
- rect.topLeft().ry() += verticalOff;
- rect.topLeft().rx() += horizontalOff;
- rect.bottomRight().ry() += verticalOff;
- rect.bottomRight().rx() += horizontalOff;
- if ( rect.contains ( point ) )
- {
- if ( index.model()->flags ( index ) & Qt::ItemIsEnabled )
- {
- return index;
- }
- return QModelIndex();
- }
- bool directionCondition;
- if ( layoutDirection() == Qt::LeftToRight )
- {
- directionCondition = point.x() > rect.bottomRight().x();
- }
- else
- {
- directionCondition = point.x() < rect.bottomLeft().x();
- }
- if ( point.y() > rect.bottomRight().y() ||
- ( point.y() > rect.topLeft().y() && point.y() < rect.bottomRight().y() && directionCondition ) )
- {
- bottom = middle + 1;
- }
- else
- {
- top = middle - 1;
- }
- }
- return QModelIndex();
-}
-
-void KCategorizedView::reset()
-{
- d->blocks.clear();
- QListView::reset();
-}
-
-void KCategorizedView::paintEvent ( QPaintEvent *event )
-{
- if ( !d->isCategorized() )
- {
- QListView::paintEvent ( event );
- return;
- }
-
- const QPair<QModelIndex, QModelIndex> intersecting = d->intersectingIndexesWithRect ( viewport()->rect().intersected ( event->rect() ) );
-
- QPainter p ( viewport() );
- p.save();
-
- Q_ASSERT ( selectionModel()->model() == d->proxyModel );
-
- //BEGIN: draw categories
- QHash<QString, Private::Block>::ConstIterator it ( d->blocks.constBegin() );
- while ( it != d->blocks.constEnd() )
- {
- const Private::Block &block = *it;
- const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() );
- QStyleOptionViewItemV4 option ( viewOptions() );
- option.features |= d->alternatingBlockColors && block.alternate ? QStyleOptionViewItemV4::Alternate
- : QStyleOptionViewItemV4::None;
- option.state |= !d->collapsibleBlocks || !block.collapsed ? QStyle::State_Open
- : QStyle::State_None;
- const int height = d->categoryDrawer->categoryHeight ( categoryIndex, option );
- QPoint pos = d->blockPosition ( it.key() );
- pos.ry() -= height;
- option.rect.setTopLeft ( pos );
- option.rect.setWidth ( d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin() );
- option.rect.setHeight ( height + d->blockHeight ( it.key() ) );
- option.rect = d->mapToViewport ( option.rect );
- if ( !option.rect.intersects ( viewport()->rect() ) )
- {
- ++it;
- continue;
- }
- d->categoryDrawer->drawCategory ( categoryIndex, d->proxyModel->sortRole(), option, &p );
- ++it;
- }
- //END: draw categories
-
- if ( intersecting.first.isValid() && intersecting.second.isValid() )
- {
- //BEGIN: draw items
- int i = intersecting.first.row();
- int indexToCheckIfBlockCollapsed = i;
- QModelIndex categoryIndex;
- QString category;
- Private::Block *block = 0;
- while ( i <= intersecting.second.row() )
- {
- //BEGIN: first check if the block is collapsed. if so, we have to skip the item painting
- if ( i == indexToCheckIfBlockCollapsed )
- {
- categoryIndex = d->proxyModel->index ( i, d->proxyModel->sortColumn(), rootIndex() );
- category = categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString();
- block = &d->blocks[category];
- indexToCheckIfBlockCollapsed = block->firstIndex.row() + block->items.count();
- if ( block->collapsed )
- {
- i = indexToCheckIfBlockCollapsed;
- continue;
- }
- }
- //END: first check if the block is collapsed. if so, we have to skip the item painting
-
- Q_ASSERT ( block );
-
- const bool alternateItem = ( i - block->firstIndex.row() ) % 2;
-
- const QModelIndex index = d->proxyModel->index ( i, modelColumn(), rootIndex() );
- const Qt::ItemFlags flags = d->proxyModel->flags ( index );
- QStyleOptionViewItemV4 option ( viewOptions() );
- option.rect = visualRect ( index );
- option.widget = this;
- option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText
- : QStyleOptionViewItemV2::None;
- option.features |= alternatingRowColors() && alternateItem ? QStyleOptionViewItemV4::Alternate
- : QStyleOptionViewItemV4::None;
- if ( flags & Qt::ItemIsSelectable )
- {
- option.state |= selectionModel()->isSelected ( index ) ? QStyle::State_Selected
- : QStyle::State_None;
- }
- else
- {
- option.state &= ~QStyle::State_Selected;
- }
- option.state |= ( index == currentIndex() ) ? QStyle::State_HasFocus
- : QStyle::State_None;
- if ( ! ( flags & Qt::ItemIsEnabled ) )
- {
- option.state &= ~QStyle::State_Enabled;
- }
- else
- {
- option.state |= ( index == d->hoveredIndex ) ? QStyle::State_MouseOver
- : QStyle::State_None;
- }
-
- itemDelegate ( index )->paint ( &p, option, index );
- ++i;
- }
- //END: draw items
- }
-
- //BEGIN: draw selection rect
- if ( isSelectionRectVisible() && d->rubberBandRect.isValid() )
- {
- QStyleOptionRubberBand opt;
- opt.initFrom ( this );
- opt.shape = QRubberBand::Rectangle;
- opt.opaque = false;
- opt.rect = d->mapToViewport ( d->rubberBandRect ).intersected ( viewport()->rect().adjusted ( -16, -16, 16, 16 ) );
- p.save();
- style()->drawControl ( QStyle::CE_RubberBand, &opt, &p );
- p.restore();
- }
- //END: draw selection rect
-
- p.restore();
-}
-
-void KCategorizedView::resizeEvent ( QResizeEvent *event )
-{
- d->regenerateAllElements();
- QListView::resizeEvent ( event );
-}
-
-void KCategorizedView::setSelection ( const QRect &rect,
- QItemSelectionModel::SelectionFlags flags )
-{
- if ( !d->isCategorized() )
- {
- QListView::setSelection ( rect, flags );
- return;
- }
-
- if ( rect.topLeft() == rect.bottomRight() )
- {
- const QModelIndex index = indexAt ( rect.topLeft() );
- selectionModel()->select ( index, flags );
- return;
- }
-
- const QPair<QModelIndex, QModelIndex> intersecting = d->intersectingIndexesWithRect ( rect );
-
- QItemSelection selection;
-
- //TODO: think of a faster implementation
- QModelIndex firstIndex;
- QModelIndex lastIndex;
- for ( int i = intersecting.first.row(); i <= intersecting.second.row(); ++i )
- {
- const QModelIndex index = d->proxyModel->index ( i, modelColumn(), rootIndex() );
- const bool visualRectIntersects = visualRect ( index ).intersects ( rect );
- if ( firstIndex.isValid() )
- {
- if ( visualRectIntersects )
- {
- lastIndex = index;
- }
- else
- {
- selection << QItemSelectionRange ( firstIndex, lastIndex );
- firstIndex = QModelIndex();
- }
- }
- else if ( visualRectIntersects )
- {
- firstIndex = index;
- lastIndex = index;
- }
- }
-
- if ( firstIndex.isValid() )
- {
- selection << QItemSelectionRange ( firstIndex, lastIndex );
- }
-
- selectionModel()->select ( selection, flags );
-}
-
-void KCategorizedView::mouseMoveEvent ( QMouseEvent *event )
-{
- QListView::mouseMoveEvent ( event );
- d->hoveredIndex = indexAt ( event->pos() );
- const SelectionMode itemViewSelectionMode = selectionMode();
- if ( state() == DragSelectingState && isSelectionRectVisible() && itemViewSelectionMode != SingleSelection
- && itemViewSelectionMode != NoSelection )
- {
- QRect rect ( d->pressedPosition, event->pos() + QPoint ( horizontalOffset(), verticalOffset() ) );
- rect = rect.normalized();
- update ( rect.united ( d->rubberBandRect ) );
- d->rubberBandRect = rect;
- }
- QHash<QString, Private::Block>::ConstIterator it ( d->blocks.constBegin() );
- while ( it != d->blocks.constEnd() )
- {
- const Private::Block &block = *it;
- const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() );
- QStyleOptionViewItemV4 option ( viewOptions() );
- const int height = d->categoryDrawer->categoryHeight ( categoryIndex, option );
- QPoint pos = d->blockPosition ( it.key() );
- pos.ry() -= height;
- option.rect.setTopLeft ( pos );
- option.rect.setWidth ( d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin() );
- option.rect.setHeight ( height + d->blockHeight ( it.key() ) );
- option.rect = d->mapToViewport ( option.rect );
- const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() );
- if ( option.rect.contains ( mousePos ) )
- {
- if ( d->categoryDrawer && d->hoveredBlock->height != -1 && *d->hoveredBlock != block )
- {
- const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() );
- const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex );
- d->categoryDrawer->mouseLeft ( categoryIndex, option.rect );
- *d->hoveredBlock = block;
- d->hoveredCategory = it.key();
- viewport()->update ( option.rect );
- }
- else if ( d->hoveredBlock->height == -1 )
- {
- *d->hoveredBlock = block;
- d->hoveredCategory = it.key();
- }
- else if ( d->categoryDrawer )
- {
- d->categoryDrawer->mouseMoved ( categoryIndex, option.rect, event );
- }
- viewport()->update ( option.rect );
- return;
- }
- ++it;
- }
- if ( d->categoryDrawer && d->hoveredBlock->height != -1 )
- {
- const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() );
- const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex );
- d->categoryDrawer->mouseLeft ( categoryIndex, option.rect );
- *d->hoveredBlock = Private::Block();
- d->hoveredCategory = QString();
- viewport()->update ( option.rect );
- }
-}
-
-void KCategorizedView::mousePressEvent ( QMouseEvent *event )
-{
- if ( event->button() == Qt::LeftButton )
- {
- d->pressedPosition = event->pos();
- d->pressedPosition.rx() += horizontalOffset();
- d->pressedPosition.ry() += verticalOffset();
- }
- if ( !d->categoryDrawer )
- {
- QListView::mousePressEvent ( event );
- return;
- }
- QHash<QString, Private::Block>::ConstIterator it ( d->blocks.constBegin() );
- while ( it != d->blocks.constEnd() )
- {
- const Private::Block &block = *it;
- const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() );
- const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex );
- const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() );
- if ( option.rect.contains ( mousePos ) )
- {
- if ( d->categoryDrawer )
- {
- d->categoryDrawer->mouseButtonPressed ( categoryIndex, option.rect, event );
- }
- viewport()->update ( option.rect );
- if ( !event->isAccepted() )
- {
- QListView::mousePressEvent ( event );
- }
- return;
- }
- ++it;
- }
- QListView::mousePressEvent ( event );
-}
-
-void KCategorizedView::mouseReleaseEvent ( QMouseEvent *event )
-{
- d->pressedPosition = QPoint();
- d->rubberBandRect = QRect();
- QHash<QString, Private::Block>::ConstIterator it ( d->blocks.constBegin() );
- while ( it != d->blocks.constEnd() )
- {
- const Private::Block &block = *it;
- const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() );
- const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex );
- const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() );
- if ( option.rect.contains ( mousePos ) )
- {
- if ( d->categoryDrawer )
- {
- d->categoryDrawer->mouseButtonReleased ( categoryIndex, option.rect, event );
- }
- viewport()->update ( option.rect );
- if ( !event->isAccepted() )
- {
- QListView::mouseReleaseEvent ( event );
- }
- return;
- }
- ++it;
- }
- QListView::mouseReleaseEvent ( event );
-}
-
-void KCategorizedView::leaveEvent ( QEvent *event )
-{
- QListView::leaveEvent ( event );
- if ( d->hoveredIndex.isValid() )
- {
- viewport()->update ( visualRect ( d->hoveredIndex ) );
- d->hoveredIndex = QModelIndex();
- }
- if ( d->categoryDrawer && d->hoveredBlock->height != -1 )
- {
- const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() );
- const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex );
- d->categoryDrawer->mouseLeft ( categoryIndex, option.rect );
- *d->hoveredBlock = Private::Block();
- d->hoveredCategory = QString();
- viewport()->update ( option.rect );
- }
-}
-
-void KCategorizedView::startDrag ( Qt::DropActions supportedActions )
-{
- QListView::startDrag ( supportedActions );
-}
-
-void KCategorizedView::dragMoveEvent ( QDragMoveEvent *event )
-{
- QListView::dragMoveEvent ( event );
- d->hoveredIndex = indexAt ( event->pos() );
-}
-
-void KCategorizedView::dragEnterEvent ( QDragEnterEvent *event )
-{
- QListView::dragEnterEvent ( event );
-}
-
-void KCategorizedView::dragLeaveEvent ( QDragLeaveEvent *event )
-{
- QListView::dragLeaveEvent ( event );
-}
-
-void KCategorizedView::dropEvent ( QDropEvent *event )
-{
- QListView::dropEvent ( event );
-}
-
-//TODO: improve se we take into account collapsed blocks
-//TODO: take into account when there is no grid and no uniformItemSizes
-QModelIndex KCategorizedView::moveCursor ( CursorAction cursorAction,
- Qt::KeyboardModifiers modifiers )
-{
- if ( !d->isCategorized() )
- {
- return QListView::moveCursor ( cursorAction, modifiers );
- }
-
- const QModelIndex current = currentIndex();
- const QRect currentRect = visualRect ( current );
- if ( !current.isValid() )
- {
- const int rowCount = d->proxyModel->rowCount ( rootIndex() );
- if ( !rowCount )
- {
- return QModelIndex();
- }
- return d->proxyModel->index ( 0, modelColumn(), rootIndex() );
- }
-
- switch ( cursorAction )
- {
- case MoveLeft:
- {
- if ( !current.row() )
- {
- return QModelIndex();
- }
- const QModelIndex previous = d->proxyModel->index ( current.row() - 1, modelColumn(), rootIndex() );
- const QRect previousRect = visualRect ( previous );
- if ( previousRect.top() == currentRect.top() )
- {
- return previous;
- }
-
- return QModelIndex();
- }
- case MoveRight:
- {
- if ( current.row() == d->proxyModel->rowCount() - 1 )
- {
- return QModelIndex();
- }
- const QModelIndex next = d->proxyModel->index ( current.row() + 1, modelColumn(), rootIndex() );
- const QRect nextRect = visualRect ( next );
- if ( nextRect.top() == currentRect.top() )
- {
- return next;
- }
-
- return QModelIndex();
- }
- case MoveDown:
- {
- if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() )
- {
- const QModelIndex current = currentIndex();
- const QSize itemSize = d->hasGrid() ? gridSize()
- : sizeHintForIndex ( current );
- const Private::Block &block = d->blocks[d->categoryForIndex ( current )];
- //HACK: Why is the -2 needed?
- const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 );
- const bool canMove = current.row() + maxItemsPerRow < block.firstIndex.row() +
- block.items.count();
-
- if ( canMove )
- {
- return d->proxyModel->index ( current.row() + maxItemsPerRow, modelColumn(), rootIndex() );
- }
-
- const int currentRelativePos = ( current.row() - block.firstIndex.row() ) % maxItemsPerRow;
- const QModelIndex nextIndex = d->proxyModel->index ( block.firstIndex.row() + block.items.count(), modelColumn(), rootIndex() );
-
- if ( !nextIndex.isValid() )
- {
- return QModelIndex();
- }
-
- const Private::Block &nextBlock = d->blocks[d->categoryForIndex ( nextIndex )];
-
- if ( nextBlock.items.count() <= currentRelativePos )
- {
- return QModelIndex();
- }
-
- if ( currentRelativePos < ( block.items.count() % maxItemsPerRow ) )
- {
- return d->proxyModel->index ( nextBlock.firstIndex.row() + currentRelativePos, modelColumn(), rootIndex() );
- }
-
- return QModelIndex();
- }
- }
- case MoveUp:
- {
- if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() )
- {
- const QModelIndex current = currentIndex();
- const QSize itemSize = d->hasGrid() ? gridSize()
- : sizeHintForIndex ( current );
- const Private::Block &block = d->blocks[d->categoryForIndex ( current )];
- //HACK: Why is the -2 needed?
- const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 );
- const bool canMove = current.row() - maxItemsPerRow >= block.firstIndex.row();
-
- if ( canMove )
- {
- return d->proxyModel->index ( current.row() - maxItemsPerRow, modelColumn(), rootIndex() );
- }
-
- const int currentRelativePos = ( current.row() - block.firstIndex.row() ) % maxItemsPerRow;
- const QModelIndex prevIndex = d->proxyModel->index ( block.firstIndex.row() - 1, modelColumn(), rootIndex() );
-
- if ( !prevIndex.isValid() )
- {
- return QModelIndex();
- }
-
- const Private::Block &prevBlock = d->blocks[d->categoryForIndex ( prevIndex )];
-
- if ( prevBlock.items.count() <= currentRelativePos )
- {
- return QModelIndex();
- }
-
- const int remainder = prevBlock.items.count() % maxItemsPerRow;
- if ( currentRelativePos < remainder )
- {
- return d->proxyModel->index ( prevBlock.firstIndex.row() + prevBlock.items.count() - remainder + currentRelativePos, modelColumn(), rootIndex() );
- }
-
- return QModelIndex();
- }
- }
- default:
- break;
- }
-
- return QModelIndex();
-}
-
-void KCategorizedView::rowsAboutToBeRemoved ( const QModelIndex &parent,
- int start,
- int end )
-{
- if ( !d->isCategorized() )
- {
- QListView::rowsAboutToBeRemoved ( parent, start, end );
- return;
- }
-
- *d->hoveredBlock = Private::Block();
- d->hoveredCategory = QString();
-
- if ( end - start + 1 == d->proxyModel->rowCount() )
- {
- d->blocks.clear();
- QListView::rowsAboutToBeRemoved ( parent, start, end );
- return;
- }
-
- // Removal feels a bit more complicated than insertion. Basically we can consider there are
- // 3 different cases when going to remove items. (*) represents an item, Items between ([) and
- // (]) are the ones which are marked for removal.
- //
- // - 1st case:
- // ... * * * * * * [ * * * ...
- //
- // The items marked for removal are the last part of this category. No need to mark any item
- // of this category as in quarantine, because no special offset will be pushed to items at
- // the right because of any changes (since the removed items are those on the right most part
- // of the category).
- //
- // - 2nd case:
- // ... * * * * * * ] * * * ...
- //
- // The items marked for removal are the first part of this category. We have to mark as in
- // quarantine all items in this category. Absolutely all. All items will have to be moved to
- // the left (or moving up, because rows got a different offset).
- //
- // - 3rd case:
- // ... * * [ * * * * ] * * ...
- //
- // The items marked for removal are in between of this category. We have to mark as in
- // quarantine only those items that are at the right of the end of the removal interval,
- // (starting on "]").
- //
- // It hasn't been explicitly said, but when we remove, we have to mark all blocks that are
- // located under the top most affected category as in quarantine (the block itself, as a whole),
- // because such a change can force it to have a different offset (note that items themselves
- // contain relative positions to the block, so marking the block as in quarantine is enough).
- //
- // Also note that removal implicitly means that we have to update correctly firstIndex of each
- // block, and in general keep updated the internal information of elements.
-
- QStringList listOfCategoriesMarkedForRemoval;
-
- QString lastCategory;
- int alreadyRemoved = 0;
- for ( int i = start; i <= end; ++i )
- {
- const QModelIndex index = d->proxyModel->index ( i, modelColumn(), parent );
-
- Q_ASSERT ( index.isValid() );
-
- const QString category = d->categoryForIndex ( index );
-
- if ( lastCategory != category )
- {
- lastCategory = category;
- alreadyRemoved = 0;
- }
-
- Private::Block &block = d->blocks[category];
- block.items.removeAt ( i - block.firstIndex.row() - alreadyRemoved );
- ++alreadyRemoved;
-
- if ( !block.items.count() )
- {
- listOfCategoriesMarkedForRemoval << category;
- }
-
- block.height = -1;
-
- viewport()->update();
- }
-
- //BEGIN: update the items that are in quarantine in affected categories
- {
- const QModelIndex lastIndex = d->proxyModel->index ( end, modelColumn(), parent );
- const QString category = d->categoryForIndex ( lastIndex );
- Private::Block &block = d->blocks[category];
- if ( block.items.count() && start <= block.firstIndex.row() && end >= block.firstIndex.row() )
- {
- block.firstIndex = d->proxyModel->index ( end + 1, modelColumn(), parent );
- }
- block.quarantineStart = block.firstIndex;
- }
- //END: update the items that are in quarantine in affected categories
-
- Q_FOREACH ( const QString &category, listOfCategoriesMarkedForRemoval )
- {
- d->blocks.remove ( category );
- }
-
- //BEGIN: mark as in quarantine those categories that are under the affected ones
- {
- //BEGIN: order for marking as alternate those blocks that are alternate
- QList<Private::Block> blockList = d->blocks.values();
- qSort ( blockList.begin(), blockList.end(), Private::Block::lessThan );
- QList<int> firstIndexesRows;
- foreach ( const Private::Block &block, blockList )
- {
- firstIndexesRows << block.firstIndex.row();
- }
- //END: order for marking as alternate those blocks that are alternate
- for ( QHash<QString, Private::Block>::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it )
- {
- Private::Block &block = *it;
- if ( block.firstIndex.row() > start )
- {
- block.outOfQuarantine = false;
- block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2;
- }
- else if ( block.firstIndex.row() == start )
- {
- block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2;
- }
- }
- }
- //END: mark as in quarantine those categories that are under the affected ones
-
- QListView::rowsAboutToBeRemoved ( parent, start, end );
-}
-
-void KCategorizedView::updateGeometries()
-{
- const int oldVerticalOffset = verticalOffset();
- const Qt::ScrollBarPolicy verticalP = verticalScrollBarPolicy(), horizontalP = horizontalScrollBarPolicy();
-
- //BEGIN bugs 213068, 287847 ------------------------------------------------------------
- /*
- * QListView::updateGeometries() has it's own opinion on whether the scrollbars should be visible (valid range) or not
- * and triggers a (sometimes additionally timered) resize through ::layoutChildren()
- * http://qt.gitorious.org/qt/qt/blobs/4.7/src/gui/itemviews/qlistview.cpp#line1499
- * (the comment above the main block isn't all accurate, layoutChldren is called regardless of the policy)
- *
- * As a result QListView and KCategorizedView occasionally started a race on the scrollbar visibility, effectively blocking the UI
- * So we prevent QListView from having an own opinion on the scrollbar visibility by
- * fixing it before calling the baseclass QListView::updateGeometries()
- *
- * Since the implicit show/hide by the followin range setting will cause further resizes if the policy is Qt::ScrollBarAsNeeded
- * we keep it static until we're done, then restore the original value and ultimately change the scrollbar visibility ourself.
- */
- if ( d->isCategorized() ) // important! - otherwise we'd pollute the setting if the view is initially not categorized
- {
- setVerticalScrollBarPolicy ( ( verticalP == Qt::ScrollBarAlwaysOn || verticalScrollBar()->isVisibleTo ( this ) ) ?
- Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff );
- setHorizontalScrollBarPolicy ( ( horizontalP == Qt::ScrollBarAlwaysOn || horizontalScrollBar()->isVisibleTo ( this ) ) ?
- Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff );
- }
- //END bugs 213068, 287847 --------------------------------------------------------------
-
- QListView::updateGeometries();
-
- if ( !d->isCategorized() )
- {
- return;
- }
-
- const int rowCount = d->proxyModel->rowCount();
- if ( !rowCount )
- {
- verticalScrollBar()->setRange ( 0, 0 );
- // unconditional, see function end todo
- horizontalScrollBar()->setRange ( 0, 0 );
- return;
- }
-
- const QModelIndex lastIndex = d->proxyModel->index ( rowCount - 1, modelColumn(), rootIndex() );
- Q_ASSERT ( lastIndex.isValid() );
- QRect lastItemRect = visualRect ( lastIndex );
-
- if ( d->hasGrid() )
- {
- lastItemRect.setSize ( lastItemRect.size().expandedTo ( gridSize() ) );
- }
- else
- {
- if ( uniformItemSizes() )
- {
- QSize itemSize = sizeHintForIndex ( lastIndex );
- itemSize.setHeight ( itemSize.height() + spacing() );
- lastItemRect.setSize ( itemSize );
- }
- else
- {
- QSize itemSize = sizeHintForIndex ( lastIndex );
- const QString category = d->categoryForIndex ( lastIndex );
- itemSize.setHeight ( d->highestElementInLastRow ( d->blocks[category] ) + spacing() );
- lastItemRect.setSize ( itemSize );
- }
- }
-
- const int bottomRange = lastItemRect.bottomRight().y() + verticalOffset() - viewport()->height();
-
- if ( verticalScrollMode() == ScrollPerItem )
- {
- verticalScrollBar()->setSingleStep ( lastItemRect.height() );
- const int rowsPerPage = qMax ( viewport()->height() / lastItemRect.height(), 1 );
- verticalScrollBar()->setPageStep ( rowsPerPage * lastItemRect.height() );
- }
-
- verticalScrollBar()->setRange ( 0, bottomRange );
- verticalScrollBar()->setValue ( oldVerticalOffset );
-
- //TODO: also consider working with the horizontal scroll bar. since at this level I am not still
- // supporting "top to bottom" flow, there is no real problem. If I support that someday
- // (think how to draw categories), we would have to take care of the horizontal scroll bar too.
- // In theory, as KCategorizedView has been designed, there is no need of horizontal scroll bar.
- horizontalScrollBar()->setRange ( 0, 0 );
-
- //BEGIN bugs 213068, 287847 ------------------------------------------------------------
- // restoring values from above ...
- setVerticalScrollBarPolicy ( verticalP );
- setHorizontalScrollBarPolicy ( horizontalP );
- // ... and correct the visibility
- bool validRange = verticalScrollBar()->maximum() != verticalScrollBar()->minimum();
- if ( verticalP == Qt::ScrollBarAsNeeded && ( verticalScrollBar()->isVisibleTo ( this ) != validRange ) )
- verticalScrollBar()->setVisible ( validRange );
- validRange = horizontalScrollBar()->maximum() > horizontalScrollBar()->minimum();
- if ( horizontalP == Qt::ScrollBarAsNeeded && ( horizontalScrollBar()->isVisibleTo ( this ) != validRange ) )
- horizontalScrollBar()->setVisible ( validRange );
- //END bugs 213068, 287847 --------------------------------------------------------------
-}
-
-void KCategorizedView::currentChanged ( const QModelIndex &current,
- const QModelIndex &previous )
-{
- QListView::currentChanged ( current, previous );
-}
-
-void KCategorizedView::dataChanged ( const QModelIndex &topLeft,
- const QModelIndex &bottomRight )
-{
- QListView::dataChanged ( topLeft, bottomRight );
- if ( !d->isCategorized() )
- {
- return;
- }
-
- *d->hoveredBlock = Private::Block();
- d->hoveredCategory = QString();
-
- //BEGIN: since the model changed data, we need to reconsider item sizes
- int i = topLeft.row();
- int indexToCheck = i;
- QModelIndex categoryIndex;
- QString category;
- Private::Block *block;
- while ( i <= bottomRight.row() )
- {
- const QModelIndex currIndex = d->proxyModel->index ( i, modelColumn(), rootIndex() );
- if ( i == indexToCheck )
- {
- categoryIndex = d->proxyModel->index ( i, d->proxyModel->sortColumn(), rootIndex() );
- category = categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString();
- block = &d->blocks[category];
- block->quarantineStart = currIndex;
- indexToCheck = block->firstIndex.row() + block->items.count();
- }
- visualRect ( currIndex );
- ++i;
- }
- //END: since the model changed data, we need to reconsider item sizes
-}
-
-void KCategorizedView::rowsInserted ( const QModelIndex &parent,
- int start,
- int end )
-{
- QListView::rowsInserted ( parent, start, end );
- if ( !d->isCategorized() )
- {
- return;
- }
-
- *d->hoveredBlock = Private::Block();
- d->hoveredCategory = QString();
- d->rowsInserted ( parent, start, end );
-}
-
-void KCategorizedView::slotLayoutChanged()
-{
- if ( !d->isCategorized() )
- {
- return;
- }
-
- d->blocks.clear();
- *d->hoveredBlock = Private::Block();
- d->hoveredCategory = QString();
- if ( d->proxyModel->rowCount() )
- {
- d->rowsInserted ( rootIndex(), 0, d->proxyModel->rowCount() - 1 );
- }
-}
-//END: Public part
-
-void KCategorizedView::slotCollapseOrExpandClicked ( QModelIndex idx )
-{
- d->_k_slotCollapseOrExpandClicked ( idx );
-}
-
-
-#include "categorizedview.moc"
diff --git a/depends/groupview/src/categorizedview_p.h b/depends/groupview/src/categorizedview_p.h
deleted file mode 100644
index 524bba3a..00000000
--- a/depends/groupview/src/categorizedview_p.h
+++ /dev/null
@@ -1,160 +0,0 @@
-/**
- * This file is part of the KDE project
- * Copyright (C) 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#ifndef KCATEGORIZEDVIEW_P_H
-#define KCATEGORIZEDVIEW_P_H
-
-class KCategorizedSortFilterProxyModel;
-class KCategoryDrawer;
-class KCategoryDrawerV2;
-class KCategoryDrawerV3;
-
-/**
- * @internal
- */
-class KCategorizedView::Private
-{
-public:
- struct Block;
- struct Item;
-
- Private(KCategorizedView *q);
- ~Private();
-
- /**
- * @return whether this view has all required elements to be categorized.
- */
- bool isCategorized() const;
-
- /**
- * @return the block rect for the representative @p representative.
- */
- QStyleOptionViewItemV4 blockRect(const QModelIndex &representative);
-
- /**
- * Returns the first and last element that intersects with rect.
- *
- * @note see that here we cannot take out items between first and last (as we could
- * do with the rubberband).
- *
- * Complexity: O(log(n)) where n is model()->rowCount().
- */
- QPair<QModelIndex, QModelIndex> intersectingIndexesWithRect(const QRect &rect) const;
-
- /**
- * Returns the position of the block of @p category.
- *
- * Complexity: O(n) where n is the number of different categories when the block has been
- * marked as in quarantine. O(1) the rest of the times (the vast majority).
- */
- QPoint blockPosition(const QString &category);
-
- /**
- * Returns the height of the block determined by @p category.
- */
- int blockHeight(const QString &category);
-
- /**
- * Returns the actual viewport width.
- */
- int viewportWidth() const;
-
- /**
- * Marks all elements as in quarantine.
- *
- * Complexity: O(n) where n is model()->rowCount().
- *
- * @warning this is an expensive operation
- */
- void regenerateAllElements();
-
- /**
- * Update internal information, and keep sync with the real information that the model contains.
- */
- void rowsInserted(const QModelIndex &parent, int start, int end);
-
- /**
- * Returns @p rect in viewport terms, taking in count horizontal and vertical offsets.
- */
- QRect mapToViewport(const QRect &rect) const;
-
- /**
- * Returns @p rect in absolute terms, converted from viewport position.
- */
- QRect mapFromViewport(const QRect &rect) const;
-
- /**
- * Returns the height of the highest element in last row. This is only applicable if there is
- * no grid set and uniformItemSizes is false.
- *
- * @param block in which block are we searching. Necessary to stop the search if we hit the
- * first item in this block.
- */
- int highestElementInLastRow(const Block &block) const;
-
- /**
- * Returns whether the view has a valid grid size.
- */
- bool hasGrid() const;
-
- /**
- * Returns the category for the given index.
- */
- QString categoryForIndex(const QModelIndex &index) const;
-
- /**
- * Updates the visual rect for item when flow is LeftToRight.
- */
- void leftToRightVisualRect(const QModelIndex &index, Item &item,
- const Block &block, const QPoint &blockPos) const;
-
- /**
- * Updates the visual rect for item when flow is TopToBottom.
- * @note we only support viewMode == ListMode in this case.
- */
- void topToBottomVisualRect(const QModelIndex &index, Item &item,
- const Block &block, const QPoint &blockPos) const;
-
- /**
- * Called when expand or collapse has been clicked on the category drawer.
- */
- void _k_slotCollapseOrExpandClicked(QModelIndex);
-
- KCategorizedView *q = nullptr;
- KCategorizedSortFilterProxyModel *proxyModel = nullptr;
- KCategoryDrawer *categoryDrawer = nullptr;
- int categorySpacing = 5;
- bool alternatingBlockColors = false;
- bool collapsibleBlocks = false;
- bool constantItemWidth = false;
-
- // FIXME: this is some really weird logic. Investigate.
- Block *hoveredBlock;
- QString hoveredCategory;
- QModelIndex hoveredIndex;
-
- QPoint pressedPosition;
- QRect rubberBandRect;
-
- QHash<QString, Block> blocks;
-};
-
-#endif // KCATEGORIZEDVIEW_P_H
-
diff --git a/depends/groupview/src/categorydrawer.cpp b/depends/groupview/src/categorydrawer.cpp
deleted file mode 100644
index 214ce3b2..00000000
--- a/depends/groupview/src/categorydrawer.cpp
+++ /dev/null
@@ -1,231 +0,0 @@
-/**
- * This file is part of the KDE project
- * Copyright (C) 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include "categorydrawer.h"
-
-#include <QPainter>
-#include <QStyleOption>
-#include <QApplication>
-
-#include <categorizedview.h>
-#include <categorizedsortfilterproxymodel.h>
-
-#define HORIZONTAL_HINT 3
-
-class KCategoryDrawer::Private
-{
-public:
- Private(KCategorizedView *view)
- : view(view)
- , leftMargin(0)
- , rightMargin(0)
- {
- }
-
- ~Private()
- {
- }
- KCategorizedView *view;
- int leftMargin;
- int rightMargin;
-};
-
-KCategoryDrawer::KCategoryDrawer(KCategorizedView *view)
- : QObject(view)
- , d(new Private(view))
-{
- setLeftMargin(2);
- setRightMargin(2);
-}
-
-KCategoryDrawer::~KCategoryDrawer()
-{
- delete d;
-}
-
-
-void KCategoryDrawer::drawCategory(const QModelIndex &index,
- int /*sortRole*/,
- const QStyleOption &option,
- QPainter *painter) const
-{
- painter->setRenderHint(QPainter::Antialiasing);
-
- const QString category = index.model()->data(index, KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
- const QRect optRect = option.rect;
- QFont font(QApplication::font());
- font.setBold(true);
- const QFontMetrics fontMetrics = QFontMetrics(font);
-
- QColor outlineColor = option.palette.text().color();
- outlineColor.setAlphaF(0.35);
-
- //BEGIN: top left corner
- {
- painter->save();
- painter->setPen(outlineColor);
- const QPointF topLeft(optRect.topLeft());
- QRectF arc(topLeft, QSizeF(4, 4));
- arc.translate(0.5, 0.5);
- painter->drawArc(arc, 1440, 1440);
- painter->restore();
- }
- //END: top left corner
-
- //BEGIN: left vertical line
- {
- QPoint start(optRect.topLeft());
- start.ry() += 3;
- QPoint verticalGradBottom(optRect.topLeft());
- verticalGradBottom.ry() += fontMetrics.height() + 5;
- QLinearGradient gradient(start, verticalGradBottom);
- gradient.setColorAt(0, outlineColor);
- gradient.setColorAt(1, Qt::transparent);
- painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient);
- }
- //END: left vertical line
-
- //BEGIN: horizontal line
- {
- QPoint start(optRect.topLeft());
- start.rx() += 3;
- QPoint horizontalGradTop(optRect.topLeft());
- horizontalGradTop.rx() += optRect.width() - 6;
- painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor);
- }
- //END: horizontal line
-
- //BEGIN: top right corner
- {
- painter->save();
- painter->setPen(outlineColor);
- QPointF topRight(optRect.topRight());
- topRight.rx() -= 4;
- QRectF arc(topRight, QSizeF(4, 4));
- arc.translate(0.5, 0.5);
- painter->drawArc(arc, 0, 1440);
- painter->restore();
- }
- //END: top right corner
-
- //BEGIN: right vertical line
- {
- QPoint start(optRect.topRight());
- start.ry() += 3;
- QPoint verticalGradBottom(optRect.topRight());
- verticalGradBottom.ry() += fontMetrics.height() + 5;
- QLinearGradient gradient(start, verticalGradBottom);
- gradient.setColorAt(0, outlineColor);
- gradient.setColorAt(1, Qt::transparent);
- painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient);
- }
- //END: right vertical line
-
- //BEGIN: text
- {
- QRect textRect(option.rect);
- textRect.setTop(textRect.top() + 7);
- textRect.setLeft(textRect.left() + 7);
- textRect.setHeight(fontMetrics.height());
- textRect.setRight(textRect.right() - 7);
-
- painter->save();
- painter->setFont(font);
- QColor penColor(option.palette.text().color());
- penColor.setAlphaF(0.6);
- painter->setPen(penColor);
- painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, category);
- painter->restore();
- }
- //END: text
-}
-
-int KCategoryDrawer::categoryHeight(const QModelIndex &index, const QStyleOption &option) const
-{
- Q_UNUSED(index);
- Q_UNUSED(option)
-
- QFont font(QApplication::font());
- font.setBold(true);
- QFontMetrics fontMetrics(font);
-
- const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */
- + 11 /* top and bottom separation */;
- return height;
-}
-
-int KCategoryDrawer::leftMargin() const
-{
- return d->leftMargin;
-}
-
-void KCategoryDrawer::setLeftMargin(int leftMargin)
-{
- d->leftMargin = leftMargin;
-}
-
-int KCategoryDrawer::rightMargin() const
-{
- return d->rightMargin;
-}
-
-void KCategoryDrawer::setRightMargin(int rightMargin)
-{
- d->rightMargin = rightMargin;
-}
-
-KCategoryDrawer &KCategoryDrawer::operator=(const KCategoryDrawer &cd)
-{
- d->leftMargin = cd.d->leftMargin;
- d->rightMargin = cd.d->rightMargin;
- d->view = cd.d->view;
- return *this;
-}
-
-KCategorizedView *KCategoryDrawer::view() const
-{
- return d->view;
-}
-
-void KCategoryDrawer::mouseButtonPressed(const QModelIndex&, const QRect&, QMouseEvent *event)
-{
- event->ignore();
-}
-
-void KCategoryDrawer::mouseButtonReleased(const QModelIndex&, const QRect&, QMouseEvent *event)
-{
- event->ignore();
-}
-
-void KCategoryDrawer::mouseMoved(const QModelIndex&, const QRect&, QMouseEvent *event)
-{
- event->ignore();
-}
-
-void KCategoryDrawer::mouseButtonDoubleClicked(const QModelIndex&, const QRect&, QMouseEvent *event)
-{
- event->ignore();
-}
-
-void KCategoryDrawer::mouseLeft(const QModelIndex&, const QRect&)
-{
-}
-
-#include "categorydrawer.moc"
diff --git a/depends/util/CMakeLists.txt b/depends/util/CMakeLists.txt
index db7d70e6..7f6573bd 100644
--- a/depends/util/CMakeLists.txt
+++ b/depends/util/CMakeLists.txt
@@ -35,6 +35,9 @@ src/userutils.cpp
include/cmdutils.h
src/cmdutils.cpp
+
+include/modutils.h
+src/modutils.cpp
)
# Set the include dir path.
diff --git a/depends/util/include/modutils.h b/depends/util/include/modutils.h
new file mode 100644
index 00000000..e04db66f
--- /dev/null
+++ b/depends/util/include/modutils.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <QString>
+#include "libutil_config.h"
+
+class QUrl;
+
+namespace Util
+{
+struct Version
+{
+ Version(const QString &str);
+
+ bool operator<(const Version &other) const;
+ bool operator<=(const Version &other) const;
+ bool operator>(const Version &other) const;
+ bool operator==(const Version &other) const;
+ bool operator!=(const Version &other) const;
+
+ QString toString() const
+ {
+ return m_string;
+ }
+
+private:
+ QString m_string;
+};
+
+LIBUTIL_EXPORT QUrl expandQMURL(const QString &in);
+LIBUTIL_EXPORT bool versionIsInInterval(const QString &version, const QString &interval);
+}
+
diff --git a/depends/util/src/modutils.cpp b/depends/util/src/modutils.cpp
new file mode 100644
index 00000000..44a04b72
--- /dev/null
+++ b/depends/util/src/modutils.cpp
@@ -0,0 +1,216 @@
+#include "include/modutils.h"
+
+#include <QStringList>
+#include <QUrl>
+#include <QRegularExpression>
+#include <QRegularExpressionMatch>
+
+Util::Version::Version(const QString &str) : m_string(str)
+{
+}
+
+bool Util::Version::operator<(const Version &other) const
+{
+ QStringList parts1 = m_string.split('.');
+ QStringList parts2 = other.m_string.split('.');
+
+ while (!parts1.isEmpty() && !parts2.isEmpty())
+ {
+ QString part1 = parts1.isEmpty() ? "0" : parts1.takeFirst();
+ QString part2 = parts2.isEmpty() ? "0" : parts2.takeFirst();
+ bool ok1 = false;
+ bool ok2 = false;
+ int int1 = part1.toInt(&ok1);
+ int int2 = part2.toInt(&ok2);
+ if (ok1 && ok2)
+ {
+ if (int1 == int2)
+ {
+ continue;
+ }
+ else
+ {
+ return int1 < int2;
+ }
+ }
+ else
+ {
+ if (part1 == part2)
+ {
+ continue;
+ }
+ else
+ {
+ return part1 < part2;
+ }
+ }
+ }
+
+ return false;
+}
+bool Util::Version::operator<=(const Util::Version &other) const
+{
+ return *this < other || *this == other;
+}
+bool Util::Version::operator>(const Version &other) const
+{
+ QStringList parts1 = m_string.split('.');
+ QStringList parts2 = other.m_string.split('.');
+
+ while (!parts1.isEmpty() && !parts2.isEmpty())
+ {
+ QString part1 = parts1.isEmpty() ? "0" : parts1.takeFirst();
+ QString part2 = parts2.isEmpty() ? "0" : parts2.takeFirst();
+ bool ok1 = false;
+ bool ok2 = false;
+ int int1 = part1.toInt(&ok1);
+ int int2 = part2.toInt(&ok2);
+ if (ok1 && ok2)
+ {
+ if (int1 == int2)
+ {
+ continue;
+ }
+ else
+ {
+ return int1 > int2;
+ }
+ }
+ else
+ {
+ if (part1 == part2)
+ {
+ continue;
+ }
+ else
+ {
+ return part1 > part2;
+ }
+ }
+ }
+
+ return false;
+}
+bool Util::Version::operator==(const Version &other) const
+{
+ QStringList parts1 = m_string.split('.');
+ QStringList parts2 = other.m_string.split('.');
+
+ while (!parts1.isEmpty() && !parts2.isEmpty())
+ {
+ QString part1 = parts1.isEmpty() ? "0" : parts1.takeFirst();
+ QString part2 = parts2.isEmpty() ? "0" : parts2.takeFirst();
+ bool ok1 = false;
+ bool ok2 = false;
+ int int1 = part1.toInt(&ok1);
+ int int2 = part2.toInt(&ok2);
+ if (ok1 && ok2)
+ {
+ if (int1 == int2)
+ {
+ continue;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (part1 == part2)
+ {
+ continue;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+bool Util::Version::operator!=(const Version &other) const
+{
+ return !operator==(other);
+}
+
+QUrl Util::expandQMURL(const QString &in)
+{
+ QUrl inUrl(in);
+ if (inUrl.scheme() == "github")
+ {
+ // needed because QUrl makes the host all lower cases
+ const QString repo = in.mid(in.indexOf(inUrl.host(), 0, Qt::CaseInsensitive), inUrl.host().size());
+ QUrl out;
+ out.setScheme("https");
+ out.setHost("raw.github.com");
+ out.setPath(QString("/%1/%2/%3%4")
+ .arg(inUrl.userInfo(), repo,
+ inUrl.fragment().isEmpty() ? "master" : inUrl.fragment(), inUrl.path()));
+ return out;
+ }
+ else if (inUrl.scheme() == "mcf")
+ {
+ QUrl out;
+ out.setScheme("http");
+ out.setHost("www.minecraftforum.net");
+ out.setPath(QString("/topic/%1-").arg(inUrl.path()));
+ return out;
+ }
+ else
+ {
+ return in;
+ }
+}
+
+bool Util::versionIsInInterval(const QString &version, const QString &interval)
+{
+ if (interval.isEmpty() || version == interval)
+ {
+ return true;
+ }
+
+ // Interval notation is used
+ QRegularExpression exp(
+ "(?<start>[\\[\\]\\(\\)])(?<bottom>.*?)(,(?<top>.*?))?(?<end>[\\[\\]\\(\\)])");
+ QRegularExpressionMatch match = exp.match(interval);
+ if (match.hasMatch())
+ {
+ const QChar start = match.captured("start").at(0);
+ const QChar end = match.captured("end").at(0);
+ const QString bottom = match.captured("bottom");
+ const QString top = match.captured("top");
+
+ // check if in range (bottom)
+ if (!bottom.isEmpty())
+ {
+ if ((start == '[') && !(version >= bottom))
+ {
+ return false;
+ }
+ else if ((start == '(') && !(version > bottom))
+ {
+ return false;
+ }
+ }
+
+ // check if in range (top)
+ if (!top.isEmpty())
+ {
+ if ((end == ']') && !(version <= top))
+ {
+ return false;
+ }
+ else if ((end == ')') && !(version < top))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
diff --git a/gui/ConsoleWindow.cpp b/gui/ConsoleWindow.cpp
index dc36a8ff..ccc037f2 100644
--- a/gui/ConsoleWindow.cpp
+++ b/gui/ConsoleWindow.cpp
@@ -84,7 +84,7 @@ void ConsoleWindow::iconActivated(QSystemTrayIcon::ActivationReason reason)
}
}
-void ConsoleWindow::writeColor(QString text, const char *color)
+void ConsoleWindow::writeColor(QString text, const char *color, const char * background)
{
// append a paragraph
QString newtext;
@@ -92,6 +92,8 @@ void ConsoleWindow::writeColor(QString text, const char *color)
{
if (color)
newtext += QString("color:") + color + ";";
+ if (background)
+ newtext += QString("background-color:") + background + ";";
newtext += "font-family: monospace;";
}
newtext += "\">";
@@ -127,26 +129,26 @@ void ConsoleWindow::write(QString data, MessageLevel::Enum mode)
QListIterator<QString> iter(paragraphs);
if (mode == MessageLevel::MultiMC)
while (iter.hasNext())
- writeColor(iter.next(), "blue");
+ writeColor(iter.next(), "blue", 0);
else if (mode == MessageLevel::Error)
while (iter.hasNext())
- writeColor(iter.next(), "red");
+ writeColor(iter.next(), "red", 0);
else if (mode == MessageLevel::Warning)
while (iter.hasNext())
- writeColor(iter.next(), "orange");
+ writeColor(iter.next(), "orange", 0);
else if (mode == MessageLevel::Fatal)
while (iter.hasNext())
- writeColor(iter.next(), "pink");
+ writeColor(iter.next(), "red", "black");
else if (mode == MessageLevel::Debug)
while (iter.hasNext())
- writeColor(iter.next(), "green");
+ writeColor(iter.next(), "green", 0);
else if (mode == MessageLevel::PrePost)
while (iter.hasNext())
- writeColor(iter.next(), "grey");
+ writeColor(iter.next(), "grey", 0);
// TODO: implement other MessageLevels
else
while (iter.hasNext())
- writeColor(iter.next());
+ writeColor(iter.next(), 0, 0);
if(isVisible())
{
if (m_scroll_active)
diff --git a/gui/ConsoleWindow.h b/gui/ConsoleWindow.h
index 9291320e..7fe90c52 100644
--- a/gui/ConsoleWindow.h
+++ b/gui/ConsoleWindow.h
@@ -47,7 +47,7 @@ private:
* this will only insert a single paragraph.
* \n are ignored. a real \n is always appended.
*/
- void writeColor(QString data, const char *color = nullptr);
+ void writeColor(QString text, const char *color, const char *background);
signals:
void isClosing();
diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp
index ee9c3fad..29f7c8e8 100644
--- a/gui/MainWindow.cpp
+++ b/gui/MainWindow.cpp
@@ -26,6 +26,7 @@
#include <QInputDialog>
#include <QDesktopServices>
+#include <QKeyEvent>
#include <QUrl>
#include <QDir>
#include <QFileInfo>
@@ -37,12 +38,12 @@
#include "userutils.h"
#include "pathutils.h"
-#include "categorizedview.h"
-#include "categorydrawer.h"
+#include "gui/groupview/GroupView.h"
+#include "gui/groupview/InstanceDelegate.h"
#include "gui/Platform.h"
-#include "gui/widgets/InstanceDelegate.h"
+
#include "gui/widgets/LabeledToolButton.h"
#include "gui/dialogs/SettingsDialog.h"
@@ -71,7 +72,6 @@
#include "logic/auth/flows/AuthenticateTask.h"
#include "logic/auth/flows/RefreshTask.h"
-#include "logic/auth/flows/ValidateTask.h"
#include "logic/updater/DownloadUpdateTask.h"
@@ -132,28 +132,30 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel);
- QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked);
- QObject::connect(MMC->newsChecker().get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel);
+ QObject::connect(newsLabel, &QAbstractButton::clicked, this,
+ &MainWindow::newsButtonClicked);
+ QObject::connect(MMC->newsChecker().get(), &NewsChecker::newsLoaded, this,
+ &MainWindow::updateNewsLabel);
updateNewsLabel();
}
// Create the instance list widget
{
- view = new KCategorizedView(ui->centralWidget);
- drawer = new KCategoryDrawer(view);
+ view = new GroupView(ui->centralWidget);
+
view->setSelectionMode(QAbstractItemView::SingleSelection);
- view->setCategoryDrawer(drawer);
- view->setCollapsibleBlocks(true);
- view->setViewMode(QListView::IconMode);
- view->setFlow(QListView::LeftToRight);
- view->setWordWrap(true);
- view->setMouseTracking(true);
- view->viewport()->setAttribute(Qt::WA_Hover);
+ // view->setCategoryDrawer(drawer);
+ // view->setCollapsibleBlocks(true);
+ // view->setViewMode(QListView::IconMode);
+ // view->setFlow(QListView::LeftToRight);
+ // view->setWordWrap(true);
+ // view->setMouseTracking(true);
+ // view->viewport()->setAttribute(Qt::WA_Hover);
auto delegate = new ListViewDelegate();
view->setItemDelegate(delegate);
- view->setSpacing(10);
- view->setUniformItemWidths(true);
+ // view->setSpacing(10);
+ // view->setUniformItemWidths(true);
// do not show ugly blue border on the mac
view->setAttribute(Qt::WA_MacShowFocusRect, false);
@@ -173,8 +175,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
view->setModel(proxymodel);
view->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(view, SIGNAL(customContextMenuRequested(const QPoint&)),
- this, SLOT(showInstanceContextMenu(const QPoint&)));
+ connect(view, SIGNAL(customContextMenuRequested(const QPoint &)), this,
+ SLOT(showInstanceContextMenu(const QPoint &)));
ui->horizontalLayout->addWidget(view);
}
@@ -213,8 +215,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
// Start status checker
{
- connect(MMC->statusChecker().get(), &StatusChecker::statusLoaded, this, &MainWindow::updateStatusUI);
- connect(MMC->statusChecker().get(), &StatusChecker::statusLoadingFailed, this, &MainWindow::updateStatusFailedUI);
+ connect(MMC->statusChecker().get(), &StatusChecker::statusLoaded, this,
+ &MainWindow::updateStatusUI);
+ connect(MMC->statusChecker().get(), &StatusChecker::statusLoadingFailed, this,
+ &MainWindow::updateStatusFailedUI);
connect(m_statusRefresh, &QAbstractButton::clicked, this, &MainWindow::reloadStatus);
connect(&statusTimer, &QTimer::timeout, this, &MainWindow::reloadStatus);
@@ -313,8 +317,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
if (MMC->settings()->get("AutoUpdate").toBool())
on_actionCheckUpdate_triggered();
- connect(MMC->notificationChecker().get(), &NotificationChecker::notificationCheckFinished,
- this, &MainWindow::notificationsChanged);
+ connect(MMC->notificationChecker().get(),
+ &NotificationChecker::notificationCheckFinished, this,
+ &MainWindow::notificationsChanged);
}
setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString());
@@ -327,12 +332,11 @@ MainWindow::~MainWindow()
{
delete ui;
delete proxymodel;
- delete drawer;
}
-void MainWindow::showInstanceContextMenu(const QPoint& pos)
+void MainWindow::showInstanceContextMenu(const QPoint &pos)
{
- if(!view->indexAt(pos).isValid())
+ if (!view->indexAt(pos).isValid())
{
return;
}
@@ -522,9 +526,12 @@ static QString convertStatus(const QString &status)
{
QString ret = "?";
- if(status == "green") ret = "↑";
- else if(status == "yellow") ret = "-";
- else if(status == "red") ret="↓";
+ if (status == "green")
+ ret = "↑";
+ else if (status == "yellow")
+ ret = "-";
+ else if (status == "red")
+ ret = "↓";
return "<span style=\"font-size:11pt; font-weight:600;\">" + ret + "</span>";
}
@@ -533,7 +540,7 @@ void MainWindow::reloadStatus()
{
m_statusRefresh->setChecked(true);
MMC->statusChecker()->reloadStatus();
- //updateStatusUI();
+ // updateStatusUI();
}
static QString makeStatusString(const QMap<QString, QString> statuses)
@@ -632,7 +639,8 @@ void MainWindow::notificationsChanged()
}
QMessageBox box(icon, tr("Notification"), entry.message, QMessageBox::Close, this);
- QPushButton *dontShowAgainButton = box.addButton(tr("Don't show again"), QMessageBox::AcceptRole);
+ QPushButton *dontShowAgainButton =
+ box.addButton(tr("Don't show again"), QMessageBox::AcceptRole);
box.setDefaultButton(QMessageBox::Close);
box.exec();
if (box.clickedButton() == dontShowAgainButton)
@@ -657,9 +665,9 @@ void MainWindow::downloadUpdates(QString repo, int versionId, bool installOnExit
if (updateDlg.exec(&updateTask))
{
UpdateFlags baseFlags = None;
- #ifdef MultiMC_UPDATER_DRY_RUN
- baseFlags |= DryRun;
- #endif
+#ifdef MultiMC_UPDATER_DRY_RUN
+ baseFlags |= DryRun;
+#endif
if (installOnExit)
MMC->installUpdates(updateTask.updateFilesDir(), baseFlags | OnExit);
else
@@ -677,7 +685,7 @@ void MainWindow::setCatBackground(bool enabled)
{
if (enabled)
{
- view->setStyleSheet("QListView"
+ view->setStyleSheet("GroupView"
"{"
"background-image: url(:/backgrounds/kitteh);"
"background-attachment: fixed;"
@@ -751,7 +759,7 @@ void MainWindow::on_actionAddInstance_triggered()
if (MMC->accounts()->anyAccountIsValid())
{
ProgressDialog loadDialog(this);
- auto update = newInstance->doUpdate(false);
+ auto update = newInstance->doUpdate();
connect(update.get(), &Task::failed, [this](QString reason)
{
QString error = QString("Instance load failed: %1").arg(reason);
@@ -837,7 +845,7 @@ void MainWindow::on_actionChangeInstIcon_triggered()
void MainWindow::iconUpdated(QString icon)
{
- if(icon == m_currentInstIcon)
+ if (icon == m_currentInstIcon)
{
ui->actionChangeInstIcon->setIcon(MMC->icons()->getBigIcon(m_currentInstIcon));
}
@@ -860,7 +868,8 @@ void MainWindow::setSelectedInstanceById(const QString &id)
selectionIndex = proxymodel->mapFromSource(index);
}
}
- view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect);
+ view->selectionModel()->setCurrentIndex(selectionIndex,
+ QItemSelectionModel::ClearAndSelect);
}
void MainWindow::on_actionChangeInstGroup_triggered()
@@ -1060,7 +1069,16 @@ void MainWindow::on_actionLaunchInstance_triggered()
}
}
-void MainWindow::doLaunch()
+void MainWindow::on_actionLaunchInstanceOffline_triggered()
+{
+ if (m_selectedInstance)
+ {
+ NagUtils::checkJVMArgs(m_selectedInstance->settings().get("JvmArgs").toString(), this);
+ doLaunch(false);
+ }
+}
+
+void MainWindow::doLaunch(bool online)
{
if (!m_selectedInstance)
return;
@@ -1104,89 +1122,111 @@ void MainWindow::doLaunch()
if (!account.get())
return;
+ // we try empty password first :)
+ QString password;
+ // we loop until the user succeeds in logging in or gives up
+ bool tryagain = true;
+ // the failure. the default failure.
QString failReason = tr("Your account is currently not logged in. Please enter "
"your password to log in again.");
- // do the login. if the account has an access token, try to refresh it first.
- if (account->accountStatus() != NotVerified)
+
+ while (tryagain)
{
- // We'll need to validate the access token to make sure the account is still logged in.
- ProgressDialog progDialog(this);
- progDialog.setSkipButton(true, tr("Play Offline"));
- auto task = account->login();
- progDialog.exec(task.get());
-
- auto status = account->accountStatus();
- if (status != NotVerified)
- {
- updateInstance(m_selectedInstance, account);
- }
- else
+ AuthSessionPtr session(new AuthSession());
+ session->wants_online = online;
+ auto task = account->login(session, password);
+ if (task)
{
+ // We'll need to validate the access token to make sure the account
+ // is still logged in.
+ ProgressDialog progDialog(this);
+ if (online)
+ progDialog.setSkipButton(true, tr("Play Offline"));
+ progDialog.exec(task.get());
if (!task->successful())
{
failReason = task->failReason();
}
- if (loginWithPassword(account, failReason))
- updateInstance(m_selectedInstance, account);
}
- // in any case, revert from online to verified.
- account->downgrade();
- }
- else
- {
- if (loginWithPassword(account, failReason))
+ switch (session->status)
+ {
+ case AuthSession::Undetermined:
{
- updateInstance(m_selectedInstance, account);
- account->downgrade();
+ QLOG_ERROR() << "Received undetermined session status during login. Bye.";
+ tryagain = false;
+ break;
}
- // in any case, revert from online to verified.
- account->downgrade();
- }
-}
-
-bool MainWindow::loginWithPassword(MojangAccountPtr account, const QString &errorMsg)
-{
- EditAccountDialog passDialog(errorMsg, this, EditAccountDialog::PasswordField);
- if (passDialog.exec() == QDialog::Accepted)
- {
- // To refresh the token, we just create an authenticate task with the given account and
- // the user's password.
- ProgressDialog progDialog(this);
- auto task = account->login(passDialog.password());
- progDialog.exec(task.get());
- if (task->successful())
- return true;
- else
+ case AuthSession::RequiresPassword:
{
- // If the authentication task failed, recurse with the task's error message.
- return loginWithPassword(account, task->failReason());
+ EditAccountDialog passDialog(failReason, this, EditAccountDialog::PasswordField);
+ if (passDialog.exec() == QDialog::Accepted)
+ {
+ password = passDialog.password();
+ }
+ else
+ {
+ tryagain = false;
+ }
+ break;
+ }
+ case AuthSession::PlayableOffline:
+ {
+ // we ask the user for a player name
+ bool ok = false;
+ QString usedname = session->player_name;
+ QString name = QInputDialog::getText(this, tr("Player name"),
+ tr("Choose your offline mode player name."),
+ QLineEdit::Normal, session->player_name, &ok);
+ if (!ok)
+ {
+ tryagain = false;
+ break;
+ }
+ if (name.length())
+ {
+ usedname = name;
+ }
+ session->MakeOffline(usedname);
+ // offline flavored game from here :3
+ }
+ case AuthSession::PlayableOnline:
+ {
+ // update first if the server actually responded
+ if (session->auth_server_online)
+ {
+ updateInstance(m_selectedInstance, session);
+ }
+ else
+ {
+ launchInstance(m_selectedInstance, session);
+ }
+ tryagain = false;
+ }
}
}
- return false;
}
-void MainWindow::updateInstance(BaseInstance *instance, MojangAccountPtr account)
+void MainWindow::updateInstance(BaseInstance *instance, AuthSessionPtr session)
{
- bool only_prepare = account->accountStatus() != Online;
- auto updateTask = instance->doUpdate(only_prepare);
+ auto updateTask = instance->doUpdate();
if (!updateTask)
{
- launchInstance(instance, account);
+ launchInstance(instance, session);
return;
}
ProgressDialog tDialog(this);
- connect(updateTask.get(), &Task::succeeded, [this, instance, account]
- { launchInstance(instance, account); });
+ connect(updateTask.get(), &Task::succeeded, [this, instance, session]
+ { launchInstance(instance, session); });
connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
tDialog.exec(updateTask.get());
}
-void MainWindow::launchInstance(BaseInstance *instance, MojangAccountPtr account)
+void MainWindow::launchInstance(BaseInstance *instance, AuthSessionPtr session)
{
Q_ASSERT_X(instance != NULL, "launchInstance", "instance is NULL");
- Q_ASSERT_X(account.get() != nullptr, "launchInstance", "account is NULL");
+ Q_ASSERT_X(session.get() != nullptr, "launchInstance", "session is NULL");
- proc = instance->prepareForLaunch(account);
+ proc = instance->prepareForLaunch(session);
if (!proc)
return;
@@ -1195,7 +1235,7 @@ void MainWindow::launchInstance(BaseInstance *instance, MojangAccountPtr account
console = new ConsoleWindow(proc);
connect(console, SIGNAL(isClosing()), this, SLOT(instanceEnded()));
- proc->setLogin(account);
+ proc->setLogin(session);
proc->launch();
}
@@ -1258,7 +1298,7 @@ void MainWindow::on_actionChangeInstMCVersion_triggered()
VersionSelectDialog vselect(m_selectedInstance->versionList().get(),
tr("Change Minecraft version"), this);
vselect.setFilter(1, "OneSix");
- if(!vselect.exec() || !vselect.selectedVersion())
+ if (!vselect.exec() || !vselect.selectedVersion())
return;
if (!MMC->accounts()->anyAccountIsValid())
@@ -1276,7 +1316,7 @@ void MainWindow::on_actionChangeInstMCVersion_triggered()
auto result = CustomMessageBox::selectable(
this, tr("Are you sure?"),
tr("This will remove any library/version customization you did previously. "
- "This includes things like Forge install and similar."),
+ "This includes things like Forge install and similar."),
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Abort,
QMessageBox::Abort)->exec();
@@ -1285,7 +1325,7 @@ void MainWindow::on_actionChangeInstMCVersion_triggered()
}
m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor());
- auto updateTask = m_selectedInstance->doUpdate(false);
+ auto updateTask = m_selectedInstance->doUpdate();
if (!updateTask)
{
return;
@@ -1384,7 +1424,7 @@ void MainWindow::instanceEnded()
void MainWindow::checkMigrateLegacyAssets()
{
int legacyAssets = AssetsUtils::findLegacyAssets();
- if(legacyAssets > 0)
+ if (legacyAssets > 0)
{
ProgressDialog migrateDlg(this);
AssetsMigrateTask migrateTask(legacyAssets, &migrateDlg);
diff --git a/gui/MainWindow.h b/gui/MainWindow.h
index eb478776..4d9e165d 100644
--- a/gui/MainWindow.h
+++ b/gui/MainWindow.h
@@ -27,9 +27,6 @@
class QToolButton;
class LabeledToolButton;
class QLabel;
-class InstanceProxyModel;
-class KCategorizedView;
-class KCategoryDrawer;
class MinecraftProcess;
class ConsoleWindow;
@@ -96,6 +93,8 @@ slots:
void on_actionLaunchInstance_triggered();
+ void on_actionLaunchInstanceOffline_triggered();
+
void on_actionDeleteInstance_triggered();
void on_actionRenameInstance_triggered();
@@ -112,25 +111,18 @@ slots:
* Launches the currently selected instance with the default account.
* If no default account is selected, prompts the user to pick an account.
*/
- void doLaunch();
-
- /*!
- * Opens an input dialog, allowing the user to input their password and refresh its access token.
- * This function will execute the proper Yggdrasil task to refresh the access token.
- * Returns true if successful. False if the user cancelled.
- */
- bool loginWithPassword(MojangAccountPtr account, const QString& errorMsg="");
+ void doLaunch(bool online = true);
/*!
* Launches the given instance with the given account.
* This function assumes that the given account has a valid, usable access token.
*/
- void launchInstance(BaseInstance* instance, MojangAccountPtr account);
+ void launchInstance(BaseInstance *instance, AuthSessionPtr session);
/*!
* Prepares the given instance for launch with the given account.
*/
- void updateInstance(BaseInstance* instance, MojangAccountPtr account);
+ void updateInstance(BaseInstance *instance, AuthSessionPtr account);
void onGameUpdateError(QString error);
@@ -190,8 +182,7 @@ protected:
private:
Ui::MainWindow *ui;
- KCategoryDrawer *drawer;
- KCategorizedView *view;
+ class GroupView *view;
InstanceProxyModel *proxymodel;
MinecraftProcess *proc;
ConsoleWindow *console;
diff --git a/gui/MainWindow.ui b/gui/MainWindow.ui
index 25af6f60..8cf26d18 100644
--- a/gui/MainWindow.ui
+++ b/gui/MainWindow.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>688</width>
- <height>460</height>
+ <width>694</width>
+ <height>563</height>
</rect>
</property>
<property name="windowTitle">
@@ -107,6 +107,7 @@
</attribute>
<addaction name="actionChangeInstIcon"/>
<addaction name="actionLaunchInstance"/>
+ <addaction name="actionLaunchInstanceOffline"/>
<addaction name="separator"/>
<addaction name="actionEditInstNotes"/>
<addaction name="actionChangeInstGroup"/>
@@ -152,7 +153,9 @@
</widget>
<action name="actionAddInstance">
<property name="icon">
- <iconset theme="new"/>
+ <iconset theme="new">
+ <normaloff/>
+ </iconset>
</property>
<property name="text">
<string>Add Instance</string>
@@ -166,7 +169,9 @@
</action>
<action name="actionViewInstanceFolder">
<property name="icon">
- <iconset theme="viewfolder"/>
+ <iconset theme="viewfolder">
+ <normaloff/>
+ </iconset>
</property>
<property name="text">
<string>View Instance Folder</string>
@@ -180,7 +185,9 @@
</action>
<action name="actionRefresh">
<property name="icon">
- <iconset theme="refresh"/>
+ <iconset theme="refresh">
+ <normaloff/>
+ </iconset>
</property>
<property name="text">
<string>Refresh</string>
@@ -194,7 +201,9 @@
</action>
<action name="actionViewCentralModsFolder">
<property name="icon">
- <iconset theme="centralmods"/>
+ <iconset theme="centralmods">
+ <normaloff/>
+ </iconset>
</property>
<property name="text">
<string>View Central Mods Folder</string>
@@ -208,7 +217,9 @@
</action>
<action name="actionCheckUpdate">
<property name="icon">
- <iconset theme="checkupdate"/>
+ <iconset theme="checkupdate">
+ <normaloff/>
+ </iconset>
</property>
<property name="text">
<string>Check for Updates</string>
@@ -222,7 +233,9 @@
</action>
<action name="actionSettings">
<property name="icon">
- <iconset theme="settings"/>
+ <iconset theme="settings">
+ <normaloff/>
+ </iconset>
</property>
<property name="text">
<string>Settings</string>
@@ -239,7 +252,9 @@
</action>
<action name="actionReportBug">
<property name="icon">
- <iconset theme="bug"/>
+ <iconset theme="bug">
+ <normaloff/>
+ </iconset>
</property>
<property name="text">
<string>Report a Bug</string>
@@ -253,7 +268,9 @@
</action>
<action name="actionMoreNews">
<property name="icon">
- <iconset theme="news"/>
+ <iconset theme="news">
+ <normaloff/>
+ </iconset>
</property>
<property name="text">
<string>More News</string>
@@ -270,7 +287,9 @@
</action>
<action name="actionAbout">
<property name="icon">
- <iconset theme="about"/>
+ <iconset theme="about">
+ <normaloff/>
+ </iconset>
</property>
<property name="text">
<string>About MultiMC</string>
@@ -463,7 +482,9 @@
<bool>true</bool>
</property>
<property name="icon">
- <iconset theme="cat"/>
+ <iconset theme="cat">
+ <normaloff/>
+ </iconset>
</property>
<property name="text">
<string>Meow</string>
@@ -474,7 +495,9 @@
</action>
<action name="actionCopyInstance">
<property name="icon">
- <iconset theme="copy"/>
+ <iconset theme="copy">
+ <normaloff/>
+ </iconset>
</property>
<property name="text">
<string>Copy Instance</string>
@@ -494,6 +517,17 @@
<string>Manage your Mojang or Minecraft accounts.</string>
</property>
</action>
+ <action name="actionLaunchInstanceOffline">
+ <property name="text">
+ <string>Play Offline</string>
+ </property>
+ <property name="toolTip">
+ <string>Launch the selected instance in offline mode.</string>
+ </property>
+ <property name="statusTip">
+ <string>Launch the selected instance.</string>
+ </property>
+ </action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>
diff --git a/gui/dialogs/AccountListDialog.cpp b/gui/dialogs/AccountListDialog.cpp
index 1712e352..a38035a6 100644
--- a/gui/dialogs/AccountListDialog.cpp
+++ b/gui/dialogs/AccountListDialog.cpp
@@ -126,7 +126,7 @@ void AccountListDialog::addAccount(const QString& errMsg)
MojangAccountPtr account = MojangAccount::createFromUsername(username);
ProgressDialog progDialog(this);
- auto task = account->login(password);
+ auto task = account->login(nullptr, password);
progDialog.exec(task.get());
if(task->successful())
{
diff --git a/gui/dialogs/IconPickerDialog.cpp b/gui/dialogs/IconPickerDialog.cpp
index f7970b37..9b1c26ff 100644
--- a/gui/dialogs/IconPickerDialog.cpp
+++ b/gui/dialogs/IconPickerDialog.cpp
@@ -23,7 +23,7 @@
#include "ui_IconPickerDialog.h"
#include "gui/Platform.h"
-#include "gui/widgets/InstanceDelegate.h"
+#include "gui/groupview/InstanceDelegate.h"
#include "logic/icons/IconList.h"
diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp
index 3982f17d..9e585de5 100644
--- a/gui/dialogs/OneSixModEditDialog.cpp
+++ b/gui/dialogs/OneSixModEditDialog.cpp
@@ -39,6 +39,18 @@
#include "logic/lists/ForgeVersionList.h"
#include "logic/ForgeInstaller.h"
#include "logic/LiteLoaderInstaller.h"
+#include "logic/OneSixVersionBuilder.h"
+
+template<typename A, typename B>
+QMap<A, B> invert(const QMap<B, A> &in)
+{
+ QMap<A, B> out;
+ for (auto it = in.begin(); it != in.end(); ++it)
+ {
+ out.insert(it.value(), it.key());
+ }
+ return out;
+}
OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent)
: QDialog(parent), ui(new Ui::OneSixModEditDialog), m_inst(inst)
@@ -55,7 +67,8 @@ OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent)
main_model->setSourceModel(m_version.get());
ui->libraryTreeView->setModel(main_model);
ui->libraryTreeView->installEventFilter(this);
- ui->mainClassEdit->setText(m_version->mainClass);
+ connect(ui->libraryTreeView->selectionModel(), &QItemSelectionModel::currentChanged,
+ this, &OneSixModEditDialog::versionCurrent);
updateVersionControls();
}
else
@@ -81,6 +94,8 @@ OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent)
ui->resPackTreeView->installEventFilter(this);
m_resourcepacks->startWatching();
}
+
+ connect(m_inst, &OneSixInstance::versionReloaded, this, &OneSixModEditDialog::updateVersionControls);
}
OneSixModEditDialog::~OneSixModEditDialog()
@@ -92,95 +107,138 @@ OneSixModEditDialog::~OneSixModEditDialog()
void OneSixModEditDialog::updateVersionControls()
{
- bool customVersion = m_inst->versionIsCustom();
- ui->customizeBtn->setEnabled(!customVersion);
- ui->revertBtn->setEnabled(customVersion);
ui->forgeBtn->setEnabled(true);
- ui->liteloaderBtn->setEnabled(LiteLoaderInstaller(m_inst->intendedVersionId()).canApply());
- ui->customEditorBtn->setEnabled(customVersion);
+ ui->liteloaderBtn->setEnabled(LiteLoaderInstaller().canApply(m_inst));
+ ui->mainClassEdit->setText(m_version->mainClass);
}
void OneSixModEditDialog::disableVersionControls()
{
- ui->customizeBtn->setEnabled(false);
- ui->revertBtn->setEnabled(false);
ui->forgeBtn->setEnabled(false);
ui->liteloaderBtn->setEnabled(false);
- ui->customEditorBtn->setEnabled(false);
+ ui->reloadLibrariesBtn->setEnabled(false);
+ ui->removeLibraryBtn->setEnabled(false);
+ ui->mainClassEdit->setText("");
}
-void OneSixModEditDialog::on_customizeBtn_clicked()
+void OneSixModEditDialog::on_reloadLibrariesBtn_clicked()
{
- if (m_inst->customizeVersion())
- {
- m_version = m_inst->getFullVersion();
- main_model->setSourceModel(m_version.get());
- updateVersionControls();
- }
+ m_inst->reloadVersion(this);
}
-void OneSixModEditDialog::on_revertBtn_clicked()
+void OneSixModEditDialog::on_removeLibraryBtn_clicked()
{
- auto response = CustomMessageBox::selectable(
- this, tr("Revert?"), tr("Do you want to revert the "
- "version of this instance to its original configuration?"),
- QMessageBox::Question, QMessageBox::Yes | QMessageBox::No)->exec();
- if (response == QMessageBox::Yes)
+ if (ui->libraryTreeView->currentIndex().isValid())
{
- if (m_inst->revertCustomVersion())
+ if (!m_version->remove(ui->libraryTreeView->currentIndex().row()))
+ {
+ QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file"));
+ }
+ else
{
- m_version = m_inst->getFullVersion();
- main_model->setSourceModel(m_version.get());
- updateVersionControls();
+ m_inst->reloadVersion(this);
}
}
}
-void OneSixModEditDialog::on_customEditorBtn_clicked()
+void OneSixModEditDialog::on_resetLibraryOrderBtn_clicked()
{
- if (m_inst->versionIsCustom())
+ QDir(m_inst->instanceRoot()).remove("order.json");
+ m_inst->reloadVersion(this);
+}
+void OneSixModEditDialog::on_moveLibraryUpBtn_clicked()
+{
+
+ QMap<QString, int> order = getExistingOrder();
+ if (order.size() < 2 || ui->libraryTreeView->selectionModel()->selectedIndexes().isEmpty())
{
- if (!MMC->openJsonEditor(m_inst->instanceRoot() + "/custom.json"))
- {
- QMessageBox::warning(this, tr("Error"), tr("Unable to open custom.json, check the settings"));
- }
+ return;
+ }
+ const int ourRow = ui->libraryTreeView->selectionModel()->selectedIndexes().first().row();
+ const QString ourId = m_version->versionFileId(ourRow);
+ const int ourOrder = order[ourId];
+ if (ourId.isNull() || ourId.startsWith("org.multimc."))
+ {
+ return;
+ }
+
+ QMap<int, QString> sortedOrder = invert(order);
+
+ QList<int> sortedOrders = sortedOrder.keys();
+ const int ourIndex = sortedOrders.indexOf(ourOrder);
+ if (ourIndex <= 0)
+ {
+ return;
+ }
+ const int ourNewOrder = sortedOrders.at(ourIndex - 1);
+ order[ourId] = ourNewOrder;
+ order[sortedOrder[sortedOrders[ourIndex - 1]]] = ourOrder;
+
+ if (!OneSixVersionBuilder::writeOverrideOrders(order, m_inst))
+ {
+ QMessageBox::critical(this, tr("Error"), tr("Couldn't save the new order"));
+ }
+ else
+ {
+ m_inst->reloadVersion(this);
+ ui->libraryTreeView->selectionModel()->select(m_version->index(ourRow - 1), QItemSelectionModel::SelectCurrent);
+ }
+}
+void OneSixModEditDialog::on_moveLibraryDownBtn_clicked()
+{
+ QMap<QString, int> order = getExistingOrder();
+ if (order.size() < 2 || ui->libraryTreeView->selectionModel()->selectedIndexes().isEmpty())
+ {
+ return;
+ }
+ const int ourRow = ui->libraryTreeView->selectionModel()->selectedIndexes().first().row();
+ const QString ourId = m_version->versionFileId(ourRow);
+ const int ourOrder = order[ourId];
+ if (ourId.isNull() || ourId.startsWith("org.multimc."))
+ {
+ return;
+ }
+
+ QMap<int, QString> sortedOrder = invert(order);
+
+ QList<int> sortedOrders = sortedOrder.keys();
+ const int ourIndex = sortedOrders.indexOf(ourOrder);
+ if ((ourIndex + 1) >= sortedOrders.size())
+ {
+ return;
+ }
+ const int ourNewOrder = sortedOrders.at(ourIndex + 1);
+ order[ourId] = ourNewOrder;
+ order[sortedOrder[sortedOrders[ourIndex + 1]]] = ourOrder;
+
+ if (!OneSixVersionBuilder::writeOverrideOrders(order, m_inst))
+ {
+ QMessageBox::critical(this, tr("Error"), tr("Couldn't save the new order"));
+ }
+ else
+ {
+ m_inst->reloadVersion(this);
+ ui->libraryTreeView->selectionModel()->select(m_version->index(ourRow + 1), QItemSelectionModel::SelectCurrent);
}
}
void OneSixModEditDialog::on_forgeBtn_clicked()
{
+ if (QDir(m_inst->instanceRoot()).exists("custom.json"))
+ {
+ if (QMessageBox::question(this, tr("Revert?"), tr("This action will remove your custom.json. Continue?")) != QMessageBox::Yes)
+ {
+ return;
+ }
+ QDir(m_inst->instanceRoot()).remove("custom.json");
+ m_inst->reloadVersion(this);
+ }
VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this);
vselect.setFilter(1, m_inst->currentVersionId());
+ vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") +
+ m_inst->currentVersionId());
if (vselect.exec() && vselect.selectedVersion())
{
- if (m_inst->versionIsCustom())
- {
- auto reply = QMessageBox::question(
- this, tr("Revert?"),
- tr("This will revert any "
- "changes you did to the version up to this point. Is that "
- "OK?"),
- QMessageBox::Yes | QMessageBox::No);
- if (reply == QMessageBox::Yes)
- {
- m_inst->revertCustomVersion();
- m_inst->customizeVersion();
- {
- m_version = m_inst->getFullVersion();
- main_model->setSourceModel(m_version.get());
- updateVersionControls();
- }
- }
- else
- return;
- }
- else
- {
- m_inst->customizeVersion();
- m_version = m_inst->getFullVersion();
- main_model->setSourceModel(m_version.get());
- updateVersionControls();
- }
ForgeVersionPtr forgeVersion =
std::dynamic_pointer_cast<ForgeVersion>(vselect.selectedVersion());
if (!forgeVersion)
@@ -197,9 +255,9 @@ void OneSixModEditDialog::on_forgeBtn_clicked()
// install
QString forgePath = entry->getFullPath();
ForgeInstaller forge(forgePath, forgeVersion->universal_url);
- if (!forge.apply(m_version))
+ if (!forge.add(m_inst))
{
- // failure notice
+ QLOG_ERROR() << "Failure installing forge";
}
}
else
@@ -212,18 +270,28 @@ void OneSixModEditDialog::on_forgeBtn_clicked()
// install
QString forgePath = entry->getFullPath();
ForgeInstaller forge(forgePath, forgeVersion->universal_url);
- if (!forge.apply(m_version))
+ if (!forge.add(m_inst))
{
- // failure notice
+ QLOG_ERROR() << "Failure installing forge";
}
}
}
+ m_inst->reloadVersion(this);
}
void OneSixModEditDialog::on_liteloaderBtn_clicked()
{
- LiteLoaderInstaller liteloader(m_inst->intendedVersionId());
- if (!liteloader.canApply())
+ if (QDir(m_inst->instanceRoot()).exists("custom.json"))
+ {
+ if (QMessageBox::question(this, tr("Revert?"), tr("This action will remove your custom.json. Continue?")) != QMessageBox::Yes)
+ {
+ return;
+ }
+ QDir(m_inst->instanceRoot()).remove("custom.json");
+ m_inst->reloadVersion(this);
+ }
+ LiteLoaderInstaller liteloader;
+ if (!liteloader.canApply(m_inst))
{
QMessageBox::critical(
this, tr("LiteLoader"),
@@ -231,18 +299,15 @@ void OneSixModEditDialog::on_liteloaderBtn_clicked()
"into this version of Minecraft"));
return;
}
- if (!m_inst->versionIsCustom())
+ if (!liteloader.add(m_inst))
{
- m_inst->customizeVersion();
- m_version = m_inst->getFullVersion();
- main_model->setSourceModel(m_version.get());
- updateVersionControls();
+ QMessageBox::critical(this, tr("LiteLoader"),
+ tr("For reasons unknown, the LiteLoader installation failed. "
+ "Check your MultiMC log files for details."));
}
- if (!liteloader.apply(m_version))
+ else
{
- QMessageBox::critical(
- this, tr("LiteLoader"),
- tr("For reasons unknown, the LiteLoader installation failed. Check your MultiMC log files for details."));
+ m_inst->reloadVersion(this);
}
}
@@ -278,6 +343,35 @@ bool OneSixModEditDialog::resourcePackListFilter(QKeyEvent *keyEvent)
return QDialog::eventFilter(ui->resPackTreeView, keyEvent);
}
+QMap<QString, int> OneSixModEditDialog::getExistingOrder() const
+{
+
+ QMap<QString, int> order;
+ // default
+ {
+ for (OneSixVersion::VersionFile file : m_version->versionFiles)
+ {
+ if (file.id.startsWith("org.multimc."))
+ {
+ continue;
+ }
+ order.insert(file.id, file.order);
+ }
+ }
+ // overriden
+ {
+ QMap<QString, int> overridenOrder = OneSixVersionBuilder::readOverrideOrders(m_inst);
+ for (auto id : order.keys())
+ {
+ if (overridenOrder.contains(id))
+ {
+ order[id] = overridenOrder[id];
+ }
+ }
+ }
+ return order;
+}
+
bool OneSixModEditDialog::eventFilter(QObject *obj, QEvent *ev)
{
if (ev->type() != QEvent::KeyPress)
@@ -362,3 +456,15 @@ void OneSixModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previou
Mod &m = m_mods->operator[](row);
ui->frame->updateWithMod(m);
}
+
+void OneSixModEditDialog::versionCurrent(const QModelIndex &current, const QModelIndex &previous)
+{
+ if (!current.isValid())
+ {
+ ui->removeLibraryBtn->setDisabled(true);
+ }
+ else
+ {
+ ui->removeLibraryBtn->setEnabled(m_version->canRemove(current.row()));
+ }
+}
diff --git a/gui/dialogs/OneSixModEditDialog.h b/gui/dialogs/OneSixModEditDialog.h
index 2510c59c..f44b336b 100644
--- a/gui/dialogs/OneSixModEditDialog.h
+++ b/gui/dialogs/OneSixModEditDialog.h
@@ -45,9 +45,11 @@ slots:
void on_buttonBox_rejected();
void on_forgeBtn_clicked();
void on_liteloaderBtn_clicked();
- void on_customizeBtn_clicked();
- void on_revertBtn_clicked();
- void on_customEditorBtn_clicked();
+ void on_reloadLibrariesBtn_clicked();
+ void on_removeLibraryBtn_clicked();
+ void on_resetLibraryOrderBtn_clicked();
+ void on_moveLibraryUpBtn_clicked();
+ void on_moveLibraryDownBtn_clicked();
void updateVersionControls();
void disableVersionControls();
@@ -63,7 +65,11 @@ private:
std::shared_ptr<ModList> m_resourcepacks;
EnabledItemFilter *main_model;
OneSixInstance *m_inst;
+
+ QMap<QString, int> getExistingOrder() const;
+
public
slots:
void loaderCurrent(QModelIndex current, QModelIndex previous);
+ void versionCurrent(const QModelIndex &current, const QModelIndex &previous);
};
diff --git a/gui/dialogs/OneSixModEditDialog.ui b/gui/dialogs/OneSixModEditDialog.ui
index 899e0cbf..eaf8f7fd 100644
--- a/gui/dialogs/OneSixModEditDialog.ui
+++ b/gui/dialogs/OneSixModEditDialog.ui
@@ -26,7 +26,7 @@
</size>
</property>
<property name="currentIndex">
- <number>1</number>
+ <number>0</number>
</property>
<widget class="QWidget" name="libTab">
<attribute name="title">
@@ -43,6 +43,9 @@
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
</widget>
</item>
<item>
@@ -85,61 +88,30 @@
</widget>
</item>
<item>
- <widget class="QPushButton" name="customizeBtn">
- <property name="toolTip">
- <string>Create an customized copy of the base version</string>
- </property>
- <property name="text">
- <string>Customize</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="revertBtn">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="toolTip">
- <string>Revert to original base version</string>
- </property>
- <property name="text">
- <string>Revert</string>
- </property>
- </widget>
- </item>
- <item>
<widget class="Line" name="line">
- <property name="frameShadow">
- <enum>QFrame::Sunken</enum>
- </property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
- <widget class="QPushButton" name="addLibraryBtn">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="toolTip">
- <string>Add new libraries</string>
- </property>
+ <widget class="QPushButton" name="reloadLibrariesBtn">
<property name="text">
- <string>&amp;Add</string>
+ <string>Reload</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeLibraryBtn">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="toolTip">
- <string>Remove selected libraries</string>
+ <property name="text">
+ <string>Remove</string>
</property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="resetLibraryOrderBtn">
<property name="text">
- <string>&amp;Remove</string>
+ <string>Reset order</string>
</property>
</widget>
</item>
@@ -151,9 +123,16 @@
</widget>
</item>
<item>
- <widget class="QPushButton" name="customEditorBtn">
+ <widget class="QPushButton" name="moveLibraryUpBtn">
+ <property name="text">
+ <string>Move up</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="moveLibraryDownBtn">
<property name="text">
- <string>Open custom.json</string>
+ <string>Move down</string>
</property>
</widget>
</item>
diff --git a/gui/dialogs/VersionSelectDialog.cpp b/gui/dialogs/VersionSelectDialog.cpp
index d6efe3c0..0f379f56 100644
--- a/gui/dialogs/VersionSelectDialog.cpp
+++ b/gui/dialogs/VersionSelectDialog.cpp
@@ -51,6 +51,11 @@ VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title,
}
}
+void VersionSelectDialog::setEmptyString(QString emptyString)
+{
+ ui->listView->setEmptyString(emptyString);
+}
+
VersionSelectDialog::~VersionSelectDialog()
{
delete ui;
diff --git a/gui/dialogs/VersionSelectDialog.h b/gui/dialogs/VersionSelectDialog.h
index e36341db..61fa8ab6 100644
--- a/gui/dialogs/VersionSelectDialog.h
+++ b/gui/dialogs/VersionSelectDialog.h
@@ -44,6 +44,7 @@ public:
BaseVersionPtr selectedVersion() const;
void setFilter(int column, QString filter);
+ void setEmptyString(QString emptyString);
void setResizeOn(int column);
private
diff --git a/gui/dialogs/VersionSelectDialog.ui b/gui/dialogs/VersionSelectDialog.ui
index 58264f24..07e9e73e 100644
--- a/gui/dialogs/VersionSelectDialog.ui
+++ b/gui/dialogs/VersionSelectDialog.ui
@@ -15,7 +15,7 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="QTreeView" name="listView">
+ <widget class="VersionListView" name="listView">
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
@@ -65,6 +65,13 @@
</item>
</layout>
</widget>
+ <customwidgets>
+ <customwidget>
+ <class>VersionListView</class>
+ <extends>QTreeView</extends>
+ <header>gui/widgets/VersionListView.h</header>
+ </customwidget>
+ </customwidgets>
<resources/>
<connections>
<connection>
diff --git a/gui/groupview/Group.cpp b/gui/groupview/Group.cpp
new file mode 100644
index 00000000..51aa6658
--- /dev/null
+++ b/gui/groupview/Group.cpp
@@ -0,0 +1,269 @@
+#include "Group.h"
+
+#include <QModelIndex>
+#include <QPainter>
+#include <QtMath>
+#include <QApplication>
+
+#include "GroupView.h"
+
+Group::Group(const QString &text, GroupView *view) : view(view), text(text), collapsed(false)
+{
+}
+
+Group::Group(const Group *other)
+ : view(other->view), text(other->text), collapsed(other->collapsed)
+{
+}
+
+void Group::update()
+{
+ firstItemIndex = firstItem().row();
+
+ rowHeights = QVector<int>(numRows());
+ for (int i = 0; i < numRows(); ++i)
+ {
+ rowHeights[i] = view->categoryRowHeight(
+ view->model()->index(i * view->itemsPerRow() + firstItemIndex, 0));
+ }
+}
+
+Group::HitResults Group::hitScan(const QPoint &pos) const
+{
+ Group::HitResults results = Group::NoHit;
+ int y_start = verticalPosition();
+ int body_start = y_start + headerHeight();
+ int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5?
+ int y = pos.y();
+ // int x = pos.x();
+ if (y < y_start)
+ {
+ results = Group::NoHit;
+ }
+ else if (y < body_start)
+ {
+ results = Group::HeaderHit;
+ int collapseSize = headerHeight() - 4;
+
+ // the icon
+ QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize);
+ if (iconRect.contains(pos))
+ {
+ results |= Group::CheckboxHit;
+ }
+ }
+ else if (y < body_end)
+ {
+ results |= Group::BodyHit;
+ }
+ return results;
+}
+
+void Group::drawHeader(QPainter *painter, const QStyleOptionViewItem &option)
+{
+ painter->setRenderHint(QPainter::Antialiasing);
+
+ const QRect optRect = option.rect;
+ QFont font(QApplication::font());
+ font.setBold(true);
+ const QFontMetrics fontMetrics = QFontMetrics(font);
+
+ QColor outlineColor = option.palette.text().color();
+ outlineColor.setAlphaF(0.35);
+
+ //BEGIN: top left corner
+ {
+ painter->save();
+ painter->setPen(outlineColor);
+ const QPointF topLeft(optRect.topLeft());
+ QRectF arc(topLeft, QSizeF(4, 4));
+ arc.translate(0.5, 0.5);
+ painter->drawArc(arc, 1440, 1440);
+ painter->restore();
+ }
+ //END: top left corner
+
+ //BEGIN: left vertical line
+ {
+ QPoint start(optRect.topLeft());
+ start.ry() += 3;
+ QPoint verticalGradBottom(optRect.topLeft());
+ verticalGradBottom.ry() += fontMetrics.height() + 5;
+ QLinearGradient gradient(start, verticalGradBottom);
+ gradient.setColorAt(0, outlineColor);
+ gradient.setColorAt(1, Qt::transparent);
+ painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient);
+ }
+ //END: left vertical line
+
+ //BEGIN: horizontal line
+ {
+ QPoint start(optRect.topLeft());
+ start.rx() += 3;
+ QPoint horizontalGradTop(optRect.topLeft());
+ horizontalGradTop.rx() += optRect.width() - 6;
+ painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor);
+ }
+ //END: horizontal line
+
+ //BEGIN: top right corner
+ {
+ painter->save();
+ painter->setPen(outlineColor);
+ QPointF topRight(optRect.topRight());
+ topRight.rx() -= 4;
+ QRectF arc(topRight, QSizeF(4, 4));
+ arc.translate(0.5, 0.5);
+ painter->drawArc(arc, 0, 1440);
+ painter->restore();
+ }
+ //END: top right corner
+
+ //BEGIN: right vertical line
+ {
+ QPoint start(optRect.topRight());
+ start.ry() += 3;
+ QPoint verticalGradBottom(optRect.topRight());
+ verticalGradBottom.ry() += fontMetrics.height() + 5;
+ QLinearGradient gradient(start, verticalGradBottom);
+ gradient.setColorAt(0, outlineColor);
+ gradient.setColorAt(1, Qt::transparent);
+ painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient);
+ }
+ //END: right vertical line
+
+ //BEGIN: checkboxy thing
+ {
+ painter->save();
+ painter->setRenderHint(QPainter::Antialiasing, false);
+ painter->setFont(font);
+ QColor penColor(option.palette.text().color());
+ penColor.setAlphaF(0.6);
+ painter->setPen(penColor);
+ QRect iconSubRect(option.rect);
+ iconSubRect.setTop(iconSubRect.top() + 7);
+ iconSubRect.setLeft(iconSubRect.left() + 7);
+
+ int sizing = fontMetrics.height();
+ int even = ( (sizing - 1) % 2 );
+
+ iconSubRect.setHeight(sizing - even);
+ iconSubRect.setWidth(sizing - even);
+ painter->drawRect(iconSubRect);
+
+
+ /*
+ if(collapsed)
+ painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "+");
+ else
+ painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "-");
+ */
+ painter->setBrush(option.palette.text());
+ painter->fillRect(iconSubRect.x(), iconSubRect.y() + iconSubRect.height() / 2,
+ iconSubRect.width(), 2, penColor);
+ if (collapsed)
+ {
+ painter->fillRect(iconSubRect.x() + iconSubRect.width() / 2, iconSubRect.y(), 2,
+ iconSubRect.height(), penColor);
+ }
+
+ painter->restore();
+ }
+ //END: checkboxy thing
+
+ //BEGIN: text
+ {
+ QRect textRect(option.rect);
+ textRect.setTop(textRect.top() + 7);
+ textRect.setLeft(textRect.left() + 7 + fontMetrics.height() + 7);
+ textRect.setHeight(fontMetrics.height());
+ textRect.setRight(textRect.right() - 7);
+
+ painter->save();
+ painter->setFont(font);
+ QColor penColor(option.palette.text().color());
+ penColor.setAlphaF(0.6);
+ painter->setPen(penColor);
+ painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text);
+ painter->restore();
+ }
+ //END: text
+}
+
+int Group::totalHeight() const
+{
+ return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'?
+}
+
+int Group::headerHeight() const
+{
+ QFont font(QApplication::font());
+ font.setBold(true);
+ QFontMetrics fontMetrics(font);
+
+ const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */
+ + 11 /* top and bottom separation */;
+ return height;
+ /*
+ int raw = view->viewport()->fontMetrics().height() + 4;
+ // add english. maybe. depends on font height.
+ if (raw % 2 == 0)
+ raw++;
+ return std::min(raw, 25);
+ */
+}
+
+int Group::contentHeight() const
+{
+ if (collapsed)
+ {
+ return 0;
+ }
+ int result = 0;
+ for (int i = 0; i < rowHeights.size(); ++i)
+ {
+ result += rowHeights[i];
+ }
+ return result;
+}
+
+int Group::numRows() const
+{
+ return qMax(1, qCeil((qreal)numItems() / (qreal)view->itemsPerRow()));
+}
+
+int Group::verticalPosition() const
+{
+ return m_verticalPosition;
+}
+
+QList<QModelIndex> Group::items() const
+{
+ QList<QModelIndex> indices;
+ for (int i = 0; i < view->model()->rowCount(); ++i)
+ {
+ const QModelIndex index = view->model()->index(i, 0);
+ if (index.data(GroupViewRoles::GroupRole).toString() == text)
+ {
+ indices.append(index);
+ }
+ }
+ return indices;
+}
+
+int Group::numItems() const
+{
+ return items().size();
+}
+
+QModelIndex Group::firstItem() const
+{
+ QList<QModelIndex> indices = items();
+ return indices.isEmpty() ? QModelIndex() : indices.first();
+}
+
+QModelIndex Group::lastItem() const
+{
+ QList<QModelIndex> indices = items();
+ return indices.isEmpty() ? QModelIndex() : indices.last();
+}
diff --git a/gui/groupview/Group.h b/gui/groupview/Group.h
new file mode 100644
index 00000000..3b797f4c
--- /dev/null
+++ b/gui/groupview/Group.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <QString>
+#include <QRect>
+#include <QVector>
+#include <QStyleOption>
+
+class GroupView;
+class QPainter;
+class QModelIndex;
+
+struct Group
+{
+/* constructors */
+ Group(const QString &text, GroupView *view);
+ Group(const Group *other);
+
+/* data */
+ GroupView *view = nullptr;
+ QString text;
+ bool collapsed = false;
+ QVector<int> rowHeights;
+ int firstItemIndex = 0;
+ int m_verticalPosition = 0;
+
+/* logic */
+ /// do stuff. and things. TODO: redo.
+ void update();
+
+ /// draw the header at y-position.
+ void drawHeader(QPainter *painter, const QStyleOptionViewItem &option);
+
+ /// height of the group, in total. includes a small bit of padding.
+ int totalHeight() const;
+
+ /// height of the group header, in pixels
+ int headerHeight() const;
+
+ /// height of the group content, in pixels
+ int contentHeight() const;
+
+ /// the number of visual rows this group has
+ int numRows() const;
+
+ /// the height at which this group starts, in pixels
+ int verticalPosition() const;
+
+ enum HitResult
+ {
+ NoHit = 0x0,
+ TextHit = 0x1,
+ CheckboxHit = 0x2,
+ HeaderHit = 0x4,
+ BodyHit = 0x8
+ };
+ Q_DECLARE_FLAGS(HitResults, HitResult)
+
+ /// shoot! BANG! what did we hit?
+ HitResults hitScan (const QPoint &pos) const;
+
+ /// super derpy thing.
+ QList<QModelIndex> items() const;
+ /// I don't even
+ int numItems() const;
+ QModelIndex firstItem() const;
+ QModelIndex lastItem() const;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(Group::HitResults)
diff --git a/gui/groupview/GroupView.cpp b/gui/groupview/GroupView.cpp
new file mode 100644
index 00000000..25042d02
--- /dev/null
+++ b/gui/groupview/GroupView.cpp
@@ -0,0 +1,931 @@
+#include "GroupView.h"
+
+#include <QPainter>
+#include <QApplication>
+#include <QtMath>
+#include <QDebug>
+#include <QMouseEvent>
+#include <QListView>
+#include <QPersistentModelIndex>
+#include <QDrag>
+#include <QMimeData>
+#include <QScrollBar>
+
+#include "Group.h"
+
+template <typename T> bool listsIntersect(const QList<T> &l1, const QList<T> t2)
+{
+ for (auto &item : l1)
+ {
+ if (t2.contains(item))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+GroupView::GroupView(QWidget *parent)
+ : QAbstractItemView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5),
+ m_categoryMargin(5) //, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0)
+{
+ // setViewMode(IconMode);
+ // setMovement(Snap);
+ setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ // setWordWrap(true);
+ // setDragDropMode(QListView::InternalMove);
+ setAcceptDrops(true);
+ m_spacing = 5;
+}
+
+GroupView::~GroupView()
+{
+ qDeleteAll(m_groups);
+ m_groups.clear();
+}
+
+void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
+ const QVector<int> &roles)
+{
+ scheduleDelayedItemsLayout();
+}
+void GroupView::rowsInserted(const QModelIndex &parent, int start, int end)
+{
+ scheduleDelayedItemsLayout();
+}
+
+void GroupView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
+{
+ scheduleDelayedItemsLayout();
+}
+
+void GroupView::updateGeometries()
+{
+ int previousScroll = verticalScrollBar()->value();
+
+ QMap<QString, Group *> cats;
+
+ for (int i = 0; i < model()->rowCount(); ++i)
+ {
+ const QString groupName =
+ model()->index(i, 0).data(GroupViewRoles::GroupRole).toString();
+ if (!cats.contains(groupName))
+ {
+ Group *old = this->category(groupName);
+ if (old)
+ {
+ cats.insert(groupName, new Group(old));
+ }
+ else
+ {
+ cats.insert(groupName, new Group(groupName, this));
+ }
+ }
+ }
+
+ /*if (m_editedCategory)
+ {
+ m_editedCategory = cats[m_editedCategory->text];
+ }*/
+
+ qDeleteAll(m_groups);
+ m_groups = cats.values();
+
+ for (auto cat : m_groups)
+ {
+ cat->update();
+ }
+
+ if (m_groups.isEmpty())
+ {
+ verticalScrollBar()->setRange(0, 0);
+ }
+ else
+ {
+ int totalHeight = 0;
+ // top margin
+ totalHeight += m_categoryMargin;
+ int itemScroll = 0;
+ for (auto category : m_groups)
+ {
+ category->m_verticalPosition = totalHeight;
+ totalHeight += category->totalHeight() + m_categoryMargin;
+ if(!itemScroll && category->totalHeight() != 0)
+ {
+ itemScroll = category->contentHeight() / category->numRows();
+ }
+ }
+ // do not divide by zero
+ if(itemScroll == 0)
+ itemScroll = 64;
+
+ totalHeight += m_bottomMargin;
+ verticalScrollBar()->setSingleStep ( itemScroll );
+ const int rowsPerPage = qMax ( viewport()->height() / itemScroll, 1 );
+ verticalScrollBar()->setPageStep ( rowsPerPage * itemScroll );
+
+ verticalScrollBar()->setRange(0, totalHeight - height());
+ }
+
+ verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum()));
+
+ viewport()->update();
+}
+
+bool GroupView::isIndexHidden(const QModelIndex &index) const
+{
+ Group *cat = category(index);
+ if (cat)
+ {
+ return cat->collapsed;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+Group *GroupView::category(const QModelIndex &index) const
+{
+ return category(index.data(GroupViewRoles::GroupRole).toString());
+}
+
+Group *GroupView::category(const QString &cat) const
+{
+ for (auto group : m_groups)
+ {
+ if (group->text == cat)
+ {
+ return group;
+ }
+ }
+ return nullptr;
+}
+
+Group *GroupView::categoryAt(const QPoint &pos) const
+{
+ for (auto group : m_groups)
+ {
+ if(group->hitScan(pos) & Group::CheckboxHit)
+ {
+ return group;
+ }
+ }
+ return nullptr;
+}
+
+int GroupView::itemsPerRow() const
+{
+ return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing));
+}
+
+int GroupView::contentWidth() const
+{
+ return width() - m_leftMargin - m_rightMargin;
+}
+
+int GroupView::itemWidth() const
+{
+ return itemDelegate()
+ ->sizeHint(viewOptions(), model()->index(model()->rowCount() - 1, 0))
+ .width();
+}
+
+int GroupView::categoryRowHeight(const QModelIndex &index) const
+{
+ QModelIndexList indices;
+ int internalRow = categoryInternalPosition(index).second;
+ for (auto &i : category(index)->items())
+ {
+ if (categoryInternalPosition(i).second == internalRow)
+ {
+ indices.append(i);
+ }
+ }
+
+ int largestHeight = 0;
+ for (auto &i : indices)
+ {
+ largestHeight =
+ qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height());
+ }
+ return largestHeight + m_spacing;
+}
+
+QPair<int, int> GroupView::categoryInternalPosition(const QModelIndex &index) const
+{
+ QList<QModelIndex> indices = category(index)->items();
+ int x = 0;
+ int y = 0;
+ const int perRow = itemsPerRow();
+ for (int i = 0; i < indices.size(); ++i)
+ {
+ if (indices.at(i) == index)
+ {
+ break;
+ }
+ ++x;
+ if (x == perRow)
+ {
+ x = 0;
+ ++y;
+ }
+ }
+ return qMakePair(x, y);
+}
+
+int GroupView::categoryInternalRowTop(const QModelIndex &index) const
+{
+ Group *cat = category(index);
+ int categoryInternalRow = categoryInternalPosition(index).second;
+ int result = 0;
+ for (int i = 0; i < categoryInternalRow; ++i)
+ {
+ result += cat->rowHeights.at(i);
+ }
+ return result;
+}
+
+int GroupView::itemHeightForCategoryRow(const Group *category, const int internalRow) const
+{
+ for (auto &i : category->items())
+ {
+ QPair<int, int> pos = categoryInternalPosition(i);
+ if (pos.second == internalRow)
+ {
+ return categoryRowHeight(i);
+ }
+ }
+ return -1;
+}
+
+void GroupView::mousePressEvent(QMouseEvent *event)
+{
+ // endCategoryEditor();
+
+ QPoint visualPos = event->pos();
+ QPoint geometryPos = event->pos() + offset();
+
+ QPersistentModelIndex index = indexAt(visualPos);
+
+ m_pressedIndex = index;
+ m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex);
+ QItemSelectionModel::SelectionFlags selection_flags = selectionCommand(index, event);
+ m_pressedPosition = geometryPos;
+
+ m_pressedCategory = categoryAt(geometryPos);
+ if (m_pressedCategory)
+ {
+ setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState);
+ event->accept();
+ return;
+ }
+
+ if (index.isValid() && (index.flags() & Qt::ItemIsEnabled))
+ {
+ // we disable scrollTo for mouse press so the item doesn't change position
+ // when the user is interacting with it (ie. clicking on it)
+ bool autoScroll = hasAutoScroll();
+ setAutoScroll(false);
+ selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
+ setAutoScroll(autoScroll);
+ QRect rect(geometryPos, geometryPos);
+ setSelection(rect, QItemSelectionModel::ClearAndSelect);
+
+ // signal handlers may change the model
+ emit pressed(index);
+ }
+ else
+ {
+ // Forces a finalize() even if mouse is pressed, but not on a item
+ selectionModel()->select(QModelIndex(), QItemSelectionModel::Select);
+ }
+}
+
+void GroupView::mouseMoveEvent(QMouseEvent *event)
+{
+ QPoint topLeft;
+ QPoint visualPos = event->pos();
+ QPoint geometryPos = event->pos() + offset();
+
+ if (state() == ExpandingState || state() == CollapsingState)
+ {
+ return;
+ }
+
+ if (state() == DraggingState)
+ {
+ topLeft = m_pressedPosition - offset();
+ if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance())
+ {
+ m_pressedIndex = QModelIndex();
+ startDrag(model()->supportedDragActions());
+ setState(NoState);
+ stopAutoScroll();
+ }
+ return;
+ }
+
+ if (selectionMode() != SingleSelection)
+ {
+ topLeft = m_pressedPosition - offset();
+ }
+ else
+ {
+ topLeft = geometryPos;
+ }
+
+ if (m_pressedIndex.isValid() && (state() != DragSelectingState) &&
+ (event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty())
+ {
+ setState(DraggingState);
+ return;
+ }
+
+ if ((event->buttons() & Qt::LeftButton) && selectionModel())
+ {
+ setState(DragSelectingState);
+
+ setSelection(QRect(geometryPos, geometryPos), QItemSelectionModel::ClearAndSelect);
+ QModelIndex index = indexAt(visualPos);
+
+ // set at the end because it might scroll the view
+ if (index.isValid() && (index != selectionModel()->currentIndex()) &&
+ (index.flags() & Qt::ItemIsEnabled))
+ {
+ selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
+ }
+ }
+}
+
+void GroupView::mouseReleaseEvent(QMouseEvent *event)
+{
+ QPoint visualPos = event->pos();
+ QPoint geometryPos = event->pos() + offset();
+ QPersistentModelIndex index = indexAt(visualPos);
+
+ bool click = (index == m_pressedIndex && index.isValid()) ||
+ (m_pressedCategory && m_pressedCategory == categoryAt(geometryPos));
+
+ if (click && m_pressedCategory)
+ {
+ if (state() == ExpandingState)
+ {
+ m_pressedCategory->collapsed = false;
+ updateGeometries();
+ viewport()->update();
+ event->accept();
+ return;
+ }
+ else if (state() == CollapsingState)
+ {
+ m_pressedCategory->collapsed = true;
+ updateGeometries();
+ viewport()->update();
+ event->accept();
+ return;
+ }
+ }
+
+ m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate;
+
+ setState(NoState);
+
+ if (click)
+ {
+ if (event->button() == Qt::LeftButton)
+ {
+ emit clicked(index);
+ }
+ QStyleOptionViewItem option = viewOptions();
+ if (m_pressedAlreadySelected)
+ {
+ option.state |= QStyle::State_Selected;
+ }
+ if ((model()->flags(index) & Qt::ItemIsEnabled) &&
+ style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this))
+ {
+ emit activated(index);
+ }
+ }
+}
+
+void GroupView::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ QModelIndex index = indexAt(event->pos());
+ if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index))
+ {
+ QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(),
+ event->screenPos(), event->button(), event->buttons(),
+ event->modifiers());
+ mousePressEvent(&me);
+ return;
+ }
+ // signal handlers may change the model
+ QPersistentModelIndex persistent = index;
+ emit doubleClicked(persistent);
+}
+
+void GroupView::paintEvent(QPaintEvent *event)
+{
+ QPainter painter(this->viewport());
+
+ QStyleOptionViewItemV4 option(viewOptions());
+ option.widget = this;
+
+ int wpWidth = viewport()->width();
+ option.rect.setWidth(wpWidth);
+ for (int i = 0; i < m_groups.size(); ++i)
+ {
+ Group *category = m_groups.at(i);
+ int y = category->verticalPosition();
+ y -= verticalOffset();
+ QRect backup = option.rect;
+ int height = category->totalHeight();
+ option.rect.setTop(y);
+ option.rect.setHeight(height);
+ option.rect.setLeft(m_leftMargin);
+ option.rect.setRight(wpWidth - m_rightMargin);
+ category->drawHeader(&painter, option);
+ y += category->totalHeight() + m_categoryMargin;
+ option.rect = backup;
+ }
+
+ for (int i = 0; i < model()->rowCount(); ++i)
+ {
+ const QModelIndex index = model()->index(i, 0);
+ if (isIndexHidden(index))
+ {
+ continue;
+ }
+ Qt::ItemFlags flags = index.flags();
+ option.rect = visualRect(index);
+ option.features |=
+ QStyleOptionViewItemV2::WrapText; // FIXME: what is the meaning of this anyway?
+ if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index))
+ {
+ option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected
+ : QStyle::State_None;
+ }
+ else
+ {
+ option.state &= ~QStyle::State_Selected;
+ }
+ option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None;
+ if (!(flags & Qt::ItemIsEnabled))
+ {
+ option.state &= ~QStyle::State_Enabled;
+ }
+ itemDelegate()->paint(&painter, option, index);
+ }
+
+ /*
+ * Drop indicators for manual reordering...
+ */
+#if 0
+ if (!m_lastDragPosition.isNull())
+ {
+ QPair<Group *, int> pair = rowDropPos(m_lastDragPosition);
+ Group *category = pair.first;
+ int row = pair.second;
+ if (category)
+ {
+ int internalRow = row - category->firstItemIndex;
+ QLine line;
+ if (internalRow >= category->numItems())
+ {
+ QRect toTheRightOfRect = visualRect(category->lastItem());
+ line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight());
+ }
+ else
+ {
+ QRect toTheLeftOfRect = visualRect(model()->index(row, 0));
+ line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft());
+ }
+ painter.save();
+ painter.setPen(QPen(Qt::black, 3));
+ painter.drawLine(line);
+ painter.restore();
+ }
+ }
+#endif
+}
+
+void GroupView::resizeEvent(QResizeEvent *event)
+{
+ // QListView::resizeEvent(event);
+
+ // if (m_categoryEditor)
+ // {
+ // m_categoryEditor->resize(qMax(contentWidth() / 2,
+ // m_editedCategory->textRect.width()),
+ // m_categoryEditor->height());
+ // }
+
+ updateGeometries();
+}
+
+void GroupView::dragEnterEvent(QDragEnterEvent *event)
+{
+ if (!isDragEventAccepted(event))
+ {
+ return;
+ }
+ m_lastDragPosition = event->pos() + offset();
+ viewport()->update();
+ event->accept();
+}
+
+void GroupView::dragMoveEvent(QDragMoveEvent *event)
+{
+ if (!isDragEventAccepted(event))
+ {
+ return;
+ }
+ m_lastDragPosition = event->pos() + offset();
+ viewport()->update();
+ event->accept();
+}
+
+void GroupView::dragLeaveEvent(QDragLeaveEvent *event)
+{
+ m_lastDragPosition = QPoint();
+ viewport()->update();
+}
+
+void GroupView::dropEvent(QDropEvent *event)
+{
+ m_lastDragPosition = QPoint();
+
+ stopAutoScroll();
+ setState(NoState);
+
+ if (event->source() != this || !(event->possibleActions() & Qt::MoveAction))
+ {
+ return;
+ }
+
+ QPair<Group *, int> dropPos = rowDropPos(event->pos() + offset());
+ const Group *category = dropPos.first;
+ const int row = dropPos.second;
+
+ if (row == -1)
+ {
+ viewport()->update();
+ return;
+ }
+
+ const QString categoryText = category->text;
+ if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex()))
+ {
+ model()->setData(model()->index(row, 0), categoryText,
+ GroupViewRoles::GroupRole);
+ event->setDropAction(Qt::MoveAction);
+ event->accept();
+ }
+ updateGeometries();
+ viewport()->update();
+}
+
+void GroupView::startDrag(Qt::DropActions supportedActions)
+{
+ QModelIndexList indexes = selectionModel()->selectedIndexes();
+ if (indexes.count() > 0)
+ {
+ QMimeData *data = model()->mimeData(indexes);
+ if (!data)
+ {
+ return;
+ }
+ QRect rect;
+ QPixmap pixmap = renderToPixmap(indexes, &rect);
+ //rect.translate(offset());
+ // rect.adjust(horizontalOffset(), verticalOffset(), 0, 0);
+ QDrag *drag = new QDrag(this);
+ drag->setPixmap(pixmap);
+ drag->setMimeData(data);
+ Qt::DropAction defaultDropAction = Qt::IgnoreAction;
+ if (this->defaultDropAction() != Qt::IgnoreAction &&
+ (supportedActions & this->defaultDropAction()))
+ {
+ defaultDropAction = this->defaultDropAction();
+ }
+ if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction)
+ {
+ const QItemSelection selection = selectionModel()->selection();
+
+ for (auto it = selection.constBegin(); it != selection.constEnd(); ++it)
+ {
+ QModelIndex parent = (*it).parent();
+ if ((*it).left() != 0)
+ {
+ continue;
+ }
+ if ((*it).right() != (model()->columnCount(parent) - 1))
+ {
+ continue;
+ }
+ int count = (*it).bottom() - (*it).top() + 1;
+ model()->removeRows((*it).top(), count, parent);
+ }
+ }
+ }
+}
+
+QRect GroupView::visualRect(const QModelIndex &index) const
+{
+ return geometryRect(index).translated(-offset());
+}
+
+QRect GroupView::geometryRect(const QModelIndex &index) const
+{
+ if (!index.isValid() || isIndexHidden(index) || index.column() > 0)
+ {
+ return QRect();
+ }
+
+ const Group *cat = category(index);
+ QPair<int, int> pos = categoryInternalPosition(index);
+ int x = pos.first;
+ // int y = pos.second;
+
+ QRect out;
+ out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + categoryInternalRowTop(index));
+ out.setLeft(m_spacing + x * (itemWidth() + m_spacing));
+ out.setSize(itemDelegate()->sizeHint(viewOptions(), index));
+
+ return out;
+}
+
+/*
+void CategorizedView::startCategoryEditor(Category *category)
+{
+ if (m_categoryEditor != 0)
+ {
+ return;
+ }
+ m_editedCategory = category;
+ m_categoryEditor = new QLineEdit(m_editedCategory->text, this);
+ QRect rect = m_editedCategory->textRect;
+ rect.setWidth(qMax(contentWidth() / 2, rect.width()));
+ m_categoryEditor->setGeometry(rect);
+ m_categoryEditor->show();
+ m_categoryEditor->setFocus();
+ connect(m_categoryEditor, &QLineEdit::returnPressed, this,
+&CategorizedView::endCategoryEditor);
+}
+
+void CategorizedView::endCategoryEditor()
+{
+ if (m_categoryEditor == 0)
+ {
+ return;
+ }
+ m_editedCategory->text = m_categoryEditor->text();
+ m_updatesDisabled = true;
+ foreach (const QModelIndex &index, itemsForCategory(m_editedCategory))
+ {
+ const_cast<QAbstractItemModel *>(index.model())->setData(index,
+m_categoryEditor->text(), CategoryRole);
+ }
+ m_updatesDisabled = false;
+ delete m_categoryEditor;
+ m_categoryEditor = 0;
+ m_editedCategory = 0;
+ updateGeometries();
+}
+*/
+
+QModelIndex GroupView::indexAt(const QPoint &point) const
+{
+ for (int i = 0; i < model()->rowCount(); ++i)
+ {
+ QModelIndex index = model()->index(i, 0);
+ if (visualRect(index).contains(point))
+ {
+ return index;
+ }
+ }
+ return QModelIndex();
+}
+
+// FIXME: is rect supposed to be geometry or visual coords?
+void GroupView::setSelection(const QRect &rect,
+ const QItemSelectionModel::SelectionFlags commands)
+{
+ for (int i = 0; i < model()->rowCount(); ++i)
+ {
+ QModelIndex index = model()->index(i, 0);
+ QRect itemRect = geometryRect(index);
+ if (itemRect.intersects(rect))
+ {
+ selectionModel()->select(index, commands);
+ update(itemRect.translated(-offset()));
+ }
+ }
+
+}
+
+QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) const
+{
+ Q_ASSERT(r);
+ auto paintPairs = draggablePaintPairs(indices, r);
+ if (paintPairs.isEmpty())
+ {
+ return QPixmap();
+ }
+ QPixmap pixmap(r->size());
+ pixmap.fill(Qt::transparent);
+ QPainter painter(&pixmap);
+ QStyleOptionViewItem option = viewOptions();
+ option.state |= QStyle::State_Selected;
+ for (int j = 0; j < paintPairs.count(); ++j)
+ {
+ option.rect = paintPairs.at(j).first.translated(-r->topLeft());
+ const QModelIndex &current = paintPairs.at(j).second;
+ itemDelegate()->paint(&painter, option, current);
+ }
+ return pixmap;
+}
+
+QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices,
+ QRect *r) const
+{
+ Q_ASSERT(r);
+ QRect &rect = *r;
+ QList<QPair<QRect, QModelIndex>> ret;
+ for (int i = 0; i < indices.count(); ++i)
+ {
+ const QModelIndex &index = indices.at(i);
+ const QRect current = geometryRect(index);
+ ret += qMakePair(current, index);
+ rect |= current;
+ }
+ return ret;
+}
+
+bool GroupView::isDragEventAccepted(QDropEvent *event)
+{
+ if (event->source() != this)
+ {
+ return false;
+ }
+ if (!listsIntersect(event->mimeData()->formats(), model()->mimeTypes()))
+ {
+ return false;
+ }
+ if (!model()->canDropMimeData(event->mimeData(), event->dropAction(),
+ rowDropPos(event->pos()).second, 0, QModelIndex()))
+ {
+ return false;
+ }
+ return true;
+}
+
+QPair<Group *, int> GroupView::rowDropPos(const QPoint &pos)
+{
+ // check that we aren't on a category header and calculate which category we're in
+ Group *category = 0;
+ {
+ int y = 0;
+ for (auto cat : m_groups)
+ {
+ if (pos.y() > y && pos.y() < (y + cat->headerHeight()))
+ {
+ return qMakePair<Group*, int>(nullptr, -1);
+ }
+ y += cat->totalHeight() + m_categoryMargin;
+ if (pos.y() < y)
+ {
+ category = cat;
+ break;
+ }
+ }
+ if (category == 0)
+ {
+ return qMakePair<Group*, int>(nullptr, -1);
+ }
+ }
+
+ QList<QModelIndex> indices = category->items();
+
+ // calculate the internal column
+ int internalColumn = -1;
+ {
+ const int itemWidth = this->itemWidth();
+ if (pos.x() >= (itemWidth * itemsPerRow()))
+ {
+ internalColumn = itemsPerRow();
+ }
+ else
+ {
+ for (int i = 0, c = 0; i < contentWidth(); i += itemWidth + 10 /*spacing()*/, ++c)
+ {
+ if (pos.x() > (i - itemWidth / 2) && pos.x() <= (i + itemWidth / 2))
+ {
+ internalColumn = c;
+ break;
+ }
+ }
+ }
+ if (internalColumn == -1)
+ {
+ return qMakePair<Group*, int>(nullptr, -1);
+ }
+ }
+
+ // calculate the internal row
+ int internalRow = -1;
+ {
+ // FIXME rework the drag and drop code
+ const int top = category->verticalPosition();
+ for (int r = 0, h = top; r < category->numRows();
+ h += itemHeightForCategoryRow(category, r), ++r)
+ {
+ if (pos.y() > h && pos.y() < (h + itemHeightForCategoryRow(category, r)))
+ {
+ internalRow = r;
+ break;
+ }
+ }
+ if (internalRow == -1)
+ {
+ return qMakePair<Group*, int>(nullptr, -1);
+ }
+ // this happens if we're in the margin between a one category and another
+ // categories header
+ if (internalRow > (indices.size() / itemsPerRow()))
+ {
+ return qMakePair<Group*, int>(nullptr, -1);
+ }
+ }
+
+ // flaten the internalColumn/internalRow to one row
+ int categoryRow = internalRow * itemsPerRow() + internalColumn;
+
+ // this is used if we're past the last item
+ if (categoryRow >= indices.size())
+ {
+ return qMakePair(category, indices.last().row() + 1);
+ }
+
+ return qMakePair(category, indices.at(categoryRow).row());
+}
+
+QPoint GroupView::offset() const
+{
+ return QPoint(horizontalOffset(), verticalOffset());
+}
+
+QRegion GroupView::visualRegionForSelection(const QItemSelection &selection) const
+{
+ QRegion region;
+ for (auto &range : selection)
+ {
+ int start_row = range.top();
+ int end_row = range.bottom();
+ for (int row = start_row; row <= end_row; ++row)
+ {
+ int start_column = range.left();
+ int end_column = range.right();
+ for (int column = start_column; column <= end_column; ++column)
+ {
+ QModelIndex index = model()->index(row, column, rootIndex());
+ region += visualRect(index); // OK
+ }
+ }
+ }
+ return region;
+}
+QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction,
+ Qt::KeyboardModifiers modifiers)
+{
+ auto current = currentIndex();
+ if(!current.isValid())
+ {
+ qDebug() << "model row: invalid";
+ return current;
+ }
+ qDebug() << "model row: " << current.row();
+ auto cat = category(current);
+ int i = m_groups.indexOf(cat);
+ if(i >= 0)
+ {
+ // this is a pile of something foul
+ auto real_group = m_groups[i];
+ int beginning_row = 0;
+ for(auto group: m_groups)
+ {
+ if(group == real_group)
+ break;
+ beginning_row += group->numRows();
+ }
+ qDebug() << "category: " << real_group->text;
+ QPair<int, int> pos = categoryInternalPosition(current);
+ int row = beginning_row + pos.second;
+ qDebug() << "row: " << row;
+ qDebug() << "column: " << pos.first;
+ }
+ return current;
+}
diff --git a/gui/groupview/GroupView.h b/gui/groupview/GroupView.h
new file mode 100644
index 00000000..e8f9107c
--- /dev/null
+++ b/gui/groupview/GroupView.h
@@ -0,0 +1,143 @@
+#pragma once
+
+#include <QListView>
+#include <QLineEdit>
+#include <QScrollBar>
+
+struct GroupViewRoles
+{
+ enum
+ {
+ GroupRole = Qt::UserRole,
+ ProgressValueRole,
+ ProgressMaximumRole
+ };
+};
+
+struct Group;
+
+class GroupView : public QAbstractItemView
+{
+ Q_OBJECT
+
+public:
+ GroupView(QWidget *parent = 0);
+ ~GroupView();
+
+ /// return geometry rectangle occupied by the specified model item
+ QRect geometryRect(const QModelIndex &index) const;
+ /// return visual rectangle occupied by the specified model item
+ virtual QRect visualRect(const QModelIndex &index) const override;
+ /// get the model index at the specified visual point
+ virtual QModelIndex indexAt(const QPoint &point) const override;
+ void setSelection(const QRect &rect,
+ const QItemSelectionModel::SelectionFlags commands) override;
+
+ virtual int horizontalOffset() const override
+ {
+ return horizontalScrollBar()->value();
+ }
+
+ virtual int verticalOffset() const override
+ {
+ return verticalScrollBar()->value();
+ }
+
+ virtual void scrollContentsBy(int dx, int dy) override
+ {
+ scrollDirtyRegion(dx, dy);
+ viewport()->scroll(dx, dy);
+ }
+
+ /*
+ * TODO!
+ */
+ virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override
+ {
+ return;
+ }
+
+ virtual QModelIndex moveCursor(CursorAction cursorAction,
+ Qt::KeyboardModifiers modifiers) override;
+
+ virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override;
+
+protected
+slots:
+ virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
+ const QVector<int> &roles) override;
+ virtual void rowsInserted(const QModelIndex &parent, int start, int end) override;
+ virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override;
+ virtual void updateGeometries() override;
+
+protected:
+ virtual bool isIndexHidden(const QModelIndex &index) const override;
+ void mousePressEvent(QMouseEvent *event) override;
+ void mouseMoveEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+ void mouseDoubleClickEvent(QMouseEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+
+ void dragEnterEvent(QDragEnterEvent *event) override;
+ void dragMoveEvent(QDragMoveEvent *event) override;
+ void dragLeaveEvent(QDragLeaveEvent *event) override;
+ void dropEvent(QDropEvent *event) override;
+
+ void startDrag(Qt::DropActions supportedActions) override;
+
+private:
+ friend struct Group;
+
+ QList<Group *> m_groups;
+
+ int m_leftMargin;
+ int m_rightMargin;
+ int m_bottomMargin;
+ int m_categoryMargin;
+
+ // bool m_updatesDisabled;
+
+ Group *category(const QModelIndex &index) const;
+ Group *category(const QString &cat) const;
+ Group *categoryAt(const QPoint &pos) const;
+
+ int itemsPerRow() const;
+ int contentWidth() const;
+
+private:
+ int itemWidth() const;
+ int categoryRowHeight(const QModelIndex &index) const;
+
+ /*QLineEdit *m_categoryEditor;
+ Category *m_editedCategory;
+ void startCategoryEditor(Category *category);
+
+private slots:
+ void endCategoryEditor();*/
+
+private: /* variables */
+ /// point where the currently active mouse action started in geometry coordinates
+ QPoint m_pressedPosition;
+ QPersistentModelIndex m_pressedIndex;
+ bool m_pressedAlreadySelected;
+ Group *m_pressedCategory;
+ QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag;
+ QPoint m_lastDragPosition;
+ int m_spacing = 5;
+
+private: /* methods */
+ QPair<int, int> categoryInternalPosition(const QModelIndex &index) const;
+ int categoryInternalRowTop(const QModelIndex &index) const;
+ int itemHeightForCategoryRow(const Group *category, const int internalRow) const;
+
+ QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const;
+ QList<QPair<QRect, QModelIndex>> draggablePaintPairs(const QModelIndexList &indices,
+ QRect *r) const;
+
+ bool isDragEventAccepted(QDropEvent *event);
+
+ QPair<Group *, int> rowDropPos(const QPoint &pos);
+
+ QPoint offset() const;
+};
diff --git a/gui/groupview/GroupedProxyModel.cpp b/gui/groupview/GroupedProxyModel.cpp
new file mode 100644
index 00000000..d9d6ac78
--- /dev/null
+++ b/gui/groupview/GroupedProxyModel.cpp
@@ -0,0 +1,26 @@
+#include "GroupedProxyModel.h"
+
+#include "GroupView.h"
+
+GroupedProxyModel::GroupedProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
+{
+}
+
+bool GroupedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+{
+ const QString leftCategory = left.data(GroupViewRoles::GroupRole).toString();
+ const QString rightCategory = right.data(GroupViewRoles::GroupRole).toString();
+ if (leftCategory == rightCategory)
+ {
+ return subSortLessThan(left, right);
+ }
+ else
+ {
+ return leftCategory < rightCategory;
+ }
+}
+
+bool GroupedProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const
+{
+ return left.row() < right.row();
+}
diff --git a/gui/groupview/GroupedProxyModel.h b/gui/groupview/GroupedProxyModel.h
new file mode 100644
index 00000000..12edee0f
--- /dev/null
+++ b/gui/groupview/GroupedProxyModel.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <QSortFilterProxyModel>
+
+class GroupedProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ GroupedProxyModel(QObject *parent = 0);
+
+protected:
+ virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
+ virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const;
+};
diff --git a/gui/widgets/InstanceDelegate.cpp b/gui/groupview/InstanceDelegate.cpp
index 5020b8b6..8a273758 100644
--- a/gui/widgets/InstanceDelegate.cpp
+++ b/gui/groupview/InstanceDelegate.cpp
@@ -20,6 +20,8 @@
#include <QApplication>
#include <QtCore/qmath.h>
+#include "GroupView.h"
+
// Origin: Qt
static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
qreal &widthUsed)
@@ -85,6 +87,27 @@ void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, cons
painter->setRenderHint(QPainter::Antialiasing);
}
+// TODO this can be made a lot prettier
+void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option,
+ const int value, const int maximum)
+{
+ if (maximum == 0 || value == maximum)
+ {
+ return;
+ }
+
+ painter->save();
+
+ qreal percent = (qreal)value / (qreal)maximum;
+ QColor color = option.palette.color(QPalette::Dark);
+ color.setAlphaF(0.70f);
+ painter->setBrush(color);
+ painter->setPen(QPen(QBrush(), 0));
+ painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16);
+
+ painter->restore();
+}
+
static QSize viewItemTextSize(const QStyleOptionViewItemV4 *option)
{
QStyle *style = option->widget ? option->widget->style() : QApplication::style();
@@ -150,6 +173,7 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
opt2.palette.setCurrentColorGroup(cg);
// fill in background, if any
+
if (opt.backgroundBrush.style() != Qt::NoBrush)
{
QPointF oldBO = painter->brushOrigin();
@@ -158,6 +182,9 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
painter->setBrushOrigin(oldBO);
}
+ drawSelectionRect(painter, opt2, textHighlightRect);
+
+ /*
if (opt.showDecorationSelected)
{
drawSelectionRect(painter, opt2, opt.rect);
@@ -177,6 +204,7 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
drawFocusRect(painter, opt2, textHighlightRect);
}
}
+ */
}
// draw the icon
@@ -229,6 +257,10 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
line.draw(painter, position);
}
+ drawProgressOverlay(painter, opt,
+ index.data(GroupViewRoles::ProgressValueRole).toInt(),
+ index.data(GroupViewRoles::ProgressMaximumRole).toInt());
+
painter->restore();
}
diff --git a/gui/widgets/InstanceDelegate.h b/gui/groupview/InstanceDelegate.h
index 6f924405..de2f429b 100644
--- a/gui/widgets/InstanceDelegate.h
+++ b/gui/groupview/InstanceDelegate.h
@@ -20,8 +20,10 @@
class ListViewDelegate : public QStyledItemDelegate
{
public:
- explicit ListViewDelegate ( QObject* parent = 0 );
+ explicit ListViewDelegate(QObject *parent = 0);
+
protected:
- void paint ( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
- QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
+ void paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const;
+ QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
diff --git a/gui/widgets/Common.cpp b/gui/widgets/Common.cpp
new file mode 100644
index 00000000..9b730d6c
--- /dev/null
+++ b/gui/widgets/Common.cpp
@@ -0,0 +1,27 @@
+#include "Common.h"
+
+// Origin: Qt
+QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
+ qreal &widthUsed)
+{
+ QStringList lines;
+ height = 0;
+ widthUsed = 0;
+ textLayout.beginLayout();
+ QString str = textLayout.text();
+ while (true)
+ {
+ QTextLine line = textLayout.createLine();
+ if (!line.isValid())
+ break;
+ if (line.textLength() == 0)
+ break;
+ line.setLineWidth(lineWidth);
+ line.setPosition(QPointF(0, height));
+ height += line.height();
+ lines.append(str.mid(line.textStart(), line.textLength()));
+ widthUsed = qMax(widthUsed, line.naturalTextWidth());
+ }
+ textLayout.endLayout();
+ return lines;
+}
diff --git a/gui/widgets/Common.h b/gui/widgets/Common.h
new file mode 100644
index 00000000..fc46e08f
--- /dev/null
+++ b/gui/widgets/Common.h
@@ -0,0 +1,6 @@
+#pragma once
+#include <QStringList>
+#include <QTextLayout>
+
+QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
+ qreal &widthUsed); \ No newline at end of file
diff --git a/gui/widgets/VersionListView.cpp b/gui/widgets/VersionListView.cpp
new file mode 100644
index 00000000..b7f45f27
--- /dev/null
+++ b/gui/widgets/VersionListView.cpp
@@ -0,0 +1,150 @@
+/* Copyright 2013 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 <QHeaderView>
+#include <QApplication>
+#include <QMouseEvent>
+#include <QDrag>
+#include <QPainter>
+#include "VersionListView.h"
+#include "Common.h"
+
+VersionListView::VersionListView(QWidget *parent)
+ :QTreeView ( parent )
+{
+ m_emptyString = tr("No versions are currently available.");
+}
+
+void VersionListView::rowsInserted(const QModelIndex &parent, int start, int end)
+{
+ if(!m_itemCount)
+ viewport()->update();
+ m_itemCount += end-start+1;
+ QTreeView::rowsInserted(parent, start, end);
+}
+
+
+void VersionListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
+{
+ m_itemCount -= end-start+1;
+ if(!m_itemCount)
+ viewport()->update();
+ QTreeView::rowsInserted(parent, start, end);
+}
+
+void VersionListView::setModel(QAbstractItemModel *model)
+{
+ m_itemCount = model->rowCount();
+ if(!m_itemCount)
+ viewport()->update();
+ QTreeView::setModel(model);
+}
+
+void VersionListView::reset()
+{
+ if(model())
+ {
+ m_itemCount = model()->rowCount();
+ }
+ viewport()->update();
+ QTreeView::reset();
+}
+
+void VersionListView::setEmptyString(QString emptyString)
+{
+ m_emptyString = emptyString;
+ if(!m_itemCount)
+ {
+ viewport()->update();
+ }
+}
+
+void VersionListView::paintEvent(QPaintEvent *event)
+{
+ if(m_itemCount)
+ {
+ QTreeView::paintEvent(event);
+ }
+ else
+ {
+ paintInfoLabel(event);
+ }
+}
+
+void VersionListView::paintInfoLabel(QPaintEvent *event)
+{
+ int scrollInterval = 500;
+
+ //calculate the rect for the overlay
+ QPainter painter(viewport());
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ const QChar letter = 'Q';
+ QFont font("sans", 20);
+ font.setBold(true);
+
+ QRect bounds = viewport()->geometry();
+ bounds.moveTop(0);
+ QTextLayout layout(m_emptyString, font);
+ qreal height = 0.0;
+ qreal widthUsed = 0.0;
+ QStringList lines = viewItemTextLayout(layout, bounds.width() - 20, height, widthUsed);
+ QRect rect (0,0, widthUsed, height);
+ rect.setWidth(rect.width()+20);
+ rect.setHeight(rect.height()+20);
+ rect.moveCenter(bounds.center());
+ //check if we are allowed to draw in our area
+ if (!event->rect().intersects(rect)) {
+ return;
+ }
+ //draw the letter of the topmost item semitransparent in the middle
+ QColor background = QApplication::palette().color(QPalette::Foreground);
+ QColor foreground = QApplication::palette().color(QPalette::Base);
+ /*
+ background.setAlpha(128 - scrollFade);
+ foreground.setAlpha(128 - scrollFade);
+ */
+ painter.setBrush(QBrush(background));
+ painter.setPen(foreground);
+ painter.drawRoundedRect(rect, 5.0, 5.0);
+ foreground.setAlpha(190);
+ painter.setPen(foreground);
+ painter.setFont(font);
+ painter.drawText(rect, Qt::AlignCenter, lines.join("\n"));
+
+}
+
+/*
+void ModListView::setModel ( QAbstractItemModel* model )
+{
+ QTreeView::setModel ( model );
+ auto head = header();
+ head->setStretchLastSection(false);
+ // HACK: this is true for the checkbox column of mod lists
+ auto string = model->headerData(0,head->orientation()).toString();
+ if(!string.size())
+ {
+ head->setSectionResizeMode(0, QHeaderView::ResizeToContents);
+ head->setSectionResizeMode(1, QHeaderView::Stretch);
+ for(int i = 2; i < head->count(); i++)
+ head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
+ }
+ else
+ {
+ head->setSectionResizeMode(0, QHeaderView::Stretch);
+ for(int i = 1; i < head->count(); i++)
+ head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
+ }
+}
+*/ \ No newline at end of file
diff --git a/gui/widgets/VersionListView.h b/gui/widgets/VersionListView.h
new file mode 100644
index 00000000..af9b1f6a
--- /dev/null
+++ b/gui/widgets/VersionListView.h
@@ -0,0 +1,43 @@
+/* Copyright 2013 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 <QTreeView>
+
+class Mod;
+
+class VersionListView : public QTreeView
+{
+ Q_OBJECT
+public:
+ explicit VersionListView(QWidget *parent = 0);
+ virtual void paintEvent(QPaintEvent *event) override;
+ void setEmptyString(QString emptyString);
+ virtual void setModel ( QAbstractItemModel* model );
+
+public slots:
+ virtual void reset() override;
+
+protected slots:
+ virtual void rowsAboutToBeRemoved(const QModelIndex & parent, int start, int end) override;
+ virtual void rowsInserted(const QModelIndex &parent, int start, int end) override;
+
+private: /* methods */
+ void paintInfoLabel(QPaintEvent *event);
+
+private: /* variables */
+ int m_itemCount = 0;
+ QString m_emptyString;
+};
diff --git a/logic/BaseInstaller.cpp b/logic/BaseInstaller.cpp
new file mode 100644
index 00000000..92aa0c92
--- /dev/null
+++ b/logic/BaseInstaller.cpp
@@ -0,0 +1,66 @@
+/* Copyright 2013 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 "BaseInstaller.h"
+
+#include <QFile>
+
+#include "OneSixVersion.h"
+#include "OneSixLibrary.h"
+#include "OneSixInstance.h"
+
+#include "cmdutils.h"
+
+BaseInstaller::BaseInstaller()
+{
+
+}
+
+bool BaseInstaller::isApplied(OneSixInstance *on)
+{
+ return QFile::exists(filename(on->instanceRoot()));
+}
+
+bool BaseInstaller::add(OneSixInstance *to)
+{
+ if (!patchesDir(to->instanceRoot()).exists())
+ {
+ QDir(to->instanceRoot()).mkdir("patches");
+ }
+
+ if (isApplied(to))
+ {
+ if (!remove(to))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool BaseInstaller::remove(OneSixInstance *from)
+{
+ return QFile::remove(filename(from->instanceRoot()));
+}
+
+QString BaseInstaller::filename(const QString &root) const
+{
+ return patchesDir(root).absoluteFilePath(id() + ".json");
+}
+QDir BaseInstaller::patchesDir(const QString &root) const
+{
+ return QDir(root + "/patches/");
+}
diff --git a/depends/groupview/include/groupview_config.h b/logic/BaseInstaller.h
index c63acbde..df7eab89 100644
--- a/depends/groupview/include/groupview_config.h
+++ b/logic/BaseInstaller.h
@@ -15,14 +15,25 @@
#pragma once
-#include <QtCore/QtGlobal>
+#include <memory>
-#ifdef LIBGROUPVIEW_STATIC
- #define LIBGROUPVIEW_EXPORT
-#else
- #ifdef LIBGROUPVIEW_LIBRARY
- #define LIBGROUPVIEW_EXPORT Q_DECL_EXPORT
- #else
- #define LIBGROUPVIEW_EXPORT Q_DECL_IMPORT
- #endif
-#endif
+class OneSixInstance;
+class QDir;
+class QString;
+
+class BaseInstaller
+{
+public:
+ BaseInstaller();
+
+ virtual bool canApply(OneSixInstance *instance) const { return true; }
+ bool isApplied(OneSixInstance *on);
+
+ virtual bool add(OneSixInstance *to);
+ virtual bool remove(OneSixInstance *from);
+
+protected:
+ virtual QString id() const = 0;
+ QString filename(const QString &root) const;
+ QDir patchesDir(const QString &root) const;
+};
diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h
index a861e9b2..cd49f99b 100644
--- a/logic/BaseInstance.h
+++ b/logic/BaseInstance.h
@@ -155,10 +155,10 @@ public:
virtual SettingsObject &settings() const;
/// returns a valid update task
- virtual std::shared_ptr<Task> doUpdate(bool only_prepare) = 0;
+ virtual std::shared_ptr<Task> doUpdate() = 0;
/// returns a valid minecraft process, ready for launch with the given account.
- virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) = 0;
+ virtual MinecraftProcess *prepareForLaunch(AuthSessionPtr account) = 0;
/// do any necessary cleanups after the instance finishes. also runs before
/// 'prepareForLaunch'
diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp
index 8d4c5b41..3e18d17f 100644
--- a/logic/ForgeInstaller.cpp
+++ b/logic/ForgeInstaller.cpp
@@ -21,7 +21,15 @@
#include <quazipfile.h>
#include <pathutils.h>
#include <QStringList>
+#include <QRegularExpression>
+#include <QRegularExpressionMatch>
#include "MultiMC.h"
+#include "OneSixInstance.h"
+
+#include <QJsonDocument>
+#include <QJsonArray>
+#include <QSaveFile>
+#include <QCryptographicHash>
ForgeInstaller::ForgeInstaller(QString filename, QString universal_url)
{
@@ -66,6 +74,7 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url)
QJsonObject installObj = installVal.toObject();
QString libraryName = installObj.value("path").toString();
internalPath = installObj.value("filePath").toString();
+ m_forgeVersionString = installObj.value("version").toString().remove("Forge").trimmed();
// where do we put the library? decode the mojang path
OneSixLibrary lib(libraryName);
@@ -103,13 +112,22 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url)
realVersionId = m_forge_version->id = installObj.value("minecraft").toString();
}
-bool ForgeInstaller::apply(std::shared_ptr<OneSixVersion> to)
+bool ForgeInstaller::add(OneSixInstance *to)
{
+ if (!BaseInstaller::add(to))
+ {
+ return false;
+ }
+
+ QJsonObject obj;
+ obj.insert("order", 5);
+
if (!m_forge_version)
return false;
- to->externalUpdateStart();
int sliding_insert_window = 0;
{
+ QJsonArray librariesPlus;
+
// for each library in the version we are adding (except for the blacklisted)
QSet<QString> blacklist{"lwjgl", "lwjgl_util", "lwjgl-platform"};
for (auto lib : m_forge_version->libraries)
@@ -128,28 +146,83 @@ bool ForgeInstaller::apply(std::shared_ptr<OneSixVersion> to)
if (blacklist.contains(libName))
continue;
- // find an entry that matches this one
+ QJsonObject libObj = lib->toJson();
+
bool found = false;
- for (auto tolib : to->libraries)
+ bool equals = false;
+ // find an entry that matches this one
+ for (auto tolib : to->getVanillaVersion()->libraries)
{
if (tolib->name() != libName)
continue;
found = true;
+ if (tolib->toJson() == libObj)
+ {
+ equals = true;
+ }
// replace lib
- tolib = lib;
+ libObj.insert("insert", QString("replace"));
break;
}
+ if (equals)
+ {
+ continue;
+ }
if (!found)
{
// add lib
- to->libraries.insert(sliding_insert_window, lib);
+ libObj.insert("insert", QString("prepend"));
+ if (lib->name() == "minecraftforge")
+ {
+ libObj.insert("MMC-depend", QString("hard"));
+ }
sliding_insert_window++;
}
+ librariesPlus.prepend(libObj);
+ }
+ obj.insert("+libraries", librariesPlus);
+ obj.insert("mainClass", m_forge_version->mainClass);
+ QString args = m_forge_version->minecraftArguments;
+ QStringList tweakers;
+ {
+ QRegularExpression expression("--tweakClass ([a-zA-Z0-9\\.]*)");
+ QRegularExpressionMatch match = expression.match(args);
+ while (match.hasMatch())
+ {
+ tweakers.append(match.captured(1));
+ args.remove(match.capturedStart(), match.capturedLength());
+ match = expression.match(args);
+ }
+ }
+ if (!args.isEmpty() && args != to->getVanillaVersion()->minecraftArguments)
+ {
+ obj.insert("minecraftArguments", args);
+ }
+ if (!tweakers.isEmpty())
+ {
+ obj.insert("+tweakers", QJsonArray::fromStringList(tweakers));
+ }
+ if (!m_forge_version->processArguments.isEmpty() &&
+ m_forge_version->processArguments != to->getVanillaVersion()->processArguments)
+ {
+ obj.insert("processArguments", m_forge_version->processArguments);
}
- to->mainClass = m_forge_version->mainClass;
- to->minecraftArguments = m_forge_version->minecraftArguments;
- to->processArguments = m_forge_version->processArguments;
}
- to->externalUpdateFinish();
- return to->toOriginalFile();
+
+ obj.insert("name", QString("Forge"));
+ obj.insert("fileId", id());
+ obj.insert("version", m_forgeVersionString);
+ obj.insert("mcVersion", to->intendedVersionId());
+
+ QFile file(filename(to->instanceRoot()));
+ if (!file.open(QFile::WriteOnly))
+ {
+ QLOG_ERROR() << "Error opening" << file.fileName()
+ << "for reading:" << file.errorString();
+ return false;
+ }
+ file.write(QJsonDocument(obj).toJson());
+ file.close();
+
+ return true;
}
diff --git a/logic/ForgeInstaller.h b/logic/ForgeInstaller.h
index 0b9f9c77..c5052092 100644
--- a/logic/ForgeInstaller.h
+++ b/logic/ForgeInstaller.h
@@ -14,17 +14,22 @@
*/
#pragma once
+
+#include "BaseInstaller.h"
+
#include <QString>
#include <memory>
class OneSixVersion;
-class ForgeInstaller
+class ForgeInstaller : public BaseInstaller
{
public:
ForgeInstaller(QString filename, QString universal_url);
- bool apply(std::shared_ptr<OneSixVersion> to);
+ bool add(OneSixInstance *to) override;
+
+ QString id() const override { return "net.minecraftforge"; }
private:
// the version, read from the installer
@@ -32,5 +37,6 @@ private:
QString internalPath;
QString finalPath;
QString realVersionId;
+ QString m_forgeVersionString;
QString m_universal_url;
};
diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp
index 1f1a5879..807bccd0 100644
--- a/logic/InstanceFactory.cpp
+++ b/logic/InstanceFactory.cpp
@@ -24,6 +24,7 @@
#include "OneSixInstance.h"
#include "OneSixFTBInstance.h"
#include "NostalgiaInstance.h"
+#include "OneSixInstance.h"
#include "BaseVersion.h"
#include "MinecraftVersion.h"
@@ -50,13 +51,13 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst
QString inst_type = m_settings->get("InstanceType").toString();
// FIXME: replace with a map lookup, where instance classes register their types
- if (inst_type == "Legacy")
+ if (inst_type == "OneSix")
{
- inst = new LegacyInstance(instDir, m_settings, this);
+ inst = new OneSixInstance(instDir, m_settings, this);
}
- else if (inst_type == "OneSix")
+ else if (inst_type == "Legacy")
{
- inst = new OneSixInstance(instDir, m_settings, this);
+ inst = new LegacyInstance(instDir, m_settings, this);
}
else if (inst_type == "Nostalgia")
{
@@ -101,6 +102,7 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *&
switch (mcVer->type)
{
case MinecraftVersion::Legacy:
+ // TODO new instance type
m_settings->set("InstanceType", "Legacy");
inst = new LegacyInstance(instDir, m_settings, this);
inst->setIntendedVersionId(version->descriptor());
diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp
index 2828bcbf..a9f0d112 100644
--- a/logic/LegacyInstance.cpp
+++ b/logic/LegacyInstance.cpp
@@ -42,15 +42,15 @@ LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings,
settings->registerSetting("IntendedJarVersion", "");
}
-std::shared_ptr<Task> LegacyInstance::doUpdate(bool only_prepare)
+std::shared_ptr<Task> LegacyInstance::doUpdate()
{
// make sure the jar mods list is initialized by asking for it.
auto list = jarModList();
// create an update task
- return std::shared_ptr<Task>(new LegacyUpdate(this, only_prepare, this));
+ return std::shared_ptr<Task>(new LegacyUpdate(this, this));
}
-MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account)
+MinecraftProcess *LegacyInstance::prepareForLaunch(AuthSessionPtr account)
{
MinecraftProcess *proc = new MinecraftProcess(this);
@@ -66,13 +66,14 @@ MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account)
if (settings().get("LaunchMaximized").toBool())
windowParams = "max";
else
- windowParams = QString("%1x%2").arg(settings().get("MinecraftWinWidth").toInt()).arg(
- settings().get("MinecraftWinHeight").toInt());
+ windowParams = QString("%1x%2")
+ .arg(settings().get("MinecraftWinWidth").toInt())
+ .arg(settings().get("MinecraftWinHeight").toInt());
QString lwjgl = QDir(MMC->settings()->get("LWJGLDir").toString() + "/" + lwjglVersion())
.absolutePath();
- launchScript += "userName " + account->currentProfile()->name + "\n";
- launchScript += "sessionId " + account->sessionId() + "\n";
+ launchScript += "userName " + account->player_name + "\n";
+ launchScript += "sessionId " + account->session + "\n";
launchScript += "windowTitle " + windowTitle() + "\n";
launchScript += "windowParams " + windowParams + "\n";
launchScript += "lwjgl " + lwjgl + "\n";
diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h
index 1e7d9eb6..636addeb 100644
--- a/logic/LegacyInstance.h
+++ b/logic/LegacyInstance.h
@@ -76,9 +76,9 @@ public:
virtual bool shouldUpdate() const override;
virtual void setShouldUpdate(bool val) override;
- virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override;
+ virtual std::shared_ptr<Task> doUpdate() override;
- virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override;
+ virtual MinecraftProcess *prepareForLaunch(AuthSessionPtr account) override;
virtual void cleanupAfterRun() override;
virtual QDialog *createModEditDialog(QWidget *parent) override;
diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp
index cb3598a7..5d82a76b 100644
--- a/logic/LegacyUpdate.cpp
+++ b/logic/LegacyUpdate.cpp
@@ -27,13 +27,13 @@
#include "logger/QsLog.h"
#include "logic/net/URLConstants.h"
-LegacyUpdate::LegacyUpdate(BaseInstance *inst, bool only_prepare, QObject *parent)
- : Task(parent), m_inst(inst), m_only_prepare(only_prepare)
+LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
{
}
void LegacyUpdate::executeTask()
{
+ /*
if(m_only_prepare)
{
// FIXME: think this through some more.
@@ -49,8 +49,9 @@ void LegacyUpdate::executeTask()
}
else
{
- lwjglStart();
- }
+ */
+ lwjglStart();
+ //}
}
void LegacyUpdate::lwjglStart()
@@ -268,7 +269,6 @@ void LegacyUpdate::jarStart()
auto dljob = new NetJob("Minecraft.jar for version " + version_id);
-
auto metacache = MMC->metacache();
auto entry = metacache->resolveEntry("versions", localPath);
dljob->addNetAction(CacheDownload::make(QUrl(urlstr), entry));
@@ -425,7 +425,7 @@ void LegacyUpdate::ModTheJar()
auto &mod = modList->operator[](i);
// do not merge disabled mods.
- if(!mod.enabled())
+ if (!mod.enabled())
continue;
if (mod.type() == Mod::MOD_ZIPFILE)
diff --git a/logic/LegacyUpdate.h b/logic/LegacyUpdate.h
index 0b573ca5..613eb1f9 100644
--- a/logic/LegacyUpdate.h
+++ b/logic/LegacyUpdate.h
@@ -31,7 +31,7 @@ class LegacyUpdate : public Task
{
Q_OBJECT
public:
- explicit LegacyUpdate(BaseInstance *inst, bool only_prepare, QObject *parent = 0);
+ explicit LegacyUpdate(BaseInstance *inst, QObject *parent = 0);
virtual void executeTask();
private
@@ -72,5 +72,4 @@ private:
private:
NetJobPtr legacyDownloadJob;
BaseInstance *m_inst = nullptr;
- bool m_only_prepare = false;
};
diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp
index 07fffff3..c363cad6 100644
--- a/logic/LiteLoaderInstaller.cpp
+++ b/logic/LiteLoaderInstaller.cpp
@@ -15,12 +15,19 @@
#include "LiteLoaderInstaller.h"
+#include <QJsonArray>
+#include <QJsonDocument>
+
+#include "logger/QsLog.h"
+
#include "OneSixVersion.h"
#include "OneSixLibrary.h"
+#include "OneSixInstance.h"
QMap<QString, QString> LiteLoaderInstaller::m_launcherWrapperVersionMapping;
-LiteLoaderInstaller::LiteLoaderInstaller(const QString &mcVersion) : m_mcVersion(mcVersion)
+LiteLoaderInstaller::LiteLoaderInstaller()
+ : BaseInstaller()
{
if (m_launcherWrapperVersionMapping.isEmpty())
{
@@ -31,72 +38,60 @@ LiteLoaderInstaller::LiteLoaderInstaller(const QString &mcVersion) : m_mcVersion
}
}
-bool LiteLoaderInstaller::canApply() const
+bool LiteLoaderInstaller::canApply(OneSixInstance *instance) const
{
- return m_launcherWrapperVersionMapping.contains(m_mcVersion);
+ return m_launcherWrapperVersionMapping.contains(instance->intendedVersionId());
}
-bool LiteLoaderInstaller::apply(std::shared_ptr<OneSixVersion> to)
+bool LiteLoaderInstaller::add(OneSixInstance *to)
{
- to->externalUpdateStart();
-
- applyLaunchwrapper(to);
- applyLiteLoader(to);
-
- to->mainClass = "net.minecraft.launchwrapper.Launch";
- if (!to->minecraftArguments.contains(
- " --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker"))
+ if (!BaseInstaller::add(to))
{
- to->minecraftArguments.append(
- " --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker");
+ return false;
}
- to->externalUpdateFinish();
- return to->toOriginalFile();
-}
+ QJsonObject obj;
-void LiteLoaderInstaller::applyLaunchwrapper(std::shared_ptr<OneSixVersion> to)
-{
- const QString intendedVersion = m_launcherWrapperVersionMapping[m_mcVersion];
+ obj.insert("mainClass", QString("net.minecraft.launchwrapper.Launch"));
+ obj.insert("+tweakers", QJsonArray::fromStringList(QStringList() << "com.mumfrey.liteloader.launch.LiteLoaderTweaker"));
+ obj.insert("order", 10);
- QMutableListIterator<std::shared_ptr<OneSixLibrary>> it(to->libraries);
- while (it.hasNext())
+ QJsonArray libraries;
+
+ // launchwrapper
{
- it.next();
- if (it.value()->rawName().startsWith("net.minecraft:launchwrapper:"))
- {
- if (it.value()->version() >= intendedVersion)
- {
- return;
- }
- else
- {
- it.remove();
- }
- }
+ OneSixLibrary launchwrapperLib("net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[to->intendedVersionId()]);
+ launchwrapperLib.finalize();
+ QJsonObject lwLibObj = launchwrapperLib.toJson();
+ lwLibObj.insert("insert", QString("prepend"));
+ libraries.append(lwLibObj);
}
- std::shared_ptr<OneSixLibrary> lib(new OneSixLibrary(
- "net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[m_mcVersion]));
- lib->finalize();
- to->libraries.prepend(lib);
-}
+ // liteloader
+ {
+ OneSixLibrary liteloaderLib("com.mumfrey:liteloader:" + to->intendedVersionId());
+ liteloaderLib.setBaseUrl("http://dl.liteloader.com/versions/");
+ liteloaderLib.finalize();
+ QJsonObject llLibObj = liteloaderLib.toJson();
+ llLibObj.insert("insert", QString("prepend"));
+ llLibObj.insert("MMC-depend", QString("hard"));
+ libraries.append(llLibObj);
+ }
-void LiteLoaderInstaller::applyLiteLoader(std::shared_ptr<OneSixVersion> to)
-{
- QMutableListIterator<std::shared_ptr<OneSixLibrary>> it(to->libraries);
- while (it.hasNext())
+ obj.insert("+libraries", libraries);
+ obj.insert("name", QString("LiteLoader"));
+ obj.insert("fileId", id());
+ obj.insert("version", to->intendedVersionId());
+ obj.insert("mcVersion", to->intendedVersionId());
+
+ QFile file(filename(to->instanceRoot()));
+ if (!file.open(QFile::WriteOnly))
{
- it.next();
- if (it.value()->rawName().startsWith("com.mumfrey:liteloader:"))
- {
- it.remove();
- }
+ QLOG_ERROR() << "Error opening" << file.fileName() << "for reading:" << file.errorString();
+ return false;
}
+ file.write(QJsonDocument(obj).toJson());
+ file.close();
- std::shared_ptr<OneSixLibrary> lib(
- new OneSixLibrary("com.mumfrey:liteloader:" + m_mcVersion));
- lib->setBaseUrl("http://dl.liteloader.com/versions/");
- lib->finalize();
- to->libraries.prepend(lib);
+ return true;
}
diff --git a/logic/LiteLoaderInstaller.h b/logic/LiteLoaderInstaller.h
index 44b306d6..5e01b16c 100644
--- a/logic/LiteLoaderInstaller.h
+++ b/logic/LiteLoaderInstaller.h
@@ -14,26 +14,22 @@
*/
#pragma once
+
+#include "BaseInstaller.h"
+
#include <QString>
#include <QMap>
-#include <memory>
-
-class OneSixVersion;
-class LiteLoaderInstaller
+class LiteLoaderInstaller : public BaseInstaller
{
public:
- LiteLoaderInstaller(const QString &mcVersion);
+ LiteLoaderInstaller();
- bool canApply() const;
-
- bool apply(std::shared_ptr<OneSixVersion> to);
+ bool canApply(OneSixInstance *instance) const override;
+ bool add(OneSixInstance *to) override;
private:
- QString m_mcVersion;
-
- void applyLaunchwrapper(std::shared_ptr<OneSixVersion> to);
- void applyLiteLoader(std::shared_ptr<OneSixVersion> to);
+ virtual QString id() const override { return "com.mumfrey.liteloader"; }
static QMap<QString, QString> m_launcherWrapperVersionMapping;
};
diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp
index 84610021..70a9d55f 100644
--- a/logic/MinecraftProcess.cpp
+++ b/logic/MinecraftProcess.cpp
@@ -79,26 +79,18 @@ void MinecraftProcess::setWorkdir(QString path)
QString MinecraftProcess::censorPrivateInfo(QString in)
{
- if(!m_account)
+ if(!m_session)
return in;
- QString sessionId = m_account->sessionId();
- QString accessToken = m_account->accessToken();
- QString clientToken = m_account->clientToken();
- in.replace(sessionId, "<SESSION ID>");
- in.replace(accessToken, "<ACCESS TOKEN>");
- in.replace(clientToken, "<CLIENT TOKEN>");
- auto profile = m_account->currentProfile();
- if(profile)
- {
- QString profileId = profile->id;
- QString profileName = profile->name;
- in.replace(profileId, "<PROFILE ID>");
- in.replace(profileName, "<PROFILE NAME>");
- }
+ if(m_session->session != "-")
+ in.replace(m_session->session, "<SESSION ID>");
+ in.replace(m_session->access_token, "<ACCESS TOKEN>");
+ in.replace(m_session->client_token, "<CLIENT TOKEN>");
+ in.replace(m_session->uuid, "<PROFILE ID>");
+ in.replace(m_session->player_name, "<PROFILE NAME>");
- auto i = m_account->user().properties.begin();
- while (i != m_account->user().properties.end())
+ auto i = m_session->u.properties.begin();
+ while (i != m_session->u.properties.end())
{
in.replace(i.value(), "<" + i.key().toUpper() + ">");
++i;
@@ -121,6 +113,8 @@ MessageLevel::Enum MinecraftProcess::guessLevel(const QString &line, MessageLeve
level = MessageLevel::Fatal;
if (line.contains("[DEBUG]"))
level = MessageLevel::Debug;
+ if(line.contains("overwriting existing"))
+ level = MessageLevel::Fatal;
return level;
}
diff --git a/logic/MinecraftProcess.h b/logic/MinecraftProcess.h
index 70e5df52..26214026 100644
--- a/logic/MinecraftProcess.h
+++ b/logic/MinecraftProcess.h
@@ -78,9 +78,9 @@ public:
void killMinecraft();
- inline void setLogin(MojangAccountPtr account)
+ inline void setLogin(AuthSessionPtr session)
{
- m_account = account;
+ m_session = session;
}
signals:
@@ -117,7 +117,7 @@ protected:
QString m_out_leftover;
QProcess m_prepostlaunchprocess;
bool killed = false;
- MojangAccountPtr m_account;
+ AuthSessionPtr m_session;
QString launchScript;
QString m_nativeFolder;
diff --git a/logic/Mod.cpp b/logic/Mod.cpp
index 6732446d..22ac36c8 100644
--- a/logic/Mod.cpp
+++ b/logic/Mod.cpp
@@ -164,6 +164,16 @@ void Mod::ReadMCModInfo(QByteArray contents)
m_name = firstObj.value("name").toString();
m_version = firstObj.value("version").toString();
m_homeurl = firstObj.value("url").toString();
+ m_homeurl = m_homeurl.trimmed();
+ if(!m_homeurl.isEmpty())
+ {
+ // fix up url.
+ if (!m_homeurl.startsWith("http://") && !m_homeurl.startsWith("https://") &&
+ !m_homeurl.startsWith("ftp://"))
+ {
+ m_homeurl.prepend("http://");
+ }
+ }
m_description = firstObj.value("description").toString();
QJsonArray authors = firstObj.value("authors").toArray();
if (authors.size() == 0)
@@ -178,7 +188,8 @@ void Mod::ReadMCModInfo(QByteArray contents)
}
m_credits = firstObj.value("credits").toString();
return;
- };
+ }
+ ;
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
// this is the very old format that had just the array
@@ -227,17 +238,17 @@ void Mod::ReadLiteModInfo(QByteArray contents)
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
auto object = jsonDoc.object();
- if(object.contains("name"))
+ if (object.contains("name"))
{
m_mod_id = m_name = object.value("name").toString();
}
- if(object.contains("version"))
+ if (object.contains("version"))
{
- m_version=object.value("version").toString("");
+ m_version = object.value("version").toString("");
}
else
{
- m_version=object.value("revision").toString("");
+ m_version = object.value("revision").toString("");
}
m_mcversion = object.value("mcversion").toString();
m_authors = object.value("author").toString();
diff --git a/logic/ModList.cpp b/logic/ModList.cpp
index 499623bf..79b56986 100644
--- a/logic/ModList.cpp
+++ b/logic/ModList.cpp
@@ -62,6 +62,19 @@ void ModList::stopWatching()
}
}
+void ModList::internalSort(QList<Mod> &what)
+{
+ auto predicate = [](const Mod & left, const Mod & right)
+ {
+ if (left.name() == right.name())
+ {
+ return left.mmc_id().localeAwareCompare(right.mmc_id()) <= 0;
+ }
+ return left.name().localeAwareCompare(right.name()) <= 0;
+ };
+ std::sort(what.begin(), what.end(), predicate);
+}
+
bool ModList::update()
{
if (!isValid())
@@ -98,7 +111,7 @@ bool ModList::update()
isEnabled = idxEnabled >= 0;
}
int idx = isEnabled ? idxEnabled : idxDisabled;
- QFileInfo & info = isEnabled ? infoEnabled : infoDisabled;
+ QFileInfo &info = isEnabled ? infoEnabled : infoDisabled;
// if the file from the index file exists
if (idx != -1)
{
@@ -122,8 +135,7 @@ bool ModList::update()
{
newMods.append(Mod(entry));
}
- std::sort(newMods.begin(), newMods.end(), [](const Mod & left, const Mod & right)
- { return left.name().localeAwareCompare(right.name()) <= 0; });
+ internalSort(newMods);
orderedMods.append(newMods);
orderOrStateChanged = true;
}
@@ -236,8 +248,8 @@ bool ModList::installMod(const QFileInfo &filename, int index)
int idx = mods.indexOf(m);
if (idx != -1)
{
- int idx2 = mods.indexOf(m,idx+1);
- if(idx2 != -1)
+ int idx2 = mods.indexOf(m, idx + 1);
+ if (idx2 != -1)
return false;
if (mods[idx].replace(m))
{
@@ -416,7 +428,7 @@ QVariant ModList::data(const QModelIndex &index, int role) const
switch (index.column())
{
case ActiveColumn:
- return mods[row].enabled() ? Qt::Checked: Qt::Unchecked;
+ return mods[row].enabled() ? Qt::Checked : Qt::Unchecked;
default:
return QVariant();
}
@@ -567,8 +579,7 @@ bool ModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row
if (m_list_file.isEmpty())
{
beginResetModel();
- std::sort(mods.begin(), mods.end(), [](const Mod & left, const Mod & right)
- { return left.name().localeAwareCompare(right.name()) <= 0; });
+ internalSort(mods);
endResetModel();
}
}
diff --git a/logic/ModList.h b/logic/ModList.h
index 0d6507fb..95f52061 100644
--- a/logic/ModList.h
+++ b/logic/ModList.h
@@ -127,6 +127,7 @@ public:
}
private:
+ void internalSort(QList<Mod> & what);
struct OrderItem
{
QString id;
diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp
index e50a5b53..ca88142a 100644
--- a/logic/OneSixFTBInstance.cpp
+++ b/logic/OneSixFTBInstance.cpp
@@ -55,15 +55,13 @@ slots:
setStatus(tr("Installing Forge..."));
QString forgePath = entry->getFullPath();
ForgeInstaller forge(forgePath, forgeVersion->universal_url);
- if (!instance->reloadFullVersion())
+ if (!instance->reloadVersion())
{
emitFailed(tr("Couldn't load the version config"));
return;
}
- instance->revertCustomVersion();
- instance->customizeVersion();
auto version = instance->getFullVersion();
- if (!forge.apply(version))
+ if (!forge.add(instance))
{
emitFailed(tr("Couldn't install Forge"));
return;
@@ -106,7 +104,7 @@ bool OneSixFTBInstance::menuActionEnabled(QString action_name) const
return false;
}
-std::shared_ptr<Task> OneSixFTBInstance::doUpdate(bool only_prepare)
+std::shared_ptr<Task> OneSixFTBInstance::doUpdate()
{
std::shared_ptr<SequentialTask> task;
task.reset(new SequentialTask(this));
@@ -114,11 +112,11 @@ std::shared_ptr<Task> OneSixFTBInstance::doUpdate(bool only_prepare)
{
task->addTask(std::shared_ptr<Task>(MMC->forgelist()->getLoadTask()));
}
- task->addTask(OneSixInstance::doUpdate(only_prepare));
+ task->addTask(OneSixInstance::doUpdate());
task->addTask(std::shared_ptr<Task>(new OneSixFTBInstanceForge(m_forge->version(), this, this)));
//FIXME: yes. this may appear dumb. but the previous step can change the list, so we do it all again.
//TODO: Add a graph task. Construct graphs of tasks so we may capture the logic properly.
- task->addTask(OneSixInstance::doUpdate(only_prepare));
+ task->addTask(OneSixInstance::doUpdate());
return task;
}
diff --git a/logic/OneSixFTBInstance.h b/logic/OneSixFTBInstance.h
index dc028819..bc543aeb 100644
--- a/logic/OneSixFTBInstance.h
+++ b/logic/OneSixFTBInstance.h
@@ -13,7 +13,7 @@ public:
virtual QString getStatusbarDescription();
virtual bool menuActionEnabled(QString action_name) const;
- virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override;
+ virtual std::shared_ptr<Task> doUpdate() override;
virtual QString id() const;
diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp
index ab87a1db..ae172f21 100644
--- a/logic/OneSixInstance.cpp
+++ b/logic/OneSixInstance.cpp
@@ -13,37 +13,42 @@
* limitations under the License.
*/
-#include "MultiMC.h"
#include "OneSixInstance.h"
+
+#include <QIcon>
+
#include "OneSixInstance_p.h"
#include "OneSixUpdate.h"
-#include "MinecraftProcess.h"
#include "OneSixVersion.h"
-#include "JavaChecker.h"
-#include "logic/icons/IconList.h"
-
-#include <setting.h>
-#include <pathutils.h>
-#include <cmdutils.h>
-#include <JlCompress.h>
-#include "gui/dialogs/OneSixModEditDialog.h"
+#include "pathutils.h"
#include "logger/QsLog.h"
-#include "logic/assets/AssetsUtils.h"
-#include <QIcon>
+#include "assets/AssetsUtils.h"
+#include "MultiMC.h"
+#include "icons/IconList.h"
+#include "MinecraftProcess.h"
+#include "gui/dialogs/OneSixModEditDialog.h"
-OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_obj,
- QObject *parent)
- : BaseInstance(new OneSixInstancePrivate(), rootDir, setting_obj, parent)
+OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, QObject *parent)
+ : BaseInstance(new OneSixInstancePrivate(), rootDir, settings, parent)
{
I_D(OneSixInstance);
d->m_settings->registerSetting("IntendedVersion", "");
d->m_settings->registerSetting("ShouldUpdate", false);
- reloadFullVersion();
+ d->version.reset(new OneSixVersion(this, this));
+ d->vanillaVersion.reset(new OneSixVersion(this, this));
+ if (QDir(instanceRoot()).exists("version.json"))
+ {
+ reloadVersion();
+ }
+ else
+ {
+ clearVersion();
+ }
}
-std::shared_ptr<Task> OneSixInstance::doUpdate(bool only_prepare)
+std::shared_ptr<Task> OneSixInstance::doUpdate()
{
- return std::shared_ptr<Task>(new OneSixUpdate(this, only_prepare));
+ return std::shared_ptr<Task>(new OneSixUpdate(this));
}
QString replaceTokensIn(QString text, QMap<QString, QString> with)
@@ -130,25 +135,23 @@ QDir OneSixInstance::reconstructAssets(std::shared_ptr<OneSixVersion> version)
return virtualRoot;
}
-QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account)
+QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session)
{
I_D(OneSixInstance);
auto version = d->version;
QString args_pattern = version->minecraftArguments;
+ for (auto tweaker : version->tweakers)
+ {
+ args_pattern += " --tweakClass " + tweaker;
+ }
QMap<QString, QString> token_mapping;
// yggdrasil!
- token_mapping["auth_username"] = account->username();
- token_mapping["auth_session"] = account->sessionId();
- token_mapping["auth_access_token"] = account->accessToken();
- token_mapping["auth_player_name"] = account->currentProfile()->name;
- token_mapping["auth_uuid"] = account->currentProfile()->id;
-
- // this is for offline?:
- /*
- map["auth_player_name"] = "Player";
- map["auth_player_name"] = "00000000-0000-0000-0000-000000000000";
- */
+ token_mapping["auth_username"] = session->username;
+ token_mapping["auth_session"] = session->session;
+ token_mapping["auth_access_token"] = session->access_token;
+ token_mapping["auth_player_name"] = session->player_name;
+ token_mapping["auth_uuid"] = session->uuid;
// these do nothing and are stupid.
token_mapping["profile_name"] = name();
@@ -159,17 +162,8 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account)
QString absAssetsDir = QDir("assets/").absolutePath();
token_mapping["game_assets"] = reconstructAssets(d->version).absolutePath();
- auto user = account->user();
- QJsonObject userAttrs;
- for (auto key : user.properties.keys())
- {
- auto array = QJsonArray::fromStringList(user.properties.values(key));
- userAttrs.insert(key, array);
- }
- QJsonDocument value(userAttrs);
-
- token_mapping["user_properties"] = value.toJson(QJsonDocument::Compact);
- token_mapping["user_type"] = account->currentProfile()->legacy ? "legacy" : "mojang";
+ token_mapping["user_properties"] = session->serializeUserProperties();
+ token_mapping["user_type"] = session->user_type;
// 1.7.3+ assets tokens
token_mapping["assets_root"] = absAssetsDir;
token_mapping["assets_index_name"] = version->assets;
@@ -182,7 +176,7 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account)
return parts;
}
-MinecraftProcess *OneSixInstance::prepareForLaunch(MojangAccountPtr account)
+MinecraftProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session)
{
I_D(OneSixInstance);
@@ -207,7 +201,7 @@ MinecraftProcess *OneSixInstance::prepareForLaunch(MojangAccountPtr account)
}
launchScript += "mainClass " + version->mainClass + "\n";
- for (auto param : processMinecraftArgs(account))
+ for (auto param : processMinecraftArgs(session))
{
launchScript += "param " + param + "\n";
}
@@ -282,11 +276,8 @@ bool OneSixInstance::setIntendedVersionId(QString version)
{
settings().set("IntendedVersion", version);
setShouldUpdate(true);
- auto pathCustom = PathCombine(instanceRoot(), "custom.json");
- auto pathOrig = PathCombine(instanceRoot(), "version.json");
- QFile::remove(pathCustom);
- QFile::remove(pathOrig);
- reloadFullVersion();
+ QFile::remove(PathCombine(instanceRoot(), "version.json"));
+ clearVersion();
return true;
}
@@ -312,9 +303,10 @@ bool OneSixInstance::shouldUpdate() const
bool OneSixInstance::versionIsCustom()
{
- QString verpath_custom = PathCombine(instanceRoot(), "custom.json");
- QFile versionfile(verpath_custom);
- return versionfile.exists();
+ QDir patches(PathCombine(instanceRoot(), "patches/"));
+ return (patches.exists() && patches.count() >= 0)
+ || QFile::exists(PathCombine(instanceRoot(), "custom.json"))
+ || QFile::exists(PathCombine(instanceRoot(), "user.json"));
}
QString OneSixInstance::currentVersionId() const
@@ -322,62 +314,39 @@ QString OneSixInstance::currentVersionId() const
return intendedVersionId();
}
-bool OneSixInstance::customizeVersion()
+bool OneSixInstance::reloadVersion(QWidget *widgetParent)
{
- if (!versionIsCustom())
- {
- auto pathCustom = PathCombine(instanceRoot(), "custom.json");
- auto pathOrig = PathCombine(instanceRoot(), "version.json");
- QFile::copy(pathOrig, pathCustom);
- return reloadFullVersion();
- }
- else
- return true;
-}
+ I_D(OneSixInstance);
-bool OneSixInstance::revertCustomVersion()
-{
- if (versionIsCustom())
+ bool ret = d->version->reload(widgetParent);
+ if (ret)
{
- auto path = PathCombine(instanceRoot(), "custom.json");
- QFile::remove(path);
- return reloadFullVersion();
+ ret = d->vanillaVersion->reload(widgetParent, true);
}
- else
- return true;
+ emit versionReloaded();
+ return ret;
}
-bool OneSixInstance::reloadFullVersion()
+void OneSixInstance::clearVersion()
{
I_D(OneSixInstance);
-
- QString verpath = PathCombine(instanceRoot(), "version.json");
- {
- QString verpath_custom = PathCombine(instanceRoot(), "custom.json");
- QFile versionfile(verpath_custom);
- if (versionfile.exists())
- verpath = verpath_custom;
- }
-
- auto version = OneSixVersion::fromFile(verpath);
- if (version)
- {
- d->version = version;
- return true;
- }
- else
- {
- d->version.reset();
- return false;
- }
+ d->version->clear();
+ d->vanillaVersion->clear();
+ emit versionReloaded();
}
-std::shared_ptr<OneSixVersion> OneSixInstance::getFullVersion()
+std::shared_ptr<OneSixVersion> OneSixInstance::getFullVersion() const
{
- I_D(OneSixInstance);
+ I_D(const OneSixInstance);
return d->version;
}
+std::shared_ptr<OneSixVersion> OneSixInstance::getVanillaVersion() const
+{
+ I_D(const OneSixInstance);
+ return d->vanillaVersion;
+}
+
QString OneSixInstance::defaultBaseJar() const
{
return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar";
@@ -397,7 +366,7 @@ bool OneSixInstance::menuActionEnabled(QString action_name) const
QString OneSixInstance::getStatusbarDescription()
{
- QString descr = "One Six : " + intendedVersionId();
+ QString descr = "OneSix : " + intendedVersionId();
if (versionIsCustom())
{
descr + " (custom)";
diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h
index f869e345..ae95eab1 100644
--- a/logic/OneSixInstance.h
+++ b/logic/OneSixInstance.h
@@ -15,21 +15,17 @@
#pragma once
-#include <QStringList>
-#include <QDir>
-
#include "BaseInstance.h"
-class OneSixVersion;
-class Task;
-class ModList;
+#include "OneSixVersion.h"
+#include "ModList.h"
class OneSixInstance : public BaseInstance
{
Q_OBJECT
public:
explicit OneSixInstance(const QString &rootDir, SettingsObject *settings,
- QObject *parent = 0);
+ QObject *parent = 0);
////// Mod Lists //////
std::shared_ptr<ModList> loaderModList();
@@ -40,8 +36,8 @@ public:
QString loaderModsDir() const;
virtual QString instanceConfigFolder() const override;
- virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override;
- virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override;
+ virtual std::shared_ptr<Task> doUpdate() override;
+ virtual MinecraftProcess *prepareForLaunch(AuthSessionPtr session) override;
virtual void cleanupAfterRun() override;
@@ -55,14 +51,14 @@ public:
virtual QDialog *createModEditDialog(QWidget *parent) override;
- /// reload the full version json file. return true on success!
- bool reloadFullVersion();
+ /// reload the full version json files. return true on success!
+ bool reloadVersion(QWidget *widgetParent = 0);
+ /// clears all version information in preparation for an update
+ void clearVersion();
/// get the current full version info
- std::shared_ptr<OneSixVersion> getFullVersion();
- /// revert the current custom version back to base
- bool revertCustomVersion();
- /// customize the current base version
- bool customizeVersion();
+ std::shared_ptr<OneSixVersion> getFullVersion() const;
+ /// gets the current version info, but only for version.json
+ std::shared_ptr<OneSixVersion> getVanillaVersion() const;
/// is the current version original, or custom?
virtual bool versionIsCustom() override;
@@ -72,7 +68,10 @@ public:
virtual bool menuActionEnabled(QString action_name) const override;
virtual QString getStatusbarDescription() override;
+signals:
+ void versionReloaded();
+
private:
- QStringList processMinecraftArgs(MojangAccountPtr account);
+ QStringList processMinecraftArgs(AuthSessionPtr account);
QDir reconstructAssets(std::shared_ptr<OneSixVersion> version);
};
diff --git a/logic/OneSixInstance_p.h b/logic/OneSixInstance_p.h
index 6b7ea431..0cc46f33 100644
--- a/logic/OneSixInstance_p.h
+++ b/logic/OneSixInstance_p.h
@@ -15,16 +15,14 @@
#pragma once
-#include <memory>
-
-#include "logic/BaseInstance_p.h"
-#include "logic/OneSixVersion.h"
-#include "logic/OneSixLibrary.h"
-#include "logic/ModList.h"
+#include "BaseInstance_p.h"
+#include "OneSixVersion.h"
+#include "ModList.h"
struct OneSixInstancePrivate : public BaseInstancePrivate
{
std::shared_ptr<OneSixVersion> version;
+ std::shared_ptr<OneSixVersion> vanillaVersion;
std::shared_ptr<ModList> loader_mod_list;
std::shared_ptr<ModList> resource_pack_list;
-}; \ No newline at end of file
+};
diff --git a/logic/OneSixLibrary.cpp b/logic/OneSixLibrary.cpp
index 7b80d5e7..c78679d1 100644
--- a/logic/OneSixLibrary.cpp
+++ b/logic/OneSixLibrary.cpp
@@ -46,7 +46,7 @@ void OneSixLibrary::finalize()
}
m_decentname = parts[1];
- m_decentversion = parts[2];
+ m_decentversion = minVersion = parts[2];
m_storage_path = relative;
m_download_url = m_base_url + relative;
@@ -76,11 +76,11 @@ void OneSixLibrary::finalize()
}
}
-void OneSixLibrary::setName(QString name)
+void OneSixLibrary::setName(const QString &name)
{
m_name = name;
}
-void OneSixLibrary::setBaseUrl(QString base_url)
+void OneSixLibrary::setBaseUrl(const QString &base_url)
{
m_base_url = base_url;
}
@@ -88,50 +88,54 @@ void OneSixLibrary::setIsNative()
{
m_is_native = true;
}
-void OneSixLibrary::addNative(OpSys os, QString suffix)
+void OneSixLibrary::addNative(OpSys os, const QString &suffix)
{
m_is_native = true;
m_native_suffixes[os] = suffix;
}
+void OneSixLibrary::clearSuffixes()
+{
+ m_native_suffixes.clear();
+}
void OneSixLibrary::setRules(QList<std::shared_ptr<Rule>> rules)
{
m_rules = rules;
}
-bool OneSixLibrary::isActive()
+bool OneSixLibrary::isActive() const
{
return m_is_active;
}
-bool OneSixLibrary::isNative()
+bool OneSixLibrary::isNative() const
{
return m_is_native;
}
-QString OneSixLibrary::downloadUrl()
+QString OneSixLibrary::downloadUrl() const
{
if (m_absolute_url.size())
return m_absolute_url;
return m_download_url;
}
-QString OneSixLibrary::storagePath()
+QString OneSixLibrary::storagePath() const
{
return m_storage_path;
}
-void OneSixLibrary::setAbsoluteUrl(QString absolute_url)
+void OneSixLibrary::setAbsoluteUrl(const QString &absolute_url)
{
m_absolute_url = absolute_url;
}
-QString OneSixLibrary::absoluteUrl()
+QString OneSixLibrary::absoluteUrl() const
{
return m_absolute_url;
}
-void OneSixLibrary::setHint(QString hint)
+void OneSixLibrary::setHint(const QString &hint)
{
m_hint = hint;
}
-QString OneSixLibrary::hint()
+QString OneSixLibrary::hint() const
{
return m_hint;
}
@@ -176,7 +180,7 @@ bool OneSixLibrary::extractTo(QString target_dir)
cooked_storage.replace("${arch}", "32");
QString origin = PathCombine("libraries", cooked_storage);
QString target_dir_cooked = PathCombine(target_dir, "32");
- if(!ensureFolderPathExists(target_dir_cooked))
+ if (!ensureFolderPathExists(target_dir_cooked))
{
QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked;
return false;
@@ -191,7 +195,7 @@ bool OneSixLibrary::extractTo(QString target_dir)
cooked_storage.replace("${arch}", "64");
origin = PathCombine("libraries", cooked_storage);
target_dir_cooked = PathCombine(target_dir, "64");
- if(!ensureFolderPathExists(target_dir_cooked))
+ if (!ensureFolderPathExists(target_dir_cooked))
{
QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked;
return false;
@@ -205,7 +209,7 @@ bool OneSixLibrary::extractTo(QString target_dir)
}
else
{
- if(!ensureFolderPathExists(target_dir))
+ if (!ensureFolderPathExists(target_dir))
{
QLOG_ERROR() << "Couldn't create folder " + target_dir;
return false;
@@ -230,8 +234,10 @@ QJsonObject OneSixLibrary::toJson()
libRoot.insert("MMC-hint", m_hint);
if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES &&
m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES &&
- m_base_url != "https://" + URLConstants::LIBRARY_BASE)
+ m_base_url != "https://" + URLConstants::LIBRARY_BASE && !m_base_url.isEmpty())
+ {
libRoot.insert("url", m_base_url);
+ }
if (isNative() && m_native_suffixes.size())
{
QJsonObject nativeList;
diff --git a/logic/OneSixLibrary.h b/logic/OneSixLibrary.h
index 227cdbef..371ca6f4 100644
--- a/logic/OneSixLibrary.h
+++ b/logic/OneSixLibrary.h
@@ -60,12 +60,21 @@ private:
public:
QStringList extract_excludes;
+ QString minVersion;
+
+ enum DependType
+ {
+ Soft,
+ Hard
+ };
+ DependType dependType;
public:
/// Constructor
- OneSixLibrary(QString name)
+ OneSixLibrary(const QString &name, const DependType type = Soft)
{
m_name = name;
+ dependType = type;
}
/// Returns the raw name field
@@ -84,48 +93,50 @@ public:
void finalize();
/// Set the library composite name
- void setName(QString name);
+ void setName(const QString &name);
/// get a decent-looking name
- QString name()
+ QString name() const
{
return m_decentname;
}
/// get a decent-looking version
- QString version()
+ QString version() const
{
return m_decentversion;
}
/// what kind of library is it? (for display)
- QString type()
+ QString type() const
{
return m_decenttype;
}
/// Set the url base for downloads
- void setBaseUrl(QString base_url);
+ void setBaseUrl(const QString &base_url);
/// Call this to mark the library as 'native' (it's a zip archive with DLLs)
void setIsNative();
/// Attach a name suffix to the specified OS native
- void addNative(OpSys os, QString suffix);
+ void addNative(OpSys os, const QString &suffix);
+ /// Clears all suffixes
+ void clearSuffixes();
/// Set the load rules
void setRules(QList<std::shared_ptr<Rule>> rules);
/// Returns true if the library should be loaded (or extracted, in case of natives)
- bool isActive();
+ bool isActive() const;
/// Returns true if the library is native
- bool isNative();
+ bool isNative() const;
/// Get the URL to download the library from
- QString downloadUrl();
+ QString downloadUrl() const;
/// Get the relative path where the library should be saved
- QString storagePath();
+ QString storagePath() const;
/// set an absolute URL for the library. This is an MMC extension.
- void setAbsoluteUrl(QString absolute_url);
- QString absoluteUrl();
+ void setAbsoluteUrl(const QString &absolute_url);
+ QString absoluteUrl() const;
/// set a hint about how to treat the library. This is an MMC extension.
- void setHint(QString hint);
- QString hint();
+ void setHint(const QString &hint);
+ QString hint() const;
bool extractTo(QString target_dir);
bool filesExist();
diff --git a/logic/OneSixRule.cpp b/logic/OneSixRule.cpp
index 392b1dd1..d8d13b50 100644
--- a/logic/OneSixRule.cpp
+++ b/logic/OneSixRule.cpp
@@ -18,7 +18,7 @@
#include "OneSixRule.h"
-QList<std::shared_ptr<Rule>> rulesFromJsonV4(QJsonObject &objectWithRules)
+QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules)
{
QList<std::shared_ptr<Rule>> rules;
auto rulesVal = objectWithRules.value("rules");
@@ -86,4 +86,4 @@ RuleAction RuleAction_fromString(QString name)
if (name == "disallow")
return Disallow;
return Defer;
-} \ No newline at end of file
+}
diff --git a/logic/OneSixRule.h b/logic/OneSixRule.h
index 5a13cbd9..426e2886 100644
--- a/logic/OneSixRule.h
+++ b/logic/OneSixRule.h
@@ -27,7 +27,7 @@ enum RuleAction
};
RuleAction RuleAction_fromString(QString);
-QList<std::shared_ptr<Rule>> rulesFromJsonV4(QJsonObject &objectWithRules);
+QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules);
class Rule
{
diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp
index ae647bfe..d3ac80c2 100644
--- a/logic/OneSixUpdate.cpp
+++ b/logic/OneSixUpdate.cpp
@@ -35,8 +35,8 @@
#include "pathutils.h"
#include <JlCompress.h>
-OneSixUpdate::OneSixUpdate(BaseInstance *inst, bool only_prepare, QObject *parent)
- : Task(parent), m_inst(inst), m_only_prepare(only_prepare)
+OneSixUpdate::OneSixUpdate(BaseInstance *inst, QObject *parent)
+ : Task(parent), m_inst(inst)
{
}
@@ -52,12 +52,6 @@ void OneSixUpdate::executeTask()
return;
}
- if (m_only_prepare)
- {
- prepareForLaunch();
- return;
- }
-
if (m_inst->shouldUpdate())
{
// Get a pointer to the version object that corresponds to the instance's version.
@@ -137,7 +131,7 @@ void OneSixUpdate::versionFileFinished()
{
finfo.remove();
}
- inst->reloadFullVersion();
+ inst->reloadVersion();
jarlibStart();
}
@@ -222,7 +216,7 @@ void OneSixUpdate::assetIndexFailed()
void OneSixUpdate::assetsFinished()
{
- prepareForLaunch();
+ emitSucceeded();
}
void OneSixUpdate::assetsFailed()
@@ -235,7 +229,7 @@ void OneSixUpdate::jarlibStart()
setStatus(tr("Getting the library files from Mojang..."));
QLOG_INFO() << m_inst->name() << ": downloading libraries";
OneSixInstance *inst = (OneSixInstance *)m_inst;
- bool successful = inst->reloadFullVersion();
+ bool successful = inst->reloadVersion();
if (!successful)
{
emitFailed("Failed to load the version description file. It might be "
@@ -330,43 +324,3 @@ void OneSixUpdate::jarlibFailed()
emitFailed("Failed to download the following files:\n" + failed_all +
"\n\nPlease try again.");
}
-
-void OneSixUpdate::prepareForLaunch()
-{
- setStatus(tr("Preparing for launch..."));
- QLOG_INFO() << m_inst->name() << ": preparing for launch";
- auto onesix_inst = (OneSixInstance *)m_inst;
-
- // delete any leftovers, if they are present.
- onesix_inst->cleanupAfterRun();
-
- QString natives_dir_raw = PathCombine(onesix_inst->instanceRoot(), "natives/");
- auto version = onesix_inst->getFullVersion();
- if (!version)
- {
- emitFailed("The version information for this instance is not complete. Try re-creating "
- "it or changing the version.");
- return;
- }
- /*
- for (auto lib : version->getActiveNativeLibs())
- {
- if (!lib->filesExist())
- {
- emitFailed("Native library is missing some files:\n" + lib->storagePath() +
- "\n\nRun the instance at least once in online mode to get all the "
- "required files.");
- return;
- }
- if (!lib->extractTo(natives_dir_raw))
- {
- emitFailed("Could not extract the native library:\n" + lib->storagePath() + " to " +
- natives_dir_raw +
- "\n\nMake sure MultiMC has appropriate permissions and there is enough "
- "space on the storage device.");
- return;
- }
- }
-*/
- emitSucceeded();
-}
diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h
index bc717a94..3c18211e 100644
--- a/logic/OneSixUpdate.h
+++ b/logic/OneSixUpdate.h
@@ -29,7 +29,7 @@ class OneSixUpdate : public Task
{
Q_OBJECT
public:
- explicit OneSixUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent = 0);
+ explicit OneSixUpdate(BaseInstance *inst, QObject *parent = 0);
virtual void executeTask();
private
@@ -49,9 +49,6 @@ slots:
void assetsFinished();
void assetsFailed();
- // extract the appropriate libraries
- void prepareForLaunch();
-
private:
NetJobPtr specificVersionDownloadJob;
NetJobPtr jarlibDownloadJob;
@@ -59,5 +56,4 @@ private:
// target version, determined during this task
std::shared_ptr<MinecraftVersion> targetVersion;
BaseInstance *m_inst = nullptr;
- bool m_only_prepare = false;
};
diff --git a/logic/OneSixVersion.cpp b/logic/OneSixVersion.cpp
index 8ae685f0..fb32f3a8 100644
--- a/logic/OneSixVersion.cpp
+++ b/logic/OneSixVersion.cpp
@@ -13,228 +13,95 @@
* limitations under the License.
*/
-#include "logic/OneSixVersion.h"
-#include "logic/OneSixLibrary.h"
-#include "logic/OneSixRule.h"
+#include "OneSixVersion.h"
-#include "logger/QsLog.h"
+#include <QDebug>
+#include <QFile>
-std::shared_ptr<OneSixVersion> fromJsonV4(QJsonObject root,
- std::shared_ptr<OneSixVersion> fullVersion)
-{
- fullVersion->id = root.value("id").toString();
-
- fullVersion->mainClass = root.value("mainClass").toString();
- auto procArgsValue = root.value("processArguments");
- if (procArgsValue.isString())
- {
- fullVersion->processArguments = procArgsValue.toString();
- QString toCompare = fullVersion->processArguments.toLower();
- if (toCompare == "legacy")
- {
- fullVersion->minecraftArguments = " ${auth_player_name} ${auth_session}";
- }
- else if (toCompare == "username_session")
- {
- fullVersion->minecraftArguments =
- "--username ${auth_player_name} --session ${auth_session}";
- }
- else if (toCompare == "username_session_version")
- {
- fullVersion->minecraftArguments = "--username ${auth_player_name} "
- "--session ${auth_session} "
- "--version ${profile_name}";
- }
- }
-
- auto minecraftArgsValue = root.value("minecraftArguments");
- if (minecraftArgsValue.isString())
- {
- fullVersion->minecraftArguments = minecraftArgsValue.toString();
- }
+#include "OneSixVersionBuilder.h"
- auto minecraftTypeValue = root.value("type");
- if (minecraftTypeValue.isString())
- {
- fullVersion->type = minecraftTypeValue.toString();
- }
-
- fullVersion->releaseTime = root.value("releaseTime").toString();
- fullVersion->time = root.value("time").toString();
-
- auto assetsID = root.value("assets");
- if (assetsID.isString())
- {
- fullVersion->assets = assetsID.toString();
- }
- else
- {
- fullVersion->assets = "legacy";
- }
-
- QLOG_DEBUG() << "Assets version:" << fullVersion->assets;
-
- // Iterate through the list, if it's a list.
- auto librariesValue = root.value("libraries");
- if (!librariesValue.isArray())
- return fullVersion;
-
- QJsonArray libList = root.value("libraries").toArray();
- for (auto libVal : libList)
- {
- if (!libVal.isObject())
- {
- continue;
- }
-
- QJsonObject libObj = libVal.toObject();
-
- // Library name
- auto nameVal = libObj.value("name");
- if (!nameVal.isString())
- continue;
- std::shared_ptr<OneSixLibrary> library(new OneSixLibrary(nameVal.toString()));
-
- auto urlVal = libObj.value("url");
- if (urlVal.isString())
- {
- library->setBaseUrl(urlVal.toString());
- }
- auto hintVal = libObj.value("MMC-hint");
- if (hintVal.isString())
- {
- library->setHint(hintVal.toString());
- }
- auto urlAbsVal = libObj.value("MMC-absoluteUrl");
- auto urlAbsuVal = libObj.value("MMC-absulute_url"); // compatibility
- if (urlAbsVal.isString())
- {
- library->setAbsoluteUrl(urlAbsVal.toString());
- }
- else if (urlAbsuVal.isString())
- {
- library->setAbsoluteUrl(urlAbsuVal.toString());
- }
- // Extract excludes (if any)
- auto extractVal = libObj.value("extract");
- if (extractVal.isObject())
- {
- QStringList excludes;
- auto extractObj = extractVal.toObject();
- auto excludesVal = extractObj.value("exclude");
- if (excludesVal.isArray())
- {
- auto excludesList = excludesVal.toArray();
- for (auto excludeVal : excludesList)
- {
- if (excludeVal.isString())
- excludes.append(excludeVal.toString());
- }
- library->extract_excludes = excludes;
- }
- }
-
- auto nativesVal = libObj.value("natives");
- if (nativesVal.isObject())
- {
- library->setIsNative();
- auto nativesObj = nativesVal.toObject();
- auto iter = nativesObj.begin();
- while (iter != nativesObj.end())
- {
- auto osType = OpSys_fromString(iter.key());
- if (osType == Os_Other)
- continue;
- if (!iter.value().isString())
- continue;
- library->addNative(osType, iter.value().toString());
- iter++;
- }
- }
- library->setRules(rulesFromJsonV4(libObj));
- library->finalize();
- fullVersion->libraries.append(library);
- }
- return fullVersion;
+OneSixVersion::OneSixVersion(OneSixInstance *instance, QObject *parent)
+ : QAbstractListModel(parent), m_instance(instance)
+{
+ clear();
}
-std::shared_ptr<OneSixVersion> OneSixVersion::fromJson(QJsonObject root)
+bool OneSixVersion::reload(QWidget *widgetParent, const bool onlyVanilla)
{
- std::shared_ptr<OneSixVersion> readVersion(new OneSixVersion());
- int launcher_ver = readVersion->minimumLauncherVersion =
- root.value("minimumLauncherVersion").toDouble();
+ beginResetModel();
+ bool ret = OneSixVersionBuilder::build(this, m_instance, widgetParent, onlyVanilla);
+ endResetModel();
+ return ret;
+}
- // ADD MORE HERE :D
- if (launcher_ver > 0 && launcher_ver <= 13)
- return fromJsonV4(root, readVersion);
- else
- {
- return std::shared_ptr<OneSixVersion>();
- }
+void OneSixVersion::clear()
+{
+ beginResetModel();
+ id.clear();
+ time.clear();
+ releaseTime.clear();
+ type.clear();
+ assets.clear();
+ processArguments.clear();
+ minecraftArguments.clear();
+ minimumLauncherVersion = 0xDEADBEAF;
+ mainClass.clear();
+ libraries.clear();
+ tweakers.clear();
+ versionFiles.clear();
+ endResetModel();
}
-std::shared_ptr<OneSixVersion> OneSixVersion::fromFile(QString filepath)
+void OneSixVersion::dump() const
{
- QFile file(filepath);
- if (!file.open(QIODevice::ReadOnly))
+ qDebug().nospace() << "OneSixVersion("
+ << "\n\tid=" << id
+ << "\n\ttime=" << time
+ << "\n\treleaseTime=" << releaseTime
+ << "\n\ttype=" << type
+ << "\n\tassets=" << assets
+ << "\n\tprocessArguments=" << processArguments
+ << "\n\tminecraftArguments=" << minecraftArguments
+ << "\n\tminimumLauncherVersion=" << minimumLauncherVersion
+ << "\n\tmainClass=" << mainClass
+ << "\n\tlibraries=";
+ for (auto lib : libraries)
{
- return std::shared_ptr<OneSixVersion>();
+ qDebug().nospace() << "\n\t\t" << lib.get();
}
+ qDebug().nospace() << "\n)";
+}
- auto data = file.readAll();
- QJsonParseError jsonError;
- QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
-
- if (jsonError.error != QJsonParseError::NoError)
+bool OneSixVersion::canRemove(const int index) const
+{
+ if (index < versionFiles.size())
{
- return std::shared_ptr<OneSixVersion>();
+ return versionFiles.at(index).id != "org.multimc.version.json";
}
+ return false;
+}
- if (!jsonDoc.isObject())
+QString OneSixVersion::versionFileId(const int index) const
+{
+ if (index < 0 || index >= versionFiles.size())
{
- return std::shared_ptr<OneSixVersion>();
+ return QString();
}
- QJsonObject root = jsonDoc.object();
- auto version = fromJson(root);
- if (version)
- version->original_file = filepath;
- return version;
+ return versionFiles.at(index).id;
}
-bool OneSixVersion::toOriginalFile()
+bool OneSixVersion::remove(const int index)
{
- if (original_file.isEmpty())
- return false;
- QSaveFile file(original_file);
- if (!file.open(QIODevice::WriteOnly))
- {
- return false;
- }
- // serialize base attributes (those we care about anyway)
- QJsonObject root;
- root.insert("minecraftArguments", minecraftArguments);
- root.insert("mainClass", mainClass);
- root.insert("minimumLauncherVersion", minimumLauncherVersion);
- root.insert("time", time);
- root.insert("id", id);
- root.insert("type", type);
- // screw processArguments
- root.insert("releaseTime", releaseTime);
- QJsonArray libarray;
- for (const auto &lib : libraries)
+ if (canRemove(index))
{
- libarray.append(lib->toJson());
+ return QFile::remove(versionFiles.at(index).filename);
}
- if (libarray.count())
- root.insert("libraries", libarray);
- QJsonDocument doc(root);
- file.write(doc.toJson());
- return file.commit();
+ return false;
}
-QList<std::shared_ptr<OneSixLibrary>> OneSixVersion::getActiveNormalLibs()
+QList<std::shared_ptr<OneSixLibrary> > OneSixVersion::getActiveNormalLibs()
{
- QList<std::shared_ptr<OneSixLibrary>> output;
+ QList<std::shared_ptr<OneSixLibrary> > output;
for (auto lib : libraries)
{
if (lib->isActive() && !lib->isNative())
@@ -245,9 +112,9 @@ QList<std::shared_ptr<OneSixLibrary>> OneSixVersion::getActiveNormalLibs()
return output;
}
-QList<std::shared_ptr<OneSixLibrary>> OneSixVersion::getActiveNativeLibs()
+QList<std::shared_ptr<OneSixLibrary> > OneSixVersion::getActiveNativeLibs()
{
- QList<std::shared_ptr<OneSixLibrary>> output;
+ QList<std::shared_ptr<OneSixLibrary> > output;
for (auto lib : libraries)
{
if (lib->isActive() && lib->isNative())
@@ -258,14 +125,14 @@ QList<std::shared_ptr<OneSixLibrary>> OneSixVersion::getActiveNativeLibs()
return output;
}
-void OneSixVersion::externalUpdateStart()
-{
- beginResetModel();
-}
-
-void OneSixVersion::externalUpdateFinish()
+std::shared_ptr<OneSixVersion> OneSixVersion::fromJson(const QJsonObject &obj)
{
- endResetModel();
+ std::shared_ptr<OneSixVersion> version(new OneSixVersion(0));
+ if (OneSixVersionBuilder::read(version.get(), obj))
+ {
+ return version;
+ }
+ return 0;
}
QVariant OneSixVersion::data(const QModelIndex &index, int role) const
@@ -276,7 +143,7 @@ QVariant OneSixVersion::data(const QModelIndex &index, int role) const
int row = index.row();
int column = index.column();
- if (row < 0 || row >= libraries.size())
+ if (row < 0 || row >= versionFiles.size())
return QVariant();
if (role == Qt::DisplayRole)
@@ -284,11 +151,9 @@ QVariant OneSixVersion::data(const QModelIndex &index, int role) const
switch (column)
{
case 0:
- return libraries[row]->name();
+ return versionFiles.at(row).name;
case 1:
- return libraries[row]->type();
- case 2:
- return libraries[row]->version();
+ return versionFiles.at(row).version;
default:
return QVariant();
}
@@ -296,45 +161,61 @@ QVariant OneSixVersion::data(const QModelIndex &index, int role) const
return QVariant();
}
-Qt::ItemFlags OneSixVersion::flags(const QModelIndex &index) const
+QVariant OneSixVersion::headerData(int section, Qt::Orientation orientation, int role) const
{
- if (!index.isValid())
- return Qt::NoItemFlags;
- int row = index.row();
- if (libraries[row]->isActive())
- {
- return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren;
- }
- else
+ if (orientation == Qt::Horizontal)
{
- return Qt::ItemNeverHasChildren;
+ if (role == Qt::DisplayRole)
+ {
+ switch (section)
+ {
+ case 0:
+ return tr("Name");
+ case 1:
+ return tr("Version");
+ default:
+ return QVariant();
+ }
+ }
}
- // return QAbstractListModel::flags(index);
+ return QVariant();
}
-QVariant OneSixVersion::headerData(int section, Qt::Orientation orientation, int role) const
+Qt::ItemFlags OneSixVersion::flags(const QModelIndex &index) const
{
- if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
- return QVariant();
- switch (section)
- {
- case 0:
- return QString("Name");
- case 1:
- return QString("Type");
- case 2:
- return QString("Version");
- default:
- return QString();
- }
+ if (!index.isValid())
+ return Qt::NoItemFlags;
+ return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
int OneSixVersion::rowCount(const QModelIndex &parent) const
{
- return libraries.size();
+ return versionFiles.size();
}
int OneSixVersion::columnCount(const QModelIndex &parent) const
{
- return 3;
+ return 2;
+}
+
+QDebug operator<<(QDebug &dbg, const OneSixVersion *version)
+{
+ version->dump();
+ return dbg.maybeSpace();
+}
+QDebug operator<<(QDebug &dbg, const OneSixLibrary *library)
+{
+ dbg.nospace() << "OneSixLibrary("
+ << "\n\t\t\trawName=" << library->rawName()
+ << "\n\t\t\tname=" << library->name()
+ << "\n\t\t\tversion=" << library->version()
+ << "\n\t\t\ttype=" << library->type()
+ << "\n\t\t\tisActive=" << library->isActive()
+ << "\n\t\t\tisNative=" << library->isNative()
+ << "\n\t\t\tdownloadUrl=" << library->downloadUrl()
+ << "\n\t\t\tstoragePath=" << library->storagePath()
+ << "\n\t\t\tabsolutePath=" << library->absoluteUrl()
+ << "\n\t\t\thint=" << library->hint();
+ dbg.nospace() << "\n\t\t)";
+ return dbg.maybeSpace();
}
diff --git a/logic/OneSixVersion.h b/logic/OneSixVersion.h
index 036f3d53..ba7695d5 100644
--- a/logic/OneSixVersion.h
+++ b/logic/OneSixVersion.h
@@ -14,40 +14,50 @@
*/
#pragma once
-#include <QtCore>
+
+#include <QAbstractListModel>
+
+#include <QString>
+#include <QList>
#include <memory>
-class OneSixLibrary;
+#include "OneSixLibrary.h"
+
+class OneSixInstance;
class OneSixVersion : public QAbstractListModel
{
- // Things required to implement the Qt list model
+ Q_OBJECT
public:
+ explicit OneSixVersion(OneSixInstance *instance, QObject *parent = 0);
+
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
- virtual QVariant headerData(int section, Qt::Orientation orientation,
- int role = Qt::DisplayRole) const;
virtual int columnCount(const QModelIndex &parent) const;
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
- // serialization/deserialization
-public:
- bool toOriginalFile();
- static std::shared_ptr<OneSixVersion> fromJson(QJsonObject root);
- static std::shared_ptr<OneSixVersion> fromFile(QString filepath);
+ bool reload(QWidget *widgetParent, const bool onlyVanilla = false);
+ void clear();
+
+ void dump() const;
+
+ bool canRemove(const int index) const;
+
+ QString versionFileId(const int index) const;
+
+public
+slots:
+ bool remove(const int index);
public:
QList<std::shared_ptr<OneSixLibrary>> getActiveNormalLibs();
QList<std::shared_ptr<OneSixLibrary>> getActiveNativeLibs();
- // called when something starts/stops messing with the object
- // FIXME: these are ugly in every possible way.
- void externalUpdateStart();
- void externalUpdateFinish();
+
+ static std::shared_ptr<OneSixVersion> fromJson(const QJsonObject &obj);
// data members
public:
- /// file this was read from. blank, if none
- QString original_file;
/// the ID - determines which jar to use! ACTUALLY IMPORTANT!
QString id;
/// Last updated time - as a string
@@ -76,6 +86,10 @@ public:
*/
int minimumLauncherVersion = 0xDEADBEEF;
/**
+ * A list of all tweaker classes
+ */
+ QStringList tweakers;
+ /**
* The main class to load first
*/
QString mainClass;
@@ -103,4 +117,21 @@ public:
}
*/
// QList<Rule> rules;
+
+ struct VersionFile
+ {
+ QString name;
+ QString id;
+ QString version;
+ QString mcVersion;
+ QString filename;
+ int order;
+ };
+ QList<VersionFile> versionFiles;
+
+private:
+ OneSixInstance *m_instance;
};
+
+QDebug operator<<(QDebug &dbg, const OneSixVersion *version);
+QDebug operator<<(QDebug &dbg, const OneSixLibrary *library);
diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp
new file mode 100644
index 00000000..bbd33ddc
--- /dev/null
+++ b/logic/OneSixVersionBuilder.cpp
@@ -0,0 +1,1077 @@
+/* Copyright 2013 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 "OneSixVersionBuilder.h"
+
+#include <QList>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QFile>
+#include <QFileInfo>
+#include <QMessageBox>
+#include <QObject>
+#include <QDir>
+#include <QDebug>
+
+#include "OneSixVersion.h"
+#include "OneSixInstance.h"
+#include "OneSixRule.h"
+#include "modutils.h"
+#include "logger/QsLog.h"
+
+struct VersionFile
+{
+ int order;
+ QString name;
+ QString fileId;
+ QString version;
+ // TODO use the mcVersion to determine if a version file should be removed on update
+ QString mcVersion;
+ QString filename;
+ // TODO requirements
+ // QMap<QString, QString> requirements;
+ QString id;
+ QString mainClass;
+ QString overwriteMinecraftArguments;
+ QString addMinecraftArguments;
+ QString removeMinecraftArguments;
+ QString processArguments;
+ QString type;
+ QString releaseTime;
+ QString time;
+ QString assets;
+ int minimumLauncherVersion = -1;
+
+ bool shouldOverwriteTweakers = false;
+ QStringList overwriteTweakers;
+ QStringList addTweakers;
+ QStringList removeTweakers;
+
+ struct Library
+ {
+ QString name;
+ QString url;
+ QString hint;
+ QString absoluteUrl;
+ bool applyExcludes = false;
+ QStringList excludes;
+ bool applyNatives = false;
+ QList<QPair<OpSys, QString>> natives;
+ bool applyRules = false;
+ QList<std::shared_ptr<Rule>> rules;
+
+ // user for '+' libraries
+ enum InsertType
+ {
+ Apply,
+ Append,
+ Prepend,
+ Replace
+ };
+ InsertType insertType = Append;
+ QString insertData;
+ enum DependType
+ {
+ Soft,
+ Hard
+ };
+ DependType dependType = Soft;
+ };
+ bool shouldOverwriteLibs = false;
+ QList<Library> overwriteLibs;
+ QList<Library> addLibs;
+ QList<QString> removeLibs;
+
+ static Library fromLibraryJson(const QJsonObject &libObj, const QString &filename,
+ bool &isError)
+ {
+ isError = true;
+ Library out;
+ if (!libObj.contains("name"))
+ {
+ QLOG_ERROR() << filename << "contains a library that doesn't have a 'name' field";
+ return out;
+ }
+ out.name = libObj.value("name").toString();
+
+ auto readString = [libObj, filename](const QString &key, QString &variable)
+ {
+ if (libObj.contains(key))
+ {
+ QJsonValue val = libObj.value(key);
+ if (!val.isString())
+ {
+ QLOG_WARN() << key << "is not a string in" << filename << "(skipping)";
+ }
+ else
+ {
+ variable = val.toString();
+ }
+ }
+ };
+
+ readString("url", out.url);
+ readString("MMC-hint", out.hint);
+ readString("MMC-absulute_url", out.absoluteUrl);
+ readString("MMC-absoluteUrl", out.absoluteUrl);
+ if (libObj.contains("extract"))
+ {
+ if (!libObj.value("extract").isObject())
+ {
+ QLOG_ERROR()
+ << filename
+ << "contains a library with an 'extract' field that's not an object";
+ return out;
+ }
+ QJsonObject extractObj = libObj.value("extract").toObject();
+ if (!extractObj.contains("exclude") || !extractObj.value("exclude").isArray())
+ {
+ QLOG_ERROR() << filename
+ << "contains a library with an invalid 'extract' field";
+ return out;
+ }
+ out.applyExcludes = true;
+ QJsonArray excludeArray = extractObj.value("exclude").toArray();
+ for (auto excludeVal : excludeArray)
+ {
+ if (!excludeVal.isString())
+ {
+ QLOG_WARN() << filename << "contains a library that contains an 'extract' "
+ "field that contains an invalid 'exclude' entry "
+ "(skipping)";
+ }
+ else
+ {
+ out.excludes.append(excludeVal.toString());
+ }
+ }
+ }
+ if (libObj.contains("natives"))
+ {
+ if (!libObj.value("natives").isObject())
+ {
+ QLOG_ERROR()
+ << filename
+ << "contains a library with a 'natives' field that's not an object";
+ return out;
+ }
+ out.applyNatives = true;
+ QJsonObject nativesObj = libObj.value("natives").toObject();
+ for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it)
+ {
+ if (!it.value().isString())
+ {
+ QLOG_WARN() << filename << "contains an invalid native (skipping)";
+ }
+ OpSys opSys = OpSys_fromString(it.key());
+ if (opSys != Os_Other)
+ {
+ out.natives.append(qMakePair(opSys, it.value().toString()));
+ }
+ }
+ }
+ if (libObj.contains("rules"))
+ {
+ out.applyRules = true;
+ out.rules = rulesFromJsonV4(libObj);
+ }
+ isError = false;
+ return out;
+ }
+ static VersionFile fromJson(const QJsonDocument &doc, const QString &filename,
+ const bool requireOrder, bool &isError)
+ {
+ VersionFile out;
+ isError = true;
+ if (doc.isEmpty() || doc.isNull())
+ {
+ QLOG_ERROR() << filename << "is empty or null";
+ return out;
+ }
+ if (!doc.isObject())
+ {
+ QLOG_ERROR() << "The root of" << filename << "is not an object";
+ return out;
+ }
+
+ QJsonObject root = doc.object();
+
+ if (requireOrder)
+ {
+ if (root.contains("order"))
+ {
+ if (root.value("order").isDouble())
+ {
+ out.order = root.value("order").toDouble();
+ }
+ else
+ {
+ QLOG_ERROR() << "'order' field contains an invalid value in" << filename;
+ return out;
+ }
+ }
+ else
+ {
+ QLOG_ERROR() << filename << "doesn't contain an order field";
+ }
+ }
+
+ out.name = root.value("name").toString();
+ out.fileId = root.value("fileId").toString();
+ out.version = root.value("version").toString();
+ out.mcVersion = root.value("mcVersion").toString();
+ out.filename = filename;
+
+ auto readString = [root, filename](const QString &key, QString &variable)
+ {
+ if (root.contains(key))
+ {
+ QJsonValue val = root.value(key);
+ if (!val.isString())
+ {
+ QLOG_WARN() << key << "is not a string in" << filename << "(skipping)";
+ }
+ else
+ {
+ variable = val.toString();
+ }
+ }
+ };
+
+ readString("id", out.id);
+ readString("mainClass", out.mainClass);
+ readString("processArguments", out.processArguments);
+ readString("minecraftArguments", out.overwriteMinecraftArguments);
+ readString("+minecraftArguments", out.addMinecraftArguments);
+ readString("-minecraftArguments", out.removeMinecraftArguments);
+ readString("type", out.type);
+ readString("releaseTime", out.releaseTime);
+ readString("time", out.time);
+ readString("assets", out.assets);
+ if (root.contains("minimumLauncherVersion"))
+ {
+ QJsonValue val = root.value("minimumLauncherVersion");
+ if (!val.isDouble())
+ {
+ QLOG_WARN() << "minimumLauncherVersion is not an int in" << filename
+ << "(skipping)";
+ }
+ else
+ {
+ out.minimumLauncherVersion = val.toDouble();
+ }
+ }
+
+ if (root.contains("tweakers"))
+ {
+ QJsonValue tweakersVal = root.value("tweakers");
+ if (!tweakersVal.isArray())
+ {
+ QLOG_ERROR() << filename
+ << "contains a 'tweakers' field, but it's not an array";
+ return out;
+ }
+ out.shouldOverwriteTweakers = true;
+ QJsonArray tweakers = root.value("tweakers").toArray();
+ for (auto tweakerVal : tweakers)
+ {
+ if (!tweakerVal.isString())
+ {
+ QLOG_ERROR() << filename
+ << "contains a 'tweakers' field entry that's not a string";
+ return out;
+ }
+ out.overwriteTweakers.append(tweakerVal.toString());
+ }
+ }
+ if (root.contains("+tweakers"))
+ {
+ QJsonValue tweakersVal = root.value("+tweakers");
+ if (!tweakersVal.isArray())
+ {
+ QLOG_ERROR() << filename
+ << "contains a '+tweakers' field, but it's not an array";
+ return out;
+ }
+ QJsonArray tweakers = root.value("+tweakers").toArray();
+ for (auto tweakerVal : tweakers)
+ {
+ if (!tweakerVal.isString())
+ {
+ QLOG_ERROR() << filename
+ << "contains a '+tweakers' field entry that's not a string";
+ return out;
+ }
+ out.addTweakers.append(tweakerVal.toString());
+ }
+ }
+ if (root.contains("-tweakers"))
+ {
+ QJsonValue tweakersVal = root.value("-tweakers");
+ if (!tweakersVal.isArray())
+ {
+ QLOG_ERROR() << filename
+ << "contains a '-tweakers' field, but it's not an array";
+ return out;
+ }
+ out.shouldOverwriteTweakers = true;
+ QJsonArray tweakers = root.value("-tweakers").toArray();
+ for (auto tweakerVal : tweakers)
+ {
+ if (!tweakerVal.isString())
+ {
+ QLOG_ERROR() << filename
+ << "contains a '-tweakers' field entry that's not a string";
+ return out;
+ }
+ out.removeTweakers.append(tweakerVal.toString());
+ }
+ }
+
+ if (root.contains("libraries"))
+ {
+ out.shouldOverwriteLibs = true;
+ QJsonValue librariesVal = root.value("libraries");
+ if (!librariesVal.isArray())
+ {
+ QLOG_ERROR() << filename
+ << "contains a 'libraries' field, but its not an array";
+ return out;
+ }
+ QJsonArray librariesArray = librariesVal.toArray();
+ for (auto libVal : librariesArray)
+ {
+ if (!libVal.isObject())
+ {
+ QLOG_ERROR() << filename << "contains a library that's not an object";
+ return out;
+ }
+ QJsonObject libObj = libVal.toObject();
+ bool error;
+ Library lib = fromLibraryJson(libObj, filename, error);
+ if (error)
+ {
+ QLOG_ERROR() << "Error while reading a library entry in" << filename;
+ return out;
+ }
+ out.overwriteLibs.append(lib);
+ }
+ }
+ if (root.contains("+libraries"))
+ {
+ QJsonValue librariesVal = root.value("+libraries");
+ if (!librariesVal.isArray())
+ {
+ QLOG_ERROR() << filename
+ << "contains a '+libraries' field, but its not an array";
+ return out;
+ }
+ QJsonArray librariesArray = librariesVal.toArray();
+ for (auto libVal : librariesArray)
+ {
+ if (!libVal.isObject())
+ {
+ QLOG_ERROR() << filename << "contains a library that's not an object";
+ return out;
+ }
+ QJsonObject libObj = libVal.toObject();
+ bool error;
+ Library lib = fromLibraryJson(libObj, filename, error);
+ if (error)
+ {
+ QLOG_ERROR() << "Error while reading a library entry in" << filename;
+ return out;
+ }
+ if (!libObj.contains("insert"))
+ {
+ QLOG_ERROR() << "Missing 'insert' field in '+libraries' field in"
+ << filename;
+ return out;
+ }
+ QJsonValue insertVal = libObj.value("insert");
+ QString insertString;
+ {
+ if (insertVal.isString())
+ {
+ insertString = insertVal.toString();
+ }
+ else if (insertVal.isObject())
+ {
+ QJsonObject insertObj = insertVal.toObject();
+ if (insertObj.isEmpty())
+ {
+ QLOG_ERROR() << "One library has an empty insert object in"
+ << filename;
+ return out;
+ }
+ insertString = insertObj.keys().first();
+ lib.insertData = insertObj.value(insertString).toString();
+ }
+ }
+ if (insertString == "apply")
+ {
+ lib.insertType = Library::Apply;
+ }
+ else if (insertString == "prepend")
+ {
+ lib.insertType = Library::Prepend;
+ }
+ else if (insertString == "append")
+ {
+ lib.insertType = Library::Prepend;
+ }
+ else if (insertString == "replace")
+ {
+ lib.insertType = Library::Replace;
+ }
+ else
+ {
+ QLOG_ERROR() << "A '+' library in" << filename
+ << "contains an invalid insert type";
+ return out;
+ }
+ if (libObj.contains("MMC-depend") && libObj.value("MMC-depend").isString())
+ {
+ const QString dependString = libObj.value("MMC-depend").toString();
+ if (dependString == "hard")
+ {
+ lib.dependType = Library::Hard;
+ }
+ else if (dependString == "soft")
+ {
+ lib.dependType = Library::Soft;
+ }
+ else
+ {
+ QLOG_ERROR() << "A '+' library in" << filename
+ << "contains an invalid depend type";
+ return out;
+ }
+ }
+ out.addLibs.append(lib);
+ }
+ }
+ if (root.contains("-libraries"))
+ {
+ QJsonValue librariesVal = root.value("-libraries");
+ if (!librariesVal.isArray())
+ {
+ QLOG_ERROR() << filename
+ << "contains a '-libraries' field, but its not an array";
+ return out;
+ }
+ QJsonArray librariesArray = librariesVal.toArray();
+ for (auto libVal : librariesArray)
+ {
+ if (!libVal.isObject())
+ {
+ QLOG_ERROR() << filename << "contains a library that's not an object";
+ return out;
+ }
+ QJsonObject libObj = libVal.toObject();
+ if (!libObj.contains("name"))
+ {
+ QLOG_ERROR() << filename << "contains a library without a name";
+ return out;
+ }
+ if (!libObj.value("name").isString())
+ {
+ QLOG_ERROR() << filename
+ << "contains a library without a valid 'name' field";
+ return out;
+ }
+ out.removeLibs.append(libObj.value("name").toString());
+ }
+ }
+
+ isError = false;
+ return out;
+ }
+
+ static std::shared_ptr<OneSixLibrary> createLibrary(const Library &lib)
+ {
+ std::shared_ptr<OneSixLibrary> out(new OneSixLibrary(lib.name));
+ if (!lib.url.isEmpty())
+ {
+ out->setBaseUrl(lib.url);
+ }
+ out->setHint(lib.hint);
+ if (!lib.absoluteUrl.isEmpty())
+ {
+ out->setAbsoluteUrl(lib.absoluteUrl);
+ }
+ out->setAbsoluteUrl(lib.absoluteUrl);
+ out->extract_excludes = lib.excludes;
+ for (auto native : lib.natives)
+ {
+ out->addNative(native.first, native.second);
+ }
+ out->setRules(lib.rules);
+ out->finalize();
+ return out;
+ }
+ int findLibrary(QList<std::shared_ptr<OneSixLibrary>> haystack, const QString &needle)
+ {
+ for (int i = 0; i < haystack.size(); ++i)
+ {
+ if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix)
+ .indexIn(haystack.at(i)->rawName()) != -1)
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+ void applyTo(OneSixVersion *version, bool &isError)
+ {
+ isError = true;
+ if (!version->id.isNull() && !mcVersion.isNull())
+ {
+ if (QRegExp(mcVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(version->id) == -1)
+ {
+ QLOG_ERROR() << filename << "is for a different version of Minecraft";
+ return;
+ }
+ }
+
+ if (!id.isNull())
+ {
+ version->id = id;
+ }
+ if (!mainClass.isNull())
+ {
+ version->mainClass = mainClass;
+ }
+ if (!processArguments.isNull())
+ {
+ version->processArguments = processArguments;
+ }
+ if (!type.isNull())
+ {
+ version->type = type;
+ }
+ if (!releaseTime.isNull())
+ {
+ version->releaseTime = releaseTime;
+ }
+ if (!time.isNull())
+ {
+ version->time = time;
+ }
+ if (!assets.isNull())
+ {
+ version->assets = assets;
+ }
+ if (minimumLauncherVersion >= 0)
+ {
+ version->minimumLauncherVersion = minimumLauncherVersion;
+ }
+ if (!overwriteMinecraftArguments.isNull())
+ {
+ version->minecraftArguments = overwriteMinecraftArguments;
+ }
+ if (!addMinecraftArguments.isNull())
+ {
+ version->minecraftArguments += addMinecraftArguments;
+ }
+ if (!removeMinecraftArguments.isNull())
+ {
+ version->minecraftArguments.remove(removeMinecraftArguments);
+ }
+ if (shouldOverwriteTweakers)
+ {
+ version->tweakers = overwriteTweakers;
+ }
+ for (auto tweaker : addTweakers)
+ {
+ version->tweakers += tweaker;
+ }
+ for (auto tweaker : removeTweakers)
+ {
+ version->tweakers.removeAll(tweaker);
+ }
+ if (shouldOverwriteLibs)
+ {
+ version->libraries.clear();
+ for (auto lib : overwriteLibs)
+ {
+ version->libraries.append(createLibrary(lib));
+ }
+ }
+ for (auto lib : addLibs)
+ {
+ switch (lib.insertType)
+ {
+ case Library::Apply:
+ {
+
+ int index = findLibrary(version->libraries, lib.name);
+ if (index >= 0)
+ {
+ auto library = version->libraries[index];
+ if (!lib.url.isNull())
+ {
+ library->setBaseUrl(lib.url);
+ }
+ if (!lib.hint.isNull())
+ {
+ library->setHint(lib.hint);
+ }
+ if (!lib.absoluteUrl.isNull())
+ {
+ library->setAbsoluteUrl(lib.absoluteUrl);
+ }
+ if (lib.applyExcludes)
+ {
+ library->extract_excludes = lib.excludes;
+ }
+ if (lib.applyNatives)
+ {
+ library->clearSuffixes();
+ for (auto native : lib.natives)
+ {
+ library->addNative(native.first, native.second);
+ }
+ }
+ if (lib.applyRules)
+ {
+ library->setRules(lib.rules);
+ }
+ library->finalize();
+ }
+ else
+ {
+ QLOG_WARN() << "Couldn't find" << lib.insertData << "(skipping)";
+ }
+ break;
+ }
+ case Library::Append:
+ case Library::Prepend:
+ {
+
+ const int startOfVersion = lib.name.lastIndexOf(':') + 1;
+ const int index =
+ findLibrary(version->libraries,
+ QString(lib.name).replace(startOfVersion, INT_MAX, '*'));
+ if (index < 0)
+ {
+ if (lib.insertType == Library::Append)
+ {
+ version->libraries.append(createLibrary(lib));
+ }
+ else
+ {
+ version->libraries.prepend(createLibrary(lib));
+ }
+ }
+ else
+ {
+ auto otherLib = version->libraries.at(index);
+ const Util::Version ourVersion = lib.name.mid(startOfVersion, INT_MAX);
+ const Util::Version otherVersion = otherLib->version();
+ // if the existing version is a hard dependency we can either use it or
+ // fail, but we can't change it
+ if (otherLib->dependType == OneSixLibrary::Hard)
+ {
+ // we need a higher version, or we're hard to and the versions aren't
+ // equal
+ if (ourVersion > otherVersion ||
+ (lib.dependType == Library::Hard && ourVersion != otherVersion))
+ {
+ QLOG_ERROR() << "Error resolving library dependencies between"
+ << otherLib->rawName() << "and" << lib.name << "in"
+ << filename;
+ return;
+ }
+ else
+ {
+ // the library is already existing, so we don't have to do anything
+ }
+ }
+ else if (otherLib->dependType == OneSixLibrary::Soft)
+ {
+ // if we are higher it means we should update
+ if (ourVersion > otherVersion)
+ {
+ auto library = createLibrary(lib);
+ if (Util::Version(otherLib->minVersion) < ourVersion)
+ {
+ library->minVersion = ourVersion.toString();
+ }
+ version->libraries.replace(index, library);
+ }
+ else
+ {
+ // our version is smaller than the existing version, but we require
+ // it: fail
+ if (lib.dependType == Library::Hard)
+ {
+ QLOG_ERROR() << "Error resolving library dependencies between"
+ << otherLib->rawName() << "and" << lib.name << "in"
+ << filename;
+ return;
+ }
+ }
+ }
+ }
+ break;
+ }
+ case Library::Replace:
+ {
+ int index = findLibrary(version->libraries, lib.insertData);
+ if (index >= 0)
+ {
+ version->libraries.replace(index, createLibrary(lib));
+ }
+ else
+ {
+ QLOG_WARN() << "Couldn't find" << lib.insertData << "(skipping)";
+ }
+ break;
+ }
+ }
+ }
+ for (auto lib : removeLibs)
+ {
+ int index = findLibrary(version->libraries, lib);
+ if (index >= 0)
+ {
+ version->libraries.removeAt(index);
+ }
+ else
+ {
+ QLOG_WARN() << "Couldn't find" << lib << "(skipping)";
+ }
+ }
+
+ OneSixVersion::VersionFile versionFile;
+ versionFile.name = name;
+ versionFile.id = fileId;
+ versionFile.version = this->version;
+ versionFile.mcVersion = mcVersion;
+ versionFile.filename = filename;
+ versionFile.order = order;
+ version->versionFiles.append(versionFile);
+
+ isError = false;
+ }
+};
+
+OneSixVersionBuilder::OneSixVersionBuilder()
+{
+}
+
+bool OneSixVersionBuilder::build(OneSixVersion *version, OneSixInstance *instance,
+ QWidget *widgetParent, const bool onlyVanilla)
+{
+ OneSixVersionBuilder builder;
+ builder.m_version = version;
+ builder.m_instance = instance;
+ builder.m_widgetParent = widgetParent;
+ return builder.build(onlyVanilla);
+}
+
+bool OneSixVersionBuilder::read(OneSixVersion *version, const QJsonObject &obj)
+{
+ OneSixVersionBuilder builder;
+ builder.m_version = version;
+ builder.m_instance = 0;
+ builder.m_widgetParent = 0;
+ return builder.read(obj);
+}
+
+bool OneSixVersionBuilder::build(const bool onlyVanilla)
+{
+ m_version->clear();
+
+ QDir root(m_instance->instanceRoot());
+ QDir patches(root.absoluteFilePath("patches/"));
+
+ if (QFile::exists(root.absoluteFilePath("custom.json")))
+ {
+ QLOG_INFO() << "Reading custom.json";
+ VersionFile file;
+ if (!read(QFileInfo(root.absoluteFilePath("custom.json")), false, &file))
+ {
+ return false;
+ }
+ file.name = "custom.json";
+ file.filename = "custom.json";
+ file.fileId = "org.multimc.custom.json";
+ file.version = QString();
+ bool isError = false;
+ file.applyTo(m_version, isError);
+ if (isError)
+ {
+ QMessageBox::critical(
+ m_widgetParent, QObject::tr("Error"),
+ QObject::tr(
+ "Error while applying %1. Please check MultiMC-0.log for more info.")
+ .arg(root.absoluteFilePath("custom.json")));
+ return false;
+ }
+ }
+ else
+ {
+ // version.json -> patches/*.json -> user.json
+
+ // version.json
+ {
+ QLOG_INFO() << "Reading version.json";
+ VersionFile file;
+ if (!read(QFileInfo(root.absoluteFilePath("version.json")), false, &file))
+ {
+ return false;
+ }
+ file.name = "version.json";
+ file.fileId = "org.multimc.version.json";
+ file.version = m_instance->intendedVersionId();
+ file.mcVersion = m_instance->intendedVersionId();
+ bool isError = false;
+ file.applyTo(m_version, isError);
+ if (isError)
+ {
+ QMessageBox::critical(
+ m_widgetParent, QObject::tr("Error"),
+ QObject::tr(
+ "Error while applying %1. Please check MultiMC-0.log for more info.")
+ .arg(root.absoluteFilePath("version.json")));
+ return false;
+ }
+ }
+
+ if (!onlyVanilla)
+ {
+
+ // patches/
+ {
+ // load all, put into map for ordering, apply in the right order
+ QMap<QString, int> overrideOrder = readOverrideOrders(m_instance);
+
+ QMap<int, QPair<QString, VersionFile>> files;
+ for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files))
+ {
+ QLOG_INFO() << "Reading" << info.fileName();
+ VersionFile file;
+ if (!read(info, true, &file))
+ {
+ return false;
+ }
+ if (overrideOrder.contains(file.fileId))
+ {
+ file.order = overrideOrder.value(file.fileId);
+ }
+ if (files.contains(file.order))
+ {
+ QLOG_ERROR() << file.fileId << "has the same order as" << files[file.order].second.fileId;
+ return false;
+ }
+ files.insert(file.order, qMakePair(info.fileName(), file));
+ }
+ for (auto order : files.keys())
+ {
+ QLOG_DEBUG() << "Applying file with order" << order;
+ auto filePair = files[order];
+ bool isError = false;
+ filePair.second.applyTo(m_version, isError);
+ if (isError)
+ {
+ QMessageBox::critical(
+ m_widgetParent, QObject::tr("Error"),
+ QObject::tr("Error while applying %1. Please check MultiMC-0.log "
+ "for more info.").arg(filePair.first));
+ return false;
+ }
+ }
+ }
+
+#if 0
+ // user.json
+ {
+ if (QFile::exists(root.absoluteFilePath("user.json")))
+ {
+ QLOG_INFO() << "Reading user.json";
+ VersionFile file;
+ if (!read(QFileInfo(root.absoluteFilePath("user.json")), false, &file))
+ {
+ return false;
+ }
+ file.name = "user.json";
+ file.fileId = "org.multimc.user.json";
+ file.version = QString();
+ file.mcVersion = QString();
+ bool isError = false;
+ file.applyTo(m_version, isError);
+ if (isError)
+ {
+ QMessageBox::critical(
+ m_widgetParent, QObject::tr("Error"),
+ QObject::tr(
+ "Error while applying %1. Please check MultiMC-0.log for more info.")
+ .arg(root.absoluteFilePath("user.json")));
+ return false;
+ }
+ }
+ }
+#endif
+ }
+ }
+
+ // some final touches
+ {
+ if (m_version->assets.isEmpty())
+ {
+ m_version->assets = "legacy";
+ }
+ if (m_version->minecraftArguments.isEmpty())
+ {
+ QString toCompare = m_version->processArguments.toLower();
+ if (toCompare == "legacy")
+ {
+ m_version->minecraftArguments = " ${auth_player_name} ${auth_session}";
+ }
+ else if (toCompare == "username_session")
+ {
+ m_version->minecraftArguments =
+ "--username ${auth_player_name} --session ${auth_session}";
+ }
+ else if (toCompare == "username_session_version")
+ {
+ m_version->minecraftArguments = "--username ${auth_player_name} "
+ "--session ${auth_session} "
+ "--version ${profile_name}";
+ }
+ }
+ }
+
+ return true;
+}
+
+bool OneSixVersionBuilder::read(const QJsonObject &obj)
+{
+ m_version->clear();
+
+ bool isError = false;
+ VersionFile file = VersionFile::fromJson(QJsonDocument(obj), QString(), false, isError);
+ if (isError)
+ {
+ QMessageBox::critical(
+ m_widgetParent, QObject::tr("Error"),
+ QObject::tr("Error while reading. Please check MultiMC-0.log for more info."));
+ return false;
+ }
+ file.applyTo(m_version, isError);
+ if (isError)
+ {
+ QMessageBox::critical(
+ m_widgetParent, QObject::tr("Error"),
+ QObject::tr("Error while applying. Please check MultiMC-0.log for more info."));
+ return false;
+ }
+
+ return true;
+}
+
+bool OneSixVersionBuilder::read(const QFileInfo &fileInfo, const bool requireOrder,
+ VersionFile *out)
+{
+ QFile file(fileInfo.absoluteFilePath());
+ if (!file.open(QFile::ReadOnly))
+ {
+ QMessageBox::critical(
+ m_widgetParent, QObject::tr("Error"),
+ QObject::tr("Unable to open %1: %2").arg(file.fileName(), file.errorString()));
+ return false;
+ }
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
+ if (error.error != QJsonParseError::NoError)
+ {
+ QMessageBox::critical(m_widgetParent, QObject::tr("Error"),
+ QObject::tr("Unable to parse %1: %2 at %3")
+ .arg(file.fileName(), error.errorString())
+ .arg(error.offset));
+ return false;
+ }
+ bool isError = false;
+ *out = VersionFile::fromJson(doc, file.fileName(), requireOrder, isError);
+ if (isError)
+ {
+ QMessageBox::critical(
+ m_widgetParent, QObject::tr("Error"),
+ QObject::tr("Error while reading %1. Please check MultiMC-0.log for more info.")
+ .arg(file.fileName()));
+ ;
+ }
+ return true;
+}
+
+QMap<QString, int> OneSixVersionBuilder::readOverrideOrders(OneSixInstance *instance)
+{
+ QMap<QString, int> out;
+ if (QDir(instance->instanceRoot()).exists("order.json"))
+ {
+ QFile orderFile(instance->instanceRoot() + "/order.json");
+ if (!orderFile.open(QFile::ReadOnly))
+ {
+ QLOG_ERROR() << "Couldn't open" << orderFile.fileName() << " for reading:" << orderFile.errorString();
+ QLOG_WARN() << "Ignoring overriden order";
+ }
+ else
+ {
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error);
+ if (error.error != QJsonParseError::NoError || !doc.isObject())
+ {
+ QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString();
+ QLOG_WARN() << "Ignoring overriden order";
+ }
+ else
+ {
+ QJsonObject obj = doc.object();
+ for (auto it = obj.begin(); it != obj.end(); ++it)
+ {
+ if (it.key().startsWith("org.multimc."))
+ {
+ continue;
+ }
+ out.insert(it.key(), it.value().toDouble());
+ }
+ }
+ }
+ }
+ return out;
+}
+bool OneSixVersionBuilder::writeOverrideOrders(const QMap<QString, int> &order, OneSixInstance *instance)
+{
+ QJsonObject obj;
+ for (auto it = order.cbegin(); it != order.cend(); ++it)
+ {
+ if (it.key().startsWith("org.multimc."))
+ {
+ continue;
+ }
+ obj.insert(it.key(), it.value());
+ }
+ QFile orderFile(instance->instanceRoot() + "/order.json");
+ if (!orderFile.open(QFile::WriteOnly))
+ {
+ QLOG_ERROR() << "Couldn't open" << orderFile.fileName() << "for writing:" << orderFile.errorString();
+ return false;
+ }
+ orderFile.write(QJsonDocument(obj).toJson(QJsonDocument::Indented));
+ return true;
+}
diff --git a/logic/OneSixVersionBuilder.h b/logic/OneSixVersionBuilder.h
new file mode 100644
index 00000000..ab0966df
--- /dev/null
+++ b/logic/OneSixVersionBuilder.h
@@ -0,0 +1,47 @@
+/* Copyright 2013 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <QString>
+#include <QMap>
+
+class OneSixVersion;
+class OneSixInstance;
+class QWidget;
+class QJsonObject;
+class QFileInfo;
+class VersionFile;
+
+class OneSixVersionBuilder
+{
+ OneSixVersionBuilder();
+public:
+ static bool build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent, const bool onlyVanilla);
+ static bool read(OneSixVersion *version, const QJsonObject &obj);
+ static QMap<QString, int> readOverrideOrders(OneSixInstance *instance);
+ static bool writeOverrideOrders(const QMap<QString, int> &order, OneSixInstance *instance);
+
+private:
+ OneSixVersion *m_version;
+ OneSixInstance *m_instance;
+ QWidget *m_widgetParent;
+
+ bool build(const bool onlyVanilla);
+ bool read(const QJsonObject &obj);
+
+ bool read(const QFileInfo &fileInfo, const bool requireOrder, VersionFile *out);
+
+};
diff --git a/logic/auth/AuthSession.cpp b/logic/auth/AuthSession.cpp
new file mode 100644
index 00000000..8758bfbd
--- /dev/null
+++ b/logic/auth/AuthSession.cpp
@@ -0,0 +1,30 @@
+#include "AuthSession.h"
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QStringList>
+
+QString AuthSession::serializeUserProperties()
+{
+ QJsonObject userAttrs;
+ for (auto key : u.properties.keys())
+ {
+ auto array = QJsonArray::fromStringList(u.properties.values(key));
+ userAttrs.insert(key, array);
+ }
+ QJsonDocument value(userAttrs);
+ return value.toJson(QJsonDocument::Compact);
+
+}
+
+bool AuthSession::MakeOffline(QString offline_playername)
+{
+ if (status != PlayableOffline && status != PlayableOnline)
+ {
+ return false;
+ }
+ session = "-";
+ player_name = offline_playername;
+ status = PlayableOffline;
+ return true;
+}
diff --git a/logic/auth/AuthSession.h b/logic/auth/AuthSession.h
new file mode 100644
index 00000000..2ac170fa
--- /dev/null
+++ b/logic/auth/AuthSession.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <QString>
+#include <QMultiMap>
+#include <memory>
+
+struct User
+{
+ QString id;
+ QMultiMap<QString, QString> properties;
+};
+
+struct AuthSession
+{
+ bool MakeOffline(QString offline_playername);
+
+ QString serializeUserProperties();
+
+ enum Status
+ {
+ Undetermined,
+ RequiresPassword,
+ PlayableOffline,
+ PlayableOnline
+ } status = Undetermined;
+
+ User u;
+
+ // client token
+ QString client_token;
+ // account user name
+ QString username;
+ // combined session ID
+ QString session;
+ // volatile auth token
+ QString access_token;
+ // profile name
+ QString player_name;
+ // profile ID
+ QString uuid;
+ // 'legacy' or 'mojang', depending on account type
+ QString user_type;
+ // Did the auth server reply?
+ bool auth_server_online = false;
+ // Did the user request online mode?
+ bool wants_online = true;
+};
+
+typedef std::shared_ptr<AuthSession> AuthSessionPtr;
diff --git a/logic/auth/MojangAccount.cpp b/logic/auth/MojangAccount.cpp
index f41985ec..6c937ef1 100644
--- a/logic/auth/MojangAccount.cpp
+++ b/logic/auth/MojangAccount.cpp
@@ -24,6 +24,7 @@
#include <QJsonArray>
#include <QRegExp>
#include <QStringList>
+#include <QJsonDocument>
#include <logger/QsLog.h>
@@ -165,15 +166,26 @@ AccountStatus MojangAccount::accountStatus() const
{
if (m_accessToken.isEmpty())
return NotVerified;
- if (!m_online)
+ else
return Verified;
- return Online;
}
-std::shared_ptr<YggdrasilTask> MojangAccount::login(QString password)
+std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session,
+ QString password)
{
- if (m_currentTask)
- return m_currentTask;
+ Q_ASSERT(m_currentTask.get() == nullptr);
+
+ // take care of the true offline status
+ if (accountStatus() == NotVerified && password.isEmpty())
+ {
+ if (session)
+ {
+ session->status = AuthSession::RequiresPassword;
+ fillSession(session);
+ }
+ return nullptr;
+ }
+
if (password.isEmpty())
{
m_currentTask.reset(new RefreshTask(this));
@@ -182,6 +194,8 @@ std::shared_ptr<YggdrasilTask> MojangAccount::login(QString password)
{
m_currentTask.reset(new AuthenticateTask(this, password));
}
+ m_currentTask->assignSession(session);
+
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
return m_currentTask;
@@ -189,24 +203,76 @@ std::shared_ptr<YggdrasilTask> MojangAccount::login(QString password)
void MojangAccount::authSucceeded()
{
- m_online = true;
+ auto session = m_currentTask->getAssignedSession();
+ if (session)
+ {
+ session->status =
+ session->wants_online ? AuthSession::PlayableOnline : AuthSession::PlayableOffline;
+ fillSession(session);
+ session->auth_server_online = true;
+ }
m_currentTask.reset();
emit changed();
}
void MojangAccount::authFailed(QString reason)
{
+ auto session = m_currentTask->getAssignedSession();
// This is emitted when the yggdrasil tasks time out or are cancelled.
// -> we treat the error as no-op
if (reason == "Yggdrasil task cancelled.")
{
- // do nothing
+ if (session)
+ {
+ session->status = accountStatus() == Verified ? AuthSession::PlayableOffline
+ : AuthSession::RequiresPassword;
+ session->auth_server_online = false;
+ fillSession(session);
+ }
}
else
{
- m_online = false;
m_accessToken = QString();
emit changed();
+ if (session)
+ {
+ session->status = AuthSession::RequiresPassword;
+ session->auth_server_online = true;
+ fillSession(session);
+ }
}
m_currentTask.reset();
}
+
+void MojangAccount::fillSession(AuthSessionPtr session)
+{
+ // the user name. you have to have an user name
+ session->username = m_username;
+ // volatile auth token
+ session->access_token = m_accessToken;
+ // the semi-permanent client token
+ session->client_token = m_clientToken;
+ if (currentProfile())
+ {
+ // profile name
+ session->player_name = currentProfile()->name;
+ // profile ID
+ session->uuid = currentProfile()->id;
+ // 'legacy' or 'mojang', depending on account type
+ session->user_type = currentProfile()->legacy ? "legacy" : "mojang";
+ if (!session->access_token.isEmpty())
+ {
+ session->session = "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id;
+ }
+ else
+ {
+ session->session = "-";
+ }
+ }
+ else
+ {
+ session->player_name = "Player";
+ session->session = "-";
+ }
+ session->u = user();
+}
diff --git a/logic/auth/MojangAccount.h b/logic/auth/MojangAccount.h
index dd5d54ae..a0565e2c 100644
--- a/logic/auth/MojangAccount.h
+++ b/logic/auth/MojangAccount.h
@@ -23,6 +23,7 @@
#include <QMap>
#include <memory>
+#include "AuthSession.h"
class Task;
class YggdrasilTask;
@@ -45,17 +46,10 @@ struct AccountProfile
bool legacy;
};
-struct User
-{
- QString id;
- QMultiMap<QString,QString> properties;
-};
-
enum AccountStatus
{
NotVerified,
- Verified,
- Online
+ Verified
};
/**
@@ -84,7 +78,7 @@ public: /* construction */
QJsonObject saveToJson() const;
public: /* manipulation */
- /**
+ /**
* Sets the currently selected profile to the profile with the given ID string.
* If profileId is not in the list of available profiles, the function will simply return
* false.
@@ -95,12 +89,9 @@ public: /* manipulation */
* Attempt to login. Empty password means we use the token.
* If the attempt fails because we already are performing some task, it returns false.
*/
- std::shared_ptr<YggdrasilTask> login(QString password = QString());
+ std::shared_ptr<YggdrasilTask> login(AuthSessionPtr session,
+ QString password = QString());
- void downgrade()
- {
- m_online = false;
- }
public: /* queries */
const QString &username() const
{
@@ -122,19 +113,11 @@ public: /* queries */
return m_profiles;
}
- const User & user()
+ const User &user()
{
return m_user;
}
- //! Get the session ID required for legacy Minecraft versions
- QString sessionId() const
- {
- if (m_currentProfile != -1 && !m_accessToken.isEmpty())
- return "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id;
- return "-";
- }
-
//! Returns the currently selected profile (if none, returns nullptr)
const AccountProfile *currentProfile() const;
@@ -169,16 +152,17 @@ protected: /* variables */
// the user structure, whatever it is.
User m_user;
- // true when the account is verified
- bool m_online = false;
-
// current task we are executing here
std::shared_ptr<YggdrasilTask> m_currentTask;
-private slots:
+private
+slots:
void authSucceeded();
void authFailed(QString reason);
+private:
+ void fillSession(AuthSessionPtr session);
+
public:
friend class YggdrasilTask;
friend class AuthenticateTask;
diff --git a/logic/auth/YggdrasilTask.h b/logic/auth/YggdrasilTask.h
index 85f5a1e1..4a87067a 100644
--- a/logic/auth/YggdrasilTask.h
+++ b/logic/auth/YggdrasilTask.h
@@ -36,6 +36,21 @@ public:
explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0);
/**
+ * assign a session to this task. the session will be filled with required infomration
+ * upon completion
+ */
+ void assignSession(AuthSessionPtr session)
+ {
+ m_session = session;
+ }
+
+ /// get the assigned session for filling with information.
+ AuthSessionPtr getAssignedSession()
+ {
+ return m_session;
+ }
+
+ /**
* Class describing a Yggdrasil error response.
*/
struct Error
@@ -117,4 +132,6 @@ protected:
const int timeout_max = 10000;
const int time_step = 50;
+
+ AuthSessionPtr m_session;
};
diff --git a/logic/auth/flows/RefreshTask.cpp b/logic/auth/flows/RefreshTask.cpp
index f63c736e..5a55ed91 100644
--- a/logic/auth/flows/RefreshTask.cpp
+++ b/logic/auth/flows/RefreshTask.cpp
@@ -25,8 +25,7 @@
#include "logger/QsLog.h"
-RefreshTask::RefreshTask(MojangAccount *account, QObject *parent)
- : YggdrasilTask(account, parent)
+RefreshTask::RefreshTask(MojangAccount *account) : YggdrasilTask(account)
{
}
@@ -126,7 +125,6 @@ bool RefreshTask::processResponse(QJsonObject responseData)
m_account->m_user = u;
}
-
// We've made it through the minefield of possible errors. Return true to indicate that
// we've succeeded.
QLOG_DEBUG() << "Finished reading refresh response.";
diff --git a/logic/auth/flows/RefreshTask.h b/logic/auth/flows/RefreshTask.h
index 2fd50c60..0dadc025 100644
--- a/logic/auth/flows/RefreshTask.h
+++ b/logic/auth/flows/RefreshTask.h
@@ -30,7 +30,7 @@ class RefreshTask : public YggdrasilTask
{
Q_OBJECT
public:
- RefreshTask(MojangAccount * account, QObject *parent = 0);
+ RefreshTask(MojangAccount * account);
protected:
virtual QJsonObject getRequestContent() const;
@@ -41,3 +41,4 @@ protected:
QString getStateMessage(const YggdrasilTask::State state) const;
};
+
diff --git a/logic/lists/InstanceList.cpp b/logic/lists/InstanceList.cpp
index 0d4eab95..cd59e6d6 100644
--- a/logic/lists/InstanceList.cpp
+++ b/logic/lists/InstanceList.cpp
@@ -33,6 +33,7 @@
#include "logic/BaseInstance.h"
#include "logic/InstanceFactory.h"
#include "logger/QsLog.h"
+#include <gui/groupview/GroupView.h>
const static int GROUP_FILE_FORMAT_VERSION = 1;
@@ -46,6 +47,13 @@ InstanceList::InstanceList(const QString &instDir, QObject *parent)
QDir::current().mkpath(m_instDir);
}
+ /*
+ * FIXME HACK: instances sometimes need to be created at launch. They need the versions for
+ * that.
+ *
+ * Remove this. it has no business of reloading the whole list. The instances which
+ * need it should track such events themselves and CHANGE THEIR DATA ONLY!
+ */
connect(MMC->minecraftlist().get(), &MinecraftVersionList::modelReset, this,
&InstanceList::loadList);
}
@@ -96,8 +104,7 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
return MMC->icons()->getIcon(key);
}
// for now.
- case KCategorizedSortFilterProxyModel::CategorySortRole:
- case KCategorizedSortFilterProxyModel::CategoryDisplayRole:
+ case GroupViewRoles::GroupRole:
{
return pdata->group();
}
@@ -282,16 +289,7 @@ void InstanceList::loadGroupList(QMap<QString, QString> &groupMap)
}
}
-struct FTBRecord
-{
- QString dir;
- QString name;
- QString logo;
- QString mcVersion;
- QString description;
-};
-
-void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap)
+QList<FTBRecord> InstanceList::discoverFTBInstances()
{
QList<FTBRecord> records;
QDir dir = QDir(MMC->settings()->get("FTBLauncherRoot").toString());
@@ -300,18 +298,18 @@ void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap)
{
QLOG_INFO() << "The FTB launcher directory specified does not exist. Please check your "
"settings.";
- return;
+ return records;
}
else if (!dataDir.exists())
{
QLOG_INFO() << "The FTB directory specified does not exist. Please check your settings";
- return;
+ return records;
}
dir.cd("ModPacks");
auto allFiles = dir.entryList(QDir::Readable | QDir::Files, QDir::Name);
- for(auto filename: allFiles)
+ for (auto filename : allFiles)
{
- if(!filename.endsWith(".xml"))
+ if (!filename.endsWith(".xml"))
continue;
auto fpath = dir.absoluteFilePath(filename);
QFile f(fpath);
@@ -331,11 +329,15 @@ void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap)
{
QXmlStreamAttributes attrs = reader.attributes();
FTBRecord record;
- record.dir = attrs.value("dir").toString();
- QDir test(dataDir.absoluteFilePath(record.dir));
- if(!test.exists())
+ record.dirName = attrs.value("dir").toString();
+ record.instanceDir = dataDir.absoluteFilePath(record.dirName);
+ record.templateDir = dir.absoluteFilePath(record.dirName);
+ QDir test(record.instanceDir);
+ if (!test.exists())
continue;
record.name = attrs.value("name").toString();
+ if(record.name.contains("voxel", Qt::CaseInsensitive))
+ continue;
record.logo = attrs.value("logo").toString();
record.mcVersion = attrs.value("mcVersion").toString();
record.description = attrs.value("description").toString();
@@ -353,8 +355,14 @@ void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap)
}
f.close();
}
+ return records;
+}
- if(!records.size())
+void InstanceList::loadFTBInstances(QMap<QString, QString> &groupMap,
+ QList<InstancePtr> &tempList)
+{
+ auto records = discoverFTBInstances();
+ if (!records.size())
{
QLOG_INFO() << "No FTB instances to load.";
return;
@@ -363,20 +371,13 @@ void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap)
// process the records we acquired.
for (auto record : records)
{
- auto instanceDir = dataDir.absoluteFilePath(record.dir);
- QLOG_INFO() << "Loading FTB instance from " << instanceDir;
- auto templateDir = dir.absoluteFilePath(record.dir);
- if (!QFileInfo(instanceDir).exists())
- {
- continue;
- }
-
+ QLOG_INFO() << "Loading FTB instance from " << record.instanceDir;
QString iconKey = record.logo;
iconKey.remove(QRegularExpression("\\..*"));
- MMC->icons()->addIcon(iconKey, iconKey, PathCombine(templateDir, record.logo),
+ MMC->icons()->addIcon(iconKey, iconKey, PathCombine(record.templateDir, record.logo),
MMCIcon::Transient);
- if (!QFileInfo(PathCombine(instanceDir, "instance.cfg")).exists())
+ if (!QFileInfo(PathCombine(record.instanceDir, "instance.cfg")).exists())
{
QLOG_INFO() << "Converting " << record.name << " as new.";
BaseInstance *instPtr = NULL;
@@ -384,12 +385,12 @@ void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap)
auto version = MMC->minecraftlist()->findVersion(record.mcVersion);
if (!version)
{
- QLOG_ERROR() << "Can't load instance " << instanceDir
+ QLOG_ERROR() << "Can't load instance " << record.instanceDir
<< " because minecraft version " << record.mcVersion
<< " can't be resolved.";
continue;
}
- auto error = factory.createInstance(instPtr, version, instanceDir,
+ auto error = factory.createInstance(instPtr, version, record.instanceDir,
InstanceFactory::FTBInstance);
if (!instPtr || error != InstanceFactory::NoCreateError)
@@ -400,13 +401,15 @@ void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap)
instPtr->setIconKey(iconKey);
instPtr->setIntendedVersionId(record.mcVersion);
instPtr->setNotes(record.description);
- continueProcessInstance(instPtr, error, instanceDir, groupMap);
+ if(!continueProcessInstance(instPtr, error, record.instanceDir, groupMap))
+ continue;
+ tempList.append(InstancePtr(instPtr));
}
else
{
QLOG_INFO() << "Loading existing " << record.name;
BaseInstance *instPtr = NULL;
- auto error = InstanceFactory::get().loadInstance(instPtr, instanceDir);
+ auto error = InstanceFactory::get().loadInstance(instPtr, record.instanceDir);
if (!instPtr || error != InstanceFactory::NoCreateError)
continue;
instPtr->setGroupInitial("FTB");
@@ -415,7 +418,9 @@ void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap)
if (instPtr->intendedVersionId() != record.mcVersion)
instPtr->setIntendedVersionId(record.mcVersion);
instPtr->setNotes(record.description);
- continueProcessInstance(instPtr, error, instanceDir, groupMap);
+ if(!continueProcessInstance(instPtr, error, record.instanceDir, groupMap))
+ continue;
+ tempList.append(InstancePtr(instPtr));
}
}
}
@@ -426,10 +431,7 @@ InstanceList::InstListError InstanceList::loadList()
QMap<QString, QString> groupMap;
loadGroupList(groupMap);
- beginResetModel();
-
- m_instances.clear();
-
+ QList<InstancePtr> tempList;
{
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable,
QDirIterator::FollowSymlinks);
@@ -441,15 +443,28 @@ InstanceList::InstListError InstanceList::loadList()
QLOG_INFO() << "Loading MultiMC instance from " << subDir;
BaseInstance *instPtr = NULL;
auto error = InstanceFactory::get().loadInstance(instPtr, subDir);
- continueProcessInstance(instPtr, error, subDir, groupMap);
+ if(!continueProcessInstance(instPtr, error, subDir, groupMap))
+ continue;
+ tempList.append(InstancePtr(instPtr));
}
}
if (MMC->settings()->get("TrackFTBInstances").toBool())
{
- loadForgeInstances(groupMap);
+ loadFTBInstances(groupMap, tempList);
+ }
+ beginResetModel();
+ m_instances.clear();
+ for(auto inst: tempList)
+ {
+ inst->setParent(this);
+ connect(inst.get(), SIGNAL(propertiesChanged(BaseInstance *)), this,
+ SLOT(propertiesChanged(BaseInstance *)));
+ connect(inst.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged()));
+ connect(inst.get(), SIGNAL(nuked(BaseInstance *)), this,
+ SLOT(instanceNuked(BaseInstance *)));
+ m_instances.append(inst);
}
-
endResetModel();
emit dataIsInvalid();
return NoError;
@@ -523,7 +538,7 @@ int InstanceList::getInstIndex(BaseInstance *inst) const
return -1;
}
-void InstanceList::continueProcessInstance(BaseInstance *instPtr, const int error,
+bool InstanceList::continueProcessInstance(BaseInstance *instPtr, const int error,
const QDir &dir, QMap<QString, QString> &groupMap)
{
if (error != InstanceFactory::NoLoadError && error != InstanceFactory::NotAnInstance)
@@ -539,12 +554,14 @@ void InstanceList::continueProcessInstance(BaseInstance *instPtr, const int erro
break;
}
QLOG_ERROR() << errorMsg.toUtf8();
+ return false;
}
else if (!instPtr)
{
QLOG_ERROR() << QString("Error loading instance %1. Instance loader returned null.")
.arg(QFileInfo(dir.absolutePath()).baseName())
.toUtf8();
+ return false;
}
else
{
@@ -554,13 +571,7 @@ void InstanceList::continueProcessInstance(BaseInstance *instPtr, const int erro
instPtr->setGroupInitial((*iter));
}
QLOG_INFO() << "Loaded instance " << instPtr->name() << " from " << dir.absolutePath();
- instPtr->setParent(this);
- m_instances.append(std::shared_ptr<BaseInstance>(instPtr));
- connect(instPtr, SIGNAL(propertiesChanged(BaseInstance *)), this,
- SLOT(propertiesChanged(BaseInstance *)));
- connect(instPtr, SIGNAL(groupChanged()), this, SLOT(groupChanged()));
- connect(instPtr, SIGNAL(nuked(BaseInstance *)), this,
- SLOT(instanceNuked(BaseInstance *)));
+ return true;
}
}
@@ -584,11 +595,8 @@ void InstanceList::propertiesChanged(BaseInstance *inst)
}
}
-InstanceProxyModel::InstanceProxyModel(QObject *parent)
- : KCategorizedSortFilterProxyModel(parent)
+InstanceProxyModel::InstanceProxyModel(QObject *parent) : GroupedProxyModel(parent)
{
- // disable since by default we are globally sorting by date:
- setCategorizedModel(true);
}
bool InstanceProxyModel::subSortLessThan(const QModelIndex &left,
diff --git a/logic/lists/InstanceList.h b/logic/lists/InstanceList.h
index 0ce808e5..ebe3e051 100644
--- a/logic/lists/InstanceList.h
+++ b/logic/lists/InstanceList.h
@@ -18,7 +18,7 @@
#include <QObject>
#include <QAbstractListModel>
#include <QSet>
-#include "categorizedsortfilterproxymodel.h"
+#include <gui/groupview/GroupedProxyModel.h>
#include <QIcon>
#include "logic/BaseInstance.h"
@@ -27,11 +27,24 @@ class BaseInstance;
class QDir;
+struct FTBRecord
+{
+ QString dirName;
+ QString name;
+ QString logo;
+ QString mcVersion;
+ QString description;
+ QString instanceDir;
+ QString templateDir;
+};
+
class InstanceList : public QAbstractListModel
{
Q_OBJECT
private:
void loadGroupList(QMap<QString, QString> &groupList);
+ QList<FTBRecord> discoverFTBInstances();
+ void loadFTBInstances(QMap<QString, QString> &groupMap, QList<InstancePtr> & tempList);
private
slots:
@@ -109,7 +122,6 @@ slots:
* \brief Loads the instance list. Triggers notifications.
*/
InstListError loadList();
- void loadForgeInstances(QMap<QString, QString> groupMap);
private
slots:
@@ -120,7 +132,7 @@ slots:
private:
int getInstIndex(BaseInstance *inst) const;
- void continueProcessInstance(BaseInstance *instPtr, const int error, const QDir &dir,
+ bool continueProcessInstance(BaseInstance *instPtr, const int error, const QDir &dir,
QMap<QString, QString> &groupMap);
protected:
@@ -129,7 +141,7 @@ protected:
QSet<QString> m_groups;
};
-class InstanceProxyModel : public KCategorizedSortFilterProxyModel
+class InstanceProxyModel : public GroupedProxyModel
{
public:
explicit InstanceProxyModel(QObject *parent = 0);
diff --git a/logic/lists/MinecraftVersionList.cpp b/logic/lists/MinecraftVersionList.cpp
index 91f86df0..ece31e3d 100644
--- a/logic/lists/MinecraftVersionList.cpp
+++ b/logic/lists/MinecraftVersionList.cpp
@@ -60,10 +60,15 @@ bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second)
return left->timestamp > right->timestamp;
}
+void MinecraftVersionList::sortInternal()
+{
+ qSort(m_vlist.begin(), m_vlist.end(), cmpVersions);
+}
+
void MinecraftVersionList::sort()
{
beginResetModel();
- qSort(m_vlist.begin(), m_vlist.end(), cmpVersions);
+ sortInternal();
endResetModel();
}
@@ -85,9 +90,8 @@ void MinecraftVersionList::updateListData(QList<BaseVersionPtr> versions)
beginResetModel();
m_vlist = versions;
m_loaded = true;
+ sortInternal();
endResetModel();
- // NOW SORT!!
- sort();
}
inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname)
diff --git a/logic/lists/MinecraftVersionList.h b/logic/lists/MinecraftVersionList.h
index 82af1009..167f4d11 100644
--- a/logic/lists/MinecraftVersionList.h
+++ b/logic/lists/MinecraftVersionList.h
@@ -29,6 +29,8 @@ class QNetworkReply;
class MinecraftVersionList : public BaseVersionList
{
Q_OBJECT
+private:
+ void sortInternal();
public:
friend class MCVListLoadTask;
diff --git a/logic/net/NetJob.cpp b/logic/net/NetJob.cpp
index 8b79bc54..9e800d13 100644
--- a/logic/net/NetJob.cpp
+++ b/logic/net/NetJob.cpp
@@ -31,7 +31,6 @@ void NetJob::partSucceeded(int index)
num_succeeded++;
QLOG_INFO() << m_job_name.toLocal8Bit() << "progress:" << num_succeeded << "/"
<< downloads.size();
- emit filesProgress(num_succeeded, num_failed, downloads.size());
if (num_failed + num_succeeded == downloads.size())
{
@@ -55,7 +54,6 @@ void NetJob::partFailed(int index)
{
QLOG_ERROR() << "Part" << index << "failed 3 times (" << downloads[index]->m_url << ")";
num_failed++;
- emit filesProgress(num_succeeded, num_failed, downloads.size());
if (num_failed + num_succeeded == downloads.size())
{
QLOG_ERROR() << m_job_name.toLocal8Bit() << "failed.";
diff --git a/logic/net/NetJob.h b/logic/net/NetJob.h
index 68c4c408..03d6a36e 100644
--- a/logic/net/NetJob.h
+++ b/logic/net/NetJob.h
@@ -84,7 +84,6 @@ public:
{
return m_job_name;
}
- ;
virtual bool isRunning() const
{
return m_running;
@@ -94,7 +93,6 @@ public:
signals:
void started();
void progress(qint64 current, qint64 total);
- void filesProgress(int, int, int);
void succeeded();
void failed();
public