436 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			436 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* Copyright 2013-2021 MultiMC Contributors
 | 
						|
 *
 | 
						|
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
 * you may not use this file except in compliance with the License.
 | 
						|
 * You may obtain a copy of the License at
 | 
						|
 *
 | 
						|
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 *
 | 
						|
 * Unless required by applicable law or agreed to in writing, software
 | 
						|
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
 * See the License for the specific language governing permissions and
 | 
						|
 * limitations under the License.
 | 
						|
 */
 | 
						|
 | 
						|
#include "IconList.h"
 | 
						|
#include <FileSystem.h>
 | 
						|
#include <QMap>
 | 
						|
#include <QEventLoop>
 | 
						|
#include <QMimeData>
 | 
						|
#include <QUrl>
 | 
						|
#include <QFileSystemWatcher>
 | 
						|
#include <QSet>
 | 
						|
#include <QDebug>
 | 
						|
 | 
						|
#define MAX_SIZE 1024
 | 
						|
 | 
						|
IconList::IconList(const QStringList &builtinPaths, QString path, QObject *parent) : QAbstractListModel(parent)
 | 
						|
{
 | 
						|
    QSet<QString> builtinNames;
 | 
						|
 | 
						|
    // add builtin icons
 | 
						|
    for(auto & builtinPath: builtinPaths)
 | 
						|
    {
 | 
						|
        QDir instance_icons(builtinPath);
 | 
						|
        auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
 | 
						|
        for (auto file_info : file_info_list)
 | 
						|
        {
 | 
						|
            builtinNames.insert(file_info.completeBaseName());
 | 
						|
        }
 | 
						|
    }
 | 
						|
    for(auto & builtinName : builtinNames)
 | 
						|
    {
 | 
						|
        addThemeIcon(builtinName);
 | 
						|
    }
 | 
						|
 | 
						|
    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)));
 | 
						|
 | 
						|
    directoryChanged(path);
 | 
						|
 | 
						|
    // Forces the UI to update, so that lengthy icon names are shown properly from the start
 | 
						|
    emit iconUpdated({});
 | 
						|
}
 | 
						|
 | 
						|
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(!FS::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(IconType::FileBased))
 | 
						|
            continue;
 | 
						|
        current_list.push_back(it.m_images[IconType::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)
 | 
						|
    {
 | 
						|
        qDebug() << "Removing " << remove;
 | 
						|
        QFileInfo rmfile(remove);
 | 
						|
        QString key = rmfile.completeBaseName();
 | 
						|
 | 
						|
        QString suffix = rmfile.suffix();
 | 
						|
        // The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
 | 
						|
        if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
 | 
						|
            key = rmfile.fileName();
 | 
						|
 | 
						|
        int idx = getIconIndex(key);
 | 
						|
        if (idx == -1)
 | 
						|
            continue;
 | 
						|
        icons[idx].remove(IconType::FileBased);
 | 
						|
        if (icons[idx].type() == IconType::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)
 | 
						|
    {
 | 
						|
        qDebug() << "Adding " << add;
 | 
						|
 | 
						|
        QFileInfo addfile(add);
 | 
						|
        QString key = addfile.completeBaseName();
 | 
						|
 | 
						|
        QString suffix = addfile.suffix();
 | 
						|
        // The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
 | 
						|
        if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
 | 
						|
            key = addfile.fileName();
 | 
						|
 | 
						|
        if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased))
 | 
						|
        {
 | 
						|
            m_watcher->addPath(add);
 | 
						|
            emit iconUpdated(key);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void IconList::fileChanged(const QString &path)
 | 
						|
{
 | 
						|
    qDebug() << "Checking " << path;
 | 
						|
    QFileInfo checkfile(path);
 | 
						|
    if (!checkfile.exists())
 | 
						|
        return;
 | 
						|
    QString key = checkfile.completeBaseName();
 | 
						|
    int idx = getIconIndex(key);
 | 
						|
    if (idx == -1)
 | 
						|
        return;
 | 
						|
    QIcon icon(path);
 | 
						|
    if (!icon.availableSizes().size())
 | 
						|
        return;
 | 
						|
 | 
						|
    icons[idx].m_images[IconType::FileBased].icon = icon;
 | 
						|
    dataChanged(index(idx), index(idx));
 | 
						|
    emit iconUpdated(key);
 | 
						|
}
 | 
						|
 | 
						|
void IconList::SettingChanged(const Setting &setting, QVariant value)
 | 
						|
{
 | 
						|
    if(setting.id() != "IconsDir")
 | 
						|
        return;
 | 
						|
 | 
						|
    directoryChanged(value.toString());
 | 
						|
}
 | 
						|
 | 
						|
void IconList::startWatching()
 | 
						|
{
 | 
						|
    auto abs_path = m_dir.absolutePath();
 | 
						|
    FS::ensureFolderPathExists(abs_path);
 | 
						|
    is_watching = m_watcher->addPath(abs_path);
 | 
						|
    if (is_watching)
 | 
						|
    {
 | 
						|
        qDebug() << "Started watching " << abs_path;
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        qDebug() << "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(const QStringList &iconFiles)
 | 
						|
{
 | 
						|
    for (QString file : iconFiles)
 | 
						|
    {
 | 
						|
        QFileInfo fileinfo(file);
 | 
						|
        if (!fileinfo.isReadable() || !fileinfo.isFile())
 | 
						|
            continue;
 | 
						|
        QString target = FS::PathCombine(getDirectory(), fileinfo.fileName());
 | 
						|
 | 
						|
        QString suffix = fileinfo.suffix();
 | 
						|
        if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
 | 
						|
            continue;
 | 
						|
 | 
						|
        if (!QFile::copy(file, target))
 | 
						|
            continue;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void IconList::installIcon(const QString &file, const QString &name)
 | 
						|
{
 | 
						|
    QFileInfo fileinfo(file);
 | 
						|
    if(!fileinfo.isReadable() || !fileinfo.isFile())
 | 
						|
        return;
 | 
						|
 | 
						|
    QString target = FS::PathCombine(getDirectory(), name);
 | 
						|
 | 
						|
    QFile::copy(file, target);
 | 
						|
}
 | 
						|
 | 
						|
bool IconList::iconFileExists(const QString &key) const
 | 
						|
{
 | 
						|
    auto iconEntry = icon(key);
 | 
						|
    if(!iconEntry)
 | 
						|
    {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
    return iconEntry->has(IconType::FileBased);
 | 
						|
}
 | 
						|
 | 
						|
const MMCIcon *IconList::icon(const QString &key) const
 | 
						|
{
 | 
						|
    int iconIdx = getIconIndex(key);
 | 
						|
    if (iconIdx == -1)
 | 
						|
        return nullptr;
 | 
						|
    return &icons[iconIdx];
 | 
						|
}
 | 
						|
 | 
						|
bool IconList::deleteIcon(const QString &key)
 | 
						|
{
 | 
						|
    int iconIdx = getIconIndex(key);
 | 
						|
    if (iconIdx == -1)
 | 
						|
        return false;
 | 
						|
    auto &iconEntry = icons[iconIdx];
 | 
						|
    if (iconEntry.has(IconType::FileBased))
 | 
						|
    {
 | 
						|
        return QFile::remove(iconEntry.m_images[IconType::FileBased].filename);
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
}
 | 
						|
 | 
						|
bool IconList::addThemeIcon(const QString& key)
 | 
						|
{
 | 
						|
    auto iter = name_index.find(key);
 | 
						|
    if (iter != name_index.end())
 | 
						|
    {
 | 
						|
        auto &oldOne = icons[*iter];
 | 
						|
        oldOne.replace(Builtin, key);
 | 
						|
        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 = key;
 | 
						|
            mmc_icon.m_key = key;
 | 
						|
            mmc_icon.replace(Builtin, key);
 | 
						|
            icons.push_back(mmc_icon);
 | 
						|
            name_index[key] = icons.size() - 1;
 | 
						|
        }
 | 
						|
        endInsertRows();
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
bool IconList::addIcon(const QString &key, const QString &name, const QString &path, const IconType type)
 | 
						|
{
 | 
						|
    // replace the icon even? is the input valid?
 | 
						|
    QIcon icon(path);
 | 
						|
    if (icon.isNull())
 | 
						|
        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::saveIcon(const QString &key, const QString &path, const char * format) const
 | 
						|
{
 | 
						|
    auto icon = getIcon(key);
 | 
						|
    auto pixmap = icon.pixmap(128, 128);
 | 
						|
    pixmap.save(path, format);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void IconList::reindex()
 | 
						|
{
 | 
						|
    name_index.clear();
 | 
						|
    int i = 0;
 | 
						|
    for (auto &iter : icons)
 | 
						|
    {
 | 
						|
        name_index[iter.m_key] = i;
 | 
						|
        i++;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
QIcon IconList::getIcon(const QString &key) const
 | 
						|
{
 | 
						|
    int icon_index = getIconIndex(key);
 | 
						|
 | 
						|
    if (icon_index != -1)
 | 
						|
        return icons[icon_index].icon();
 | 
						|
 | 
						|
    // Fallback for icons that don't exist.
 | 
						|
    icon_index = getIconIndex("grass");
 | 
						|
 | 
						|
    if (icon_index != -1)
 | 
						|
        return icons[icon_index].icon();
 | 
						|
    return QIcon();
 | 
						|
}
 | 
						|
 | 
						|
int IconList::getIconIndex(const QString &key) const
 | 
						|
{
 | 
						|
    auto iter = name_index.find(key == "default" ? "grass" : key);
 | 
						|
    if (iter != name_index.end())
 | 
						|
        return *iter;
 | 
						|
 | 
						|
    return -1;
 | 
						|
}
 | 
						|
 | 
						|
QString IconList::getDirectory() const
 | 
						|
{
 | 
						|
    return m_dir.absolutePath();
 | 
						|
}
 | 
						|
 | 
						|
//#include "IconList.moc"
 |