Refactor icon lists heavily
* Icon list now uses a filesystem watcher for updates * Icon folder is user-customizable * All the little details. ALL OF THEM.
This commit is contained in:
		| @@ -27,6 +27,7 @@ | ||||
|  | ||||
| #include "pathutils.h" | ||||
| #include "lists/MinecraftVersionList.h" | ||||
| #include "logic/icons/IconList.h" | ||||
|  | ||||
| BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir, | ||||
| 						   SettingsObject *settings_obj, QObject *parent) | ||||
| @@ -38,6 +39,7 @@ BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir, | ||||
|  | ||||
| 	settings().registerSetting(new Setting("name", "Unnamed Instance")); | ||||
| 	settings().registerSetting(new Setting("iconKey", "default")); | ||||
| 	connect(MMC->icons().get(), SIGNAL(iconUpdated(QString)), SLOT(iconUpdated(QString))); | ||||
| 	settings().registerSetting(new Setting("notes", "")); | ||||
| 	settings().registerSetting(new Setting("lastLaunchTime", 0)); | ||||
|  | ||||
| @@ -93,6 +95,14 @@ BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir, | ||||
| 		"AutoCloseConsole", globalSettings->getSetting("AutoCloseConsole"))); | ||||
| } | ||||
|  | ||||
| void BaseInstance::iconUpdated(QString key) | ||||
| { | ||||
| 	if(iconKey() == key) | ||||
| 	{ | ||||
| 		emit propertiesChanged(this); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void BaseInstance::nuke() | ||||
| { | ||||
| 	QDir(instanceRoot()).removeRecursively(); | ||||
|   | ||||
| @@ -184,6 +184,9 @@ signals: | ||||
| 	 */ | ||||
| 	void nuked(BaseInstance *inst); | ||||
|  | ||||
| protected slots: | ||||
| 	void iconUpdated(QString key); | ||||
|  | ||||
| protected: | ||||
| 	std::shared_ptr<BaseInstancePrivate> inst_d; | ||||
| }; | ||||
|   | ||||
| @@ -27,7 +27,7 @@ | ||||
|  | ||||
| #include "logic/MinecraftProcess.h" | ||||
| #include "logic/LegacyUpdate.h" | ||||
| #include "logic/lists/IconList.h" | ||||
| #include "logic/icons/IconList.h" | ||||
|  | ||||
| #include "gui/dialogs/LegacyModEditDialog.h" | ||||
|  | ||||
|   | ||||
							
								
								
									
										351
									
								
								logic/icons/IconList.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										351
									
								
								logic/icons/IconList.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,351 @@ | ||||
| /* 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 "IconList.h" | ||||
| #include <pathutils.h> | ||||
| #include <settingsobject.h> | ||||
| #include <QMap> | ||||
| #include <QEventLoop> | ||||
| #include <QMimeData> | ||||
| #include <QUrl> | ||||
| #include <QFileSystemWatcher> | ||||
| #include <MultiMC.h> | ||||
| #include <setting.h> | ||||
|  | ||||
| #define MAX_SIZE 1024 | ||||
|  | ||||
| IconList::IconList(QObject *parent) : QAbstractListModel(parent) | ||||
| { | ||||
| 	// add builtin icons | ||||
| 	QDir instance_icons(":/icons/instances/"); | ||||
| 	auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name); | ||||
| 	for (auto file_info : file_info_list) | ||||
| 	{ | ||||
| 		QString key = file_info.baseName(); | ||||
| 		addIcon(key, key, file_info.absoluteFilePath(), MMCIcon::Builtin); | ||||
| 	} | ||||
|  | ||||
| 	m_watcher.reset(new QFileSystemWatcher()); | ||||
| 	is_watching = false; | ||||
| 	connect(m_watcher.get(), SIGNAL(directoryChanged(QString)), | ||||
| 			SLOT(directoryChanged(QString))); | ||||
| 	connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); | ||||
| 	 | ||||
| 	auto setting = MMC->settings()->getSetting("IconsDir"); | ||||
| 	QString path = setting->get().toString(); | ||||
| 	connect(setting, SIGNAL(settingChanged(const Setting &, QVariant)), | ||||
| 			SLOT(settingChanged(const Setting &, QVariant))); | ||||
| 	directoryChanged(path); | ||||
| } | ||||
|  | ||||
| void IconList::directoryChanged(const QString &path) | ||||
| { | ||||
| 	QDir new_dir (path); | ||||
| 	if(m_dir.absolutePath() != new_dir.absolutePath()) | ||||
| 	{ | ||||
| 		m_dir.setPath(path); | ||||
| 		m_dir.refresh(); | ||||
| 		if(is_watching) | ||||
| 			stopWatching(); | ||||
| 		startWatching(); | ||||
| 	} | ||||
| 	if(!m_dir.exists()) | ||||
| 		if(!ensureFolderPathExists(m_dir.absolutePath())) | ||||
| 			return; | ||||
| 	m_dir.refresh(); | ||||
| 	auto new_list = m_dir.entryList(QDir::Files, QDir::Name); | ||||
| 	for (auto it = new_list.begin(); it != new_list.end(); it++) | ||||
| 	{ | ||||
| 		QString &foo = (*it); | ||||
| 		foo = m_dir.filePath(foo); | ||||
| 	} | ||||
| 	auto new_set = new_list.toSet(); | ||||
| 	QList<QString> current_list; | ||||
| 	for (auto &it : icons) | ||||
| 	{ | ||||
| 		if (!it.has(MMCIcon::FileBased)) | ||||
| 			continue; | ||||
| 		current_list.push_back(it.m_images[MMCIcon::FileBased].filename); | ||||
| 	} | ||||
| 	QSet<QString> current_set = current_list.toSet(); | ||||
|  | ||||
| 	QSet<QString> to_remove = current_set; | ||||
| 	to_remove -= new_set; | ||||
|  | ||||
| 	QSet<QString> to_add = new_set; | ||||
| 	to_add -= current_set; | ||||
|  | ||||
| 	for (auto remove : to_remove) | ||||
| 	{ | ||||
| 		QLOG_INFO() << "Removing " << remove; | ||||
| 		QFileInfo rmfile(remove); | ||||
| 		QString key = rmfile.baseName(); | ||||
| 		int idx = getIconIndex(key); | ||||
| 		if (idx == -1) | ||||
| 			continue; | ||||
| 		icons[idx].remove(MMCIcon::FileBased); | ||||
| 		if (icons[idx].type() == MMCIcon::ToBeDeleted) | ||||
| 		{ | ||||
| 			beginRemoveRows(QModelIndex(), idx, idx); | ||||
| 			icons.remove(idx); | ||||
| 			reindex(); | ||||
| 			endRemoveRows(); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			dataChanged(index(idx), index(idx)); | ||||
| 		} | ||||
| 		m_watcher->removePath(remove); | ||||
| 		emit iconUpdated(key); | ||||
| 	} | ||||
|  | ||||
| 	for (auto add : to_add) | ||||
| 	{ | ||||
| 		QLOG_INFO() << "Adding " << add; | ||||
| 		QFileInfo addfile(add); | ||||
| 		QString key = addfile.baseName(); | ||||
| 		if (addIcon(key, QString(), addfile.filePath(), MMCIcon::FileBased)) | ||||
| 		{ | ||||
| 			m_watcher->addPath(add); | ||||
| 			emit iconUpdated(key); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void IconList::fileChanged(const QString &path) | ||||
| { | ||||
| 	QLOG_INFO() << "Checking " << path; | ||||
| 	QFileInfo checkfile(path); | ||||
| 	if (!checkfile.exists()) | ||||
| 		return; | ||||
| 	QString key = checkfile.baseName(); | ||||
| 	int idx = getIconIndex(key); | ||||
| 	if (idx == -1) | ||||
| 		return; | ||||
| 	QIcon icon(path); | ||||
| 	if (!icon.availableSizes().size()) | ||||
| 		return; | ||||
|  | ||||
| 	icons[idx].m_images[MMCIcon::FileBased].icon = icon; | ||||
| 	dataChanged(index(idx), index(idx)); | ||||
| 	emit iconUpdated(key); | ||||
| } | ||||
|  | ||||
| void IconList::settingChanged(const Setting &setting, QVariant value) | ||||
| { | ||||
| 	if(setting.configKey() != "IconsDir") | ||||
| 		return; | ||||
|  | ||||
| 	directoryChanged(value.toString()); | ||||
| } | ||||
|  | ||||
| void IconList::startWatching() | ||||
| { | ||||
| 	auto abs_path = m_dir.absolutePath(); | ||||
| 	ensureFolderPathExists(abs_path); | ||||
| 	is_watching = m_watcher->addPath(abs_path); | ||||
| 	if (is_watching) | ||||
| 	{ | ||||
| 		QLOG_INFO() << "Started watching " << abs_path; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		QLOG_INFO() << "Failed to start watching " << abs_path; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void IconList::stopWatching() | ||||
| { | ||||
| 	m_watcher->removePaths(m_watcher->files()); | ||||
| 	m_watcher->removePaths(m_watcher->directories()); | ||||
| 	is_watching = false; | ||||
| } | ||||
|  | ||||
| QStringList IconList::mimeTypes() const | ||||
| { | ||||
| 	QStringList types; | ||||
| 	types << "text/uri-list"; | ||||
| 	return types; | ||||
| } | ||||
| Qt::DropActions IconList::supportedDropActions() const | ||||
| { | ||||
| 	return Qt::CopyAction; | ||||
| } | ||||
|  | ||||
| bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, | ||||
| 							const QModelIndex &parent) | ||||
| { | ||||
| 	if (action == Qt::IgnoreAction) | ||||
| 		return true; | ||||
| 	// check if the action is supported | ||||
| 	if (!data || !(action & supportedDropActions())) | ||||
| 		return false; | ||||
|  | ||||
| 	// files dropped from outside? | ||||
| 	if (data->hasUrls()) | ||||
| 	{ | ||||
| 		auto urls = data->urls(); | ||||
| 		QStringList iconFiles; | ||||
| 		for (auto url : urls) | ||||
| 		{ | ||||
| 			// only local files may be dropped... | ||||
| 			if (!url.isLocalFile()) | ||||
| 				continue; | ||||
| 			iconFiles += url.toLocalFile(); | ||||
| 		} | ||||
| 		installIcons(iconFiles); | ||||
| 		return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| Qt::ItemFlags IconList::flags(const QModelIndex &index) const | ||||
| { | ||||
| 	Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); | ||||
| 	if (index.isValid()) | ||||
| 		return Qt::ItemIsDropEnabled | defaultFlags; | ||||
| 	else | ||||
| 		return Qt::ItemIsDropEnabled | defaultFlags; | ||||
| } | ||||
|  | ||||
| QVariant IconList::data(const QModelIndex &index, int role) const | ||||
| { | ||||
| 	if (!index.isValid()) | ||||
| 		return QVariant(); | ||||
|  | ||||
| 	int row = index.row(); | ||||
|  | ||||
| 	if (row < 0 || row >= icons.size()) | ||||
| 		return QVariant(); | ||||
|  | ||||
| 	switch (role) | ||||
| 	{ | ||||
| 	case Qt::DecorationRole: | ||||
| 		return icons[row].icon(); | ||||
| 	case Qt::DisplayRole: | ||||
| 		return icons[row].name(); | ||||
| 	case Qt::UserRole: | ||||
| 		return icons[row].m_key; | ||||
| 	default: | ||||
| 		return QVariant(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int IconList::rowCount(const QModelIndex &parent) const | ||||
| { | ||||
| 	return icons.size(); | ||||
| } | ||||
|  | ||||
| void IconList::installIcons(QStringList iconFiles) | ||||
| { | ||||
| 	for (QString file : iconFiles) | ||||
| 	{ | ||||
| 		QFileInfo fileinfo(file); | ||||
| 		if (!fileinfo.isReadable() || !fileinfo.isFile()) | ||||
| 			continue; | ||||
| 		QString target = PathCombine("icons", fileinfo.fileName()); | ||||
|  | ||||
| 		QString suffix = fileinfo.suffix(); | ||||
| 		if (suffix != "jpeg" && suffix != "png" && suffix != "jpg") | ||||
| 			continue; | ||||
|  | ||||
| 		if (!QFile::copy(file, target)) | ||||
| 			continue; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool IconList::deleteIcon(QString key) | ||||
| { | ||||
| 	int iconIdx = getIconIndex(key); | ||||
| 	if (iconIdx == -1) | ||||
| 		return false; | ||||
| 	auto &iconEntry = icons[iconIdx]; | ||||
| 	if (iconEntry.has(MMCIcon::FileBased)) | ||||
| 	{ | ||||
| 		return QFile::remove(iconEntry.m_images[MMCIcon::FileBased].filename); | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool IconList::addIcon(QString key, QString name, QString path, MMCIcon::Type type) | ||||
| { | ||||
| 	// replace the icon even? is the input valid? | ||||
| 	QIcon icon(path); | ||||
| 	if (!icon.availableSizes().size()) | ||||
| 		return false; | ||||
| 	auto iter = name_index.find(key); | ||||
| 	if (iter != name_index.end()) | ||||
| 	{ | ||||
| 		auto &oldOne = icons[*iter]; | ||||
| 		oldOne.replace(type, icon, path); | ||||
| 		dataChanged(index(*iter), index(*iter)); | ||||
| 		return true; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		// add a new icon | ||||
| 		beginInsertRows(QModelIndex(), icons.size(), icons.size()); | ||||
| 		{ | ||||
| 			MMCIcon mmc_icon; | ||||
| 			mmc_icon.m_name = name; | ||||
| 			mmc_icon.m_key = key; | ||||
| 			mmc_icon.replace(type, icon, path); | ||||
| 			icons.push_back(mmc_icon); | ||||
| 			name_index[key] = icons.size() - 1; | ||||
| 		} | ||||
| 		endInsertRows(); | ||||
| 		return true; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void IconList::reindex() | ||||
| { | ||||
| 	name_index.clear(); | ||||
| 	int i = 0; | ||||
| 	for (auto &iter : icons) | ||||
| 	{ | ||||
| 		name_index[iter.m_key] = i; | ||||
| 		i++; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| QIcon IconList::getIcon(QString key) | ||||
| { | ||||
| 	int icon_index = getIconIndex(key); | ||||
|  | ||||
| 	if (icon_index != -1) | ||||
| 		return icons[icon_index].icon(); | ||||
|  | ||||
| 	// Fallback for icons that don't exist. | ||||
| 	icon_index = getIconIndex("infinity"); | ||||
|  | ||||
| 	if (icon_index != -1) | ||||
| 		return icons[icon_index].icon(); | ||||
| 	return QIcon(); | ||||
| } | ||||
|  | ||||
| int IconList::getIconIndex(QString key) | ||||
| { | ||||
| 	if (key == "default") | ||||
| 		key = "infinity"; | ||||
|  | ||||
| 	auto iter = name_index.find(key); | ||||
| 	if (iter != name_index.end()) | ||||
| 		return *iter; | ||||
|  | ||||
| 	return -1; | ||||
| } | ||||
|  | ||||
| //#include "IconList.moc" | ||||
| @@ -17,15 +17,21 @@ | ||||
| 
 | ||||
| #include <QMutex> | ||||
| #include <QAbstractListModel> | ||||
| #include <QFile> | ||||
| #include <QDir> | ||||
| #include <QtGui/QIcon> | ||||
| #include <memory> | ||||
| #include "MMCIcon.h" | ||||
| #include "setting.h" | ||||
| 
 | ||||
| class Private; | ||||
| class QFileSystemWatcher; | ||||
| 
 | ||||
| class IconList : public QAbstractListModel | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	IconList(); | ||||
| 	virtual ~IconList(); | ||||
| 	explicit IconList(QObject *parent = 0); | ||||
| 	virtual ~IconList() {}; | ||||
| 
 | ||||
| 	QIcon getIcon(QString key); | ||||
| 	int getIconIndex(QString key); | ||||
| @@ -33,7 +39,7 @@ public: | ||||
| 	virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; | ||||
| 	virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; | ||||
| 
 | ||||
| 	bool addIcon(QString key, QString name, QString path, bool is_builtin = false); | ||||
| 	bool addIcon(QString key, QString name, QString path, MMCIcon::Type type); | ||||
| 	bool deleteIcon(QString key); | ||||
| 
 | ||||
| 	virtual QStringList mimeTypes() const; | ||||
| @@ -44,11 +50,28 @@ public: | ||||
| 
 | ||||
| 	void installIcons(QStringList iconFiles); | ||||
| 
 | ||||
| 	void startWatching(); | ||||
| 	void stopWatching(); | ||||
| 
 | ||||
| signals: | ||||
| 	void iconUpdated(QString key); | ||||
| 
 | ||||
| private: | ||||
| 	// hide copy constructor
 | ||||
| 	IconList(const IconList &) = delete; | ||||
| 	// hide assign op
 | ||||
| 	IconList &operator=(const IconList &) = delete; | ||||
| 	void reindex(); | ||||
| 	Private *d; | ||||
| 
 | ||||
| protected | ||||
| slots: | ||||
| 	void directoryChanged(const QString &path); | ||||
| 	void fileChanged(const QString &path); | ||||
| 	void settingChanged(const Setting & setting, QVariant value); | ||||
| private: | ||||
| 	std::shared_ptr<QFileSystemWatcher> m_watcher; | ||||
| 	bool is_watching; | ||||
| 	QMap<QString, int> name_index; | ||||
| 	QVector<MMCIcon> icons; | ||||
| 	QDir m_dir; | ||||
| }; | ||||
							
								
								
									
										89
									
								
								logic/icons/MMCIcon.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								logic/icons/MMCIcon.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| /* 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 "MMCIcon.h" | ||||
| #include <QFileInfo> | ||||
|  | ||||
| MMCIcon::Type operator--(MMCIcon::Type &t, int) | ||||
| { | ||||
| 	MMCIcon::Type temp = t; | ||||
| 	switch (t) | ||||
| 	{ | ||||
| 	case MMCIcon::Type::Builtin: | ||||
| 		t = MMCIcon::Type::ToBeDeleted; | ||||
| 		break; | ||||
| 	case MMCIcon::Type::Transient: | ||||
| 		t = MMCIcon::Type::Builtin; | ||||
| 		break; | ||||
| 	case MMCIcon::Type::FileBased: | ||||
| 		t = MMCIcon::Type::Transient; | ||||
| 		break; | ||||
| 	default: | ||||
| 	{ | ||||
| 	} | ||||
| 	} | ||||
| 	return temp; | ||||
| } | ||||
|  | ||||
| MMCIcon::Type MMCIcon::type() const | ||||
| { | ||||
| 	return m_current_type; | ||||
| } | ||||
|  | ||||
| QString MMCIcon::name() const | ||||
| { | ||||
| 	if (m_name.size()) | ||||
| 		return m_name; | ||||
| 	return m_key; | ||||
| } | ||||
|  | ||||
| bool MMCIcon::has(MMCIcon::Type _type) const | ||||
| { | ||||
| 	return m_images[_type].present(); | ||||
| } | ||||
|  | ||||
| QIcon MMCIcon::icon() const | ||||
| { | ||||
| 	if (m_current_type == Type::ToBeDeleted) | ||||
| 		return QIcon(); | ||||
| 	return m_images[m_current_type].icon; | ||||
| } | ||||
|  | ||||
| void MMCIcon::remove(Type rm_type) | ||||
| { | ||||
| 	m_images[rm_type].filename = QString(); | ||||
| 	m_images[rm_type].icon = QIcon(); | ||||
| 	for (auto iter = rm_type; iter != Type::ToBeDeleted; iter--) | ||||
| 	{ | ||||
| 		if (m_images[iter].present()) | ||||
| 		{ | ||||
| 			m_current_type = iter; | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 	m_current_type = Type::ToBeDeleted; | ||||
| } | ||||
|  | ||||
| void MMCIcon::replace(MMCIcon::Type new_type, QIcon icon, QString path) | ||||
| { | ||||
| 	QFileInfo foo(path); | ||||
| 	if (new_type > m_current_type || m_current_type == MMCIcon::ToBeDeleted) | ||||
| 	{ | ||||
| 		m_current_type = new_type; | ||||
| 	} | ||||
| 	m_images[new_type].icon = icon; | ||||
| 	m_images[new_type].changed = foo.lastModified(); | ||||
| 	m_images[new_type].filename = path; | ||||
| } | ||||
							
								
								
									
										52
									
								
								logic/icons/MMCIcon.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								logic/icons/MMCIcon.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| /* 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 <QDateTime> | ||||
| #include <QIcon> | ||||
| struct MMCImage | ||||
| { | ||||
| 	QIcon icon; | ||||
| 	QString filename; | ||||
| 	QDateTime changed; | ||||
| 	bool present() const | ||||
| 	{ | ||||
| 		return !icon.isNull(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct MMCIcon | ||||
| { | ||||
| 	enum Type : unsigned | ||||
| 	{ | ||||
| 		Builtin, | ||||
| 		Transient, | ||||
| 		FileBased, | ||||
| 		ICONS_TOTAL, | ||||
| 		ToBeDeleted | ||||
| 	}; | ||||
| 	QString m_key; | ||||
| 	QString m_name; | ||||
| 	MMCImage m_images[ICONS_TOTAL]; | ||||
| 	Type m_current_type = ToBeDeleted; | ||||
|  | ||||
| 	Type type() const; | ||||
| 	QString name() const; | ||||
| 	bool has(Type _type) const; | ||||
| 	QIcon icon() const; | ||||
| 	void remove(Type rm_type); | ||||
| 	void replace(Type new_type, QIcon icon, QString path = QString()); | ||||
| }; | ||||
| @@ -1,271 +0,0 @@ | ||||
| /* 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 "IconList.h" | ||||
| #include <pathutils.h> | ||||
| #include <QMap> | ||||
| #include <QEventLoop> | ||||
| #include <QDir> | ||||
| #include <QMimeData> | ||||
| #include <QUrl> | ||||
| #define MAX_SIZE 1024 | ||||
|  | ||||
| struct entry | ||||
| { | ||||
| 	QString key; | ||||
| 	QString name; | ||||
| 	QIcon icon; | ||||
| 	bool is_builtin; | ||||
| 	QString filename; | ||||
| }; | ||||
|  | ||||
| class Private : public QObject | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	QMap<QString, int> index; | ||||
| 	QVector<entry> icons; | ||||
| 	Private() | ||||
| 	{ | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| IconList::IconList() : QAbstractListModel(), d(new Private()) | ||||
| { | ||||
| 	QDir instance_icons(":/icons/instances/"); | ||||
| 	auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name); | ||||
| 	for (auto file_info : file_info_list) | ||||
| 	{ | ||||
| 		QString key = file_info.baseName(); | ||||
| 		addIcon(key, key, file_info.absoluteFilePath(), true); | ||||
| 	} | ||||
|  | ||||
| 	// FIXME: get from settings | ||||
| 	ensureFolderPathExists("icons"); | ||||
| 	QDir user_icons("icons"); | ||||
| 	file_info_list = user_icons.entryInfoList(QDir::Files, QDir::Name); | ||||
| 	for (auto file_info : file_info_list) | ||||
| 	{ | ||||
| 		QString filename = file_info.absoluteFilePath(); | ||||
| 		QString key = file_info.baseName(); | ||||
| 		addIcon(key, key, filename); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| IconList::~IconList() | ||||
| { | ||||
| 	delete d; | ||||
| 	d = nullptr; | ||||
| } | ||||
|  | ||||
| QStringList IconList::mimeTypes() const | ||||
| { | ||||
| 	QStringList types; | ||||
| 	types << "text/uri-list"; | ||||
| 	return types; | ||||
| } | ||||
| Qt::DropActions IconList::supportedDropActions() const | ||||
| { | ||||
| 	return Qt::CopyAction; | ||||
| } | ||||
|  | ||||
| bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, | ||||
| 							const QModelIndex &parent) | ||||
| { | ||||
| 	if (action == Qt::IgnoreAction) | ||||
| 		return true; | ||||
| 	// check if the action is supported | ||||
| 	if (!data || !(action & supportedDropActions())) | ||||
| 		return false; | ||||
|  | ||||
| 	// files dropped from outside? | ||||
| 	if (data->hasUrls()) | ||||
| 	{ | ||||
| 		/* | ||||
| 		bool was_watching = is_watching; | ||||
| 		if(was_watching) | ||||
| 			stopWatching(); | ||||
| 		*/ | ||||
| 		auto urls = data->urls(); | ||||
| 		QStringList iconFiles; | ||||
| 		for (auto url : urls) | ||||
| 		{ | ||||
| 			// only local files may be dropped... | ||||
| 			if (!url.isLocalFile()) | ||||
| 				continue; | ||||
| 			iconFiles += url.toLocalFile(); | ||||
| 		} | ||||
| 		installIcons(iconFiles); | ||||
| 		/* | ||||
| 		if(was_watching) | ||||
| 			startWatching(); | ||||
| 		*/ | ||||
| 		return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| Qt::ItemFlags IconList::flags(const QModelIndex &index) const | ||||
| { | ||||
| 	Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); | ||||
| 	if (index.isValid()) | ||||
| 		return Qt::ItemIsDropEnabled | defaultFlags; | ||||
| 	else | ||||
| 		return Qt::ItemIsDropEnabled | defaultFlags; | ||||
| } | ||||
|  | ||||
| QVariant IconList::data(const QModelIndex &index, int role) const | ||||
| { | ||||
| 	if (!index.isValid()) | ||||
| 		return QVariant(); | ||||
|  | ||||
| 	int row = index.row(); | ||||
|  | ||||
| 	if (row < 0 || row >= d->icons.size()) | ||||
| 		return QVariant(); | ||||
|  | ||||
| 	switch (role) | ||||
| 	{ | ||||
| 	case Qt::DecorationRole: | ||||
| 		return d->icons[row].icon; | ||||
| 	case Qt::DisplayRole: | ||||
| 		return d->icons[row].name; | ||||
| 	case Qt::UserRole: | ||||
| 		return d->icons[row].key; | ||||
| 	default: | ||||
| 		return QVariant(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int IconList::rowCount(const QModelIndex &parent) const | ||||
| { | ||||
| 	return d->icons.size(); | ||||
| } | ||||
|  | ||||
| void IconList::installIcons(QStringList iconFiles) | ||||
| { | ||||
| 	for (QString file : iconFiles) | ||||
| 	{ | ||||
| 		QFileInfo fileinfo(file); | ||||
| 		if (!fileinfo.isReadable() || !fileinfo.isFile()) | ||||
| 			continue; | ||||
| 		QString target = PathCombine("icons", fileinfo.fileName()); | ||||
|  | ||||
| 		QString suffix = fileinfo.suffix(); | ||||
| 		if (suffix != "jpeg" && suffix != "png" && suffix != "jpg") | ||||
| 			continue; | ||||
|  | ||||
| 		if (!QFile::copy(file, target)) | ||||
| 			continue; | ||||
|  | ||||
| 		QString key = fileinfo.baseName(); | ||||
| 		addIcon(key, key, target); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool IconList::deleteIcon(QString key) | ||||
| { | ||||
| 	int iconIdx = getIconIndex(key); | ||||
| 	if (iconIdx == -1) | ||||
| 		return false; | ||||
| 	auto &iconEntry = d->icons[iconIdx]; | ||||
| 	if (iconEntry.is_builtin) | ||||
| 		return false; | ||||
| 	if (QFile::remove(iconEntry.filename)) | ||||
| 	{ | ||||
| 		beginRemoveRows(QModelIndex(), iconIdx, iconIdx); | ||||
| 		d->icons.remove(iconIdx); | ||||
| 		reindex(); | ||||
| 		endRemoveRows(); | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool IconList::addIcon(QString key, QString name, QString path, bool is_builtin) | ||||
| { | ||||
| 	auto iter = d->index.find(key); | ||||
| 	if (iter != d->index.end()) | ||||
| 	{ | ||||
| 		if (d->icons[*iter].is_builtin) | ||||
| 			return false; | ||||
|  | ||||
| 		QIcon icon(path); | ||||
| 		if (icon.isNull()) | ||||
| 			return false; | ||||
|  | ||||
| 		auto &oldOne = d->icons[*iter]; | ||||
|  | ||||
| 		if (!QFile::remove(oldOne.filename)) | ||||
| 			return false; | ||||
|  | ||||
| 		// replace the icon | ||||
| 		oldOne = {key, name, icon, is_builtin, path}; | ||||
| 		dataChanged(index(*iter), index(*iter)); | ||||
| 		return true; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		QIcon icon(path); | ||||
| 		if (icon.isNull()) | ||||
| 			return false; | ||||
|  | ||||
| 		// add a new icon | ||||
| 		beginInsertRows(QModelIndex(), d->icons.size(), d->icons.size()); | ||||
| 		d->icons.push_back({key, name, icon, is_builtin, path}); | ||||
| 		d->index[key] = d->icons.size() - 1; | ||||
| 		endInsertRows(); | ||||
| 		return true; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void IconList::reindex() | ||||
| { | ||||
| 	d->index.clear(); | ||||
| 	int i = 0; | ||||
| 	for (auto &iter : d->icons) | ||||
| 	{ | ||||
| 		d->index[iter.key] = i; | ||||
| 		i++; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| QIcon IconList::getIcon(QString key) | ||||
| { | ||||
| 	int icon_index = getIconIndex(key); | ||||
|  | ||||
| 	if (icon_index != -1) | ||||
| 		return d->icons[icon_index].icon; | ||||
|  | ||||
| 	// Fallback for icons that don't exist. | ||||
| 	icon_index = getIconIndex("infinity"); | ||||
|  | ||||
| 	if (icon_index != -1) | ||||
| 		return d->icons[icon_index].icon; | ||||
| 	return QIcon(); | ||||
| } | ||||
|  | ||||
| int IconList::getIconIndex(QString key) | ||||
| { | ||||
| 	if (key == "default") | ||||
| 		key = "infinity"; | ||||
|  | ||||
| 	auto iter = d->index.find(key); | ||||
| 	if (iter != d->index.end()) | ||||
| 		return *iter; | ||||
|  | ||||
| 	return -1; | ||||
| } | ||||
|  | ||||
| #include "IconList.moc" | ||||
| @@ -28,7 +28,7 @@ | ||||
|  | ||||
| #include "MultiMC.h" | ||||
| #include "logic/lists/InstanceList.h" | ||||
| #include "logic/lists/IconList.h" | ||||
| #include "logic/icons/IconList.h" | ||||
| #include "logic/lists/MinecraftVersionList.h" | ||||
| #include "logic/BaseInstance.h" | ||||
| #include "logic/InstanceFactory.h" | ||||
| @@ -356,12 +356,13 @@ void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap) | ||||
|  | ||||
| 		QString iconKey = record.logo; | ||||
| 		iconKey.remove(QRegularExpression("\\..*")); | ||||
| 		MMC->icons()->addIcon(iconKey, iconKey, PathCombine(templateDir, record.logo), true); | ||||
| 		MMC->icons()->addIcon(iconKey, iconKey, PathCombine(templateDir, record.logo), | ||||
| 							  MMCIcon::Transient); | ||||
|  | ||||
| 		if (!QFileInfo(PathCombine(instanceDir, "instance.cfg")).exists()) | ||||
| 		{ | ||||
| 			BaseInstance *instPtr = NULL; | ||||
| 			auto & factory = InstanceFactory::get(); | ||||
| 			auto &factory = InstanceFactory::get(); | ||||
| 			auto version = MMC->minecraftlist()->findVersion(record.mcVersion); | ||||
| 			if (!version) | ||||
| 			{ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user