diff --git a/libs/libkis/Krita.cpp b/libs/libkis/Krita.cpp index a8ce842b8e..a257036fca 100644 --- a/libs/libkis/Krita.cpp +++ b/libs/libkis/Krita.cpp @@ -1,422 +1,422 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Krita.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "View.h" #include "Document.h" #include "Window.h" #include "Extension.h" #include "DockWidgetFactoryBase.h" #include "Filter.h" #include "InfoObject.h" #include "Resource.h" Krita* Krita::s_instance = 0; struct Krita::Private { Private() {} QList extensions; bool batchMode {false}; Notifier *notifier{new Notifier()}; }; Krita::Krita(QObject *parent) : QObject(parent) , d(new Private) { qRegisterMetaType(); connect(KisPart::instance(), SIGNAL(sigMainWindowIsBeingCreated(KisMainWindow*)), SLOT(mainWindowIsBeingCreated(KisMainWindow*))); } Krita::~Krita() { qDeleteAll(d->extensions); delete d->notifier; delete d; } QList Krita::actions() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return QList(); } KActionCollection *actionCollection = mainWindow->actionCollection(); return actionCollection->actions(); } QAction *Krita::action(const QString &name) const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KActionCollection *actionCollection = mainWindow->actionCollection(); QAction *action = actionCollection->action(name); return action; } Document* Krita::activeDocument() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KisView *view = mainWindow->activeView(); if (!view) { return 0; } KisDocument *document = view->document(); Document *d = new Document(document, false); return d; } void Krita::setActiveDocument(Document* value) { Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == value->document().data()) { view->activateWindow(); break; } } } bool Krita::batchmode() const { return d->batchMode; } void Krita::setBatchmode(bool value) { d->batchMode = value; } QList Krita::documents() const { QList ret; foreach(QPointer doc, KisPart::instance()->documents()) { ret << new Document(doc, false); } return ret; } QStringList Krita::filters() const { QStringList ls = KisFilterRegistry::instance()->keys(); std::sort(ls.begin(), ls.end()); return ls; } Filter *Krita::filter(const QString &name) const { if (!filters().contains(name)) return 0; Filter *filter = new Filter(); filter->setName(name); KisFilterSP f = KisFilterRegistry::instance()->value(name); KisFilterConfigurationSP fc = f->defaultConfiguration(KisGlobalResourcesInterface::instance()); InfoObject *info = new InfoObject(fc); filter->setConfiguration(info); return filter; } QStringList Krita::colorModels() const { QSet colorModelsIds; QList ids = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(KoID id, ids) { colorModelsIds << id.id(); } #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) return QStringList(colorModelsIds.begin(), colorModelsIds.end()); #else return QStringList::fromSet(colorModelsIds); #endif } QStringList Krita::colorDepths(const QString &colorModel) const { QSet colorDepthsIds; QList ids = KoColorSpaceRegistry::instance()->colorDepthList(colorModel, KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(KoID id, ids) { colorDepthsIds << id.id(); } #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) return QStringList(colorDepthsIds.begin(), colorDepthsIds.end()); #else return QStringList::fromSet(colorDepthsIds); #endif } QStringList Krita::filterStrategies() const { return KisFilterStrategyRegistry::instance()->keys(); } QStringList Krita::profiles(const QString &colorModel, const QString &colorDepth) const { QSet profileNames; QString id = KoColorSpaceRegistry::instance()->colorSpaceId(colorModel, colorDepth); QList profiles = KoColorSpaceRegistry::instance()->profilesFor(id); Q_FOREACH(const KoColorProfile *profile, profiles) { profileNames << profile->name(); } #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) QStringList r(profileNames.begin(), profileNames.end()); #else QStringList r = QStringList::fromSet(profileNames); #endif r.sort(); return r; } bool Krita::addProfile(const QString &profilePath) { KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); return iccEngine->addProfile(profilePath); } Notifier* Krita::notifier() const { return d->notifier; } QString Krita::version() const { return KritaVersionWrapper::versionString(true); } QList Krita::views() const { QList ret; foreach(QPointer view, KisPart::instance()->views()) { ret << new View(view); } return ret; } Window *Krita::activeWindow() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } return new Window(mainWindow); } QList Krita::windows() const { QList ret; foreach(QPointer mainWin, KisPart::instance()->mainWindows()) { ret << new Window(mainWin); } return ret; } QMap Krita::resources(const QString &type) const { QMap resources; KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(type); for (int i = 0; i < resourceModel->rowCount(); ++i) { QModelIndex idx = resourceModel->index(i, 0); - int id = resourceModel->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); - QString name = resourceModel->data(idx, Qt::UserRole + KisResourceModel::Name).toString(); - QString filename = resourceModel->data(idx, Qt::UserRole + KisResourceModel::Filename).toString(); - QImage image = resourceModel->data(idx, Qt::UserRole + KisResourceModel::Thumbnail).value(); + int id = resourceModel->data(idx, Qt::UserRole + KisAbstractResourceModel::Id).toInt(); + QString name = resourceModel->data(idx, Qt::UserRole + KisAbstractResourceModel::Name).toString(); + QString filename = resourceModel->data(idx, Qt::UserRole + KisAbstractResourceModel::Filename).toString(); + QImage image = resourceModel->data(idx, Qt::UserRole + KisAbstractResourceModel::Thumbnail).value(); resources[name] = new Resource(id, type, name, filename, image, 0); } return resources; } QStringList Krita::recentDocuments() const { KConfigGroup grp = KSharedConfig::openConfig()->group(QString("RecentFiles")); QStringList keys = grp.keyList(); QStringList recentDocuments; for(int i = 0; i <= keys.filter("File").count(); i++) recentDocuments << grp.readEntry(QString("File%1").arg(i), QString("")); return recentDocuments; } Document* Krita::createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile, double resolution) { KisDocument *document = KisPart::instance()->createDocument(); document->setObjectName(name); KisPart::instance()->addDocument(document, false); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); Q_ASSERT(cs); QColor qc(Qt::white); qc.setAlpha(0); KoColor bgColor(qc, cs); if (!document->newImage(name, width, height, cs, bgColor, KisConfig::RASTER_LAYER, 1, "", double(resolution / 72) )) { return 0; } Q_ASSERT(document->image()); Document *doc = new Document(document, true); return doc; } Document* Krita::openDocument(const QString &filename) { KisDocument *document = KisPart::instance()->createDocument(); document->setFileBatchMode(this->batchmode()); KisPart::instance()->addDocument(document); document->openUrl(QUrl::fromLocalFile(filename), KisDocument::DontAddToRecent); document->setFileBatchMode(false); return new Document(document, true); } Window* Krita::openWindow() { KisMainWindow *mw = KisPart::instance()->createMainWindow(); return new Window(mw); } void Krita::addExtension(Extension* extension) { d->extensions.append(extension); } QList< Extension* > Krita::extensions() { return d->extensions; } void Krita::writeSetting(const QString &group, const QString &name, const QString &value) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); grp.writeEntry(name, value); } QString Krita::readSetting(const QString &group, const QString &name, const QString &defaultValue) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); return grp.readEntry(name, defaultValue); } QIcon Krita::icon(QString &iconName) const { return KisIconUtils::loadIcon(iconName); } void Krita::addDockWidgetFactory(DockWidgetFactoryBase* factory) { KoDockRegistry::instance()->add(factory); } Krita* Krita::instance() { if (!s_instance) { s_instance = new Krita; } return s_instance; } /** * Scripter.fromVariant(variant) * variant is a QVariant * returns instance of QObject-subclass * * This is a helper method for PyQt because PyQt cannot cast a variant to a QObject or QWidget */ QObject *Krita::fromVariant(const QVariant& v) { if (v.canConvert< QWidget* >()) { QObject* obj = qvariant_cast< QWidget* >(v); return obj; } else if (v.canConvert< QObject* >()) { QObject* obj = qvariant_cast< QObject* >(v); return obj; } else return 0; } QString Krita::krita_i18n(const QString &text) { return i18n(text.toUtf8().constData()); } QString Krita::krita_i18nc(const QString &context, const QString &text) { return i18nc(context.toUtf8().constData(), text.toUtf8().constData()); } void Krita::mainWindowIsBeingCreated(KisMainWindow *kisWindow) { Q_FOREACH(Extension *extension, d->extensions) { Window window(kisWindow); extension->createActions(&window); } } diff --git a/libs/resources/KisResourceCacheDb.cpp b/libs/resources/KisResourceCacheDb.cpp index 57c8c84ba8..dd6f9b4af9 100644 --- a/libs/resources/KisResourceCacheDb.cpp +++ b/libs/resources/KisResourceCacheDb.cpp @@ -1,1286 +1,1291 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * 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 "KisResourceCacheDb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceLocator.h" #include "KisResourceLoaderRegistry.h" const QString dbDriver = "QSQLITE"; const QString KisResourceCacheDb::dbLocationKey { "ResourceCacheDbDirectory" }; const QString KisResourceCacheDb::resourceCacheDbFilename { "resourcecache.sqlite" }; const QString KisResourceCacheDb::databaseVersion { "0.0.2" }; QStringList KisResourceCacheDb::storageTypes { QStringList() }; QStringList KisResourceCacheDb::disabledBundles { QStringList() << "Krita_3_Default_Resources.bundle" }; bool KisResourceCacheDb::s_valid {false}; QString KisResourceCacheDb::s_lastError {QString()}; bool KisResourceCacheDb::isValid() { return s_valid; } QString KisResourceCacheDb::lastError() { return s_lastError; } QSqlError createDatabase(const QString &location) { // NOTE: if the id's of Unknown and Memory in the database // will change, and that will break the queries that // remove Unknown and Memory storages on start-up. KisResourceCacheDb::storageTypes << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(1)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(2)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(3)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(4)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(5)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(6)) ; if (!QSqlDatabase::connectionNames().isEmpty()) { infoResources << "Already connected to resource cache database"; return QSqlError(); } QDir dbLocation(location); if (!dbLocation.exists()) { dbLocation.mkpath(dbLocation.path()); } QSqlDatabase db = QSqlDatabase::addDatabase(dbDriver); db.setDatabaseName(location + "/" + KisResourceCacheDb::resourceCacheDbFilename); //qDebug() << "QuerySize supported" << db.driver()->hasFeature(QSqlDriver::QuerySize); if (!db.open()) { qWarning() << "Could not connect to resource cache database"; return db.lastError(); } QStringList tables = QStringList() << "version_information" << "storage_types" << "resource_types" << "storages" << "tags" << "resources" << "versioned_resources" << "resource_tags" << "metadata" << "tags_storages"; QStringList dbTables; // Verify whether we should recreate the database { bool allTablesPresent = true; dbTables = db.tables(); Q_FOREACH(const QString &table, tables) { if (!dbTables.contains(table)) { allTablesPresent = false; } } bool schemaIsOutDated = false; QString schemaVersion = "Unknown"; QString kritaVersion = "Unknown"; int creationDate = 0; if (dbTables.contains("version_information")) { // Verify the version number QFile f(":/get_version_information.sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q(f.readAll()); if (q.size() > 0) { q.first(); schemaVersion = q.value(0).toString(); kritaVersion = q.value(1).toString(); creationDate = q.value(2).toInt(); if (schemaVersion != KisResourceCacheDb::databaseVersion) { // XXX: Implement migration schemaIsOutDated = true; qFatal("Database schema is outdated, migration is needed. Database migration has NOT been implemented yet."); } } } else { return QSqlError("Error executing SQL", "Could not open get_version_information.sql", QSqlError::StatementError); } } if (allTablesPresent && !schemaIsOutDated) { KisUsageLogger::log(QString("Database is up to date. Version: %1, created by Krita %2, at %3") .arg(schemaVersion) .arg(kritaVersion) .arg(QDateTime::fromSecsSinceEpoch(creationDate).toString())); return QSqlError(); } } // Create tables Q_FOREACH(const QString &table, tables) { QFile f(":/create_" + table + ".sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q; if (!q.exec(f.readAll())) { qWarning() << "Could not create table" << table << q.lastError(); return db.lastError(); } infoResources << "Created table" << table; } else { return QSqlError("Error executing SQL", QString("Could not find SQL file %1").arg(table), QSqlError::StatementError); } } // Create indexes QStringList indexes = QStringList() << "storages" << "versioned_resources"; Q_FOREACH(const QString &index, indexes) { QFile f(":/create_index_" + index + ".sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q; if (!q.exec(f.readAll())) { qWarning() << "Could not create index" << index; return db.lastError(); } infoResources << "Created table" << index; } else { return QSqlError("Error executing SQL", QString("Could not find SQL file %1").arg(index), QSqlError::StatementError); } } // Fill lookup tables { if (dbTables.contains("storage_types")) { QSqlQuery q; if (!q.exec("DELETE * FROM storage_types;")) { qWarning() << "Could not clear table storage_types" << db.lastError(); } } QFile f(":/fill_storage_types.sql"); if (f.open(QFile::ReadOnly)) { QString sql = f.readAll(); Q_FOREACH(const QString &originType, KisResourceCacheDb::storageTypes) { QSqlQuery q(sql); q.addBindValue(originType); if (!q.exec()) { qWarning() << "Could not insert" << originType << db.lastError() << q.executedQuery(); return db.lastError(); } } infoResources << "Filled lookup table storage_types"; } else { return QSqlError("Error executing SQL", QString("Could not find SQL fill_storage_types.sql."), QSqlError::StatementError); } } { if (dbTables.contains("resource_types")) { QSqlQuery q; if (!q.exec("DELETE * FROM resource_types;")) { qWarning() << "Could not clear table resource_types" << db.lastError(); } } QFile f(":/fill_resource_types.sql"); if (f.open(QFile::ReadOnly)) { QString sql = f.readAll(); Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { QSqlQuery q(sql); q.addBindValue(resourceType); if (!q.exec()) { qWarning() << "Could not insert" << resourceType << db.lastError() << q.executedQuery(); return db.lastError(); } } infoResources << "Filled lookup table resource_types"; } else { return QSqlError("Error executing SQL", QString("Could not find SQL fill_resource_types.sql."), QSqlError::StatementError); } } { QFile f(":/fill_version_information.sql"); if (f.open(QFile::ReadOnly)) { QString sql = f.readAll(); QSqlQuery q; q.prepare(sql); q.addBindValue(KisResourceCacheDb::databaseVersion); q.addBindValue(KritaVersionWrapper::versionString()); q.addBindValue(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); if (!q.exec()) { qWarning() << "Could not insert the current version" << db.lastError() << q.executedQuery() << q.boundValues(); return db.lastError(); } infoResources << "Filled version table"; } else { return QSqlError("Error executing SQL", QString("Could not find SQL fill_version_information.sql."), QSqlError::StatementError); } } return QSqlError(); } bool KisResourceCacheDb::initialize(const QString &location) { QSqlError err = createDatabase(location); s_valid = !err.isValid(); switch (err.type()) { case QSqlError::NoError: s_lastError = QString(); break; case QSqlError::ConnectionError: s_lastError = QString("Could not initialize the resource cache database. Connection error: %1").arg(err.text()); break; case QSqlError::StatementError: s_lastError = QString("Could not initialize the resource cache database. Statement error: %1").arg(err.text()); break; case QSqlError::TransactionError: s_lastError = QString("Could not initialize the resource cache database. Transaction error: %1").arg(err.text()); break; case QSqlError::UnknownError: s_lastError = QString("Could not initialize the resource cache database. Unknown error: %1").arg(err.text()); break; } // Delete all storages that are no longer known to the resource locator (including the memory storages) deleteTemporaryResources(); return s_valid; } int KisResourceCacheDb::resourceIdForResource(const QString &resourceName, const QString &resourceType, const QString &storageLocation) { QFile f(":/select_resource_id.sql"); f.open(QFile::ReadOnly); QSqlQuery q; if (!q.prepare(f.readAll())) { qWarning() << "Could not read and prepare resourceIdForResource" << q.lastError(); return -1; } q.bindValue(":name", resourceName); q.bindValue(":resource_type", resourceType); q.bindValue(":storage_location", storageLocation); if (!q.exec()) { qWarning() << "Could not query resourceIdForResource" << q.boundValues() << q.lastError(); return -1; } if (!q.first()) { return -1; } return q.value(0).toInt(); } bool KisResourceCacheDb::resourceNeedsUpdating(int resourceId, QDateTime timestamp) { QSqlQuery q; if (!q.prepare("SELECT timestamp\n" "FROM versioned_resources\n" "WHERE resource_id = :resource_id\n" "AND version = (SELECT MAX(version)\n" " FROM versioned_resources\n" " WHERE resource_id = :resource_id);")) { qWarning() << "Could not prepare resourceNeedsUpdating statement" << q.lastError(); return false; } q.bindValue(":resource_id", resourceId); if (!q.exec()) { qWarning() << "Could not query for the most recent timestamp" << q.boundValues() << q.lastError(); return false; } if (!q.first()) { qWarning() << "Inconsistent database: could not find a version for resource with Id" << resourceId; return false; } QVariant resourceTimeStamp = q.value(0); if (!resourceTimeStamp.isValid()) { qWarning() << "Could not retrieve timestamp from versioned_resources" << resourceId; return false; } return (timestamp.toSecsSinceEpoch() > resourceTimeStamp.toInt()); } bool KisResourceCacheDb::addResourceVersion(int resourceId, QDateTime timestamp, KisResourceStorageSP storage, KoResourceSP resource) { bool r = false; // Create the new version. The resource is expected to have an updated version number, or // this will fail on the unique index on resource_id, storage_id and version. { QSqlQuery q; r = q.prepare("INSERT INTO versioned_resources \n" "(resource_id, storage_id, version, location, timestamp, md5sum)\n" "VALUES\n" "( :resource_id\n" ", (SELECT id \n" " FROM storages \n" " WHERE location = :storage_location)\n" ", :version\n" ", :location\n" ", :timestamp\n" ", :md5sum\n" ");"); if (!r) { qWarning() << "Could not prepare addResourceVersion statement" << q.lastError(); return r; } q.bindValue(":resource_id", resourceId); q.bindValue(":storage_location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); q.bindValue(":version", resource->version()); q.bindValue(":location", QFileInfo(resource->filename()).fileName()); q.bindValue(":timestamp", timestamp.toSecsSinceEpoch()); Q_ASSERT(!resource->md5().isEmpty()); q.bindValue(":md5sum", resource->md5().toHex()); r = q.exec(); if (!r) { qWarning() << "Could not execute addResourceVersion statement" << q.boundValues() << q.lastError(); return r; } } // Update the resource itself. The resource gets a new filename when it's updated { QSqlQuery q; r = q.prepare("UPDATE resources\n" "SET name = :name\n" ", filename = :filename\n" ", tooltip = :tooltip\n" ", thumbnail = :thumbnail\n" ", version = :version\n" "WHERE id = :id"); if (!r) { qWarning() << "Could not prepare updateResource statement" << q.lastError(); return r; } qDebug() << resource->name() << resource->filename() << resource->version(); q.bindValue(":name", resource->name()); q.bindValue(":filename", QFileInfo(resource->filename()).fileName()); q.bindValue(":tooltip", i18n(resource->name().toUtf8())); q.bindValue(":version", resource->version()); QByteArray ba; QBuffer buf(&ba); buf.open(QBuffer::WriteOnly); resource->thumbnail().save(&buf, "PNG"); buf.close(); q.bindValue(":thumbnail", ba); q.bindValue(":id", resourceId); r = q.exec(); if (!r) { qWarning() << "Could not update resource" << q.boundValues() << q.lastError(); } + } return r; } bool KisResourceCacheDb::addResource(KisResourceStorageSP storage, QDateTime timestamp, KoResourceSP resource, const QString &resourceType) { bool r = false; if (!s_valid) { qWarning() << "KisResourceCacheDb::addResource: The database is not valid"; return false; } if (!resource || !resource->valid()) { qWarning() << "KisResourceCacheDb::addResource: The resource is not valid"; // We don't care about invalid resources and will just ignore them. return true; } bool temporary = (storage->type() == KisResourceStorage::StorageType::Memory); // Check whether it already exists int resourceId = resourceIdForResource(resource->name(), resourceType, KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (resourceId > -1) { if (resourceNeedsUpdating(resourceId, timestamp)) { - r = addResourceVersion(resourceId, timestamp, storage, resource); + if (addResourceVersion(resourceId, timestamp, storage, resource)) { + return true; + } + return false; } return true; } QSqlQuery q; r = q.prepare("INSERT INTO resources \n" "(storage_id, resource_type_id, name, filename, tooltip, thumbnail, status, temporary) \n" "VALUES \n" "((SELECT id " " FROM storages " " WHERE location = :storage_location)\n" ", (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type)\n" ", :name\n" ", :filename\n" ", :tooltip\n" ", :thumbnail\n" ", :status\n" ", :temporary);"); if (!r) { qWarning() << "Could not prepare addResource statement" << q.lastError(); return r; } q.bindValue(":storage_location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); q.bindValue(":resource_type", resourceType); q.bindValue(":name", resource->name()); q.bindValue(":filename", QFileInfo(resource->filename()).fileName()); q.bindValue(":tooltip", i18n(resource->name().toUtf8())); QByteArray ba; QBuffer buf(&ba); buf.open(QBuffer::WriteOnly); resource->image().save(&buf, "PNG"); buf.close(); q.bindValue(":thumbnail", ba); q.bindValue(":status", 1); q.bindValue(":temporary", (temporary ? 1 : 0)); r = q.exec(); if (!r) { qWarning() << "Could not execute addResource statement" << q.boundValues() << q.lastError(); return r; } resourceId = resourceIdForResource(resource->name(), resourceType, KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); + resource->setResourceId(resourceId); // Then add a new version r = q.prepare("INSERT INTO versioned_resources\n" "(resource_id, storage_id, version, location, timestamp, md5sum)\n" "VALUES\n" "(:resource_id\n" ", (SELECT id FROM storages\n" " WHERE location = :storage_location)\n" ", :version\n" ", :location\n" ", :timestamp\n" ", :md5sum\n" ");"); if (!r) { qWarning() << "Could not prepare intitial addResourceVersion statement" << q.lastError(); return r; } q.bindValue(":resource_id", resourceId); q.bindValue(":storage_location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); q.bindValue(":version", resource->version()); q.bindValue(":location", QFileInfo(resource->filename()).fileName()); q.bindValue(":timestamp", timestamp.toSecsSinceEpoch()); //Q_ASSERT(!resource->md5().isEmpty()); if (resource->md5().isEmpty()) { qWarning() << "No md5 for resource" << resource->name() << resourceType << storage->location(); } q.bindValue(":md5sum", resource->md5().toHex()); r = q.exec(); if (!r) { qWarning() << "Could not execute initial addResourceVersion statement" << q.boundValues() << q.lastError(); } return r; } bool KisResourceCacheDb::addResources(KisResourceStorageSP storage, QString resourceType) { QSqlDatabase::database().transaction(); QSharedPointer iter = storage->resources(resourceType); while (iter->hasNext()) { iter->next(); KoResourceSP resource = iter->resource(); if (resource && resource->valid()) { if (!addResource(storage, iter->lastModified(), resource, iter->type())) { qWarning() << "Could not add resource" << QFileInfo(resource->filename()).fileName() << "to the database"; } } } QSqlDatabase::database().commit(); return true; } -bool KisResourceCacheDb::removeResource(int resourceId) +bool KisResourceCacheDb::setResourceInActive(int resourceId) { if (resourceId < 0) { qWarning() << "Invalid resource id; cannot remove resource"; return false; } QSqlQuery q; bool r = q.prepare("UPDATE resources\n" "SET status = 0\n" "WHERE id = :resource_id"); if (!r) { qWarning() << "Could not prepare removeResource query" << q.lastError(); } q.bindValue(":resource_id", resourceId); if (!q.exec()) { qWarning() << "Could not update resource" << resourceId << "to inactive" << q.lastError(); return false; } return true; } bool KisResourceCacheDb::tagResource(KisResourceStorageSP storage, const QString resourceName, KisTagSP tag, const QString &resourceType) { // Get resource id int resourceId = resourceIdForResource(resourceName, resourceType, KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (resourceId < 0) { qWarning() << "Could not find resource to tag" << KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()) << resourceName << resourceType; return false; } // Get tag id int tagId {-1}; { QFile f(":/select_tag.sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q; if (!q.prepare(f.readAll())) { qWarning() << "Could not read and prepare select_tag.sql" << q.lastError(); return false; } q.bindValue(":url", tag->url()); q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not query tags" << q.boundValues() << q.lastError(); return false; } if (!q.first()) { qWarning() << "Could not find tag" << q.boundValues() << q.lastError(); return false; } tagId = q.value(0).toInt(); } } QSqlQuery q; if (!q.prepare("INSERT INTO resource_tags\n" "(resource_id, tag_id)\n" "VALUES\n" "(:resource_id, :tag_id);")) { qWarning() << "Could not prepare tagResource statement" << q.lastError(); return false; } q.bindValue(":resource_id", resourceId); q.bindValue(":tag_id", tagId); if (!q.exec()) { qWarning() << "Could not execute tagResource stagement" << q.boundValues() << q.lastError(); return false; } return true; } bool KisResourceCacheDb::hasTag(const QString &url, const QString &resourceType) { QFile f(":/select_tag.sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q; if (!q.prepare(f.readAll())) { qWarning() << "Could not read and prepare select_tag.sql" << q.lastError(); return false; } q.bindValue(":url", url); q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not query tags" << q.boundValues() << q.lastError(); } return q.first(); } qWarning() << "Could not open select_tag.sql"; return false; } bool KisResourceCacheDb::linkTagToStorage(const QString &url, const QString &resourceType, const QString &storageLocation) { QSqlQuery q; if (!q.prepare("INSERT INTO tags_storages\n" "(tag_id, storage_id)\n" "VALUES\n" "(\n" " ( SELECT id\n" " FROM tags\n" " WHERE url = :url\n" " AND resource_type_id = (SELECT id \n" " FROM resource_types\n" " WHERE name = :resource_type)" " )\n" ",( SELECT id\n" " FROM storages\n" " WHERE location = :storage_location\n" " )\n" ");")) { qWarning() << "Could not prepare add tag/storage statement" << q.lastError(); return false; } q.bindValue(":url", url); q.bindValue(":resource_type", resourceType); q.bindValue(":storage_location", KisResourceLocator::instance()->makeStorageLocationRelative(storageLocation)); if (!q.exec()) { qWarning() << "Could not insert tag/storage link" << q.boundValues() << q.lastError(); return false; } return true; } bool KisResourceCacheDb::addTag(const QString &resourceType, const QString storageLocation, const QString url, const QString name, const QString comment) { if (hasTag(url, resourceType)) { // Check whether this storage is already registered for this tag QSqlQuery q; if (!q.prepare("SELECT storages.location\n" "FROM tags_storages\n" ", tags\n" ", storages\n" "WHERE tags.id = tags_storages.tag_id\n" "AND storages.id = tags_storages.storage_id\n" "AND tags.resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type)\n" "AND tags.url = :url")) { qWarning() << "Could not prepare select tags from tags_storages query" << q.lastError(); } q.bindValue(":url", url); q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not execute tags_storages query" << q.boundValues() << q.lastError(); } // If this tag is not yet linked to the storage, link it if (!q.first()) { return linkTagToStorage(url, resourceType, storageLocation); } return true; } // Insert the tag { QSqlQuery q; if (!q.prepare("INSERT INTO tags\n" "(url, name, comment, resource_type_id, active)\n" "VALUES\n" "( :url\n" ", :name\n" ", :comment\n" ", (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type)\n" ", 1" ");")) { qWarning() << "Could not prepare insert tag statement" << q.lastError(); return false; } q.bindValue(":url", url); q.bindValue(":name", name); q.bindValue(":comment", comment); q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not insert tag" << q.boundValues() << q.lastError(); } } linkTagToStorage(url, resourceType, storageLocation); return true; } bool KisResourceCacheDb::addTags(KisResourceStorageSP storage, QString resourceType) { QSqlDatabase::database().transaction(); QSharedPointer iter = storage->tags(resourceType); while(iter->hasNext()) { iter->next(); if (!addTag(resourceType, storage->location(), iter->url(), iter->name(), iter->comment())) { qWarning() << "Could not add tag" << iter->url() << "to the database"; } if (!iter->tag()->defaultResources().isEmpty()) { Q_FOREACH(const QString &resourceName, iter->tag()->defaultResources()) { if (!tagResource(storage, resourceName, iter->tag(), resourceType)) { qWarning() << "Could not tag resource" << resourceName << "with tag" << iter->url(); } } } } QSqlDatabase::database().commit(); return true; } bool KisResourceCacheDb::addStorage(KisResourceStorageSP storage, bool preinstalled) { bool r = true; if (!s_valid) { qWarning() << "The database is not valid"; return false; } { QSqlQuery q; r = q.prepare("SELECT * FROM storages WHERE location = :location"); q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); r = q.exec(); if (!r) { qWarning() << "Could not select from storages"; return r; } if (q.first()) { qDebug() << "Storage already exists" << storage; return true; } } // Insert the storage; { QSqlQuery q; r = q.prepare("INSERT INTO storages\n " "(storage_type_id, location, timestamp, pre_installed, active, thumbnail)\n" "VALUES\n" "(:storage_type_id, :location, :timestamp, :pre_installed, :active, :thumbnail);"); if (!r) { qWarning() << "Could not prepare query" << q.lastError(); return r; } q.bindValue(":storage_type_id", static_cast(storage->type())); q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); q.bindValue(":timestamp", storage->timestamp().toSecsSinceEpoch()); q.bindValue(":pre_installed", preinstalled ? 1 : 0); q.bindValue(":active", !disabledBundles.contains(storage->name())); QByteArray ba; QBuffer buf(&ba); buf.open(QBuffer::WriteOnly); storage->thumbnail().save(&buf, "PNG"); buf.close(); q.bindValue(":thumbnail", ba); r = q.exec(); if (!r) qWarning() << "Could not execute query" << q.lastError(); } // Insert the metadata { QStringList keys = storage->metaDataKeys(); if (keys.size() > 0) { QSqlQuery q; if (!q.prepare("SELECT MAX(id)\n" "FROM storages\n")) { qWarning() << "Could not create select storages query for metadata" << q.lastError(); } if (!q.exec()) { qWarning() << "Could not execute select storages query for metadata" << q.lastError(); } q.first(); int id = q.value(0).toInt(); QMap metadata; Q_FOREACH(const QString &key, storage->metaDataKeys()) { metadata[key] = storage->metaData(key); } addMetaDataForId(metadata, id, "storages"); } } Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { if (!KisResourceCacheDb::addResources(storage, resourceType)) { qWarning() << "Failed to add all resources for storage" << storage; r = false; } if (!KisResourceCacheDb::addTags(storage, resourceType)) { qWarning() << "Failed to add all tags for storage" << storage; } } return r; } bool KisResourceCacheDb::deleteStorage(KisResourceStorageSP storage) { { QSqlQuery q; if (!q.prepare("DELETE FROM resources\n" "WHERE id IN (SELECT versioned_resources.resource_id\n" " FROM versioned_resources\n" " WHERE versioned_resources.storage_id = (SELECT storages.id\n" " FROM storages\n" " WHERE storages.location = :location)\n" " );")) { qWarning() << "Could not prepare delete resources query in deleteStorage" << q.lastError(); return false; } q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (!q.exec()) { qWarning() << "Could not execute delete resources query in deleteStorage" << q.lastError(); return false; } } { QSqlQuery q; if (!q.prepare("DELETE FROM versioned_resources\n" "WHERE storage_id = (SELECT storages.id\n" " FROM storages\n" " WHERE storages.location = :location);")) { qWarning() << "Could not prepare delete versioned_resources query" << q.lastError(); return false; } q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (!q.exec()) { qWarning() << "Could not execute delete versioned_resources query" << q.lastError(); return false; } } { QSqlQuery q; if (!q.prepare("DELETE FROM storages\n" "WHERE location = :location;")) { qWarning() << "Could not prepare delete storages query" << q.lastError(); return false; } q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (!q.exec()) { qWarning() << "Could not execute delete storages query" << q.lastError(); return false; } } return true; } bool KisResourceCacheDb::synchronizeStorage(KisResourceStorageSP storage) { qDebug() << "Going to synchronize" << storage->location(); QElapsedTimer t; t.start(); QSqlDatabase::database().transaction(); if (!s_valid) { qWarning() << "KisResourceCacheDb::addResource: The database is not valid"; return false; } bool success = true; // Find the storage in the database QSqlQuery q; if (!q.prepare("SELECT id\n" ", timestamp\n" ", pre_installed\n" "FROM storages\n" "WHERE location = :location\n")) { qWarning() << "Could not prepare storage timestamp statement" << q.lastError(); } q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (!q.exec()) { qWarning() << "Could not execute storage timestamp statement" << q.boundValues() << q.lastError(); } if (!q.first()) { // This is a new storage, the user must have dropped it in the path before restarting Krita, so add it. qDebug() << "Adding storage to the database:" << storage; if (!addStorage(storage, false)) { qWarning() << "Could not add new storage" << storage->name() << "to the database"; success = false; } return true; } // Only check the time stamp for container storages, not the contents if (storage->type() != KisResourceStorage::StorageType::Folder) { qDebug() << storage->location() << "is not a folder, going to check timestamps. Database:" << q.value(1).toInt() << ", File:" << storage->timestamp().toSecsSinceEpoch(); if (!q.value(0).isValid()) { qWarning() << "Could not retrieve timestamp for storage" << KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()); success = false; } if (storage->timestamp().toSecsSinceEpoch() > q.value(1).toInt()) { qDebug() << "Deleting" << storage->location() << "because the one on disk is newer."; if (!deleteStorage(storage)) { qWarning() << "Could not delete storage" << KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()); success = false; } qDebug() << "Inserting" << storage->location(); if (!addStorage(storage, q.value(2).toBool())) { qWarning() << "Could not add storage" << KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()); success = false; } } } else { // This is a folder, we need to check what's on disk and what's in the database // Check whether everything in the storage is in the database QList resourcesToBeDeleted; Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { QStringList resourcesOnDisk; // Check the folder QSharedPointer iter = storage->resources(resourceType); while (iter->hasNext()) { iter->next(); - qDebug() << "\tadding resources" << iter->url(); + // qDebug() << "\tadding resources" << iter->url(); KoResourceSP resource = iter->resource(); resourcesOnDisk << QFileInfo(iter->url()).fileName(); if (resource) { if (!addResource(storage, iter->lastModified(), resource, iter->type())) { qWarning() << "Could not add/update resource" << QFileInfo(resource->filename()).fileName() << "to the database"; success = false; } } } - qDebug() << "Checking for" << resourceType << ":" << resourcesOnDisk; + // qDebug() << "Checking for" << resourceType << ":" << resourcesOnDisk; QSqlQuery q; q.setForwardOnly(true); if (!q.prepare("SELECT resources.id, resources.filename\n" "FROM resources\n" ", resource_types\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n" "AND storage_id in (SELECT id\n" " FROM storages\n" " WHERE storage_type_id == :storage_type)")) { qWarning() << "Could not prepare resource by type query" << q.lastError(); success = false; continue; } q.bindValue(":resource_type", resourceType); q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Folder); if (!q.exec()) { qWarning() << "Could not exec resource by type query" << q.boundValues() << q.lastError(); success = false; continue; } while (q.next()) { if (!resourcesOnDisk.contains(q.value(1).toString())) { resourcesToBeDeleted << q.value(0).toInt(); } } } QSqlQuery deleteResources; if (!deleteResources.prepare("DELETE FROM resources WHERE id = :id")) { success = false; qWarning() << "Could not prepare delete Resources query"; } QSqlQuery deleteResourceVersions; if (!deleteResourceVersions.prepare("DELETE FROM versioned_resources WHERE resource_id = :id")) { success = false; qWarning() << "Could not prepare delete Resources query"; } Q_FOREACH(int id, resourcesToBeDeleted) { deleteResourceVersions.bindValue(":id", id); if (!deleteResourceVersions.exec()) { success = false; qWarning() << "Could not delete resource version" << deleteResourceVersions.boundValues() << deleteResourceVersions.lastError(); } deleteResources.bindValue(":id", id); if (!deleteResources.exec()) { success = false; qWarning() << "Could not delete resource" << deleteResources.boundValues() << deleteResources.lastError(); } } } QSqlDatabase::database().commit(); qDebug() << "Synchronizing the storages took" << t.elapsed() << "milliseconds for" << storage->location(); return success; } void KisResourceCacheDb::deleteTemporaryResources() { QSqlDatabase::database().transaction(); QSqlQuery q; if (!q.prepare("DELETE FROM versioned_resources\n" "WHERE storage_id in (SELECT id\n" " FROM storages\n" " WHERE storage_type_id == :storage_type)")) { qWarning() << "Could not prepare delete versioned resources from Unknown or Memory storages query." << q.lastError(); } q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Memory); if (!q.exec()) { qWarning() << "Could not execute delete versioned resources from Unknown or Memory storages query." << q.lastError(); } if (!q.prepare("DELETE FROM resources\n" "WHERE storage_id in (SELECT id\n" " FROM storages\n" " WHERE storage_type_id == :storage_type)")) { qWarning() << "Could not prepare delete resources from Unknown or Memory storages query." << q.lastError(); } q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Memory); if (!q.exec()) { qWarning() << "Could not execute delete resources from Unknown or Memory storages query." << q.lastError(); } if (!q.prepare("DELETE FROM versioned_resources\n" "WHERE resource_id IN (SELECT id FROM resources\n" " WHERE temporary = 1)")) { qWarning() << "Could not prepare delete temporary versioned resources query." << q.lastError(); } if (!q.exec()) { qWarning() << "Could not execute delete temporary versioned resources query." << q.lastError(); } if (!q.prepare("DELETE FROM resources\n" "WHERE temporary = 1")) { qWarning() << "Could not prepare delete temporary resources query." << q.lastError(); return; } if (!q.exec()) { qWarning() << "Could not execute delete temporary resources query." << q.lastError(); } if (!q.prepare("DELETE FROM storages\n" "WHERE storage_type_id == :storage_type\n")) { qWarning() << "Could not prepare delete Unknown or Memory storages query." << q.lastError(); } q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Memory); if (!q.exec()) { qWarning() << "Could not execute delete Unknown or Memory storages query." << q.lastError(); } QSqlDatabase::database().commit(); } bool KisResourceCacheDb::registerResourceType(const QString &resourceType) { // Check whether the type already exists { QSqlQuery q; if (!q.prepare("SELECT count(*)\n" "FROM resource_types\n" "WHERE name = :resource_type\n")) { qWarning() << "Could not prepare select from resource_types query" << q.lastError(); return false; } q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not execute select from resource_types query" << q.lastError(); return false; } q.first(); int rowCount = q.value(0).toInt(); if (rowCount > 0) { return true; } } // if not, add it QFile f(":/fill_resource_types.sql"); if (f.open(QFile::ReadOnly)) { QString sql = f.readAll(); QSqlQuery q(sql); q.addBindValue(resourceType); if (!q.exec()) { qWarning() << "Could not insert" << resourceType << q.lastError(); return false; } return true; } qWarning() << "Could not open fill_resource_types.sql"; return false; } QMap KisResourceCacheDb::metaDataForId(int id, const QString &tableName) { QMap map; QSqlQuery q; q.setForwardOnly(true); if (!q.prepare("SELECT key\n" ", value\n" "FROM metadata\n" "WHERE foreign_id = :id\n" "AND table_name = :table")) { qWarning() << "Could not prepare metadata query" << q.lastError(); return map; } q.bindValue(":id", id); q.bindValue(":table", tableName); if (!q.exec()) { qWarning() << "Could not execute metadata query" << q.lastError(); return map; } while (q.next()) { QString key = q.value(0).toString(); QByteArray ba = q.value(1).toByteArray(); QDataStream ds(QByteArray::fromBase64(ba)); QVariant value; ds >> value; map[key] = value; } return map; } bool KisResourceCacheDb::updateMetaDataForId(const QMap map, int id, const QString &tableName) { QSqlDatabase::database().transaction(); { QSqlQuery q; if (!q.prepare("DELETE FROM metadata\n" "WHERE foreign_id = :id\n" "AND table_name = :table\n")) { qWarning() << "Could not prepare delete metadata query" << q.lastError(); return false; } q.bindValue(":id", id); q.bindValue(":table", tableName); if (!q.exec()) { QSqlDatabase::database().rollback(); qWarning() << "Could not execute delete metadata query" << q.lastError(); return false; } } if (addMetaDataForId(map, id, tableName)) { QSqlDatabase::database().commit(); } else { QSqlDatabase::database().rollback(); } return true; } bool KisResourceCacheDb::addMetaDataForId(const QMap map, int id, const QString &tableName) { QSqlQuery q; if (!q.prepare("INSERT INTO metadata\n" "(foreign_id, table_name, key, value)\n" "VALUES\n" "(:id, :table, :key, :value)")) { QSqlDatabase::database().rollback(); qWarning() << "Could not create insert metadata query" << q.lastError(); return false; } QMap::const_iterator iter = map.cbegin(); while (iter != map.cend()) { q.bindValue(":id", id); q.bindValue(":table", tableName); q.bindValue(":key", iter.key()); QVariant v = iter.value(); QByteArray ba; QDataStream ds(&ba, QIODevice::WriteOnly); ds << v; ba = ba.toBase64(); q.bindValue(":value", QString::fromLatin1(ba)); if (!q.exec()) { qWarning() << "Could not insert metadata" << q.lastError(); return false; } ++iter; } return true; } diff --git a/libs/resources/KisResourceCacheDb.h b/libs/resources/KisResourceCacheDb.h index c431ff102a..116bc8a06d 100644 --- a/libs/resources/KisResourceCacheDb.h +++ b/libs/resources/KisResourceCacheDb.h @@ -1,139 +1,141 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * 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 KISRESOURCECACHEDB_H #define KISRESOURCECACHEDB_H #include #include #include /** * @brief The KisResourceCacheDb class encapsulates the database that * caches information about the resources available to the user. * * KisApplication creates and initializes the database. All other methods * are static and can be used from anywhere. */ class KRITARESOURCES_EXPORT KisResourceCacheDb { public: static const QString dbLocationKey; ///< configuration key for the location of the database static const QString resourceCacheDbFilename; ///< filename of the database static const QString databaseVersion; ///< current schema version static QStringList storageTypes; ///< kinds of places where resources can be stored static QStringList disabledBundles; ///< the list of compatibility bundles that need to inactive by default /** * @brief isValid * @return true if the database has been correctly created, false if the database cannot be used */ static bool isValid(); /** * @brief lastError returns the last SQL error. */ static QString lastError(); /** * @brief initializes the database and updates the scheme if necessary. Does not actually * fill the database with pointers to resources. * * @param location the location of the database * @return true if the database has been initialized correctly */ static bool initialize(const QString &location); /// Delete all storages that are Unknown or Memory and all resources that are marked temporary or belong to Unknown or Memory storages static void deleteTemporaryResources(); private: friend class KisResourceLocator; friend class TestResourceLocator; friend class TestResourceCacheDb; friend class KisTagModel; friend class KisResourceLoaderRegistry; explicit KisResourceCacheDb(); // Deleted ~KisResourceCacheDb(); // Deleted KisResourceCacheDb operator=(const KisResourceCacheDb&); // Deleted /** * @brief registerResourceType registers this resource type in the database * @param resourceType the string that represents the type * @return true if the type was registered or had already been registered */ static bool registerResourceType(const QString &resourceType); static int resourceIdForResource(const QString &resourceName, const QString &resourceType, const QString &storageLocation); static bool resourceNeedsUpdating(int resourceId, QDateTime timestamp); /** * @brief addResourceVersion addes a new version of the resource to the database. * The resource itself already should be updated with the updated filename and version. * @param resourceId unique identifier for the resource * @param timestamp * @param storage * @param resource * @return true if the database was successfully updated */ static bool addResourceVersion(int resourceId, QDateTime timestamp, KisResourceStorageSP storage, KoResourceSP resource); static bool addResource(KisResourceStorageSP storage, QDateTime timestamp, KoResourceSP resource, const QString &resourceType); static bool addResources(KisResourceStorageSP storage, QString resourceType); /// Make this resource inactive; this does not remove the resource from disk or from the database - static bool removeResource(int resourceId); + static bool setResourceInActive(int resourceId); static bool tagResource(KisResourceStorageSP storage, const QString resourceName, KisTagSP tag, const QString &resourceType); static bool hasTag(const QString &url, const QString &resourceType); static bool linkTagToStorage(const QString &url, const QString &resourceType, const QString &storageLocation); static bool addTag(const QString &resourceType, const QString storageLocation, const QString url, const QString name, const QString comment); static bool addTags(KisResourceStorageSP storage, QString resourceType); static bool addStorage(KisResourceStorageSP storage, bool preinstalled); + + /// Actually delete the storage and all its resources from the database (i.e., nothing is set to inactive, it's deleted) static bool deleteStorage(KisResourceStorageSP storage); static bool synchronizeStorage(KisResourceStorageSP storage); /** * @brief metaDataForId * @param id * @param tableName * @return */ static QMap metaDataForId(int id, const QString &tableName); /** * @brief setMetaDataForId removes all metadata for the given id and table name, * and inserts the metadata in the metadata table. * @param id * @param tableName * @return true if successful, false if not */ static bool updateMetaDataForId(const QMap map, int id, const QString &tableName); static bool addMetaDataForId(const QMap map, int id, const QString &tableName); static bool s_valid; static QString s_lastError; }; #endif // KISRESOURCECACHEDB_H diff --git a/libs/resources/KisResourceIterator.cpp b/libs/resources/KisResourceIterator.cpp index e8aa99734f..15bb032fee 100644 --- a/libs/resources/KisResourceIterator.cpp +++ b/libs/resources/KisResourceIterator.cpp @@ -1,164 +1,164 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * 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 "KisResourceIterator.h" #include #include KisResourceItem::KisResourceItem(KisResourceModel *resourceModel, const QModelIndex &index) : m_resourceModel(resourceModel) , m_index(index) { } int KisResourceItem::id() { if (m_index.isValid()) { - return m_index.data(Qt::UserRole + KisResourceModel::Id).toInt(); + return m_index.data(Qt::UserRole + KisAbstractResourceModel::Id).toInt(); } return -1; } QString KisResourceItem::resourceType() { if (m_index.isValid()) { - return m_index.data(Qt::UserRole + KisResourceModel::ResourceType).toString(); + return m_index.data(Qt::UserRole + KisAbstractResourceModel::ResourceType).toString(); } return QString(); } QString KisResourceItem::name() { if (m_index.isValid()) { - return m_index.data(Qt::UserRole + KisResourceModel::Name).toString(); + return m_index.data(Qt::UserRole + KisAbstractResourceModel::Name).toString(); } return QString(); } QString KisResourceItem::filename() { if (m_index.isValid()) { - return m_index.data(Qt::UserRole + KisResourceModel::Filename).toString(); + return m_index.data(Qt::UserRole + KisAbstractResourceModel::Filename).toString(); } return QString(); } QString KisResourceItem::tooltip() { if (m_index.isValid()) { - return m_index.data(Qt::UserRole + KisResourceModel::Tooltip).toString(); + return m_index.data(Qt::UserRole + KisAbstractResourceModel::Tooltip).toString(); } return QString(); } QImage KisResourceItem::thumbnail() { if (m_index.isValid()) { - return m_index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); + return m_index.data(Qt::UserRole + KisAbstractResourceModel::Thumbnail).value(); } return QImage(); } KoResourceSP KisResourceItem::resource() { if (m_index.isValid() && m_resourceModel) { return m_resourceModel->resourceForIndex(m_index); } return 0; } struct KisResourceIterator::Private { Private(KisResourceModel *_resourceModel) : resourceModel(_resourceModel) {} KisResourceModel *resourceModel {0}; int currentRow {0}; }; KisResourceIterator::KisResourceIterator(KisResourceModel *resourceModel) : d(new Private(resourceModel)) { } KisResourceIterator::~KisResourceIterator() { } bool KisResourceIterator::hasNext() const { return d->currentRow < d->resourceModel->rowCount() - 1; } bool KisResourceIterator::hasPrevious() const { return d->currentRow > 0 && d->resourceModel->rowCount() > 1 && d->currentRow < d->resourceModel->rowCount(); } const KisResourceItemSP KisResourceIterator::next() { if (d->currentRow < d->resourceModel->rowCount() - 1) { d->currentRow++; QModelIndex idx = d->resourceModel->index(d->currentRow, 0); return KisResourceItemSP(new KisResourceItem(d->resourceModel, idx)); } return KisResourceItemSP(new KisResourceItem(0, QModelIndex())); } const KisResourceItemSP KisResourceIterator::peekNext() const { if (d->currentRow < d->resourceModel->rowCount() - 2) { QModelIndex idx = d->resourceModel->index(d->currentRow + 1, 0); return KisResourceItemSP(new KisResourceItem(d->resourceModel, idx)); } return KisResourceItemSP(new KisResourceItem(0, QModelIndex())); } const KisResourceItemSP KisResourceIterator::peekPrevious() const { if (d->currentRow > 1 && d->resourceModel->rowCount() > 2) { QModelIndex idx = d->resourceModel->index(d->currentRow -1, 0); return KisResourceItemSP(new KisResourceItem(d->resourceModel, idx)); } return KisResourceItemSP(new KisResourceItem(0, QModelIndex())); } const KisResourceItemSP KisResourceIterator::previous() { if (d->currentRow > 1 && d->resourceModel->rowCount() > 2) { d->currentRow--; QModelIndex idx = d->resourceModel->index(d->currentRow, 0); return KisResourceItemSP(new KisResourceItem(d->resourceModel, idx)); } return KisResourceItemSP(new KisResourceItem(0, QModelIndex())); } void KisResourceIterator::toBack() { d->currentRow = 0; } void KisResourceIterator::toEnd() { d->currentRow = d->resourceModel->rowCount() -1; } diff --git a/libs/resources/KisResourceLocator.cpp b/libs/resources/KisResourceLocator.cpp index d65ce0c86d..b867612672 100644 --- a/libs/resources/KisResourceLocator.cpp +++ b/libs/resources/KisResourceLocator.cpp @@ -1,691 +1,665 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * 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 "KisResourceLocator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoResourcePaths.h" #include "KisResourceStorage.h" #include "KisResourceCacheDb.h" #include "KisResourceLoaderRegistry.h" #include "KisMemoryStorage.h" #include "KisResourceModelProvider.h" #include +#include const QString KisResourceLocator::resourceLocationKey {"ResourceDirectory"}; class KisResourceLocator::Private { public: QString resourceLocation; QMap storages; QHash, KoResourceSP> resourceCache; QStringList errorMessages; }; KisResourceLocator::KisResourceLocator(QObject *parent) : QObject(parent) , d(new Private()) { } KisResourceLocator *KisResourceLocator::instance() { // Not a regular Q_GLOBAL_STATIC, because we want this deleted as // part of the app destructor. KisResourceLocator *locator = qApp->findChild(QString()); if (!locator) { locator = new KisResourceLocator(qApp); } return locator; } KisResourceLocator::~KisResourceLocator() { } KisResourceLocator::LocatorError KisResourceLocator::initialize(const QString &installationResourcesLocation) { InitializationStatus initializationStatus = InitializationStatus::Unknown; KConfigGroup cfg(KSharedConfig::openConfig(), ""); d->resourceLocation = cfg.readEntry(resourceLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); if (!d->resourceLocation.endsWith('/')) d->resourceLocation += '/'; QFileInfo fi(d->resourceLocation); if (!fi.exists()) { if (!QDir().mkpath(d->resourceLocation)) { d->errorMessages << i18n("1. Could not create the resource location at %1.", d->resourceLocation); return LocatorError::CannotCreateLocation; } initializationStatus = InitializationStatus::FirstRun; } if (!fi.isWritable()) { d->errorMessages << i18n("2. The resource location at %1 is not writable.", d->resourceLocation); return LocatorError::LocationReadOnly; } // Check whether we're updating from an older version if (initializationStatus != InitializationStatus::FirstRun) { QFile fi(d->resourceLocation + '/' + "KRITA_RESOURCE_VERSION"); if (!fi.exists()) { initializationStatus = InitializationStatus::FirstUpdate; } else { fi.open(QFile::ReadOnly); QVersionNumber resource_version = QVersionNumber::fromString(QString::fromUtf8(fi.readAll())); QVersionNumber krita_version = QVersionNumber::fromString(KritaVersionWrapper::versionString()); if (krita_version > resource_version) { initializationStatus = InitializationStatus::Updating; } else { initializationStatus = InitializationStatus::Initialized; } } } if (initializationStatus != InitializationStatus::Initialized) { KisResourceLocator::LocatorError res = firstTimeInstallation(initializationStatus, installationResourcesLocation); if (res != LocatorError::Ok) { return res; } initializationStatus = InitializationStatus::Initialized; } else { if (!synchronizeDb()) { return LocatorError::CannotSynchronizeDb; } } return LocatorError::Ok; } QStringList KisResourceLocator::errorMessages() const { return d->errorMessages; } QString KisResourceLocator::resourceLocationBase() const { return d->resourceLocation; } bool KisResourceLocator::resourceCached(QString storageLocation, const QString &resourceType, const QString &filename) const { storageLocation = makeStorageLocationAbsolute(storageLocation); QPair key = QPair (storageLocation, resourceType + "/" + filename); return d->resourceCache.contains(key); } void KisResourceLocator::loadRequiredResources(KoResourceSP resource) { QList requiredResources = resource->requiredResources(KisGlobalResourcesInterface::instance()); Q_FOREACH (KoResourceSP res, requiredResources) { if (res->resourceId() < 0) { // we put all the embedded resources into the global shared "memory" storage this->addResource(res->resourceType().first, res, "memory"); } } } KoResourceSP KisResourceLocator::resource(QString storageLocation, const QString &resourceType, const QString &filename) { storageLocation = makeStorageLocationAbsolute(storageLocation); QPair key = QPair (storageLocation, resourceType + "/" + filename); KoResourceSP resource; if (d->resourceCache.contains(key)) { resource = d->resourceCache[key]; } else { KisResourceStorageSP storage = d->storages[storageLocation]; if (!storage) { qWarning() << "Could not find storage" << storageLocation; return 0; } resource = storage->resource(resourceType + "/" + filename); // Try to locate bundle in bundle modificated resources location. if (QFileInfo(storage->location() + "_modified" + "/" + resourceType + "/" + filename).exists()) { QFileInfo bundleLoc(storage->location()); storage = d->storages[bundleLoc.path() + "/"]; QString bundleFolderLocation(bundleLoc.fileName() + "_modified" + "/" + resourceType + "/" + filename); resource = storage->resource(bundleFolderLocation); key = QPair (storageLocation, bundleFolderLocation); } else { resource = storage->resource(resourceType + "/" + filename); } if (resource) { KIS_SAFE_ASSERT_RECOVER(!resource->filename().startsWith(resourceType)) {}; d->resourceCache[key] = resource; // load all the embedded resources into temporary "memory" storage loadRequiredResources(resource); } } if (!resource) { qDebug() << "KoResourceSP KisResourceLocator::resource" << storageLocation << resourceType << filename; } Q_ASSERT(resource); resource->setStorageLocation(storageLocation); Q_ASSERT(!resource->storageLocation().isEmpty()); if (resource->resourceId() < 0 || resource->version() < 0) { QSqlQuery q; if (!q.prepare("SELECT resources.id\n" ", resources.version\n" "FROM resources\n" ", storages\n" ", resource_types\n" "WHERE storages.id = resources.storage_id\n" "AND storages.location = :storage_location\n" "AND resource_types.id = resources.resource_type_id\n" "AND resource_types.name = :resource_type\n" "AND resources.filename = :filename")) { qWarning() << "Could not prepare id/version query" << q.lastError(); } q.bindValue(":storage_location", makeStorageLocationRelative(storageLocation)); q.bindValue(":resource_type", resourceType); q.bindValue(":filename", filename); if (!q.exec()) { qWarning() << "Could not execute id/version quert" << q.lastError() << q.boundValues(); } if (!q.first()) { qWarning() << "Could not find the resource in the database" << storageLocation << resourceType << filename; } resource->setResourceId(q.value(0).toInt()); Q_ASSERT(resource->resourceId() >= 0); resource->setVersion(q.value(1).toInt()); Q_ASSERT(resource->version() >= 0); } if (!resource) { qWarning() << "Could not find resource" << resourceType + "/" + filename; return 0; } return resource; } KoResourceSP KisResourceLocator::resourceForId(int resourceId) { ResourceStorage rs = getResourceStorage(resourceId); KoResourceSP r = resource(rs.storageLocation, rs.resourceType, rs.resourceFileName); return r; } -bool KisResourceLocator::removeResource(int resourceId, const QString &/*storageLocation*/) +bool KisResourceLocator::setResourceInactive(int resourceId, const QString &/*storageLocation*/) { // First remove the resource from the cache ResourceStorage rs = getResourceStorage(resourceId); QPair key = QPair (rs.storageLocation, rs.resourceType + "/" + rs.resourceFileName); d->resourceCache.remove(key); - return KisResourceCacheDb::removeResource(resourceId); + return KisResourceCacheDb::setResourceInActive(resourceId); } bool KisResourceLocator::importResourceFromFile(const QString &resourceType, const QString &fileName, const QString &storageLocation) { KisResourceLoaderBase *loader = KisResourceLoaderRegistry::instance()->loader(resourceType, KisMimeDatabase::mimeTypeForFile(fileName)); QFile f(fileName); if (!f.open(QFile::ReadOnly)) { qWarning() << "Could not open" << fileName << "for loading"; return false; } KoResourceSP resource = loader->load(QFileInfo(fileName).fileName(), f, KisGlobalResourcesInterface::instance()); if (!resource) { qWarning() << "Could not import" << fileName << ": resource doesn't load."; return false; } KisResourceStorageSP storage = d->storages[makeStorageLocationAbsolute(storageLocation)]; Q_ASSERT(storage); if (!storage->addResource(resource)) { qWarning() << "Could not add resource" << resource->filename() << "to the folder storage"; return false; } return KisResourceCacheDb::addResource(folderStorage(), QFileInfo(resource->filename()).lastModified(), resource, resourceType); } bool KisResourceLocator::addResource(const QString &resourceType, const KoResourceSP resource, const QString &storageLocation) { if (!resource || !resource->valid()) return false; KisResourceStorageSP storage = d->storages[makeStorageLocationAbsolute(storageLocation)]; Q_ASSERT(storage); //If we have gotten this far and the resource still doesn't have a filename to save to, we should generate one. if (resource->filename().isEmpty()) { if (storageLocation == "memory") { resource->setFilename("memory/" + resourceType + "/" + resource->name()); } else { resource->setFilename(resource->name().split(" ").join("_") + resource->defaultFileExtension()); } } // Save the resource to the storage storage if (!storage->addResource(resource)) { qWarning() << "Could not add resource" << resource->filename() << "to the folder storage"; return false; } // And the database return KisResourceCacheDb::addResource(storage, storage->timeStampForResource(resourceType, resource->filename()), resource, resourceType); } bool KisResourceLocator::updateResource(const QString &resourceType, const KoResourceSP resource) { QString storageLocation = makeStorageLocationAbsolute(resource->storageLocation()); - qDebug() << ">>>>>>>>>>>>>>>> storageLocation"<< storageLocation << "resource storage location" << resource->storageLocation(); + //qDebug() << ">>>>>>>>>>>>>>>> storageLocation"<< storageLocation << "resource storage location" << resource->storageLocation(); Q_ASSERT(d->storages.contains(storageLocation)); Q_ASSERT(resource->resourceId() > -1); KisResourceStorageSP storage = d->storages[storageLocation]; resource->updateThumbnail(); int version = resource->version(); // This increments the version in the resource if (!storage->addResource(resource)) { qWarning() << "Failed to save the new version of " << resource->name() << "to storage" << storageLocation; return false; } // Memory storages don't store versioned resources if (storage->type() == KisResourceStorage::StorageType::Memory) { return true; } // It's the storages that keep track of the version Q_ASSERT(resource->version() == version + 1); // The version needs already to have been incremented if (!KisResourceCacheDb::addResourceVersion(resource->resourceId(), QDateTime::currentDateTime(), storage, resource)) { qWarning() << "Failed to add a new version of the resource to the database" << resource->name(); return false; } // Update the resource in the cache QPair key = QPair (storageLocation, resourceType + "/" + QFileInfo(resource->filename()).fileName()); d->resourceCache[key] = resource; return true; } QMap KisResourceLocator::metaDataForResource(int id) const { return KisResourceCacheDb::metaDataForId(id, "resources"); } bool KisResourceLocator::setMetaDataForResource(int id, QMap map) const { return KisResourceCacheDb::updateMetaDataForId(map, id, "resources"); } QMap KisResourceLocator::metaDataForStorage(const QString &storageLocation) const { QMap metadata; if (!d->storages.contains(makeStorageLocationAbsolute(storageLocation))) { qWarning() << storageLocation << "not in" << d->storages.keys(); return metadata; } KisResourceStorageSP st = d->storages[makeStorageLocationAbsolute(storageLocation)]; if (d->storages[makeStorageLocationAbsolute(storageLocation)].isNull()) { return metadata; } Q_FOREACH(const QString key, st->metaDataKeys()) { metadata[key] = st->metaData(key); } return metadata; } void KisResourceLocator::setMetaDataForStorage(const QString &storageLocation, QMap map) const { Q_ASSERT(d->storages.contains(storageLocation)); Q_FOREACH(const QString &key, map.keys()) { d->storages[storageLocation]->setMetaData(key, map[key]); } } -bool KisResourceLocator::storageContainsResourceByFile(const QString &storageLocation, const QString &resourceType, const QString &filename) const -{ - QSqlQuery q; - if (!q.prepare("SELECT *\n" - "FROM storages\n" - ", resources\n" - ", resource_types\n" - "WHERE resources.filename = :filename\n" - "AND resources.storage_id = storages.id\n" - "AND storages.location = :storage_location\n" - "AND resources.resource_type_id = resource_types.id\n" - "AND resource_types.name = :resource_type")) - { - qWarning() << "Could not prepare storageCOntainsResourceByFile query" << q.lastError(); - return false; - } - - q.bindValue(":filename", filename); - q.bindValue(":storage_location", storageLocation); - q.bindValue(":resource_type", resourceType); - - if (!q.exec()) { - qWarning() << "Could not execute storageCOntainsResourceByFile query" << q.lastError() << q.boundValues(); - return false; - } - - return q.first(); -} - void KisResourceLocator::purge() { d->resourceCache.clear(); } bool KisResourceLocator::addStorage(const QString &storageLocation, KisResourceStorageSP storage) { Q_ASSERT(!d->storages.contains(storageLocation)); d->storages[storageLocation] = storage; if (!KisResourceCacheDb::addStorage(storage, false)) { d->errorMessages.append(i18n("Could not add %1 to the database", storage->location())); return false; } - KisResourceModelProvider::resetAllModels(); - emit storageAdded(); + + emit storageAdded(storage->location()); return true; } bool KisResourceLocator::removeStorage(const QString &document) { // Cloned documents have a document storage, but that isn't in the locator. if (!d->storages.contains(document)) return true; purge(); + KisResourceStorageSP storage = d->storages. take(document); if (!KisResourceCacheDb::deleteStorage(storage)) { d->errorMessages.append(i18n("Could not remove storage %1 from the database", storage->location())); return false; } KisResourceModelProvider::resetAllModels(); - emit storageRemoved(); + emit storageRemoved(storage->location()); + return true; } bool KisResourceLocator::hasStorage(const QString &document) { return d->storages.contains(document); } KisResourceLocator::LocatorError KisResourceLocator::firstTimeInstallation(InitializationStatus initializationStatus, const QString &installationResourcesLocation) { emit progressMessage(i18n("Krita is running for the first time. Initialization will take some time.")); Q_UNUSED(initializationStatus); Q_FOREACH(const QString &folder, KisResourceLoaderRegistry::instance()->resourceTypes()) { QDir dir(d->resourceLocation + '/' + folder + '/'); if (!dir.exists()) { if (!QDir().mkpath(d->resourceLocation + '/' + folder + '/')) { d->errorMessages << i18n("3. Could not create the resource location at %1.", dir.path()); return LocatorError::CannotCreateLocation; } } } Q_FOREACH(const QString &folder, KisResourceLoaderRegistry::instance()->resourceTypes()) { QDir dir(installationResourcesLocation + '/' + folder + '/'); if (dir.exists()) { Q_FOREACH(const QString &entry, dir.entryList(QDir::Files | QDir::Readable)) { QFile f(dir.canonicalPath() + '/'+ entry); if (!QFileInfo(d->resourceLocation + '/' + folder + '/' + entry).exists()) { if (!f.copy(d->resourceLocation + '/' + folder + '/' + entry)) { d->errorMessages << i18n("Could not copy resource %1 to %2", f.fileName(), d->resourceLocation + '/' + folder + '/' + entry); } } } } } // And add bundles and adobe libraries QStringList filters = QStringList() << "*.bundle" << "*.abr" << "*.asl"; QDirIterator iter(installationResourcesLocation, filters, QDir::Files, QDirIterator::Subdirectories); while (iter.hasNext()) { iter.next(); emit progressMessage(i18n("Installing the resources from bundle %1.", iter.filePath())); QFile f(iter.filePath()); Q_ASSERT(f.exists()); if (!f.copy(d->resourceLocation + '/' + iter.fileName())) { d->errorMessages << i18n("Could not copy resource %1 to %2", f.fileName(), d->resourceLocation); } } QFile f(d->resourceLocation + '/' + "KRITA_RESOURCE_VERSION"); f.open(QFile::WriteOnly); f.write(KritaVersionWrapper::versionString().toUtf8()); f.close(); if (!initializeDb()) { return LocatorError::CannotInitializeDb; } return LocatorError::Ok; } bool KisResourceLocator::initializeDb() { emit progressMessage(i18n("Initializing the resources.")); d->errorMessages.clear(); findStorages(); Q_FOREACH(KisResourceStorageSP storage, d->storages) { QElapsedTimer t; t.start(); if (!KisResourceCacheDb::addStorage(storage, (storage->type() == KisResourceStorage::StorageType::Folder ? false : true))) { d->errorMessages.append(i18n("Could not add storage %1 to the cache database", storage->location())); } qDebug() << "Adding storage" << storage->location() << "to the database took" << t.elapsed() << "ms"; } return (d->errorMessages.isEmpty()); } void KisResourceLocator::findStorages() { d->storages.clear(); // Add the folder KisResourceStorageSP storage = QSharedPointer::create(d->resourceLocation); Q_ASSERT(storage->location() == d->resourceLocation); d->storages[d->resourceLocation] = storage; // Add the memory storage d->storages["memory"] = QSharedPointer::create("memory"); d->storages["memory"]->setMetaData(KisResourceStorage::s_meta_name, i18n("Temporary Resources")); // And add bundles and adobe libraries QStringList filters = QStringList() << "*.bundle" << "*.abr" << "*.asl"; QDirIterator iter(d->resourceLocation, filters, QDir::Files, QDirIterator::Subdirectories); while (iter.hasNext()) { iter.next(); KisResourceStorageSP storage = QSharedPointer::create(iter.filePath()); d->storages[storage->location()] = storage; } } QList KisResourceLocator::storages() const { return d->storages.values(); } KisResourceStorageSP KisResourceLocator::storageByLocation(const QString &location) const { if (!d->storages.contains(location)) { qWarning() << "No" << location << "storage defined:" << d->storages.keys(); return 0; } KisResourceStorageSP storage = d->storages[location]; if (!storage || !storage->valid()) { qWarning() << "Could not retrieve the" << location << "storage object or the object is not valid"; return 0; } return storage; } KisResourceStorageSP KisResourceLocator::folderStorage() const { return storageByLocation(d->resourceLocation); } KisResourceStorageSP KisResourceLocator::memoryStorage() const { return storageByLocation("memory"); } KisResourceLocator::ResourceStorage KisResourceLocator::getResourceStorage(int resourceId) const { ResourceStorage rs; QSqlQuery q; bool r = q.prepare("SELECT storages.location\n" ", resource_types.name as resource_type\n" ", resources.filename\n" "FROM resources\n" ", storages\n" ", resource_types\n" "WHERE resources.id = :resource_id\n" "AND resources.storage_id = storages.id\n" "AND resource_types.id = resources.resource_type_id"); if (!r) { qWarning() << "KisResourceLocator::removeResource: could not prepare query." << q.lastError(); return rs; } q.bindValue(":resource_id", resourceId); r = q.exec(); if (!r) { qWarning() << "KisResourceLocator::removeResource: could not execute query." << q.lastError(); return rs; } q.first(); QString storageLocation = q.value("location").toString(); QString resourceType= q.value("resource_type").toString(); QString resourceFilename = q.value("filename").toString(); rs.storageLocation = makeStorageLocationAbsolute(storageLocation); rs.resourceType = resourceType; rs.resourceFileName = resourceFilename; return rs; } QString KisResourceLocator::makeStorageLocationAbsolute(QString storageLocation) const { // qDebug() << "makeStorageLocationAbsolute" << storageLocation; if (storageLocation.isEmpty()) { return resourceLocationBase(); } if (QFileInfo(storageLocation).isRelative() && (storageLocation.endsWith("bundle") || storageLocation.endsWith("asl") || storageLocation.endsWith("abr"))) { if (resourceLocationBase().endsWith('/') || resourceLocationBase().endsWith("\\")) { storageLocation = resourceLocationBase() + storageLocation; } else { storageLocation = resourceLocationBase() + '/' + storageLocation; } } // qDebug() << "\t" << storageLocation; return storageLocation; } bool KisResourceLocator::synchronizeDb() { d->errorMessages.clear(); findStorages(); Q_FOREACH(const KisResourceStorageSP storage, d->storages) { if (!KisResourceCacheDb::synchronizeStorage(storage)) { d->errorMessages.append(i18n("Could not synchronize %1 with the database", storage->location())); } } return d->errorMessages.isEmpty(); } QString KisResourceLocator::makeStorageLocationRelative(QString location) const { // qDebug() << "makeStorageLocationRelative" << location << "locationbase" << resourceLocationBase(); return location.remove(resourceLocationBase()); } diff --git a/libs/resources/KisResourceLocator.h b/libs/resources/KisResourceLocator.h index 8ffb71e184..fe9d4b9623 100644 --- a/libs/resources/KisResourceLocator.h +++ b/libs/resources/KisResourceLocator.h @@ -1,284 +1,276 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * 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 KISRESOURCELOCATOR_H #define KISRESOURCELOCATOR_H #include #include #include #include #include "kritaresources_export.h" #include /** * The KisResourceLocator class locates all resource storages (folders, * bundles, various adobe resource libraries) in the resource location. * * The resource location is always a writable folder. * * There is one resource locator which is owned by the QApplication * object. * * The resource location is configurable, but there is only one location * where Krita will look for resources. */ class KRITARESOURCES_EXPORT KisResourceLocator : public QObject { Q_OBJECT public: // The configuration key that holds the resource location // for this installation of Krita. The location is // QStandardPaths::AppDataLocation by default, but that // can be changed. static const QString resourceLocationKey; static KisResourceLocator *instance(); ~KisResourceLocator(); enum class LocatorError { Ok, LocationReadOnly, CannotCreateLocation, CannotInitializeDb, CannotSynchronizeDb }; /** * @brief initialize Setup the resource locator for use. * * @param installationResourcesLocation the place where the resources * that come packaged with Krita reside. */ LocatorError initialize(const QString &installationResourcesLocation); /** * @brief errorMessages * @return */ QStringList errorMessages() const; /** * @brief resourceLocationBase is the place where all resource storages (folder, * bundles etc. are located. This is a writable place. * @return the base location for all storages. */ QString resourceLocationBase() const; /** * @brief purge purges the local resource cache */ void purge(); /** * @brief addStorage Adds a new resource storage to the database. The storage is * will be marked as not pre-installed. * @param storageLocation a unique name for the given storage * @param storage a storage object * @return true if the storage has been added successfully */ bool addStorage(const QString &storageLocation, KisResourceStorageSP storage); /** * @brief removeStorage removes the temporary storage from the database * @param document the unique name of the document * @return true is successful. */ bool removeStorage(const QString &storageLocation); /** * @brief hasStorage can be used to check whether the given storage already exists * @param storageLocation the name of the storage * @return true if the storage is known */ bool hasStorage(const QString &storageLocation); Q_SIGNALS: void progressMessage(const QString&); /// Emitted whenever a storage is added - void storageAdded(); + void storageAdded(const QString &location); /// Emitted whenever a storage is removed - void storageRemoved(); + void storageRemoved(const QString &location); private: - friend class KisResourceModel; + friend class KisAllResourcesModel; friend class KisTagModel; friend class KisStorageModel; friend class TestResourceLocator; friend class TestResourceModel; friend class Resource; friend class KisResourceCacheDb; friend class KisStorageFilterProxyModel; /// @return true if the resource is present in the cache, false if it hasn't been loaded bool resourceCached(QString storageLocation, const QString &resourceType, const QString &filename) const; /** * @brief resource finds a physical resource in one of the storages * @param storageLocation the storage containing the resource. If empty, * this is the folder storage. * * Note that the resource does not have the version or id field set, so this cannot be used directly, * but only through KisResourceModel. * * @param resourceType the type of the resource * @param filename the filename of the resource including extension, but withou * any paths * @return A resource if found, or 0 */ KoResourceSP resource(QString storageLocation, const QString &resourceType, const QString &filename); /** * @brief resourceForId returns the resource with the given id, or 0 if no such resource exists. * The resource object will have its id set but not its version. * @param resourceId the id */ KoResourceSP resourceForId(int resourceId); /** * @brief removeResource * @param resourceId * @param optional: the storage that contains the given resource * @return */ - bool removeResource(int resourceId, const QString &storageLocation = QString()); + bool setResourceInactive(int resourceId, const QString &storageLocation = QString()); /** * @brief importResourceFromFile * @param resourceType * @param fileName * @param storageLocation: optional, the storage where the resource will be stored. Empty means in the default Folder storage. * @return */ bool importResourceFromFile(const QString &resourceType, const QString &fileName, const QString &storageLocation = QString()); /** * @brief addResource adds the given resource to the database and potentially a storage * @param resourceType the type of the resource * @param resource the actual resource object * @param storageLocation the storage where the resource will be saved. By default this is the the default folder storage. * @return true if successful */ bool addResource(const QString &resourceType, const KoResourceSP resource, const QString &storageLocation = QString()); /** * @brief updateResource * @param resourceType * @param resource * @return */ bool updateResource(const QString &resourceType, const KoResourceSP resource); /** * @brief metaDataForResource * @param id * @return */ QMap metaDataForResource(int id) const; /** * @brief setMetaDataForResource * @param id * @param map * @return */ bool setMetaDataForResource(int id, QMap map) const; /** * @brief metaDataForStorage * @param storage * @return */ QMap metaDataForStorage(const QString &storageLocation) const; /** * @brief setMetaDataForStorage * @param storage * @param map */ void setMetaDataForStorage(const QString &storageLocation, QMap map) const; - /** - * @brief storageContainsResourceByFile - * @param storageLocation - * @param filename - * @return - */ - bool storageContainsResourceByFile(const QString &storageLocation, const QString &resourceType, const QString &filename) const; - /** * Loads all the resources required by \p resource into the cache * * loadRequiredResources() also loads embedded resources and adds them * into the database. */ void loadRequiredResources(KoResourceSP resource); KisResourceLocator(QObject *parent); KisResourceLocator(const KisResourceLocator&); KisResourceLocator operator=(const KisResourceLocator&); enum class InitializationStatus { Unknown, // We don't know whether Krita has run on this system for this resource location yet Initialized, // Everything is ready to start synchronizing the database FirstRun, // Krita hasn't run for this resource location yet FirstUpdate, // Krita was installed, but it's a version from before the resource locator existed, only user-defined resources are present Updating // Krita is updating from an older version with resource locator }; LocatorError firstTimeInstallation(InitializationStatus initializationStatus, const QString &installationResourcesLocation); // First time installation bool initializeDb(); // Synchronize on restarting Krita to see whether the user has added any storages or resources to the resources location bool synchronizeDb(); void findStorages(); QList storages() const; KisResourceStorageSP storageByLocation(const QString &location) const; KisResourceStorageSP folderStorage() const; KisResourceStorageSP memoryStorage() const; struct ResourceStorage { QString storageLocation; QString resourceType; QString resourceFileName; }; ResourceStorage getResourceStorage(int resourceId) const; QString makeStorageLocationAbsolute(QString storageLocation) const; QString makeStorageLocationRelative(QString location) const; class Private; QScopedPointer d; }; #endif // KISRESOURCELOCATOR_H diff --git a/libs/resources/KisResourceModel.cpp b/libs/resources/KisResourceModel.cpp index 439432c468..3988118d12 100644 --- a/libs/resources/KisResourceModel.cpp +++ b/libs/resources/KisResourceModel.cpp @@ -1,571 +1,776 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * 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 "KisResourceModel.h" #include #include #include #include #include #include #include #include -struct KisResourceModel::Private { +struct KisAllResourcesModel::Private { QSqlQuery resourcesQuery; QSqlQuery tagQuery; QString resourceType; - int columnCount {9}; + int columnCount {10}; int cachedRowCount {-1}; }; //static int s_i = 0; -KisResourceModel::KisResourceModel(const QString &resourceType, QObject *parent) +KisAllResourcesModel::KisAllResourcesModel(const QString &resourceType, QObject *parent) : QAbstractTableModel(parent) , d(new Private) { //qDebug() << "ResourceModel" << s_i << resourceType; s_i++; + connect(KisResourceLocator::instance(), SIGNAL(storageAdded(const QString&)), this, SLOT(addStorage(const QString&))); + connect(KisResourceLocator::instance(), SIGNAL(storageRemoved(const QString&)), this, SLOT(removeStorage(const QString&))); + d->resourceType = resourceType; bool r = d->resourcesQuery.prepare("SELECT resources.id\n" ", resources.storage_id\n" ", resources.name\n" ", resources.filename\n" ", resources.tooltip\n" ", resources.thumbnail\n" ", resources.status\n" ", storages.location\n" ", resources.version\n" ", resource_types.name as resource_type\n" + ", resources.status as resource_active\n" + ", storages.active as storage_active\n" "FROM resources\n" ", resource_types\n" ", storages\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resources.storage_id = storages.id\n" "AND resource_types.name = :resource_type\n" - "AND resources.status = 1\n" - "AND storages.active = 1"); + "ORDER BY resources.id"); if (!r) { - qWarning() << "Could not prepare KisResourceModel query" << d->resourcesQuery.lastError(); + qWarning() << "Could not prepare KisAllResourcesModel query" << d->resourcesQuery.lastError(); } d->resourcesQuery.bindValue(":resource_type", d->resourceType); resetQuery(); r = d->tagQuery.prepare("SELECT tags.id\n" ", tags.url\n" ", tags.name\n" ", tags.comment\n" "FROM tags\n" ", resource_tags\n" "WHERE tags.active > 0\n" // make sure the tag is active "AND tags.id = resource_tags.tag_id\n" // join tags + resource_tags by tag_id - "AND resource_tags.resource_id = :resource_id\n"); // make sure we're looking for tags for a specific resource + "AND resource_tags.resource_id = :resource_id\n" + "ORDER BY tags.id"); // make sure we're looking for tags for a specific resource if (!r) { qWarning() << "Could not prepare TagsForResource query" << d->tagQuery.lastError(); } } -KisResourceModel::~KisResourceModel() +KisAllResourcesModel::~KisAllResourcesModel() { delete d; } -int KisResourceModel::columnCount(const QModelIndex &/*parent*/) const +int KisAllResourcesModel::columnCount(const QModelIndex &/*parent*/) const { return d->columnCount; } -QVariant KisResourceModel::data(const QModelIndex &index, int role) const +QVariant KisAllResourcesModel::data(const QModelIndex &index, int role) const { QVariant v; if (!index.isValid()) return v; if (index.row() > rowCount()) return v; if (index.column() > d->columnCount) return v; - bool pos = const_cast(this)->d->resourcesQuery.seek(index.row()); + bool pos = const_cast(this)->d->resourcesQuery.seek(index.row()); if (pos) { switch(role) { case Qt::DisplayRole: { switch(index.column()) { case Id: return d->resourcesQuery.value("id"); case StorageId: return d->resourcesQuery.value("storage_id"); case Name: return d->resourcesQuery.value("name"); case Filename: return d->resourcesQuery.value("filename"); case Tooltip: return d->resourcesQuery.value("tooltip"); case Thumbnail: { QByteArray ba = d->resourcesQuery.value("thumbnail").toByteArray(); QBuffer buf(&ba); buf.open(QBuffer::ReadOnly); QImage img; img.load(&buf, "PNG"); return QVariant::fromValue(img); } case Status: return d->resourcesQuery.value("status"); case Location: return d->resourcesQuery.value("location"); case ResourceType: return d->resourcesQuery.value("resource_type"); + case ResourceActive: + return d->resourcesQuery.value("resource_active"); + case StorageActive: + return d->resourcesQuery.value("storage_active"); default: ; }; Q_FALLTHROUGH(); } case Qt::DecorationRole: { if (index.column() == Thumbnail) { QByteArray ba = d->resourcesQuery.value("thumbnail").toByteArray(); QBuffer buf(&ba); buf.open(QBuffer::ReadOnly); QImage img; img.load(&buf, "PNG"); return QVariant::fromValue(img); } return QVariant(); } case Qt::ToolTipRole: Q_FALLTHROUGH(); case Qt::StatusTipRole: Q_FALLTHROUGH(); case Qt::WhatsThisRole: return d->resourcesQuery.value("tooltip"); case Qt::UserRole + Id: return d->resourcesQuery.value("id"); case Qt::UserRole + StorageId: return d->resourcesQuery.value("storage_id"); case Qt::UserRole + Name: return d->resourcesQuery.value("name"); case Qt::UserRole + Filename: return d->resourcesQuery.value("filename"); case Qt::UserRole + Tooltip: return d->resourcesQuery.value("tooltip"); case Qt::UserRole + Thumbnail: { QByteArray ba = d->resourcesQuery.value("thumbnail").toByteArray(); QBuffer buf(&ba); buf.open(QBuffer::ReadOnly); QImage img; img.load(&buf, "PNG"); return QVariant::fromValue(img); } case Qt::UserRole + Status: return d->resourcesQuery.value("status"); case Qt::UserRole + Location: return d->resourcesQuery.value("location"); case Qt::UserRole + ResourceType: return d->resourcesQuery.value("resource_type"); case Qt::UserRole + Tags: { QVector tags = tagsForResource(d->resourcesQuery.value("id").toInt()); QStringList tagNames; Q_FOREACH(const KisTagSP tag, tags) { tagNames << tag->name(); } return tagNames; } case Qt::UserRole + Dirty: { QString storageLocation = d->resourcesQuery.value("location").toString(); QString filename = d->resourcesQuery.value("filename").toString(); // An uncached resource has not been loaded, so it cannot be dirty if (!KisResourceLocator::instance()->resourceCached(storageLocation, d->resourceType, filename)) { return false; } else { // Now we have to check the resource, but that's cheap since it's been loaded in any case KoResourceSP resource = resourceForIndex(index); return resource->isDirty(); } } case Qt::UserRole + MetaData: { QMap r = KisResourceLocator::instance()->metaDataForResource(d->resourcesQuery.value("id").toInt()); return r; } case Qt::UserRole + KoResourceRole: { KoResourceSP tag = resourceForIndex(index); QVariant response; response.setValue(tag); return response; } - + case Qt::UserRole + ResourceActive: + { + return d->resourcesQuery.value("resource_active"); + } + case Qt::UserRole + StorageActive: + { + return d->resourcesQuery.value("storage_active"); + } default: ; } } return v; } -QVariant KisResourceModel::headerData(int section, Qt::Orientation orientation, int role) const +QVariant KisAllResourcesModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant v = QVariant(); if (role != Qt::DisplayRole) { return v; } if (orientation == Qt::Horizontal) { switch(section) { case Id: - v = i18n("Id"); - break; + return i18n("Id"); case StorageId: - v = i18n("Storage ID"); - break; + return i18n("Storage ID"); case Name: - v = i18n("Name"); - break; + return i18n("Name"); case Filename: - v = i18n("File Name"); - break; + return i18n("File Name"); case Tooltip: - v = i18n("Tooltip"); - break; + return i18n("Tooltip"); case Thumbnail: - v = i18n("Image"); - break; + return i18n("Image"); case Status: - v = i18n("Status"); - break; + return i18n("Status"); case Location: - v = i18n("Location"); - break; + return i18n("Location"); case ResourceType: - v = i18n("Resource Type"); - break; + return i18n("Resource Type"); + case ResourceActive: + return i18n("Active"); + case StorageActive: + return i18n("Storage Active"); default: - v = QString::number(section); + return QString::number(section); } - return v; } - return QAbstractItemModel::headerData(section, orientation, role); + return v; } //static int s_i2 {0}; -KoResourceSP KisResourceModel::resourceForIndex(QModelIndex index) const +KoResourceSP KisAllResourcesModel::resourceForIndex(QModelIndex index) const { KoResourceSP resource = 0; if (!index.isValid()) return resource; if (index.row() > rowCount()) return resource; if (index.column() > d->columnCount) return resource; - //qDebug() << "KisResourceModel::resourceForIndex" << s_i2 << d->resourceType; s_i2++; + //qDebug() << "KisAllResourcesModel::resourceForIndex" << s_i2 << d->resourceType; s_i2++; - bool pos = const_cast(this)->d->resourcesQuery.seek(index.row()); + bool pos = const_cast(this)->d->resourcesQuery.seek(index.row()); if (pos) { int id = d->resourcesQuery.value("id").toInt(); resource = resourceForId(id); } return resource; } -KoResourceSP KisResourceModel::resourceForId(int id) const +KoResourceSP KisAllResourcesModel::resourceForId(int id) const { return KisResourceLocator::instance()->resourceForId(id); } -KoResourceSP KisResourceModel::resourceForFilename(QString filename) const +KoResourceSP KisAllResourcesModel::resourceForFilename(QString filename) const { KoResourceSP resource = 0; QSqlQuery q; bool r = q.prepare("SELECT resources.id AS id\n" "FROM resources\n" ", resource_types\n" ", storages\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resources.storage_id = storages.id\n" "AND resources.filename = :resource_filename\n" "AND resource_types.name = :resource_type\n" "AND resources.status = 1\n" "AND storages.active = 1"); if (!r) { - qWarning() << "Could not prepare KisResourceModel query for resource name" << q.lastError(); + qWarning() << "Could not prepare KisAllResourcesModel query for resource name" << q.lastError(); } q.bindValue(":resource_filename", filename); q.bindValue(":resource_type", d->resourceType); r = q.exec(); if (!r) { qWarning() << "Could not select" << d->resourceType << "resources by filename" << q.lastError() << q.boundValues(); } if (q.first()) { int id = q.value("id").toInt(); resource = KisResourceLocator::instance()->resourceForId(id); } return resource; } -KoResourceSP KisResourceModel::resourceForName(QString name) const +KoResourceSP KisAllResourcesModel::resourceForName(QString name) const { KoResourceSP resource = 0; QSqlQuery q; bool r = q.prepare("SELECT resources.id AS id\n" "FROM resources\n" ", resource_types\n" ", storages\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resources.storage_id = storages.id\n" "AND resources.name = :resource_name\n" "AND resource_types.name = :resource_type\n" "AND resources.status = 1\n" "AND storages.active = 1"); if (!r) { - qWarning() << "Could not prepare KisResourceModel query for resource name" << q.lastError(); + qWarning() << "Could not prepare KisAllResourcesModel query for resource name" << q.lastError(); } q.bindValue(":resource_type", d->resourceType); q.bindValue(":resource_name", name); r = q.exec(); if (!r) { qWarning() << "Could not select" << d->resourceType << "resources by name" << q.lastError() << q.boundValues(); } if (q.first()) { int id = q.value("id").toInt(); resource = KisResourceLocator::instance()->resourceForId(id); } return resource; } -KoResourceSP KisResourceModel::resourceForMD5(const QByteArray md5sum) const +KoResourceSP KisAllResourcesModel::resourceForMD5(const QByteArray md5sum) const { KoResourceSP resource = 0; QSqlQuery q; bool r = q.prepare("SELECT resource_id AS id\n" "FROM versioned_resources\n" "WHERE md5sum = :md5sum"); if (!r) { - qWarning() << "Could not prepare KisResourceModel query for resource md5" << q.lastError(); + qWarning() << "Could not prepare KisAllResourcesModel query for resource md5" << q.lastError(); } q.bindValue(":md5sum", md5sum.toHex()); r = q.exec(); if (!r) { qWarning() << "Could not select" << d->resourceType << "resources by md5" << q.lastError() << q.boundValues(); } if (q.first()) { int id = q.value("id").toInt(); resource = KisResourceLocator::instance()->resourceForId(id); } return resource; } //static int s_i3 {0}; -QModelIndex KisResourceModel::indexFromResource(KoResourceSP resource) const +QModelIndex KisAllResourcesModel::indexFromResource(KoResourceSP resource) const { if (!resource || !resource->valid()) return QModelIndex(); - //qDebug() << "KisResourceModel::indexFromResource" << s_i3 << d->resourceType; s_i3++; + //qDebug() << "KisAllResourcesModel::indexFromResource" << s_i3 << d->resourceType; s_i3++; // For now a linear seek to find the first resource with the right id d->resourcesQuery.first(); do { if (d->resourcesQuery.value("id").toInt() == resource->resourceId()) { return createIndex(d->resourcesQuery.at(), 0); } } while (d->resourcesQuery.next()); return QModelIndex(); } //static int s_i4 {0}; -bool KisResourceModel::removeResource(const QModelIndex &index) +bool KisAllResourcesModel::setResourceInactive(const QModelIndex &index) { if (index.row() > rowCount()) return false; if (index.column() > d->columnCount) return false; - //qDebug() << "KisResourceModel::removeResource" << s_i4 << d->resourceType; s_i4++; + //qDebug() << "KisAllResourcesModel::setResourceInactive" << s_i4 << d->resourceType; s_i4++; bool pos = d->resourcesQuery.seek(index.row()); if (!pos) return false; int resourceId = d->resourcesQuery.value("id").toInt(); - if (!KisResourceLocator::instance()->removeResource(resourceId)) { + if (!KisResourceLocator::instance()->setResourceInactive(resourceId)) { qWarning() << "Failed to remove resource" << resourceId; return false; } - return resetQuery(); + emit dataChanged(index, index, {Qt::EditRole}); + return true; } - //static int s_i5 {0}; -bool KisResourceModel::removeResource(KoResourceSP resource) +bool KisAllResourcesModel::setResourceInactive(KoResourceSP resource) { if (!resource || !resource->valid()) return false; - //qDebug() << "KisResourceModel::remvoeResource 2" << s_i5 << d->resourceType; s_i5++; + //qDebug() << "KisAllResourcesModel::remvoeResource 2" << s_i5 << d->resourceType; s_i5++; - if (!KisResourceLocator::instance()->removeResource(resource->resourceId())) { + if (!KisResourceLocator::instance()->setResourceInactive(resource->resourceId())) { qWarning() << "Failed to remove resource" << resource->resourceId(); return false; } - return resetQuery(); + + QModelIndex index = indexFromResource(resource); + emit dataChanged(index, index, {Qt::EditRole}); + return true; } //static int s_i6 {0}; -bool KisResourceModel::importResourceFile(const QString &filename) +bool KisAllResourcesModel::importResourceFile(const QString &filename) { - //qDebug() << "KisResourceModel::importResource" << s_i6 << d->resourceType; s_i6++; - + //qDebug() << "KisAllResourcesModel::importResource" << s_i6 << d->resourceType; s_i6++; + bool r = true; + beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1); if (!KisResourceLocator::instance()->importResourceFromFile(d->resourceType, filename)) { + r = false; qWarning() << "Failed to import resource" << filename; - return false; } - return resetQuery(); + resetQuery(); + endInsertRows(); + return r; } //static int s_i7 {0}; -bool KisResourceModel::addResource(KoResourceSP resource, const QString &storageId) +bool KisAllResourcesModel::addResource(KoResourceSP resource, const QString &storageId) { if (!resource || !resource->valid()) { qWarning() << "Cannot add resource. Resource is null or not valid"; return false; } - //qDebug() << "KisResourceModel::addResource" << s_i7 << d->resourceType; s_i7++; - + //qDebug() << "KisAllResourcesModel::addResource" << s_i7 << d->resourceType; s_i7++; + bool r = true; + beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1); if (!KisResourceLocator::instance()->addResource(d->resourceType, resource, storageId)) { qWarning() << "Failed to add resource" << resource->name(); - return false; + r = false; } - return resetQuery(); + resetQuery(); + endInsertRows(); + return r; } //static int s_i8 {0}; -bool KisResourceModel::updateResource(KoResourceSP resource) +bool KisAllResourcesModel::updateResource(KoResourceSP resource) { if (!resource || !resource->valid()) { qWarning() << "Cannot update resource. Resource is null or not valid"; return false; } - //qDebug() << "KisResourceModel::updateResource" << s_i8 << d->resourceType; s_i8++; + //qDebug() << "KisAllResourcesModel::updateResource" << s_i8 << d->resourceType; s_i8++; if (!KisResourceLocator::instance()->updateResource(d->resourceType, resource)) { qWarning() << "Failed to update resource" << resource; return false; } - return resetQuery(); + bool r = resetQuery(); + QModelIndex index = indexFromResource(resource); + emit dataChanged(index, index, {Qt::EditRole}); + return r; } -bool KisResourceModel::renameResource(KoResourceSP resource, const QString &name) +bool KisAllResourcesModel::renameResource(KoResourceSP resource, const QString &name) { if (!resource || !resource->valid() || name.isEmpty()) { qWarning() << "Cannot rename resources. Resource is NULL or not valid or name is empty"; return false; } resource->setName(name); if (!KisResourceLocator::instance()->updateResource(d->resourceType, resource)) { qWarning() << "Failed to rename resource" << resource << name; return false; } - return resetQuery(); + bool r = resetQuery(); + QModelIndex index = indexFromResource(resource); + emit dataChanged(index, index, {Qt::EditRole}); + return r; } //static int s_i9 {0}; -bool KisResourceModel::setResourceMetaData(KoResourceSP resource, QMap metadata) +bool KisAllResourcesModel::setResourceMetaData(KoResourceSP resource, QMap metadata) { - //qDebug() << "KisResourceModel::setResourceMetaData" << s_i9 << d->resourceType; s_i9++; + //qDebug() << "KisAllResourcesModel::setResourceMetaData" << s_i9 << d->resourceType; s_i9++; Q_ASSERT(resource->resourceId() > -1); return KisResourceLocator::instance()->setMetaDataForResource(resource->resourceId(), metadata); } -bool KisResourceModel::resetQuery() +bool KisAllResourcesModel::resetQuery() { - QElapsedTimer t; - t.start(); - - emit beforeResourcesLayoutReset(QModelIndex()); - - beginResetModel(); +// QElapsedTimer t; +// t.start(); bool r = d->resourcesQuery.exec(); if (!r) { qWarning() << "Could not select" << d->resourceType << "resources" << d->resourcesQuery.lastError() << d->resourcesQuery.boundValues(); } d->cachedRowCount = -1; - - endResetModel(); - emit afterResourcesLayoutReset(); - - qDebug() << "KisResourceModel::resetQuery for" << d->resourceType << "took" << t.elapsed() << "ms"; +// qDebug() << "KisAllResourcesModel::resetQuery for" << d->resourceType << "took" << t.elapsed() << "ms"; return r; } -QVector KisResourceModel::tagsForResource(int resourceId) const +QVector KisAllResourcesModel::tagsForResource(int resourceId) const { return KisTagModelProvider::tagModel(d->resourceType)->tagsForResource(resourceId); } -int KisResourceModel::rowCount(const QModelIndex &) const +int KisAllResourcesModel::rowCount(const QModelIndex &) const { if (d->cachedRowCount < 0) { QSqlQuery q; q.prepare("SELECT count(*)\n" "FROM resources\n" ", resource_types\n" ", storages\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n" - "AND resources.storage_id = storages.id\n" - "AND resources.status = 1\n" - "AND storages.active = 1"); + "AND resources.storage_id = storages.id\n"); q.bindValue(":resource_type", d->resourceType); q.exec(); q.first(); - const_cast(this)->d->cachedRowCount = q.value(0).toInt(); + const_cast(this)->d->cachedRowCount = q.value(0).toInt(); } return d->cachedRowCount; } + + +void KisAllResourcesModel::addStorage(const QString &location) +{ + beginResetModel(); + resetQuery(); + endResetModel(); +} + + +void KisAllResourcesModel::removeStorage(const QString &location) +{ + beginResetModel(); + resetQuery(); + endResetModel(); +} + +struct KisResourceModel::Private +{ + ResourceFilter resourceFilter {ShowActiveResources}; + StorageFilter storageFilter {ShowActiveStorages}; +}; + +KisResourceModel::KisResourceModel(const QString &type, QObject *parent) + : QSortFilterProxyModel(parent) + , d(new Private) +{ + setSourceModel(new KisAllResourcesModel(type)); +} + +KisResourceModel::~KisResourceModel() +{ + delete d; +} + +void KisResourceModel::setResourceFilter(ResourceFilter filter) +{ + d->resourceFilter = filter; +} + +void KisResourceModel::setStorageFilter(StorageFilter filter) +{ + d->storageFilter = filter; +} + +KoResourceSP KisResourceModel::resourceForIndex(QModelIndex index) const +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->resourceForIndex(mapToSource(index)); + } + return 0; +} + +QModelIndex KisResourceModel::indexFromResource(KoResourceSP resource) const +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return mapFromSource(source->indexFromResource(resource)); + } + return QModelIndex(); +} + +bool KisResourceModel::setResourceInactive(const QModelIndex &index) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->setResourceInactive(mapToSource(index)); + } + return false; +} + +bool KisResourceModel::importResourceFile(const QString &filename) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->importResourceFile(filename); + } + return false; +} + +bool KisResourceModel::addResource(KoResourceSP resource, const QString &storageId) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->addResource(resource, storageId); + } + return false; +} + +bool KisResourceModel::updateResource(KoResourceSP resource) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->updateResource(resource); + } + return false; +} + +bool KisResourceModel::renameResource(KoResourceSP resource, const QString &name) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->renameResource(resource, name); + } + return false; +} + +bool KisResourceModel::setResourceInactive(KoResourceSP resource) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->setResourceInactive(resource); + } + return false; +} + +bool KisResourceModel::setResourceMetaData(KoResourceSP resource, QMap metadata) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->setResourceMetaData(resource, metadata); + } + return false; +} + +bool KisResourceModel::filterAcceptsColumn(int /*source_column*/, const QModelIndex &/*source_parent*/) const +{ + return true; +} + +bool KisResourceModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + if (d->resourceFilter == ShowAllResources && d->storageFilter == ShowAllStorages) { + return true; + } + + QModelIndex idx = sourceModel()->index(source_row, 0, source_parent); + ResourceFilter resourceActive = (ResourceFilter)sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::ResourceActive).toInt(); + StorageFilter storageActive = (StorageFilter)sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::StorageActive).toInt(); + + if (d->resourceFilter == ShowAllResources) { + return (storageActive == d->storageFilter); + } + + if (d->storageFilter == ShowAllStorages) { + return (resourceActive == d->resourceFilter); + } + + return ((storageActive == d->storageFilter) && (resourceActive == d->resourceFilter)); +} + +bool KisResourceModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const +{ + QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisAbstractResourceModel::Name).toString(); + QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisAbstractResourceModel::Name).toString(); + return nameLeft < nameRight; +} + + +KoResourceSP KisResourceModel::resourceForId(int id) const +{ + return static_cast(sourceModel())->resourceForId(id); +} + +KoResourceSP KisResourceModel::resourceForFilename(QString fileName) const +{ + return static_cast(sourceModel())->resourceForFilename(fileName); +} + +KoResourceSP KisResourceModel::resourceForName(QString name) const +{ + return static_cast(sourceModel())->resourceForName(name); +} + +KoResourceSP KisResourceModel::resourceForMD5(const QByteArray md5sum) const +{ + return static_cast(sourceModel())->resourceForMD5(md5sum); +} + +QVector KisResourceModel::tagsForResource(int resourceId) const +{ + return static_cast(sourceModel())->tagsForResource(resourceId); +} + +bool KisResourceModel::resetQuery() +{ + return static_cast(sourceModel())->resetQuery(); +} + diff --git a/libs/resources/KisResourceModel.h b/libs/resources/KisResourceModel.h index dc67de708b..24924acb22 100644 --- a/libs/resources/KisResourceModel.h +++ b/libs/resources/KisResourceModel.h @@ -1,216 +1,311 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * 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 KISRESOURCEMODEL_H #define KISRESOURCEMODEL_H #include +#include #include #include #include /** * KisAbstractResourceModel defines the interface for accessing resources * that is used in KisResourceModel and the various filter/proxy models */ class KRITARESOURCES_EXPORT KisAbstractResourceModel { public: + + /** + * @brief The Columns enum indexes the columns in the model. To get + * the thumbnail for a particular resource, create the index with + * QModelIndex(row, Thumbnail). + */ + enum Columns { + Id = 0, + StorageId, + Name, + Filename, + Tooltip, + Thumbnail, + Status, + Location, + ResourceType, + Tags, + /// A larger thumbnail for displaying in a tooltip. 200x200 or so. + LargeThumbnail, + /// A dirty resource is one that has been modified locally but not saved + Dirty, + /// MetaData is a map of key, value pairs that is associated with this resource + MetaData, + /// XXX: what is this used for, again? + KoResourceRole, + /// Whether the current resource is active + ResourceActive, + /// Whether the current resource's storage isa ctive + StorageActive + }; + virtual ~KisAbstractResourceModel(){} /** * @brief resourceForIndex * @param index * @return */ virtual KoResourceSP resourceForIndex(QModelIndex index = QModelIndex()) const = 0; /** * @brief indexFromResource * @param resource * @return */ virtual QModelIndex indexFromResource(KoResourceSP resource) const = 0; /** - * @brief removeResource + * @brief setResourceInactive deactivates the specified resource * @param index * @return */ - virtual bool removeResource(const QModelIndex &index) = 0; + virtual bool setResourceInactive(const QModelIndex &index) = 0; /** * @brief importResourceFile * @param filename * @return */ virtual bool importResourceFile(const QString &filename) = 0; /** * @brief addResource adds the given resource to the database and storage * @param resource the resource itself * @param storageId the id of the storage (could be "memory" for temporary * resources, the document's storage id for document storages or empty to save * to the default resources folder * @return true if adding the resoruce succeeded. */ virtual bool addResource(KoResourceSP resource, const QString &storageId = QString()) = 0; /** * @brief updateResource * @param resource * @return */ virtual bool updateResource(KoResourceSP resource) = 0; /** * @brief renameResource name the given resource. The resource will have its * name field reset, will be saved to the storage and there will be a new * version created in the database. * @param resource The resource to rename * @param name The new name * @return true if the operation succeeded. */ virtual bool renameResource(KoResourceSP resource, const QString &name) = 0; /** - * @brief removeResource + * @brief setResourceInactive sets the specified resource's status to inactive * @param resource * @return */ - virtual bool removeResource(KoResourceSP resource) = 0; + virtual bool setResourceInactive(KoResourceSP resource) = 0; /** * @brief setResourceMetaData * @param metadata * @return */ virtual bool setResourceMetaData(KoResourceSP resource, QMap metadata) = 0; }; /** - * @brief The KisResourceModel class provides access to the cache database + * @brief The KisAllresourcesModel class provides access to the cache database * for a particular resource type. Instances should be retrieved using - * KisResourceModelProvider. + * KisResourceModelProvider. All resources are part of this model, active and + * inactive, from all storages, active and inactive. */ -class KRITARESOURCES_EXPORT KisResourceModel : public QAbstractTableModel, public KisAbstractResourceModel +class KRITARESOURCES_EXPORT KisAllResourcesModel : public QAbstractTableModel, public KisAbstractResourceModel { Q_OBJECT -public: - - /** - * @brief The Columns enum indexes the columns in the model. To get - * the thumbnail for a particular resource, create the index with - * QModelIndex(row, Thumbnail). - */ - enum Columns { - Id = 0, - StorageId, - Name, - Filename, - Tooltip, - Thumbnail, - Status, - Location, - ResourceType, - Tags, - /// A larger thumbnail for displaying in a tooltip. 200x200 or so. - LargeThumbnail, - /// A dirty resource is one that has been modified locally but not saved - Dirty, - /// MetaData is a map of key, value pairs that is associated with this resource - MetaData, - KoResourceRole - }; private: - friend class KisResourceModelProvider; - friend class TestResourceModel; - KisResourceModel(const QString &resourceType, QObject *parent = 0); + friend class KisResourceModel; + KisAllResourcesModel(const QString &resourceType, QObject *parent = 0); public: - ~KisResourceModel() override; + ~KisAllResourcesModel() override; // QAbstractItemModel API int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; // Resources API /** * @brief resourceForIndex returns a properly versioned and id's resource object */ KoResourceSP resourceForIndex(QModelIndex index = QModelIndex()) const override; + QModelIndex indexFromResource(KoResourceSP resource) const override; + bool setResourceInactive(const QModelIndex &index) override; + bool setResourceInactive(KoResourceSP resource) override; + bool importResourceFile(const QString &filename) override; + bool addResource(KoResourceSP resource, const QString &storageId = QString()) override; + bool updateResource(KoResourceSP resource) override; + bool renameResource(KoResourceSP resource, const QString &name) override; + bool setResourceMetaData(KoResourceSP resource, QMap metadata) override; + +private Q_SLOTS: + + void addStorage(const QString &location); + void removeStorage(const QString &location); + +private: + KoResourceSP resourceForId(int id) const; /** * resourceForFilename returns the first resource with the given filename that * is active and is in an active store. Note that the filename does not include * a path to the storage, and if there are resources with the same filename * in several active storages, only one resource is returned. * * @return a resource if one is found, or 0 if none are found */ KoResourceSP resourceForFilename(QString fileName) const; /** * resourceForName returns the first resource with the given name that * is active and is in an active store. Note that if there are resources * with the same name in several active storages, only one resource * is returned. * * @return a resource if one is found, or 0 if none are found */ KoResourceSP resourceForName(QString name) const; KoResourceSP resourceForMD5(const QByteArray md5sum) const; + QVector tagsForResource(int resourceId) const; + + bool resetQuery(); + + struct Private; + Private *const d; + +}; + +/** + * @brief The KisResourceModel class provides the main access to resources. It is possible + * to filter the resources returned by the active status flag of the resources and the + * storages + */ +class KRITARESOURCES_EXPORT KisResourceModel : public QSortFilterProxyModel, public KisAbstractResourceModel +{ + Q_OBJECT +private: + friend class TestResourceModel; + friend class KisResourceModelProvider; + KisResourceModel(const QString &type, QObject *parent = 0); +public: + ~KisResourceModel() override; + + enum ResourceFilter { + ShowInactiveResources = 0, + ShowActiveResources, + ShowAllResources + }; + void setResourceFilter(ResourceFilter filter); + + enum StorageFilter { + ShowInactiveStorages = 0, + ShowActiveStorages, + ShowAllStorages + }; + + void setStorageFilter(StorageFilter filter); + +public: + + KoResourceSP resourceForIndex(QModelIndex index = QModelIndex()) const override; QModelIndex indexFromResource(KoResourceSP resource) const override; - bool removeResource(const QModelIndex &index) override; - bool removeResource(KoResourceSP resource) override; + bool setResourceInactive(const QModelIndex &index) override; bool importResourceFile(const QString &filename) override; bool addResource(KoResourceSP resource, const QString &storageId = QString()) override; bool updateResource(KoResourceSP resource) override; bool renameResource(KoResourceSP resource, const QString &name) override; + bool setResourceInactive(KoResourceSP resource) override; bool setResourceMetaData(KoResourceSP resource, QMap metadata) override; + +public: + + KoResourceSP resourceForId(int id) const; + + /** + * resourceForFilename returns the first resource with the given filename that + * is active and is in an active store. Note that the filename does not include + * a path to the storage, and if there are resources with the same filename + * in several active storages, only one resource is returned. + * + * @return a resource if one is found, or 0 if none are found + */ + KoResourceSP resourceForFilename(QString fileName) const; + + /** + * resourceForName returns the first resource with the given name that + * is active and is in an active store. Note that if there are resources + * with the same name in several active storages, only one resource + * is returned. + * + * @return a resource if one is found, or 0 if none are found + */ + KoResourceSP resourceForName(QString name) const; + KoResourceSP resourceForMD5(const QByteArray md5sum) const; QVector tagsForResource(int resourceId) const; -Q_SIGNALS: - // XXX: emit these signals - void beforeResourcesLayoutReset(QModelIndex activateAfterReformat); - void afterResourcesLayoutReset(); + +protected: + + bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; private: bool resetQuery(); struct Private; Private *const d; + Q_DISABLE_COPY(KisResourceModel) + }; + + #endif // KISRESOURCEMODEL_H diff --git a/libs/resources/KisResourceStorage.cpp b/libs/resources/KisResourceStorage.cpp index 262ea9e12b..3944605411 100644 --- a/libs/resources/KisResourceStorage.cpp +++ b/libs/resources/KisResourceStorage.cpp @@ -1,269 +1,280 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * 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 "KisResourceStorage.h" #include #include #include #include "KisFolderStorage.h" #include "KisBundleStorage.h" #include "KisMemoryStorage.h" const QString KisResourceStorage::s_meta_generator("meta:generator"); const QString KisResourceStorage::s_meta_author("dc:author"); const QString KisResourceStorage::s_meta_title("dc:title"); const QString KisResourceStorage::s_meta_description("dc:description"); const QString KisResourceStorage::s_meta_initial_creator("meta:initial-creator"); const QString KisResourceStorage::s_meta_creator("cd:creator"); const QString KisResourceStorage::s_meta_creation_date("meta:creation-data"); const QString KisResourceStorage::s_meta_dc_date("meta:dc-date"); const QString KisResourceStorage::s_meta_user_defined("meta:meta-userdefined"); const QString KisResourceStorage::s_meta_name("meta:name"); const QString KisResourceStorage::s_meta_value("meta:value"); const QString KisResourceStorage::s_meta_version("meta:bundle-version"); Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance); KisStoragePluginRegistry::KisStoragePluginRegistry() { m_storageFactoryMap[KisResourceStorage::StorageType::Folder] = new KisStoragePluginFactory(); m_storageFactoryMap[KisResourceStorage::StorageType::Memory] = new KisStoragePluginFactory(); m_storageFactoryMap[KisResourceStorage::StorageType::Bundle] = new KisStoragePluginFactory(); } void KisStoragePluginRegistry::addStoragePluginFactory(KisResourceStorage::StorageType storageType, KisStoragePluginFactoryBase *factory) { m_storageFactoryMap[storageType] = factory; } KisStoragePluginRegistry *KisStoragePluginRegistry::instance() { return s_instance; } class KisResourceStorage::Private { public: QString name; QString location; bool valid {false}; KisResourceStorage::StorageType storageType {KisResourceStorage::StorageType::Unknown}; QSharedPointer storagePlugin; + int storageId {-1}; }; KisResourceStorage::KisResourceStorage(const QString &location) : d(new Private()) { d->location = location; d->name = QFileInfo(d->location).fileName(); QFileInfo fi(d->location); if (fi.isDir()) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Folder]->create(location)); d->storageType = StorageType::Folder; d->valid = fi.isWritable(); } else if (d->location.endsWith(".bundle")) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Bundle]->create(location)); d->storageType = StorageType::Bundle; // XXX: should we also check whether there's a valid metadata entry? Or is this enough? d->valid = (fi.isReadable() && QuaZip(d->location).open(QuaZip::mdUnzip)); } else if (d->location.endsWith(".abr")) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::AdobeBrushLibrary]->create(location)); d->storageType = StorageType::AdobeBrushLibrary; d->valid = fi.isReadable(); } else if (d->location.endsWith(".asl")) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::AdobeStyleLibrary]->create(location)); d->storageType = StorageType::AdobeStyleLibrary; d->valid = fi.isReadable(); } else if (!d->location.isEmpty()) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Memory]->create(location)); d->name = location; d->storageType = StorageType::Memory; d->valid = true; } else { d->valid = false; } } KisResourceStorage::~KisResourceStorage() { } KisResourceStorage::KisResourceStorage(const KisResourceStorage &rhs) : d(new Private) { *this = rhs; } KisResourceStorage &KisResourceStorage::operator=(const KisResourceStorage &rhs) { if (this != &rhs) { d->name = rhs.d->name; d->location = rhs.d->location; d->storageType = rhs.d->storageType; if (d->storageType == StorageType::Memory) { d->storagePlugin = QSharedPointer(new KisMemoryStorage(*dynamic_cast(rhs.d->storagePlugin.data()))); } else { d->storagePlugin = rhs.d->storagePlugin; } d->valid = false; } return *this; } KisResourceStorageSP KisResourceStorage::clone() const { return KisResourceStorageSP(new KisResourceStorage(*this)); } QString KisResourceStorage::name() const { return d->name; } QString KisResourceStorage::location() const { return d->location; } KisResourceStorage::StorageType KisResourceStorage::type() const { return d->storageType; } QImage KisResourceStorage::thumbnail() const { return d->storagePlugin->thumbnail(); } QDateTime KisResourceStorage::timestamp() const { return d->storagePlugin->timestamp(); } QDateTime KisResourceStorage::timeStampForResource(const QString &resourceType, const QString &filename) const { QFileInfo li(d->location); if (li.suffix().toLower() == "bundle") { QFileInfo bf(d->location + "_modified/" + resourceType + "/" + filename); if (bf.exists()) { return bf.lastModified(); } } else if (QFileInfo(d->location + "/" + resourceType + "/" + filename).exists()) { return QFileInfo(d->location + "/" + resourceType + "/" + filename).lastModified(); } return this->timestamp(); } KisResourceStorage::ResourceItem KisResourceStorage::resourceItem(const QString &url) { return d->storagePlugin->resourceItem(url); } KoResourceSP KisResourceStorage::resource(const QString &url) { return d->storagePlugin->resource(url); } QSharedPointer KisResourceStorage::resources(const QString &resourceType) const { return d->storagePlugin->resources(resourceType); } QSharedPointer KisResourceStorage::tags(const QString &resourceType) const { return d->storagePlugin->tags(resourceType); } bool KisResourceStorage::addTag(const QString &resourceType, KisTagSP tag) { return d->storagePlugin->addTag(resourceType, tag); } bool KisResourceStorage::addResource(KoResourceSP resource) { return d->storagePlugin->addResource(resource->resourceType().first, resource); } void KisResourceStorage::setMetaData(const QString &key, const QVariant &value) { d->storagePlugin->setMetaData(key, value); } bool KisResourceStorage::valid() const { return d->valid; } QStringList KisResourceStorage::metaDataKeys() const { return d->storagePlugin->metaDataKeys(); } QVariant KisResourceStorage::metaData(const QString &key) const { return d->storagePlugin->metaData(key); } +void KisResourceStorage::setStorageId(int storageId) +{ + d->storageId = storageId; +} + +int KisResourceStorage::storageId() +{ + return d->storageId; +} + bool KisStorageVersioningHelper::addVersionedResource(const QString &filename, const QString &saveLocation, KoResourceSP resource) { // Find a new filename for the resource if it already exists: we do not rename old resources, but rename updated resources if (!QFileInfo(filename).exists()) { // Simply save it QFile f(filename); if (!f.open(QFile::WriteOnly)) { qWarning() << "Could not open resource file for writing" << filename; return false; } if (!resource->saveToDevice(&f)) { qWarning() << "Could not save resource file" << filename; return false; } f.close(); } else { QFileInfo fi(filename); // Save the new resource QString newFilename = fi.baseName() + "." + QString("%1").arg(resource->version() + 1, 4, 10, QChar('0')) + "." + fi.suffix(); QFile f(saveLocation + "/" + newFilename); if (!f.open(QFile::WriteOnly)) { qWarning() << "Could not open resource file for writing" << newFilename; return false; } if (!resource->saveToDevice(&f)) { qWarning() << "Could not save resource file" << newFilename; return false; } resource->setFilename(newFilename); f.close(); } return true; }; diff --git a/libs/resources/KisResourceStorage.h b/libs/resources/KisResourceStorage.h index 321023d4b1..c19c98f04c 100644 --- a/libs/resources/KisResourceStorage.h +++ b/libs/resources/KisResourceStorage.h @@ -1,271 +1,278 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * 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 KISRESOURCESTORAGE_H #define KISRESOURCESTORAGE_H #include #include #include #include #include #include #include #include #include class KisStoragePlugin; class KisStoragePluginFactoryBase { public: virtual ~KisStoragePluginFactoryBase(){} virtual KisStoragePlugin *create(const QString &/*location*/) { return 0; } }; template class KisStoragePluginFactory : public KisStoragePluginFactoryBase { public: KisStoragePlugin *create(const QString &location) override { return new T(location); } }; class KisResourceStorage; typedef QSharedPointer KisResourceStorageSP; /** * The KisResourceStorage class is the base class for * places where resources can be stored. Examples are * folders, bundles or Adobe resource libraries like * ABR files. */ class KRITARESOURCES_EXPORT KisResourceStorage { public: /// A resource item is simply an entry in the storage, struct ResourceItem { virtual ~ResourceItem() {} QString url; QString folder; QString resourceType; QDateTime lastModified; }; class TagIterator { public: virtual ~TagIterator() {} virtual bool hasNext() const = 0; /// The iterator is only valid if next() has been called at least once. virtual void next() = 0; /// The untranslated name of the tag, to be used for making connections to resources virtual QString url() const = 0; /// The translated name of the tag, to be shown in the GUI virtual QString name() const = 0; /// An extra, optional comment for the tag virtual QString comment() const = 0; /// A tag object on which we can set properties and which we can save virtual KisTagSP tag() const = 0; }; class ResourceIterator { public: virtual ~ResourceIterator() {} virtual bool hasNext() const = 0; /// The iterator is only valid if next() has been called at least once. virtual void next() = 0; virtual QString url() const = 0; virtual QString type() const = 0; virtual QDateTime lastModified() const = 0; /// This only loads the resource when called virtual KoResourceSP resource() const = 0; }; enum class StorageType : int { Unknown = 1, Folder = 2, Bundle = 3, AdobeBrushLibrary = 4, AdobeStyleLibrary = 5, Memory = 6 }; static QString storageTypeToString(StorageType storageType) { switch (storageType) { case StorageType::Unknown: return i18n("Unknown"); case StorageType::Folder: return i18n("Folder"); case StorageType::Bundle: return i18n("Bundle"); case StorageType::AdobeBrushLibrary: return i18n("Adobe Brush Library"); case StorageType::AdobeStyleLibrary: return i18n("Adobe Style Library"); case StorageType::Memory: return i18n("Memory"); default: return i18n("Invalid"); } } static QString storageTypeToUntranslatedString(StorageType storageType) { switch (storageType) { case StorageType::Unknown: return ("Unknown"); case StorageType::Folder: return ("Folder"); case StorageType::Bundle: return ("Bundle"); case StorageType::AdobeBrushLibrary: return ("Adobe Brush Library"); case StorageType::AdobeStyleLibrary: return ("Adobe Style Library"); case StorageType::Memory: return ("Memory"); default: return ("Invalid"); } } KisResourceStorage(const QString &location); ~KisResourceStorage(); KisResourceStorage(const KisResourceStorage &rhs); KisResourceStorage &operator=(const KisResourceStorage &rhs); KisResourceStorageSP clone() const; /// The filename of the storage if it's a bundle or Adobe Library. This can /// also be empty (for the folder storage) or "memory" for the storage for /// temporary resources, a UUID for storages associated with documents. QString name() const; /// The absolute location of the storage QString location() const; /// true if the storage exists and can be used bool valid() const; /// The type of the storage StorageType type() const; /// The icond for the storage QImage thumbnail() const; /// The time and date when the storage was last modified, or created /// for memory storages. QDateTime timestamp() const; /// The time and date when the resource was last modified /// For filestorage QDateTime timeStampForResource(const QString &resourceType, const QString &filename) const; /// And entry in the storage; this is not the loaded resource ResourceItem resourceItem(const QString &url); /// The loaded resource for an entry in the storage KoResourceSP resource(const QString &url); /// An iterator over all the resources in the storage QSharedPointer resources(const QString &resourceType) const; /// An iterator over all the tags in the resource QSharedPointer tags(const QString &resourceType) const; /// Adds a tag to the storage, however, it does not store the links between /// tags and resources. bool addTag(const QString &resourceType, KisTagSP tag); /// Adds the given resource to the storage. bool addResource(KoResourceSP resource); static const QString s_meta_generator; static const QString s_meta_author; static const QString s_meta_title; static const QString s_meta_description; static const QString s_meta_initial_creator; static const QString s_meta_creator; static const QString s_meta_creation_date; static const QString s_meta_dc_date; static const QString s_meta_user_defined; static const QString s_meta_name; static const QString s_meta_value; static const QString s_meta_version; void setMetaData(const QString &key, const QVariant &value); QStringList metaDataKeys() const; QVariant metaData(const QString &key) const; private: + friend class KisStorageModel; + friend class KisResourceLocator; + friend class KisResourceCacheDb; + + void setStorageId(int storageId); + int storageId(); + class Private; QScopedPointer d; }; inline QDebug operator<<(QDebug dbg, const KisResourceStorageSP storage) { if (storage.isNull()) { dbg.nospace() << "[RESOURCESTORAGE] NULL"; } else { dbg.nospace() << "[RESOURCESTORAGE] Name: " << storage->name() << " Version: " << storage->location() << " Valid: " << storage->valid() << " Storage: " << KisResourceStorage::storageTypeToString(storage->type()) << " Timestamp: " << storage->timestamp(); } return dbg.space(); } class KRITARESOURCES_EXPORT KisStoragePluginRegistry { public: KisStoragePluginRegistry(); void addStoragePluginFactory(KisResourceStorage::StorageType storageType, KisStoragePluginFactoryBase *factory); static KisStoragePluginRegistry *instance(); private: friend class KisResourceStorage; QMap m_storageFactoryMap; }; class KisStorageVersioningHelper { public: static bool addVersionedResource(const QString &filename, const QString &saveLocation, KoResourceSP resource); }; #endif // KISRESOURCESTORAGE_H diff --git a/libs/resources/KisResourcesActiveFilterModel.cpp b/libs/resources/KisResourcesActiveFilterModel.cpp new file mode 100644 index 0000000000..33beb43c73 --- /dev/null +++ b/libs/resources/KisResourcesActiveFilterModel.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2020 Boudewijn Rempt + * + * 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 "KisResourcesActiveFilterModel.h" + +struct KisResourceModel::Private +{ + int column {-1}; + ResourceFilter resourceFilter {ShowActiveResources}; + StorageFilter storageFilter {ShowActiveStorages}; +}; + +KisResourceModel::KisResourceModel(int column, QObject *parent) + : QSortFilterProxyModel(parent) + , d(new Private) +{ + d->column = column; +} + +KisResourceModel::~KisResourceModel() +{ + delete d; +} + +void KisResourceModel::setResourceFilter(ResourceFilter filter) +{ + d->resourceFilter = filter; +} + +void KisResourceModel::setStorageFilter(StorageFilter filter) +{ + d->storageFilter = filter; +} + +KoResourceSP KisResourceModel::resourceForIndex(QModelIndex index) const +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->resourceForIndex(mapToSource(index)); + } + return 0; +} + +QModelIndex KisResourceModel::indexFromResource(KoResourceSP resource) const +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return mapFromSource(source->indexFromResource(resource)); + } + return QModelIndex(); +} + +bool KisResourceModel::setResourceInactive(const QModelIndex &index) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->setResourceInactive(mapToSource(index)); + } + return false; +} + +bool KisResourceModel::importResourceFile(const QString &filename) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->importResourceFile(filename); + } + return false; +} + +bool KisResourceModel::addResource(KoResourceSP resource, const QString &storageId) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->addResource(resource, storageId); + } + return false; +} + +bool KisResourceModel::updateResource(KoResourceSP resource) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->updateResource(resource); + } + return false; +} + +bool KisResourceModel::renameResource(KoResourceSP resource, const QString &name) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->renameResource(resource, name); + } + return false; +} + +bool KisResourceModel::setResourceInactive(KoResourceSP resource) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->setResourceInactive(resource); + } + return false; +} + +bool KisResourceModel::setResourceMetaData(KoResourceSP resource, QMap metadata) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->setResourceMetaData(resource, metadata); + } + return false; +} + + +bool KisResourceModel::filterAcceptsColumn(int /*source_column*/, const QModelIndex &/*source_parent*/) const +{ + return true; +} + +bool KisResourceModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + if (d->resourceFilter == ShowAllResources && d->storageFilter == ShowAllStorages) { + return true; + } + + QModelIndex idx = sourceModel()->index(source_row, 0, source_parent); + ResourceFilter resourceActive = (ResourceFilter)sourceModel()->data(idx, Qt::UserRole + KisResourceModel::ResourceActive).toInt(); + StorageFilter storageActive = (StorageFilter)sourceModel()->data(idx, Qt::UserRole + KisResourceModel::StorageActive).toInt(); + + if (d->resourceFilter == ShowAllResources) { + return (storageActive == d->storageFilter); + } + + if (d->storageFilter == ShowAllStorages) { + return (resourceActive == d->resourceFilter); + } + + return ((storageActive == d->storageFilter) && (resourceActive == d->resourceFilter)); +} + +bool KisResourceModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const +{ + QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisResourceModel::Name).toString(); + QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisResourceModel::Name).toString(); + return nameLeft < nameRight; +} + + diff --git a/libs/resources/KisTagFilterResourceProxyModel.h b/libs/resources/KisResourcesActiveFilterModel.h similarity index 63% copy from libs/resources/KisTagFilterResourceProxyModel.h copy to libs/resources/KisResourcesActiveFilterModel.h index 7585c7e608..296c499b6d 100644 --- a/libs/resources/KisTagFilterResourceProxyModel.h +++ b/libs/resources/KisResourcesActiveFilterModel.h @@ -1,78 +1,81 @@ /* - * Copyright (C) 2018 Boudewijn Rempt + * Copyright (C) 2020 Boudewijn Rempt * * 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 KISTAGFILTERRESOURCEPROXYMODEL_H -#define KISTAGFILTERRESOURCEPROXYMODEL_H +#ifndef KISACTIVEFILTERMODEL_H +#define KISACTIVEFILTERMODEL_H #include -#include #include #include -#include -#include #include "kritaresources_export.h" -class KRITARESOURCES_EXPORT KisTagFilterResourceProxyModel : public QSortFilterProxyModel, public KisAbstractResourceModel +/** + * @brief The KisActiveResourcesModel class + */ +class KRITARESOURCES_EXPORT KisResourceModel : public QSortFilterProxyModel, public KisAbstractResourceModel { Q_OBJECT public: - KisTagFilterResourceProxyModel(KisTagModel* model = 0, QObject *parent = 0); - ~KisTagFilterResourceProxyModel() override; + KisResourceModel(int column, QObject *parent); + ~KisResourceModel() override; + + enum ResourceFilter { + ShowInactiveResources = 0, + ShowActiveResources, + ShowAllResources + }; + + void setResourceFilter(ResourceFilter filter); + + enum StorageFilter { + ShowInactiveStorages = 0, + ShowActiveStorages, + ShowAllStorages + }; + + void setStorageFilter(StorageFilter filter); - // KisAbstractResourceModel interface public: + KoResourceSP resourceForIndex(QModelIndex index = QModelIndex()) const override; QModelIndex indexFromResource(KoResourceSP resource) const override; - bool removeResource(const QModelIndex &index) override; + bool setResourceInactive(const QModelIndex &index) override; bool importResourceFile(const QString &filename) override; bool addResource(KoResourceSP resource, const QString &storageId = QString()) override; bool updateResource(KoResourceSP resource) override; bool renameResource(KoResourceSP resource, const QString &name) override; - bool removeResource(KoResourceSP resource) override; + bool setResourceInactive(KoResourceSP resource) override; bool setResourceMetaData(KoResourceSP resource, QMap metadata) override; - /** - * @brief setTag - * @param tag - */ - void setTag(const KisTagSP tag); - void setSearchBoxText(const QString& seatchBoxText); - void setFilterByCurrentTag(bool filterInCurrentTag); - protected: bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; - bool resourceHasCurrentTag(KisTagSP currentTag, QVector tagsForResource) const; - -private Q_SLOTS: - void slotModelReset(); - - private: + struct Private; Private *const d; - Q_DISABLE_COPY(KisTagFilterResourceProxyModel) + Q_DISABLE_COPY(KisResourcesActiveFilterModel) + }; -#endif // KISTAGFILTERRESOURCEPROXYMODEL_H +#endif // KISACTIVEFILTERMODEL_H diff --git a/libs/resources/KisStorageFilterProxyModel.cpp b/libs/resources/KisStorageFilterProxyModel.cpp index 82210f9b11..f4ccba4037 100644 --- a/libs/resources/KisStorageFilterProxyModel.cpp +++ b/libs/resources/KisStorageFilterProxyModel.cpp @@ -1,102 +1,106 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * 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 "KisStorageFilterProxyModel.h" #include #include #include #include #include struct KisStorageFilterProxyModel::Private { FilterType filterType {KisStorageFilterProxyModel::ByStorageType}; QVariant filter; }; KisStorageFilterProxyModel::KisStorageFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) , d(new Private) { } KisStorageFilterProxyModel::~KisStorageFilterProxyModel() { delete d; } KisResourceStorageSP KisStorageFilterProxyModel::storageForIndex(QModelIndex index) const { KisStorageModel *source = dynamic_cast(sourceModel()); if (source) { return source->storageForIndex(mapToSource(index)); } return 0; } void KisStorageFilterProxyModel::setFilter(KisStorageFilterProxyModel::FilterType filterType, QVariant filter) { d->filter = filter; d->filterType = filterType; } bool KisStorageFilterProxyModel::filterAcceptsColumn(int /*source_column*/, const QModelIndex &/*source_parent*/) const { return true; } bool KisStorageFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (d->filter.isNull()) return true; - QModelIndex idx = sourceModel()->index(source_row, KisResourceModel::Name, source_parent); + QModelIndex idx = sourceModel()->index(source_row, KisAbstractResourceModel::Name, source_parent); switch (d->filterType) { case ByFileName: { - QMap v = d->filter.toMap(); - return KisResourceLocator::instance()->storageContainsResourceByFile(sourceModel()->data(idx, Qt::UserRole + KisStorageModel::Location).toString() - , v["resourcetype"].toString() - , v["filename"].toString()); + QString filename = d->filter.toString(); + return (sourceModel()->data(idx, Qt::UserRole + KisStorageModel::Location).toString().contains(filename)); } case ByStorageType: { QString storageType = sourceModel()->data(idx, Qt::UserRole + KisStorageModel::StorageType).toString(); return (d->filter.toStringList().contains(storageType)); } + case ByActive: + { + bool active = d->filter.toBool(); + bool isActive = sourceModel()->data(idx, Qt::UserRole + KisStorageModel::Active).toBool(); + return (active == isActive); + } default: ; } return false; } bool KisStorageFilterProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { - QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisResourceModel::Name).toString(); - QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisResourceModel::Name).toString(); + QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisAbstractResourceModel::Name).toString(); + QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisAbstractResourceModel::Name).toString(); return nameLeft < nameRight; } void KisStorageFilterProxyModel::slotModelReset() { invalidateFilter(); } diff --git a/libs/resources/KisStorageFilterProxyModel.h b/libs/resources/KisStorageFilterProxyModel.h index 80ce1e2006..a6de2f6d86 100644 --- a/libs/resources/KisStorageFilterProxyModel.h +++ b/libs/resources/KisStorageFilterProxyModel.h @@ -1,71 +1,74 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * 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 KISSTORAGEFILTERPROXYMODEL_H #define KISSTORAGEFILTERPROXYMODEL_H #include #include #include #include #include "kritaresources_export.h" /** * KisStorageFilterProxyModel provides a filtered view on the available storages. * It can be used to find the storages that have resource with a particular file * name, or storages of particular types. * * Filtering by file name takes a string, filtering by storage type a list * of untranslated strings (there is a method in KisResourceStorage for retrieving * those strings from the ResourceType). */ class KRITARESOURCES_EXPORT KisStorageFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: KisStorageFilterProxyModel(QObject *parent = 0); ~KisStorageFilterProxyModel() override; enum FilterType { - ByFileName = 0, - ByStorageType + ByFileName = 0, ///< Pass a string: all storages whose name contains the + /// string will be returned. + ByStorageType, ///< Pass a string list of storage types + ByActive ///< Pass a boolean, false to filter out active bundles, + /// true to filter out inactive bundles }; KisResourceStorageSP storageForIndex(QModelIndex index = QModelIndex()) const; void setFilter(FilterType filterType, QVariant filter); protected: bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; private Q_SLOTS: void slotModelReset(); private: struct Private; Private *const d; Q_DISABLE_COPY(KisStorageFilterProxyModel) }; #endif diff --git a/libs/resources/KisStorageModel.cpp b/libs/resources/KisStorageModel.cpp index 1187675a47..2334c86911 100644 --- a/libs/resources/KisStorageModel.cpp +++ b/libs/resources/KisStorageModel.cpp @@ -1,291 +1,319 @@ /* * Copyright (c) 2019 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisStorageModel.h" #include #include #include #include Q_GLOBAL_STATIC(KisStorageModel, s_instance) struct KisStorageModel::Private { int cachedRowCount {-1}; - QSqlQuery query; + QList storages; }; KisStorageModel::KisStorageModel(QObject *parent) : QAbstractTableModel(parent) , d(new Private()) { - prepareQuery(); - connect(KisResourceLocator::instance(), SIGNAL(storageAdded()), this, SLOT(resetQuery())); - connect(KisResourceLocator::instance(), SIGNAL(storageRemoved()), this, SLOT(resetQuery())); + connect(KisResourceLocator::instance(), SIGNAL(storageAdded(const QString&)), this, SLOT(addStorage(const QString&))); + connect(KisResourceLocator::instance(), SIGNAL(storageRemoved(const QString&)), this, SLOT(removeStorage(const QString&))); + + QSqlQuery query; + + bool r = query.prepare("SELECT location\n" + "FROM storages\n" + "ORDER BY id"); + if (!r) { + qWarning() << "Could not prepare KisStorageModel query" << query.lastError(); + } + + r = query.exec(); + + if (!r) { + qWarning() << "Could not execute KisStorageModel query" << query.lastError(); + } + + while (query.next()) { + d->storages << query.value(0).toString(); + } } KisStorageModel *KisStorageModel::instance() { return s_instance; } KisStorageModel::~KisStorageModel() { } int KisStorageModel::rowCount(const QModelIndex & /*parent*/) const { - if (d->cachedRowCount < 0) { - QSqlQuery q; - q.prepare("SELECT count(*)\n" - "FROM storages\n"); - q.exec(); - q.first(); - - const_cast(this)->d->cachedRowCount = q.value(0).toInt(); - } - return d->cachedRowCount; + return d->storages.size(); } int KisStorageModel::columnCount(const QModelIndex &/*parent*/) const { return 8; } QVariant KisStorageModel::data(const QModelIndex &index, int role) const { QVariant v; if (!index.isValid()) return v; if (index.row() > rowCount()) return v; if (index.column() > (int)MetaData) return v; - bool pos = d->query.seek(index.row()); + QString location = d->storages.at(index.row()); + + QSqlQuery query; + + bool r = query.prepare("SELECT storages.id as id\n" + ", storage_types.name as storage_type\n" + ", location\n" + ", timestamp\n" + ", pre_installed\n" + ", active\n" + ", thumbnail\n" + "FROM storages\n" + ", storage_types\n" + "WHERE storages.storage_type_id = storage_types.id\n" + "AND location = :location"); + + if (!r) { + qWarning() << "Could not prepare KisStorageModel data query" << query.lastError(); + return v; + } + + query.bindValue(":location", location); + + r = query.exec(); + + if (!r) { + qWarning() << "Could not execute KisStorageModel data query" << query.lastError() << query.boundValues(); + return v; + } + + if (!query.first()) { + qWarning() << "KisStorageModel data query did not return anything"; + return v; + } - if (pos) { + if ((role == Qt::DisplayRole || role == Qt::EditRole) && index.column() == Active) { + return query.value("active"); + } + else + { switch(role) { case Qt::DisplayRole: { switch(index.column()) { case Id: - return d->query.value("id"); + return query.value("id"); case StorageType: - return d->query.value("storage_type"); + return query.value("storage_type"); case Location: - return d->query.value("location"); + return query.value("location"); case TimeStamp: - return d->query.value("timestamp"); + return query.value("timestamp"); case PreInstalled: - return d->query.value("pre_installed"); + return query.value("pre_installed"); case Active: - return d->query.value("active"); + return query.value("active"); case Thumbnail: { - QByteArray ba = d->query.value("thumbnail").toByteArray(); + QByteArray ba = query.value("thumbnail").toByteArray(); QBuffer buf(&ba); buf.open(QBuffer::ReadOnly); QImage img; img.load(&buf, "PNG"); return QVariant::fromValue(img); } case DisplayName: { - QMap r = KisResourceLocator::instance()->metaDataForStorage(d->query.value("location").toString()); - QVariant name = d->query.value("location"); + QMap r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString()); + QVariant name = query.value("location"); if (r.contains(KisResourceStorage::s_meta_name) && !r[KisResourceStorage::s_meta_name].isNull()) { name = r[KisResourceStorage::s_meta_name]; } else if (r.contains(KisResourceStorage::s_meta_title) && !r[KisResourceStorage::s_meta_title].isNull()) { name = r[KisResourceStorage::s_meta_title]; } return name; } case Qt::UserRole + MetaData: { - QMap r = KisResourceLocator::instance()->metaDataForStorage(d->query.value("location").toString()); + QMap r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString()); return r; } default: return v; } } case Qt::UserRole + Id: - return d->query.value("id"); + return query.value("id"); case Qt::UserRole + DisplayName: { - QMap r = KisResourceLocator::instance()->metaDataForStorage(d->query.value("location").toString()); - QVariant name = d->query.value("location"); + QMap r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString()); + QVariant name = query.value("location"); if (r.contains(KisResourceStorage::s_meta_name) && !r[KisResourceStorage::s_meta_name].isNull()) { name = r[KisResourceStorage::s_meta_name]; } else if (r.contains(KisResourceStorage::s_meta_title) && !r[KisResourceStorage::s_meta_title].isNull()) { name = r[KisResourceStorage::s_meta_title]; } return name; } case Qt::UserRole + StorageType: - return d->query.value("storage_type"); + return query.value("storage_type"); case Qt::UserRole + Location: - return d->query.value("location"); + return query.value("location"); case Qt::UserRole + TimeStamp: - return d->query.value("timestamp"); + return query.value("timestamp"); case Qt::UserRole + PreInstalled: - return d->query.value("pre_installed"); + return query.value("pre_installed"); case Qt::UserRole + Active: - return d->query.value("active"); + return query.value("active"); case Qt::UserRole + Thumbnail: { - QByteArray ba = d->query.value("thumbnail").toByteArray(); + QByteArray ba = query.value("thumbnail").toByteArray(); QBuffer buf(&ba); buf.open(QBuffer::ReadOnly); QImage img; img.load(&buf, "PNG"); return QVariant::fromValue(img); } case Qt::UserRole + MetaData: { - QMap r = KisResourceLocator::instance()->metaDataForStorage(d->query.value("location").toString()); + QMap r = KisResourceLocator::instance()->metaDataForStorage(query.value("location").toString()); return r; } default: ; } } - return v; + return v; } bool KisStorageModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid()) { if (role == Qt::CheckStateRole) { - QSqlQuery q; - bool r = q.prepare("UPDATE storages\n" - "SET active = :active\n" - "WHERE id = :id\n"); - q.bindValue(":active", value); - q.bindValue(":id", index.data(Qt::UserRole + Id)); + QSqlQuery query; + bool r = query.prepare("UPDATE storages\n" + "SET active = :active\n" + "WHERE id = :id\n"); + query.bindValue(":active", value); + query.bindValue(":id", index.data(Qt::UserRole + Id)); + if (!r) { - qWarning() << "Could not prepare KisStorageModel update query" << d->query.lastError(); + qWarning() << "Could not prepare KisStorageModel update query" << query.lastError(); return false; } - r = q.exec(); + + r = query.exec(); + if (!r) { - qWarning() << "Could not execute KisStorageModel update query" << d->query.lastError(); + qWarning() << "Could not execute KisStorageModel update query" << query.lastError(); return false; } } } - QAbstractTableModel::setData(index, value, role); - KisResourceModelProvider::resetAllModels(); - return prepareQuery(); + emit dataChanged(index, index, {role}); + return true; } Qt::ItemFlags KisStorageModel::flags(const QModelIndex &index) const { + if (!index.isValid()) { + return Qt::ItemIsEnabled; + } return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; } KisResourceStorageSP KisStorageModel::storageForIndex(const QModelIndex &index) const { - return KisResourceLocator::instance()->storageByLocation(KisResourceLocator::instance()->makeStorageLocationAbsolute(index.data(Qt::UserRole + Location).toString())); + + if (!index.isValid()) return 0; + if (index.row() > rowCount()) return 0; + if (index.column() > (int)MetaData) return 0; + + QString location = d->storages.at(index.row()); + + return KisResourceLocator::instance()->storageByLocation(KisResourceLocator::instance()->makeStorageLocationAbsolute(location)); } QVariant KisStorageModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant v = QVariant(); if (role != Qt::DisplayRole) { return v; } if (orientation == Qt::Horizontal) { switch(section) { - case 0: - v = i18n("Id"); - break; - case 1: - v = i18n("Type"); - break; - case 2: - v = i18n("Location"); - break; - case 3: - v = i18n("Creation Date"); - break; - case 4: - v = i18n("Preinstalled"); - break; - case 5: - v = i18n("Active"); - break; - case 6: - v = i18n("Thumbnail"); - break; - case 7: - v = i18n("Name"); - break; + case Id: + return i18n("Id"); + case StorageType: + return i18n("Type"); + case Location: + return i18n("Location"); + case TimeStamp: + return i18n("Creation Date"); + case PreInstalled: + return i18n("Preinstalled"); + case Active: + return i18n("Active"); + case Thumbnail: + return i18n("Thumbnail"); + case DisplayName: + return i18n("Name"); default: v = QString::number(section); } return v; } return QAbstractTableModel::headerData(section, orientation, role); } -bool KisStorageModel::resetQuery() +void KisStorageModel::addStorage(const QString &location) { - QElapsedTimer t; - t.start(); - - beginResetModel(); - bool r = d->query.exec(); - if (!r) { - qWarning() << "Could not select storages" << d->query.lastError() << d->query.boundValues(); - } - d->cachedRowCount = -1; + qDebug() << "before" << d->storages << rowCount(); - endResetModel(); - qDebug() << "KisStorageModel::resetQuery took" << t.elapsed() << "ms"; + beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1); + d->storages.append(location); + endInsertRows(); - return r; + qDebug() << "after" << d->storages << rowCount(); } -bool KisStorageModel::prepareQuery() +void KisStorageModel::removeStorage(const QString &location) { - beginResetModel(); - bool r = d->query.prepare("SELECT storages.id as id\n" - ", storage_types.name as storage_type\n" - ", location\n" - ", timestamp\n" - ", pre_installed\n" - ", active\n" - ", thumbnail\n" - "FROM storages\n" - ", storage_types\n" - "WHERE storages.storage_type_id = storage_types.id\n"); - if (!r) { - qWarning() << "Could not prepare KisStorageModel query" << d->query.lastError(); - } - r = d->query.exec(); - if (!r) { - qWarning() << "Could not execute KisStorageModel query" << d->query.lastError(); - } - d->cachedRowCount = -1; - endResetModel(); - return r; + int index = d->storages.indexOf(location); + beginRemoveRows(QModelIndex(), index, index); + d->storages.removeAt(index); + endRemoveRows(); } + + diff --git a/libs/resources/KisStorageModel.h b/libs/resources/KisStorageModel.h index 3d13c4f92f..f9044f19a5 100644 --- a/libs/resources/KisStorageModel.h +++ b/libs/resources/KisStorageModel.h @@ -1,82 +1,81 @@ /* * Copyright (c) 2019 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISSTORAGEMODEL_H #define KISSTORAGEMODEL_H #include #include #include #include "KisResourceStorage.h" #include "kritaresources_export.h" /** * KisStorageModel provides a model of all registered storages, like * the folder storages, the bundle storages or the memory storages. Note * that inactive storages are also part of this model. */ class KRITARESOURCES_EXPORT KisStorageModel : public QAbstractTableModel { Q_OBJECT public: enum Columns { Id = 0, StorageType, Location, TimeStamp, PreInstalled, Active, Thumbnail, DisplayName, MetaData }; KisStorageModel(QObject *parent = 0); ~KisStorageModel() override; - static KisStorageModel * instance(); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; Qt::ItemFlags flags(const QModelIndex &index) const override; KisResourceStorageSP storageForIndex(const QModelIndex &index) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; -public Q_SLOTS: +private Q_SLOTS: - bool resetQuery(); + void addStorage(const QString &location); + void removeStorage(const QString &location); private: KisStorageModel(const KisStorageModel&); KisStorageModel operator=(const KisStorageModel&); - bool prepareQuery(); - struct Private; QScopedPointer d; }; #endif // KISSTORAGEMODEL_H diff --git a/libs/resources/KisTagFilterResourceProxyModel.cpp b/libs/resources/KisTagFilterResourceProxyModel.cpp index e97e2a9d64..24e8a34360 100644 --- a/libs/resources/KisTagFilterResourceProxyModel.cpp +++ b/libs/resources/KisTagFilterResourceProxyModel.cpp @@ -1,224 +1,224 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * 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 "KisTagFilterResourceProxyModel.h" #include #include #include #include struct KisTagFilterResourceProxyModel::Private { Private() : filter(new KisResourceSearchBoxFilter()) { } QList tags; KisTagModel* tagModel; QScopedPointer filter; bool filterInCurrentTag {false}; }; KisTagFilterResourceProxyModel::KisTagFilterResourceProxyModel(KisTagModel* model, QObject *parent) : QSortFilterProxyModel(parent) , d(new Private) { d->tagModel = model; //connect(model, SIGNAL(modelReset()), this, SLOT(slotModelReset())); } KisTagFilterResourceProxyModel::~KisTagFilterResourceProxyModel() { delete d; } KoResourceSP KisTagFilterResourceProxyModel::resourceForIndex(QModelIndex index) const { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->resourceForIndex(mapToSource(index)); } return 0; } QModelIndex KisTagFilterResourceProxyModel::indexFromResource(KoResourceSP resource) const { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return mapFromSource(source->indexFromResource(resource)); } return QModelIndex(); } -bool KisTagFilterResourceProxyModel::removeResource(const QModelIndex &index) +bool KisTagFilterResourceProxyModel::setResourceInactive(const QModelIndex &index) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { - return source->removeResource(mapToSource(index)); + return source->setResourceInactive(mapToSource(index)); } return false; } bool KisTagFilterResourceProxyModel::importResourceFile(const QString &filename) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->importResourceFile(filename); } return false; } bool KisTagFilterResourceProxyModel::addResource(KoResourceSP resource, const QString &storageId) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->addResource(resource, storageId); } return false; } bool KisTagFilterResourceProxyModel::updateResource(KoResourceSP resource) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->updateResource(resource); } return false; } bool KisTagFilterResourceProxyModel::renameResource(KoResourceSP resource, const QString &name) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->renameResource(resource, name); } return false; } -bool KisTagFilterResourceProxyModel::removeResource(KoResourceSP resource) +bool KisTagFilterResourceProxyModel::setResourceInactive(KoResourceSP resource) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { - return source->removeResource(resource); + return source->setResourceInactive(resource); } return false; } bool KisTagFilterResourceProxyModel::setResourceMetaData(KoResourceSP resource, QMap metadata) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->setResourceMetaData(resource, metadata); } return false; } void KisTagFilterResourceProxyModel::setTag(const KisTagSP tag) { d->tags.clear(); if (!tag.isNull()) { d->tags << tag; } invalidateFilter(); } void KisTagFilterResourceProxyModel::setSearchBoxText(const QString& seatchBoxText) { d->filter->setFilter(seatchBoxText); invalidateFilter(); } void KisTagFilterResourceProxyModel::setFilterByCurrentTag(const bool filterInCurrentTag) { d->filterInCurrentTag = filterInCurrentTag; invalidateFilter(); } bool KisTagFilterResourceProxyModel::filterAcceptsColumn(int /*source_column*/, const QModelIndex &/*source_parent*/) const { return true; } bool KisTagFilterResourceProxyModel::resourceHasCurrentTag(KisTagSP currentTag, QVector tagsForResource) const { if (!d->filterInCurrentTag && !d->filter->isEmpty()) { // we don't need to check anything else because the user wants to search in all resources // but if the filter text is empty, we do need to filter by the current tag return true; } if (currentTag.isNull()) { // no tag set; all resources are allowed return true; } else { if (currentTag->id() == KisTagModel::All) { // current tag is "All", all resources are allowed return true; } else if (currentTag->id() == KisTagModel::AllUntagged) { // current tag is "All untagged", all resources without any tags are allowed return tagsForResource.size() == 0; } else { // checking whether the current tag is on the list of tags assigned to the resource Q_FOREACH(KisTagSP temp, tagsForResource) { if (temp->id() == currentTag->id()) { return true; } } } } return false; } bool KisTagFilterResourceProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (d->tagModel == 0) { return true; } - QModelIndex idx = sourceModel()->index(source_row, KisResourceModel::Name, source_parent); - int resourceId = sourceModel()->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); - QString resourceName = sourceModel()->data(idx, Qt::UserRole + KisResourceModel::Name).toString(); + QModelIndex idx = sourceModel()->index(source_row, KisAbstractResourceModel::Name, source_parent); + int resourceId = sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::Id).toInt(); + QString resourceName = sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::Name).toString(); QVector tagsForResource = d->tagModel->tagsForResource(resourceId); KisTagSP tag = d->tags.isEmpty() ? KisTagSP() : d->tags.first(); bool hasCurrentTag = resourceHasCurrentTag(tag, tagsForResource); if (!hasCurrentTag) { return false; } bool currentFilterMatches = d->filter->matchesResource(resourceName); return currentFilterMatches; } bool KisTagFilterResourceProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { - QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisResourceModel::Name).toString(); - QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisResourceModel::Name).toString(); + QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisAbstractResourceModel::Name).toString(); + QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisAbstractResourceModel::Name).toString(); return nameLeft < nameRight; } void KisTagFilterResourceProxyModel::slotModelReset() { invalidateFilter(); } diff --git a/libs/resources/KisTagFilterResourceProxyModel.h b/libs/resources/KisTagFilterResourceProxyModel.h index 7585c7e608..b1b12f2a96 100644 --- a/libs/resources/KisTagFilterResourceProxyModel.h +++ b/libs/resources/KisTagFilterResourceProxyModel.h @@ -1,78 +1,81 @@ /* * Copyright (C) 2018 Boudewijn Rempt * * 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 KISTAGFILTERRESOURCEPROXYMODEL_H #define KISTAGFILTERRESOURCEPROXYMODEL_H #include #include #include #include #include #include #include "kritaresources_export.h" +/** + * @brief The KisTagFilterResourceProxyModel class filters the resources by tag or resource name + */ class KRITARESOURCES_EXPORT KisTagFilterResourceProxyModel : public QSortFilterProxyModel, public KisAbstractResourceModel { Q_OBJECT public: KisTagFilterResourceProxyModel(KisTagModel* model = 0, QObject *parent = 0); ~KisTagFilterResourceProxyModel() override; // KisAbstractResourceModel interface public: KoResourceSP resourceForIndex(QModelIndex index = QModelIndex()) const override; QModelIndex indexFromResource(KoResourceSP resource) const override; - bool removeResource(const QModelIndex &index) override; + bool setResourceInactive(const QModelIndex &index) override; bool importResourceFile(const QString &filename) override; bool addResource(KoResourceSP resource, const QString &storageId = QString()) override; bool updateResource(KoResourceSP resource) override; bool renameResource(KoResourceSP resource, const QString &name) override; - bool removeResource(KoResourceSP resource) override; + bool setResourceInactive(KoResourceSP resource) override; bool setResourceMetaData(KoResourceSP resource, QMap metadata) override; /** * @brief setTag * @param tag */ void setTag(const KisTagSP tag); void setSearchBoxText(const QString& seatchBoxText); void setFilterByCurrentTag(bool filterInCurrentTag); protected: bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; bool resourceHasCurrentTag(KisTagSP currentTag, QVector tagsForResource) const; private Q_SLOTS: void slotModelReset(); private: struct Private; Private *const d; Q_DISABLE_COPY(KisTagFilterResourceProxyModel) }; #endif // KISTAGFILTERRESOURCEPROXYMODEL_H diff --git a/libs/resources/KisTagModel.cpp b/libs/resources/KisTagModel.cpp index a1337dcca3..c416a16ece 100644 --- a/libs/resources/KisTagModel.cpp +++ b/libs/resources/KisTagModel.cpp @@ -1,611 +1,611 @@ /* * Copyright (c) 2018 boud * Copyright (c) 2020 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisTagModel.h" #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QSharedPointer) struct KisTagModel::Private { QSqlQuery query; QSqlQuery tagsForResourceQuery; QSqlQuery resourcesForTagQuery; QString resourceType; int columnCount {5}; int cachedRowCount {-1}; int fakeRowsCount {2}; }; KisTagModel::KisTagModel(const QString &resourceType, QObject *parent) : QAbstractTableModel(parent) , d(new Private()) { d->resourceType = resourceType; if (!d->resourceType.isEmpty()) { prepareQuery(); } } KisTagModel::~KisTagModel() { delete d; } int KisTagModel::rowCount(const QModelIndex &/*parent*/) const { if (d->cachedRowCount < 0) { QSqlQuery q; q.prepare("SELECT count(*)\n" "FROM tags\n" ", resource_types\n" "WHERE active = 1\n" "AND tags.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n"); q.bindValue(":resource_type", d->resourceType); q.exec(); q.first(); const_cast(this)->d->cachedRowCount = q.value(0).toInt() + d->fakeRowsCount; } return d->cachedRowCount; } int KisTagModel::columnCount(const QModelIndex &/*parent*/) const { return d->columnCount; } QVariant KisTagModel::data(const QModelIndex &index, int role) const { QVariant v; if (!index.isValid()) return v; if (index.row() > rowCount()) return v; if (index.column() > d->columnCount) return v; // The first row is All // XXX: Should we also add an "All Untagged"? if (index.row() < d->fakeRowsCount) { if (index.row() == KisTagModel::All + d->fakeRowsCount) { switch(role) { case Qt::DisplayRole: // fallthrough case Qt::ToolTipRole: // fallthrough case Qt::StatusTipRole: // fallthrough case Qt::WhatsThisRole: case Qt::UserRole + Name: return i18n("All"); case Qt::UserRole + Id: return QString::number(KisTagModel::All); case Qt::UserRole + Url: { return "All"; } case Qt::UserRole + ResourceType: return d->resourceType; case Qt::UserRole + Active: return true; case Qt::UserRole + KisTagRole: { KisTagSP tag = tagForIndex(index); QVariant response; response.setValue(tag); return response; } default: ; } } else if (index.row() == KisTagModel::AllUntagged + d->fakeRowsCount) { switch(role) { case Qt::DisplayRole: // fallthrough case Qt::ToolTipRole: // fallthrough case Qt::StatusTipRole: // fallthrough case Qt::WhatsThisRole: case Qt::UserRole + Name: return i18n("All untagged"); case Qt::UserRole + Id: return QString::number(KisTagModel::AllUntagged); case Qt::UserRole + Url: { return "All untagged"; } case Qt::UserRole + ResourceType: return d->resourceType; case Qt::UserRole + Active: return true; case Qt::UserRole + KisTagRole: { KisTagSP tag = tagForIndex(index); QVariant response; response.setValue(tag); return response; } default: ; } } } else { bool pos = const_cast(this)->d->query.seek(index.row() - d->fakeRowsCount); if (pos) { switch(role) { case Qt::DisplayRole: return d->query.value("name"); case Qt::ToolTipRole: // fallthrough case Qt::StatusTipRole: // fallthrough case Qt::WhatsThisRole: return d->query.value("comment"); case Qt::UserRole + Id: return d->query.value("id"); case Qt::UserRole + Name: return d->query.value("name"); case Qt::UserRole + Url: return d->query.value("url"); case Qt::UserRole + ResourceType: return d->query.value("resource_type"); case Qt::UserRole + Active: return d->query.value("active"); case Qt::UserRole + KisTagRole: { KisTagSP tag = tagForIndex(index); QVariant response; response.setValue(tag); return response; } default: ; } } } return v; } void KisTagModel::setResourceType(const QString &resourceType) { d->resourceType = resourceType; prepareQuery(); } KisTagSP KisTagModel::tagForIndex(QModelIndex index) const { KisTagSP tag = 0; if (!index.isValid()) return tag; if (index.row() > rowCount()) return tag; if (index.column() > columnCount()) return tag; if (index.row() < d->fakeRowsCount) { if (index.row() == KisTagModel::All + d->fakeRowsCount) { tag.reset(new KisTag()); tag->setName(i18n("All")); tag->setUrl("All"); tag->setComment(i18n("All")); tag->setId(KisTagModel::All); tag->setActive(true); tag->setValid(true); } else if (index.row() == KisTagModel::AllUntagged + d->fakeRowsCount) { tag.reset(new KisTag()); tag->setName(i18n("All untagged")); tag->setUrl("All untagged"); tag->setComment(i18n("All untagged")); tag->setId(KisTagModel::AllUntagged); tag->setActive(true); tag->setValid(true); } } else { bool pos = const_cast(this)->d->query.seek(index.row() - d->fakeRowsCount); if (pos) { tag.reset(new KisTag()); tag->setUrl(d->query.value("url").toString()); tag->setName(d->query.value("name").toString()); tag->setComment(d->query.value("comment").toString()); tag->setId(d->query.value("id").toInt()); tag->setActive(d->query.value("active").toBool()); tag->setValid(true); } } return tag; } bool KisTagModel::addEmptyTag(const QString& tagName, QVector taggedResouces) { qDebug() << "bool KisTagModel::addEmptyTag(const QString& tagName, QVector taggedResouces) ### " << tagName; KisTagSP tag = KisTagSP(new KisTag()); tag->setName(tagName); tag->setUrl(tagName); return addEmptyTag(tag, taggedResouces); } bool KisTagModel::addEmptyTag(const KisTagSP tag, QVector taggedResouces) { qDebug() << "bool KisTagModel::addEmptyTag(const KisTagSP tag, QVector taggedResouces) ### " << tag; tag->setValid(true); tag->setActive(true); return addTag(tag, taggedResouces); } bool KisTagModel::addTag(const KisTagSP tag, QVector taggedResouces) { qDebug() << "######################"; qDebug() << "bool KisTagModel::addTag(const KisTagSP tag, QVector taggedResouces) " << tag; if (tag.isNull()) return false; if (!tag) return false; if (!tag->valid()) return false; // A new tag doesn't have an ID yet, that comes from the database if (tag->id() >= 0) return false; if (!KisResourceCacheDb::hasTag(tag->url(), d->resourceType)) { qDebug() << "it doesn't have the tag!" << tag->url() << tag->name() << tag->comment(); if (!KisResourceCacheDb::addTag(d->resourceType, "", tag->url(), tag->name(), tag->comment())) { qWarning() << "Could not add tag" << tag; return false; } } else { QSqlQuery q; if (!q.prepare("UPDATE tags\n" "SET active = 1\n" "WHERE url = :url\n" "AND resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type\n)")) { qWarning() << "Couild not prepare make existing tag active query" << tag << q.lastError(); return false; } q.bindValue(":url", tag->url()); q.bindValue(":resource_type", d->resourceType); if (!q.exec()) { qWarning() << "Couild not execute make existing tag active query" << q.boundValues(), q.lastError(); return false; } } qDebug() << "tag = " << tag; if (!taggedResouces.isEmpty()) { qDebug() << "********************"; qDebug() << "tag = " << tag; qDebug() << "tag url = " << tag->url(); KisTagSP tagFromDb = tagByUrl(tag->url()); qDebug() << "tag from db: " << tagFromDb << tag->id(); Q_FOREACH(const KoResourceSP resource, taggedResouces) { if (!resource) continue; if (!resource->valid()) continue; if (resource->resourceId() < 0) continue; tagResource(tagFromDb, resource); } } qDebug() << "^^^^^^^^^^^^^^^^^^^^^^^^^"; return prepareQuery(); } bool KisTagModel::removeTag(const KisTagSP tag) { if (!tag) return false; if (!tag->valid()) return false; if (tag->id() < 0) return false; QSqlQuery q; if (!q.prepare("UPDATE tags\n" "SET active = 0\n" "WHERE id = :id")) { qWarning() << "Could not prepare remove tag query" << q.lastError(); return false; } q.bindValue(":id", tag->id()); if (!q.exec()) { qWarning() << "Could not execute remove tag query" << q.lastError(); return false; } return prepareQuery(); } bool KisTagModel::tagResource(const KisTagSP tag, const KoResourceSP resource) { if (!tag) return false; if (!tag->valid()) return false; if (tag->id() < 0) return false; qDebug() << tag << " tag id " << tag->id(); if (!resource) return false; if (!resource->valid()) return false; if (resource->resourceId() < 0) return false; QSqlQuery q; bool r = q.prepare("INSERT INTO resource_tags\n" "(resource_id, tag_id)\n" "VALUES\n" "( (SELECT id\n" " FROM resources\n" " WHERE id = :resource_id)\n" ", (SELECT id\n" " FROM tags\n" " WHERE id = :tag_id\n" " AND resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type" " \n)" " )\n" ")\n"); if (!r) { qWarning() << "Could not prepare insert into resource tags statement" << q.lastError(); return false; } q.bindValue(":resource_id", resource->resourceId()); q.bindValue(":tag_id", tag->id()); q.bindValue(":resource_type", d->resourceType); if (!q.exec()) { qWarning() << "Could not execute insert into resource tags statement" << q.boundValues() << q.lastError(); return false; } KisResourceModelProvider::resetModel(d->resourceType); return true; } bool KisTagModel::untagResource(const KisTagSP tag, const KoResourceSP resource) { if (!tag) return false; if (!tag->valid()) return false; if (!tag->id()) return false; if (!resource) return false; if (!resource->valid()) return false; if (resource->resourceId() < 0) return false; // we need to delete an entry in resource_tags QSqlQuery query; bool r = query.prepare("DELETE FROM resource_tags\n" "WHERE resource_id = :resource_id\n" "AND tag_id = :tag_id"); if (!r) { qWarning() << "Could not prepare KisTagModel query untagResource " << query.lastError(); } query.bindValue(":resource_id", resource->resourceId()); query.bindValue(":tag_id", tag->id()); r = query.exec(); if (!r) { qWarning() << "Could not select tags" << query.lastError(); } KisResourceModelProvider::resetModel(d->resourceType); return true; } bool KisTagModel::renameTag(const KisTagSP tag, const QString &name) { if (!tag) return false; if (!tag->valid()) return false; if (name.isEmpty()) return false; QSqlQuery q; if (!q.prepare("UPDATE tags\n" "SET name = :name\n" "WHERE url = :url\n" "AND resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type\n)")) { qWarning() << "Couild not prepare make existing tag active query" << tag << q.lastError(); return false; } q.bindValue(":name", name); q.bindValue(":url", tag->url()); q.bindValue(":resource_type", d->resourceType); if (!q.exec()) { qWarning() << "Couild not execute make existing tag active query" << q.boundValues(), q.lastError(); return false; } return prepareQuery(); } bool KisTagModel::changeTagActive(const KisTagSP tag, bool active) { if (!tag) return false; if (!tag->valid()) return false; QSqlQuery q; if (!q.prepare("UPDATE tags\n" "SET active = :active\n" "WHERE url = :url\n" "AND resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type\n)")) { qWarning() << "Couild not prepare make existing tag active query" << tag << q.lastError(); return false; } q.bindValue(":active", active); q.bindValue(":url", tag->url()); q.bindValue(":resource_type", d->resourceType); if (!q.exec()) { qWarning() << "Couild not execute make existing tag active query" << q.boundValues(), q.lastError(); return false; } return prepareQuery(); } QVector KisTagModel::tagsForResource(int resourceId) const { bool r = d->tagsForResourceQuery.prepare("SELECT tags.id\n" ", tags.url\n" ", tags.name\n" ", tags.comment\n" "FROM tags\n" ", resource_tags\n" "WHERE tags.active > 0\n" // make sure the tag is active "AND tags.id = resource_tags.tag_id\n" // join tags + resource_tags by tag_id "AND resource_tags.resource_id = :resource_id\n"); // make sure we're looking for tags for a specific resource if (!r) { qWarning() << "Could not prepare TagsForResource query" << d->tagsForResourceQuery.lastError(); } d->tagsForResourceQuery.bindValue(":resource_id", resourceId); r = d->tagsForResourceQuery.exec(); if (!r) { qWarning() << "Could not select tags for" << resourceId << d->tagsForResourceQuery.lastError() << d->tagsForResourceQuery.boundValues(); } QVector tags; while (d->tagsForResourceQuery.next()) { //qDebug() << d->tagQuery.value(0).toString() << d->tagQuery.value(1).toString() << d->tagQuery.value(2).toString(); KisTagSP tag(new KisTag()); tag->setId(d->tagsForResourceQuery.value("id").toInt()); tag->setUrl(d->tagsForResourceQuery.value("url").toString()); tag->setName(d->tagsForResourceQuery.value("name").toString()); tag->setComment(d->tagsForResourceQuery.value("comment").toString()); tag->setValid(true); tag->setActive(true); tags << tag; } return tags; } KisTagSP KisTagModel::tagByUrl(const QString& tagUrl) const { if (tagUrl.isEmpty()) { return KisTagSP(); } QSqlQuery query; bool r = query.prepare("SELECT tags.id\n" ", tags.url\n" ", tags.name\n" ", tags.comment\n" ", tags.active\n" ", resource_types.name as resource_type\n" "FROM tags\n" ", resource_types\n" "WHERE tags.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n" "AND tags.active = 1\n" "AND tags.url = :tag_url\n" "AND tags.storage_id in (SELECT id\n" " FROM storages\n" " WHERE storages.active == 1)"); if (!r) { qWarning() << "Could not prepare KisTagModel::tagByUrl query" << query.lastError(); } query.bindValue(":resource_type", d->resourceType); QString tagUrlForSql = tagUrl; query.bindValue(":tag_url", tagUrlForSql); r = query.exec(); if (!r) { qWarning() << "Could not execute KisTagModel::tagByUrl query" << query.lastError(); } KisTagSP tag(new KisTag()); query.next(); tag->setUrl(query.value("url").toString()); tag->setName(query.value("name").toString()); tag->setComment(query.value("comment").toString()); tag->setId(query.value("id").toInt()); tag->setActive(query.value("active").toBool()); tag->setValid(true); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(tagUrl == tag->url(), KisTagSP()); return tag; } bool KisTagModel::prepareQuery() { - QElapsedTimer t; - t.start(); +// QElapsedTimer t; +// t.start(); beginResetModel(); bool r = d->query.prepare("SELECT tags.id\n" ", tags.url\n" ", tags.name\n" ", tags.comment\n" ", tags.active\n" ", resource_types.name as resource_type\n" "FROM tags\n" ", resource_types\n" "WHERE tags.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n" "AND tags.active = 1\n"); if (!r) { qWarning() << "Could not prepare KisTagModel query" << d->query.lastError(); } d->query.bindValue(":resource_type", d->resourceType); r = d->query.exec(); if (!r) { qWarning() << "Could not select tags" << d->query.lastError(); } d->cachedRowCount = -1; endResetModel(); - qDebug() << "bool KisTagModel::prepareQuery() ### RESET TAG MODEL ### for " << d->resourceType << " took " << t.elapsed() << " ms."; + // qDebug() << "bool KisTagModel::prepareQuery() ### RESET TAG MODEL ### for " << d->resourceType << " took " << t.elapsed() << " ms."; return r; } diff --git a/libs/resources/KoResource.h b/libs/resources/KoResource.h index b40ee137e9..78834f79aa 100644 --- a/libs/resources/KoResource.h +++ b/libs/resources/KoResource.h @@ -1,240 +1,241 @@ /* This file is part of the KDE project Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KORESOURCE_H #define KORESOURCE_H #include #include #include #include #include #include "KisResourceTypes.h" #include #include class QDomDocument; class QDomElement; class KoResource; typedef QSharedPointer KoResourceSP; class KisResourcesInterface; typedef QSharedPointer KisResourcesInterfaceSP; /** * The KoResource class provides a representation of resources. This * includes, but not limited to, brushes and patterns. * * A resource knows its filename, but not the location where it's stored. * A new version of a resource is stored with an updated filename, the old * version isn't renamed. * */ class KRITARESOURCES_EXPORT KoResource : public boost::equality_comparable { public: /** * Creates a new KoResource object using @p filename. No file is opened * in the constructor, you have to call load. * * @param filename the file name to save and load from. */ explicit KoResource(const QString &filename); virtual ~KoResource(); KoResource(const KoResource &rhs); KoResource &operator=(const KoResource &rhs) = delete; virtual KoResourceSP clone() const = 0; bool operator==(const KoResource &other) const; public: /** * Load this resource. * @return true if loading the resource succeeded. */ virtual bool load(KisResourcesInterfaceSP resourcesInterface); virtual bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) = 0; /** * Save this resource. *@return true if saving the resource succeeded. */ virtual bool save(); virtual bool saveToDevice(QIODevice* dev) const; /** * @returns a QImage image representing this resource: in the case * of some resources, it is the actual resource. * * This image could be null. The image can be in any valid format. */ QImage image() const; void setImage(const QImage &image); /** * @brief updateThumbnail updates the thumbnail for this resource. * Reimplement if your thumbnail is something else than the image * set with setImage. */ virtual void updateThumbnail(); /** * @brief thumbnail the thumbnail image to use in resource selectors * @return a valid qimage. All thumbnails for a given resource have the * same size (which is not true for image(), but that size need not * be square. By default it's the same as image(), but that is not guaranteed. */ virtual QImage thumbnail() const; /// @return the md5sum calculated over the contents of the resource. QByteArray md5() const; /// @return the unique identifier of this resource within the container (folder, bundle, ...) QString filename() const; void setFilename(const QString& filename); /// @return the user-visible name of the resource QString name() const; void setName(const QString& name); /// @return true if the resource is ready for use bool valid() const; void setValid(bool valid); /// @return the default file extension which should be used when saving the resource virtual QString defaultFileExtension() const; /// @return true if the resource is permanent and can't be removed by the user bool permanent() const; void setPermanent(bool permanent); /// @return the name of the storage location of the resource QString storageLocation() const; /// Mark the preset as modified but not saved void setDirty(bool value); /// @return true if the preset has been modified, but not saved bool isDirty() const; /// store the given key, value pair in the resource void addMetaData(QString key, QVariant value); /// get a map with all the metadata QMap metadata() const; /// Get the version of the resource int version() const; /// @return the unique id of the resource in the resource database int resourceId() const; /// @return the resource type virtual QPair resourceType() const = 0; /** * Loads all the required resources either from \p globalResourcesInterface or * from embedded data. The preset first tries to fetch the required resource * from the global source, and only if it fails, tries to load it from the * embedded data. One can check if the loaded resource is embedded by checking * its resourceId(). * * The set of resources returned is basically: linkedResources() + embeddedResources() */ QList requiredResources(KisResourcesInterfaceSP globalResourcesInterface) const; /** * @return all the resources that are needed but (*this) resource and * are not embedded into it. The resources are fetched from * \p globalResourcesInterface. If fetching of some resources is failed, * then (*this) resource is invalid. */ virtual QList linkedResources(KisResourcesInterfaceSP globalResourcesInterface) const; /** * @return all the resources that were embedded into (*this) resource. * If the resources were already added to the global database, then they * are fetched from \p globalResourcesInterface to save time/memory. */ virtual QList embeddedResources(KisResourcesInterfaceSP globalResourcesInterface) const; private: + friend class KisResourceCacheDb; friend class KisResourceModel; friend class KisResourceLocator; friend class TestResourceModel; friend class TestResourceLocator; friend class TestFolderStorage; friend class KisFolderStorage; friend class KisBundleStorage; friend class KisStorageVersioningHelper; friend class KisMemoryStorage; void setVersion(int version); void setResourceId(int id); void setStorageLocation(const QString &location); protected: /// override generateMD5 and in your resource subclass virtual QByteArray generateMD5() const; /// call this when the contents of the resource change so the md5 needs to be recalculated void setMD5(const QByteArray &md5); private: struct Private; Private* const d; }; static inline bool operator==(const KoResource &resource1, const KoResource &resource2) { return (resource1.md5() == resource2.md5()); } static inline uint qHash(const KoResource &resource) { return qHash(resource.md5()); } Q_DECLARE_METATYPE(QSharedPointer) inline QDebug operator<<(QDebug dbg, const KoResourceSP res) { if (!res) { dbg.noquote() << "NULL Resource"; } else { dbg.nospace() << "[RESOURCE] Name: " << res->name() << " Version: " << res->version() << " Filename: " << res->filename() << " Valid: " << res->valid() << " Storage: " << res->storageLocation(); } return dbg.space(); } #endif // KORESOURCE_H_ diff --git a/libs/resources/KoResourceServer.h b/libs/resources/KoResourceServer.h index c4a5be28a9..648c79f348 100644 --- a/libs/resources/KoResourceServer.h +++ b/libs/resources/KoResourceServer.h @@ -1,359 +1,359 @@ /* This file is part of the KDE project Copyright (c) 1999 Matthias Elter Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Sven Langkamp Copyright (c) 2007 Jan Hambrecht Copyright (C) 2011 Srikanth Tiyyagura Copyright (c) 2013 Sascha Suelzer Copyright (c) 2003-2019 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KORESOURCESERVER_H #define KORESOURCESERVER_H #include #include #include #include #include #include #include #include #include #include #include "KoResource.h" #include "KoResourceServerObserver.h" #include "KoResourcePaths.h" #include "ksharedconfig.h" #include #include #include #include #include #include class KoResource; /** * KoResourceServer is a shim around KisResourceModel. It knows * nothing by its own, and does nothing on its own. It can only * be used in the gui thread. */ template class KoResourceServer { public: typedef KoResourceServerObserver ObserverType; KoResourceServer(const QString& type) : m_resourceModel(KisResourceModelProvider::resourceModel(type)) , m_tagModel(KisTagModelProvider::tagModel(type)) , m_type(type) { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } } virtual ~KoResourceServer() { Q_FOREACH (ObserverType* observer, m_observers) { observer->unsetResourceServer(); } } /// @return the active resource model KisResourceModel *resourceModel() const { QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } return m_resourceModel; } /// Return the first resource available QSharedPointer firstResource() const { QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } return m_resourceModel->resourceForIndex(m_resourceModel->index(0, 0)).dynamicCast(); } int resourceCount() const { QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); return m_resourceModel->rowCount(); } /// Adds an already loaded resource to the server bool addResource(QSharedPointer resource, bool save = true) { QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } if (!resource->valid()) { warnResource << "Tried to add an invalid resource!"; return false; } if (m_resourceModel->addResource(resource, save ? resource->storageLocation() : "memory")) { notifyResourceAdded(resource); return true; } return false; } /// Remove a resource from Resource Server but not from a file bool removeResourceFromServer(QSharedPointer resource){ QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } - if (m_resourceModel->removeResource(resource)) { + if (m_resourceModel->setResourceInactive(resource)) { notifyRemovingResource(resource); return true; } return false; } QList> resources() { QMutexLocker l(&m_mutex); qDebug() << "KoResourceServer::resources()" << m_type; KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } KIS_SAFE_ASSERT_RECOVER_NOOP(m_type != "paintoppresets"); QList> resourceList; for (int row = 0; row < m_resourceModel->rowCount(); ++row) { resourceList << m_resourceModel->resourceForIndex(m_resourceModel->index(row, 0)).dynamicCast(); } return resourceList; } /// Returns path where to save user defined and imported resources to QString saveLocation() { return KoResourcePaths::saveLocation(m_type.toLatin1()); } /** * Creates a new resource from a given file and adds them to the resource server * The base implementation does only load one resource per file, override to implement collections * @param filename file name of the resource file to be imported * @param fileCreation decides whether to create the file in the saveLocation() directory */ bool importResourceFile(const QString &filename) { QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } return m_resourceModel->importResourceFile(filename); } /// Removes the resource file from the resource server void removeResourceFile(const QString & filename) { QFileInfo fi(filename); QSharedPointer resource = resourceByFilename(fi.fileName()); if (!resource) { warnResource << "Resource file do not exist "; return; } removeResourceFromServer(resource); } /** * Addes an observer to the server * @param observer the observer to be added * @param notifyLoadedResources determines if the observer should be notified about the already loaded resources */ void addObserver(ObserverType* observer) { if (observer && !m_observers.contains(observer)) { m_observers.append(observer); } } /** * Removes an observer from the server * @param observer the observer to be removed */ void removeObserver(ObserverType* observer) { int index = m_observers.indexOf(observer); if (index < 0) { return; } m_observers.removeAt( index ); } QSharedPointer resourceByFilename(const QString& filename) const { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } QMutexLocker l(&m_mutex); - qDebug() << "resourceByFilename" << filename; + //qDebug() << "resourceByFilename" << filename; if (filename.isEmpty() || filename.isNull()) { return 0; } return m_resourceModel->resourceForFilename(filename).dynamicCast(); } QSharedPointer resourceByName(const QString& name) const { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } QMutexLocker l(&m_mutex); - qDebug() << "resourceByName" << name; + //qDebug() << "resourceByName" << name; if (name.isEmpty() || name.isNull()) { return 0; } return m_resourceModel->resourceForName(name).dynamicCast(); } QSharedPointer resourceByMD5(const QByteArray& md5) const { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } QMutexLocker l(&m_mutex); - qDebug() << "resourceByMD5" << md5.toHex(); + //qDebug() << "resourceByMD5" << md5.toHex(); if (md5.isEmpty() || md5.isNull()) { return 0; } return m_resourceModel->resourceForMD5(md5).dynamicCast(); } /** * Call after changing the content of a resource; * Notifies the connected views. */ void updateResource(QSharedPointer resource) { QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } m_resourceModel->updateResource(resource); notifyResourceChanged(resource); } QVector assignedTagsList(KoResourceSP resource) const { if (resource.isNull()) { return QVector(); } return m_resourceModel->tagsForResource(resource->resourceId()); } protected: void notifyResourceAdded(QSharedPointer resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->resourceAdded(resource); } } void notifyRemovingResource(QSharedPointer resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->removingResource(resource); } } void notifyResourceChanged(QSharedPointer resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->resourceChanged(resource); } } private: QList m_observers; KisResourceModel *m_resourceModel {0}; KisTagModel *m_tagModel {0}; QString m_type; mutable QMutex m_mutex; }; #endif // KORESOURCESERVER_H diff --git a/libs/resources/tests/CMakeLists.txt b/libs/resources/tests/CMakeLists.txt index fd18530727..71e4d57b19 100644 --- a/libs/resources/tests/CMakeLists.txt +++ b/libs/resources/tests/CMakeLists.txt @@ -1,26 +1,27 @@ add_definitions(-DFILES_DEST_DIR="${CMAKE_CURRENT_BINARY_DIR}/data/") set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() ecm_add_tests( TestResourceCacheDb TestResourceLoaderRegistry TestResourceLocator TestResourceStorage TestFolderStorage TestMemoryStorage TestTag TestBundleStorage TestResourceModel TestTagFilterResourceProxyModel TestTagModel TestResourceTypeModel TestStorageModel TestResourceSearchBoxFilter + TestStorageFilterProxyModel NAME_PREFIX "libs-kritaresources-" LINK_LIBRARIES kritaglobal kritaplugin kritaresources kritaversion KF5::ConfigCore Qt5::Sql Qt5::Test) diff --git a/libs/resources/tests/TestResourceLocator.cpp b/libs/resources/tests/TestResourceLocator.cpp index 86b636ae0f..b645e6e54e 100644 --- a/libs/resources/tests/TestResourceLocator.cpp +++ b/libs/resources/tests/TestResourceLocator.cpp @@ -1,186 +1,182 @@ /* * Copyright (C) 2017 Boudewijn Rempt * * 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 "TestResourceLocator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing installing resources" #endif #ifndef FILES_DEST_DIR #error "FILES_DEST_DIR not set. A directory where data will be written to for testing installing resources" #endif void TestResourceLocator::initTestCase() { ResourceTestHelper::initTestDb(); m_srcLocation = QString(FILES_DATA_DIR); QVERIFY2(QDir(m_srcLocation).exists(), m_srcLocation.toUtf8()); m_dstLocation = QString(FILES_DEST_DIR); ResourceTestHelper::cleanDstLocation(m_dstLocation); KConfigGroup cfg(KSharedConfig::openConfig(), ""); cfg.writeEntry(KisResourceLocator::resourceLocationKey, m_dstLocation); m_locator = KisResourceLocator::instance(); ResourceTestHelper::createDummyLoaderRegistry(); } void TestResourceLocator::testLocatorInitialization() { KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); KisResourceLocator::LocatorError r = m_locator->initialize(m_srcLocation); if (!m_locator->errorMessages().isEmpty()) qDebug() << m_locator->errorMessages(); QVERIFY(r == KisResourceLocator::LocatorError::Ok); QVERIFY(QDir(m_dstLocation).exists()); Q_FOREACH(const QString &folder, KisResourceLoaderRegistry::instance()->resourceTypes()) { QDir dstDir(m_dstLocation + '/' + folder + '/'); QDir srcDir(m_srcLocation + '/' + folder + '/'); QVERIFY(dstDir.exists()); QVERIFY(dstDir.entryList(QDir::Files | QDir::NoDotAndDotDot) == srcDir.entryList(QDir::Files | QDir::NoDotAndDotDot)); } QFile f(m_dstLocation + '/' + "KRITA_RESOURCE_VERSION"); QVERIFY(f.exists()); f.open(QFile::ReadOnly); QVersionNumber version = QVersionNumber::fromString(QString::fromUtf8(f.readAll())); QVERIFY(version == QVersionNumber::fromString(KritaVersionWrapper::versionString())); } void TestResourceLocator::testStorageInitialization() { Q_FOREACH(KisResourceStorageSP storage, m_locator->storages()) { QVERIFY(KisResourceCacheDb::addStorage(storage, true)); } QSqlQuery query; bool r = query.exec("SELECT COUNT(*) FROM storages"); QVERIFY(r); QVERIFY(query.lastError() == QSqlError()); query.first(); QCOMPARE(query.value(0).toInt(), m_locator->storages().count()); } void TestResourceLocator::testLocatorSynchronization() { QVERIFY(m_locator->synchronizeDb()); { QSqlQuery query; bool r = query.exec("SELECT COUNT(*) FROM resources"); QVERIFY(r); QVERIFY(query.lastError() == QSqlError()); query.first(); QCOMPARE(query.value(0).toInt(), 7); } { QSqlQuery query; bool r = query.exec("SELECT COUNT(*) FROM tags"); QVERIFY(r); QVERIFY(query.lastError() == QSqlError()); query.first(); QCOMPARE(query.value(0).toInt(), 1); } } void TestResourceLocator::testResourceLocationBase() { QCOMPARE(m_locator->resourceLocationBase(), QString(FILES_DEST_DIR)); } void TestResourceLocator::testResource() { KoResourceSP res = m_locator->resource("", ResourceType::PaintOpPresets, "test0.kpp"); QVERIFY(res); } void TestResourceLocator::testResourceForId() { KoResourceSP res = m_locator->resource("", ResourceType::PaintOpPresets, "test0.kpp"); int resourceId = KisResourceCacheDb::resourceIdForResource("test0.kpp", ResourceType::PaintOpPresets, ""); QVERIFY(resourceId > -1); KoResourceSP res2 = m_locator->resourceForId(resourceId); QCOMPARE(res, res2); } -void TestResourceLocator::testStorageContainsResourceByFile() -{ - QVERIFY(m_locator->storageContainsResourceByFile("", "paintoppresets", "test0.kpp") > 0); - QVERIFY(m_locator->storageContainsResourceByFile("", "paintoppresets", "XSLKDJSADLKSAJDA") == 0); -} - void TestResourceLocator::testDocumentStorage() { const QString &documentName("document"); KisResourceModel *model = KisResourceModelProvider::resourceModel(ResourceType::PaintOpPresets); int rowcount = model->rowCount(); KisResourceStorageSP documentStorage = QSharedPointer::create(documentName); KoResourceSP resource(new DummyResource("test")); documentStorage->addResource(resource); m_locator->addStorage(documentName, documentStorage); QVERIFY(model->rowCount() > rowcount); QVERIFY(m_locator->hasStorage(documentName)); + m_locator->removeStorage(documentName); QVERIFY(!m_locator->hasStorage(documentName)); + QVERIFY(model->rowCount() == rowcount); } void TestResourceLocator::cleanupTestCase() { //ResourceTestHelper::rmTestDb(); ResourceTestHelper::cleanDstLocation(m_dstLocation); } QTEST_MAIN(TestResourceLocator) diff --git a/libs/resources/tests/TestResourceLocator.h b/libs/resources/tests/TestResourceLocator.h index 115a3d7966..d1a525a136 100644 --- a/libs/resources/tests/TestResourceLocator.h +++ b/libs/resources/tests/TestResourceLocator.h @@ -1,53 +1,52 @@ /* * Copyright (C) 2017 Boudewijn Rempt * * 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 TESTRESOURCELOCATOR_H #define TESTRESOURCELOCATOR_H #include class KisResourceLocator; class TestResourceLocator : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testLocatorInitialization(); void testStorageInitialization(); void testLocatorSynchronization(); void testResourceLocationBase(); void testResource(); void testResourceForId(); - void testStorageContainsResourceByFile(); void testDocumentStorage(); void cleanupTestCase(); private: QString m_srcLocation; QString m_dstLocation; KisResourceLocator *m_locator; }; #endif diff --git a/libs/resources/tests/TestResourceModel.cpp b/libs/resources/tests/TestResourceModel.cpp index 8bad9113cc..22cfabd05b 100644 --- a/libs/resources/tests/TestResourceModel.cpp +++ b/libs/resources/tests/TestResourceModel.cpp @@ -1,307 +1,307 @@ /* * Copyright (c) 2018 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestResourceModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing installing resources" #endif #ifndef FILES_DEST_DIR #error "FILES_DEST_DIR not set. A directory where data will be written to for testing installing resources" #endif void TestResourceModel::initTestCase() { ResourceTestHelper::initTestDb(); ResourceTestHelper::createDummyLoaderRegistry(); m_srcLocation = QString(FILES_DATA_DIR); QVERIFY2(QDir(m_srcLocation).exists(), m_srcLocation.toUtf8()); m_dstLocation = QString(FILES_DEST_DIR); ResourceTestHelper::cleanDstLocation(m_dstLocation); KConfigGroup cfg(KSharedConfig::openConfig(), ""); cfg.writeEntry(KisResourceLocator::resourceLocationKey, m_dstLocation); m_locator = KisResourceLocator::instance(); if (!KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) { qWarning() << "Could not initialize KisResourceCacheDb on" << QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); } QVERIFY(KisResourceCacheDb::isValid()); KisResourceLocator::LocatorError r = m_locator->initialize(m_srcLocation); if (!m_locator->errorMessages().isEmpty()) { qDebug() << m_locator->errorMessages(); } QVERIFY(r == KisResourceLocator::LocatorError::Ok); QVERIFY(QDir(m_dstLocation).exists()); } void TestResourceModel::testRowCount() { QSqlQuery q; QVERIFY(q.prepare("SELECT count(*)\n" "FROM resources\n" ", resource_types\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type")); q.bindValue(":resource_type", m_resourceType); QVERIFY(q.exec()); q.first(); int rowCount = q.value(0).toInt(); QVERIFY(rowCount == 3); KisResourceModel resourceModel(m_resourceType); QCOMPARE(resourceModel.rowCount(), rowCount); } void TestResourceModel::testData() { KisResourceModel resourceModel(m_resourceType); QStringList resourceNames; for (int i = 0; i < resourceModel.rowCount(); ++i) { - QVariant v = resourceModel.data(resourceModel.index(i, KisResourceModel::Name), Qt::DisplayRole); + QVariant v = resourceModel.data(resourceModel.index(i, KisAbstractResourceModel::Name), Qt::DisplayRole); resourceNames << v.toString(); } QVERIFY(resourceNames.contains("test0.kpp")); QVERIFY(resourceNames.contains("test1.kpp")); QVERIFY(resourceNames.contains("test2.kpp")); } void TestResourceModel::testResourceForIndex() { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(0, 0)); QVERIFY(resource); QVERIFY(resource->resourceId() > -1); } void TestResourceModel::testIndexFromResource() { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(1, 0)); QModelIndex idx = resourceModel.indexFromResource(resource); QVERIFY(idx.row() == 1); QVERIFY(idx.column() == 0); } void TestResourceModel::testRemoveResourceByIndex() { KisResourceModel resourceModel(m_resourceType); int resourceCount = resourceModel.rowCount(); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(1, 0)); - bool r = resourceModel.removeResource(resourceModel.index(1, 0)); + bool r = resourceModel.setResourceInactive(resourceModel.index(1, 0)); QVERIFY(r); QCOMPARE(resourceCount - 1, resourceModel.rowCount()); QVERIFY(!resourceModel.indexFromResource(resource).isValid()); } void TestResourceModel::testRemoveResource() { KisResourceModel resourceModel(m_resourceType); int resourceCount = resourceModel.rowCount(); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(1, 0)); QVERIFY(resource); - bool r = resourceModel.removeResource(resource); + bool r = resourceModel.setResourceInactive(resource); QVERIFY(r); QCOMPARE(resourceCount - 1, resourceModel.rowCount()); QVERIFY(!resourceModel.indexFromResource(resource).isValid()); } void TestResourceModel::testImportResourceFile() { KisResourceModel resourceModel(m_resourceType); QTemporaryFile f(QDir::tempPath() + "/testresourcemodel-testimportresourcefile-XXXXXX.kpp"); f.open(); f.write("0"); f.close(); int resourceCount = resourceModel.rowCount(); bool r = resourceModel.importResourceFile(f.fileName()); QVERIFY(r); QCOMPARE(resourceCount + 1, resourceModel.rowCount()); } void TestResourceModel::testAddResource() { KisResourceModel resourceModel(m_resourceType); int resourceCount = resourceModel.rowCount(); KoResourceSP resource(new DummyResource("dummy.kpp")); resource->setValid(true); bool r = resourceModel.addResource(resource); QVERIFY(r); QCOMPARE(resourceCount + 1, resourceModel.rowCount()); } void TestResourceModel::testAddTemporaryResource() { KisResourceModel resourceModel(m_resourceType); int resourceCount = resourceModel.rowCount(); KoResourceSP resource(new DummyResource("dummy.kpp")); resource->setValid(true); bool r = resourceModel.addResource(resource, "memory"); QVERIFY(r); QCOMPARE(resourceCount + 1, resourceModel.rowCount()); } void TestResourceModel::testUpdateResource() { int resourceId; { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(0, 0)); QVERIFY(resource); QVERIFY(resource.dynamicCast()->something().isEmpty()); resource.dynamicCast()->setSomething("It's changed"); resourceId = resource->resourceId(); bool r = resourceModel.updateResource(resource); QVERIFY(r); } { // Check the resource itself KisResourceLocator::instance()->purge(); KoResourceSP resource = KisResourceLocator::instance()->resourceForId(resourceId); QVERIFY(resource); QVERIFY(!resource.dynamicCast()->something().isEmpty()); QVERIFY(resource->resourceId() == resourceId); // Check the versions in the database QSqlQuery q; QVERIFY(q.prepare("SELECT count(*)\n" "FROM versioned_resources\n" "WHERE resource_id = :resource_id\n")); q.bindValue(":resource_id", resourceId); QVERIFY(q.exec()); q.first(); int rowCount = q.value(0).toInt(); QCOMPARE(rowCount, 2); } } void TestResourceModel::testResourceForId() { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(0, 0)); QVERIFY(!resource.isNull()); KoResourceSP resource2 = resourceModel.resourceForId(resource->resourceId()); QVERIFY(!resource2.isNull()); QCOMPARE(resource, resource2); } void TestResourceModel::testResourceForName() { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(0, 0)); QVERIFY(!resource.isNull()); KoResourceSP resource2 = resourceModel.resourceForName(resource->name()); QVERIFY(!resource2.isNull()); QCOMPARE(resource, resource2); } void TestResourceModel::testResourceForFileName() { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(0, 0)); QVERIFY(!resource.isNull()); KoResourceSP resource2 = resourceModel.resourceForFilename(resource->filename()); QVERIFY(!resource2.isNull()); QCOMPARE(resource, resource2); } void TestResourceModel::testResourceForMD5() { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(0, 0)); QVERIFY(!resource.isNull()); KoResourceSP resource2 = resourceModel.resourceForMD5(resource->md5()); QVERIFY(!resource2.isNull()); QCOMPARE(resource->md5(), resource2->md5()); } void TestResourceModel::testRenameResource() { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(1, 0)); QVERIFY(!resource.isNull()); const QString name = resource->name(); bool r = resourceModel.renameResource(resource, "A New Name"); QVERIFY(r); QSqlQuery q; if (!q.prepare("SELECT name\n" "FROM resources\n" "WHERE id = :resource_id\n")) { qWarning() << "Could not prepare testRenameResource Query" << q.lastError(); } q.bindValue(":resource_id", resource->resourceId()); if (!q.exec()) { qWarning() << "Could not execute testRenameResource Query" << q.lastError(); } q.first(); QString newName = q.value(0).toString(); QVERIFY(name != newName); QCOMPARE("A New Name", newName); } void TestResourceModel::cleanupTestCase() { ResourceTestHelper::rmTestDb(); ResourceTestHelper::cleanDstLocation(m_dstLocation); } QTEST_MAIN(TestResourceModel) diff --git a/libs/resources/tests/TestTagFilterResourceProxyModel.cpp b/libs/resources/tests/TestStorageFilterProxyModel.cpp similarity index 57% copy from libs/resources/tests/TestTagFilterResourceProxyModel.cpp copy to libs/resources/tests/TestStorageFilterProxyModel.cpp index 8cbb7bebcd..f6a156f01b 100644 --- a/libs/resources/tests/TestTagFilterResourceProxyModel.cpp +++ b/libs/resources/tests/TestStorageFilterProxyModel.cpp @@ -1,136 +1,120 @@ /* * Copyright (c) 2019 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "TestTagFilterResourceProxyModel.h" +#include "TestStorageFilterProxyModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include +#include + + #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing installing resources" #endif #ifndef FILES_DEST_DIR #error "FILES_DEST_DIR not set. A directory where data will be written to for testing installing resources" #endif -void TestTagFilterResourceProxyModel::initTestCase() +void TestStorageFilterProxyModel::initTestCase() { ResourceTestHelper::initTestDb(); ResourceTestHelper::createDummyLoaderRegistry(); m_srcLocation = QString(FILES_DATA_DIR); QVERIFY2(QDir(m_srcLocation).exists(), m_srcLocation.toUtf8()); m_dstLocation = QString(FILES_DEST_DIR); ResourceTestHelper::cleanDstLocation(m_dstLocation); KConfigGroup cfg(KSharedConfig::openConfig(), ""); cfg.writeEntry(KisResourceLocator::resourceLocationKey, m_dstLocation); m_locator = KisResourceLocator::instance(); if (!KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) { qDebug() << "Could not initialize KisResourceCacheDb on" << QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); } QVERIFY(KisResourceCacheDb::isValid()); KisResourceLocator::LocatorError r = m_locator->initialize(m_srcLocation); if (!m_locator->errorMessages().isEmpty()) qDebug() << m_locator->errorMessages(); QVERIFY(r == KisResourceLocator::LocatorError::Ok); QVERIFY(QDir(m_dstLocation).exists()); } -void TestTagFilterResourceProxyModel::testRowCount() +void TestStorageFilterProxyModel::testFilterByName() { - QSqlQuery q; - QVERIFY(q.prepare("SELECT count(*)\n" - "FROM resources\n" - ", resource_types\n" - "WHERE resources.resource_type_id = resource_types.id\n" - "AND resource_types.name = :resource_type")); - q.bindValue(":resource_type", resourceType); - QVERIFY(q.exec()); - q.first(); - int rowCount = q.value(0).toInt(); - QVERIFY(rowCount == 3); - - KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(resourceType); - KisTagFilterResourceProxyModel proxyModel; - proxyModel.setSourceModel(resourceModel); - - QCOMPARE(proxyModel.rowCount(), rowCount); + QScopedPointer proxyModel(new KisStorageFilterProxyModel()); + proxyModel->setSourceModel(KisStorageModel::instance()); + + QString fileName = "test1"; + + proxyModel->setFilter(KisStorageFilterProxyModel::ByStorageType, fileName); + } -void TestTagFilterResourceProxyModel::testData() +void TestStorageFilterProxyModel::testFilterByType() { - KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(resourceType); - KisTagFilterResourceProxyModel proxyModel; - proxyModel.setSourceModel(resourceModel); - - QStringList names = QStringList() << "test0.kpp" - << "test1.kpp" - << "test2.kpp"; - for (int i = 0; i < proxyModel.rowCount(); ++i) { - QVariant v = resourceModel->data(proxyModel.mapToSource(proxyModel.index(i, 0)), Qt::UserRole + KisResourceModel::Name); - QVERIFY(names.contains(v.toString())); - } -} + QScopedPointer proxyModel(new KisStorageFilterProxyModel()); + proxyModel->setSourceModel(KisStorageModel::instance()); + proxyModel->setFilter(KisStorageFilterProxyModel::ByStorageType, + QStringList() + << KisResourceStorage::storageTypeToUntranslatedString(KisResourceStorage::StorageType::Bundle) + << KisResourceStorage::storageTypeToUntranslatedString(KisResourceStorage::StorageType::Folder)); +} -void TestTagFilterResourceProxyModel::testResource() +void TestStorageFilterProxyModel::testFilterByActive() { - KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(resourceType); - KisTagFilterResourceProxyModel proxyModel; - proxyModel.setSourceModel(resourceModel); - - KoResourceSP resource = resourceModel->resourceForIndex(proxyModel.mapToSource(proxyModel.index(0, 0))); - QVERIFY(resource); + QScopedPointer proxyModel(new KisStorageFilterProxyModel()); + proxyModel->setSourceModel(KisStorageModel::instance()); + proxyModel->setFilter(KisStorageFilterProxyModel::ByStorageType, true); } -void TestTagFilterResourceProxyModel::cleanupTestCase() +void TestStorageFilterProxyModel::cleanupTestCase() { ResourceTestHelper::rmTestDb(); ResourceTestHelper::cleanDstLocation(m_dstLocation); } -QTEST_MAIN(TestTagFilterResourceProxyModel) +QTEST_MAIN(TestStorageFilterProxyModel) diff --git a/libs/resources/tests/TestStorageFilterProxyModel.h b/libs/resources/tests/TestStorageFilterProxyModel.h new file mode 100644 index 0000000000..c5b5c4dada --- /dev/null +++ b/libs/resources/tests/TestStorageFilterProxyModel.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 boud + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef TESTAGFILTERTRESOURCEPROXYMODEL_H +#define TESTAGFILTERTRESOURCEPROXYMODEL_H + +#include +#include + +class KisResourceLocator; + +class TestStorageFilterProxyModel : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void testFilterByName(); + void testFilterByType(); + void testFilterByActive(); + void cleanupTestCase(); +private: + + QString m_srcLocation; + QString m_dstLocation; + + KisResourceLocator *m_locator; + +}; + +#endif diff --git a/libs/resources/tests/TestStorageModel.cpp b/libs/resources/tests/TestStorageModel.cpp index da12267ea3..5de19c159f 100644 --- a/libs/resources/tests/TestStorageModel.cpp +++ b/libs/resources/tests/TestStorageModel.cpp @@ -1,155 +1,158 @@ /* * Copyright (c) 2018 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestStorageModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing installing resources" #endif #ifndef FILES_DEST_DIR #error "FILES_DEST_DIR not set. A directory where data will be written to for testing installing resources" #endif void TestStorageModel::initTestCase() { ResourceTestHelper::initTestDb(); ResourceTestHelper::createDummyLoaderRegistry(); m_srcLocation = QString(FILES_DATA_DIR); QVERIFY2(QDir(m_srcLocation).exists(), m_srcLocation.toUtf8()); m_dstLocation = QString(FILES_DEST_DIR); ResourceTestHelper::cleanDstLocation(m_dstLocation); KConfigGroup cfg(KSharedConfig::openConfig(), ""); cfg.writeEntry(KisResourceLocator::resourceLocationKey, m_dstLocation); m_locator = KisResourceLocator::instance(); if (!KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) { qDebug() << "Could not initialize KisResourceCacheDb on" << QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); } QVERIFY(KisResourceCacheDb::isValid()); KisResourceLocator::LocatorError r = m_locator->initialize(m_srcLocation); if (!m_locator->errorMessages().isEmpty()) qDebug() << m_locator->errorMessages(); QVERIFY(r == KisResourceLocator::LocatorError::Ok); QVERIFY(QDir(m_dstLocation).exists()); } void TestStorageModel::testRowCount() { QSqlQuery q; QVERIFY(q.prepare("SELECT count(*)\n" "FROM storages")); QVERIFY(q.exec()); q.first(); int rowCount = q.value(0).toInt(); KisStorageModel storageModel; QCOMPARE(storageModel.rowCount(), rowCount); } void TestStorageModel::testSetActive() { KisStorageModel storageModel; + for (int i = 0; i < storageModel.rowCount(); ++i) { + QModelIndex idx = storageModel.index(i, 0); storageModel.setData(idx, QVariant(true), Qt::CheckStateRole); + + idx = storageModel.index(i, 0); QVERIFY(idx.data(Qt::UserRole + KisStorageModel::Active).toBool() == true); storageModel.setData(idx, QVariant(false), Qt::CheckStateRole); idx = storageModel.index(i, 0); QVERIFY(idx.data(Qt::UserRole + KisStorageModel::Active).toBool() == false); } } void TestStorageModel::cleanupTestCase() { ResourceTestHelper::rmTestDb(); ResourceTestHelper::cleanDstLocation(m_dstLocation); } void TestStorageModel::testMetaData() { KisStorageModel storageModel; int rowCount = storageModel.rowCount(); KisResourceStorageSP storage {new KisResourceStorage("My Named Memory Storage")}; KisResourceLocator::instance()->addStorage("My Named Memory Storage", storage); storage->setMetaData(KisResourceStorage::s_meta_name, "My Named Memory Storage"); - storageModel.resetQuery(); QVERIFY(storageModel.rowCount() > rowCount); QModelIndex idx; for (int row = 0; row < storageModel.rowCount(); ++row) { idx = storageModel.index(row, 7); KisResourceStorageSP st = storageModel.storageForIndex(idx); QVERIFY(st); if (st == storage) { break; } } QVERIFY(idx.isValid()); QString displayName = storageModel.data(idx, Qt::DisplayRole).toString(); QCOMPARE("My Named Memory Storage", displayName); idx = storageModel.index(idx.row(), 0); QMap metadata = storageModel.data(idx, Qt::UserRole + KisStorageModel::MetaData).toMap(); QVERIFY(metadata.contains(KisResourceStorage::s_meta_name)); QVERIFY(metadata[KisResourceStorage::s_meta_name] == "My Named Memory Storage"); } QTEST_MAIN(TestStorageModel) diff --git a/libs/resources/tests/TestTagFilterResourceProxyModel.cpp b/libs/resources/tests/TestTagFilterResourceProxyModel.cpp index 8cbb7bebcd..906a92db9f 100644 --- a/libs/resources/tests/TestTagFilterResourceProxyModel.cpp +++ b/libs/resources/tests/TestTagFilterResourceProxyModel.cpp @@ -1,136 +1,136 @@ /* * Copyright (c) 2019 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestTagFilterResourceProxyModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing installing resources" #endif #ifndef FILES_DEST_DIR #error "FILES_DEST_DIR not set. A directory where data will be written to for testing installing resources" #endif void TestTagFilterResourceProxyModel::initTestCase() { ResourceTestHelper::initTestDb(); ResourceTestHelper::createDummyLoaderRegistry(); m_srcLocation = QString(FILES_DATA_DIR); QVERIFY2(QDir(m_srcLocation).exists(), m_srcLocation.toUtf8()); m_dstLocation = QString(FILES_DEST_DIR); ResourceTestHelper::cleanDstLocation(m_dstLocation); KConfigGroup cfg(KSharedConfig::openConfig(), ""); cfg.writeEntry(KisResourceLocator::resourceLocationKey, m_dstLocation); m_locator = KisResourceLocator::instance(); if (!KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) { qDebug() << "Could not initialize KisResourceCacheDb on" << QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); } QVERIFY(KisResourceCacheDb::isValid()); KisResourceLocator::LocatorError r = m_locator->initialize(m_srcLocation); if (!m_locator->errorMessages().isEmpty()) qDebug() << m_locator->errorMessages(); QVERIFY(r == KisResourceLocator::LocatorError::Ok); QVERIFY(QDir(m_dstLocation).exists()); } void TestTagFilterResourceProxyModel::testRowCount() { QSqlQuery q; QVERIFY(q.prepare("SELECT count(*)\n" "FROM resources\n" ", resource_types\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type")); q.bindValue(":resource_type", resourceType); QVERIFY(q.exec()); q.first(); int rowCount = q.value(0).toInt(); QVERIFY(rowCount == 3); KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(resourceType); KisTagFilterResourceProxyModel proxyModel; proxyModel.setSourceModel(resourceModel); QCOMPARE(proxyModel.rowCount(), rowCount); } void TestTagFilterResourceProxyModel::testData() { KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(resourceType); KisTagFilterResourceProxyModel proxyModel; proxyModel.setSourceModel(resourceModel); QStringList names = QStringList() << "test0.kpp" << "test1.kpp" << "test2.kpp"; for (int i = 0; i < proxyModel.rowCount(); ++i) { - QVariant v = resourceModel->data(proxyModel.mapToSource(proxyModel.index(i, 0)), Qt::UserRole + KisResourceModel::Name); + QVariant v = resourceModel->data(proxyModel.mapToSource(proxyModel.index(i, 0)), Qt::UserRole + KisAbstractResourceModel::Name); QVERIFY(names.contains(v.toString())); } } void TestTagFilterResourceProxyModel::testResource() { KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(resourceType); KisTagFilterResourceProxyModel proxyModel; proxyModel.setSourceModel(resourceModel); KoResourceSP resource = resourceModel->resourceForIndex(proxyModel.mapToSource(proxyModel.index(0, 0))); QVERIFY(resource); } void TestTagFilterResourceProxyModel::cleanupTestCase() { ResourceTestHelper::rmTestDb(); ResourceTestHelper::cleanDstLocation(m_dstLocation); } QTEST_MAIN(TestTagFilterResourceProxyModel) diff --git a/libs/resourcewidgets/KisIconToolTip.cpp b/libs/resourcewidgets/KisIconToolTip.cpp index f3d319ee24..93a7e33c26 100644 --- a/libs/resourcewidgets/KisIconToolTip.cpp +++ b/libs/resourcewidgets/KisIconToolTip.cpp @@ -1,76 +1,76 @@ /* This file is part of the KDE project * Copyright (c) 1999 Carsten Pfeiffer (pfeiffer@kde.org) * Copyright (c) 2002 Igor Jansen (rm@kde.org) * Copyright (c) 2018 Boudewijn Rempt * * 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 "KisIconToolTip.h" #include #include #include #include #include KisIconToolTip::KisIconToolTip() { } KisIconToolTip::~KisIconToolTip() { } QTextDocument *KisIconToolTip::createDocument(const QModelIndex &index) { QTextDocument *doc = new QTextDocument(this); QImage thumb = index.data(Qt::DecorationRole).value(); if (thumb.isNull()) { - thumb = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); + thumb = index.data(Qt::UserRole + KisAbstractResourceModel::Thumbnail).value(); } doc->addResource(QTextDocument::ImageResource, QUrl("data:thumbnail"), thumb); QString name = index.data(Qt::DisplayRole).toString(); - QString presetDisplayName = index.data(Qt::UserRole + KisResourceModel::Name).toString().replace("_", " "); + QString presetDisplayName = index.data(Qt::UserRole + KisAbstractResourceModel::Name).toString().replace("_", " "); //This is to ensure that the other uses of this class don't get an empty string, while resource management should get a nice string. if (!presetDisplayName.isEmpty()) { name = presetDisplayName; } QString tags; - QString tagsData = index.data(Qt::UserRole + KisResourceModel::Tags).toStringList().join(", "); + QString tagsData = index.data(Qt::UserRole + KisAbstractResourceModel::Tags).toStringList().join(", "); if (tagsData.length() > 0) { const QString list = QString("
    %1
").arg(tagsData); tags = QString("

%1:%2

").arg(i18n("Tags"), list); } const QString image = QString("
"); const QString body = QString("

%1

%2%3").arg(name, image, tags); const QString html = QString("%1").arg(body); doc->setHtml(html); const int margin = 16; doc->setTextWidth(qMin(doc->size().width() + 2 * margin, qreal(500.0))); doc->setDocumentMargin(margin); doc->setUseDesignMetrics(true); return doc; } diff --git a/libs/resourcewidgets/KisResourceItemChooser.cpp b/libs/resourcewidgets/KisResourceItemChooser.cpp index 7c7ec5920a..b0ea3ed2e8 100644 --- a/libs/resourcewidgets/KisResourceItemChooser.cpp +++ b/libs/resourcewidgets/KisResourceItemChooser.cpp @@ -1,548 +1,529 @@ /* This file is part of the KDE project Copyright (c) 2002 Patrick Julien Copyright (c) 2007 Jan Hambrecht Copyright (c) 2007 Sven Langkamp Copyright (C) 2011 Srikanth Tiyyagura Copyright (c) 2011 José Luis Vergara Copyright (c) 2013 Sascha Suelzer 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 "KisResourceItemChooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceItemListView.h" #include "KisResourceItemDelegate.h" #include "KisTagFilterWidget.h" #include "KisTagChooserWidget.h" #include "KisResourceItemChooserSync.h" #include "KisResourceTaggingManager.h" #include "KisTagModelProvider.h" #include "KisStorageChooserWidget.h" #include "kis_assert.h" class Q_DECL_HIDDEN KisResourceItemChooser::Private { public: Private(QString _resourceType) : resourceType(_resourceType) {} QString resourceType; KisResourceModel *resourceModel {0}; KisTagFilterResourceProxyModel *tagFilterProxyModel {0}; QSortFilterProxyModel *extraFilterModel {0}; KisResourceTaggingManager *tagManager {0}; KisResourceItemListView *view {0}; QButtonGroup *buttonGroup {0}; QToolButton *viewModeButton {0}; KisStorageChooserWidget *storagePopupButton {0}; QScrollArea *previewScroller {0}; QLabel *previewLabel {0}; QSplitter *splitter {0}; QGridLayout *buttonLayout {0}; QPushButton *importButton {0}; QPushButton *deleteButton {0}; bool usePreview {false}; bool tiledPreview {false}; bool grayscalePreview {false}; bool synced {false}; bool updatesBlocked {false}; QModelIndex savedResourceWhileReset; // Indexes on the proxyModel, not the source resource model QList customButtons; }; KisResourceItemChooser::KisResourceItemChooser(const QString &resourceType, bool usePreview, QWidget *parent, QSortFilterProxyModel *extraFilterProxy) : QWidget(parent) , d(new Private(resourceType)) { d->splitter = new QSplitter(this); d->resourceModel = KisResourceModelProvider::resourceModel(resourceType); d->tagFilterProxyModel = new KisTagFilterResourceProxyModel(KisTagModelProvider::tagModel(resourceType), this); d->extraFilterModel = extraFilterProxy; if (d->extraFilterModel) { d->extraFilterModel->setParent(this); d->extraFilterModel->setSourceModel(d->resourceModel); d->tagFilterProxyModel->setSourceModel(d->extraFilterModel); } else { d->tagFilterProxyModel->setSourceModel(d->resourceModel); } - connect(d->resourceModel, SIGNAL(beforeResourcesLayoutReset(QModelIndex)), SLOT(slotBeforeResourcesLayoutReset(QModelIndex))); - connect(d->resourceModel, SIGNAL(afterResourcesLayoutReset()), SLOT(slotAfterResourcesLayoutReset())); - d->view = new KisResourceItemListView(this); d->view->setObjectName("ResourceItemview"); d->view->setModel(d->tagFilterProxyModel); d->view->setItemDelegate(new KisResourceItemDelegate(this)); d->view->setSelectionMode(QAbstractItemView::SingleSelection); d->view->viewport()->installEventFilter(this); connect(d->view, SIGNAL(currentResourceChanged(QModelIndex)), this, SLOT(activated(QModelIndex))); connect(d->view, SIGNAL(currentResourceClicked(QModelIndex)), this, SLOT(clicked(QModelIndex))); connect(d->view, SIGNAL(contextMenuRequested(QPoint)), this, SLOT(contextMenuRequested(QPoint))); connect(d->view, SIGNAL(sigSizeChanged()), this, SLOT(updateView())); d->splitter->addWidget(d->view); d->splitter->setStretchFactor(0, 2); d->usePreview = usePreview; if (d->usePreview) { d->previewScroller = new QScrollArea(this); d->previewScroller->setWidgetResizable(true); d->previewScroller->setBackgroundRole(QPalette::Dark); d->previewScroller->setVisible(true); d->previewScroller->setAlignment(Qt::AlignCenter); d->previewLabel = new QLabel(this); d->previewScroller->setWidget(d->previewLabel); d->splitter->addWidget(d->previewScroller); if (d->splitter->count() == 2) { d->splitter->setSizes(QList() << 280 << 160); } QScroller* scroller = KisKineticScroller::createPreconfiguredScroller(d->previewScroller); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } } d->splitter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); connect(d->splitter, SIGNAL(splitterMoved(int,int)), SIGNAL(splitterMoved())); d->buttonGroup = new QButtonGroup(this); d->buttonGroup->setExclusive(false); QGridLayout *layout = new QGridLayout(this); d->buttonLayout = new QGridLayout(); d->importButton = new QPushButton(this); d->importButton->setToolTip(i18nc("@info:tooltip", "Import resource")); d->importButton->setEnabled(true); d->buttonGroup->addButton(d->importButton, Button_Import); d->buttonLayout->addWidget(d->importButton, 0, 0); d->deleteButton = new QPushButton(this); d->deleteButton->setToolTip(i18nc("@info:tooltip", "Delete resource")); d->deleteButton->setEnabled(false); d->buttonGroup->addButton(d->deleteButton, Button_Remove); d->buttonLayout->addWidget(d->deleteButton, 0, 1); connect(d->buttonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotButtonClicked(int))); d->buttonLayout->setColumnStretch(0, 1); d->buttonLayout->setColumnStretch(1, 1); d->buttonLayout->setColumnStretch(2, 2); d->buttonLayout->setSpacing(0); d->buttonLayout->setMargin(0); d->viewModeButton = new QToolButton(this); d->viewModeButton->setPopupMode(QToolButton::InstantPopup); d->viewModeButton->setVisible(false); d->tagManager = new KisResourceTaggingManager(resourceType, d->tagFilterProxyModel, this); d->storagePopupButton = new KisStorageChooserWidget(this); layout->addWidget(d->tagManager->tagChooserWidget(), 0, 0); layout->addWidget(d->viewModeButton, 0, 1); layout->addWidget(d->storagePopupButton, 0, 2); layout->addWidget(d->splitter, 1, 0, 1, 3); layout->addWidget(d->tagManager->tagFilterWidget(), 2, 0, 1, 3); layout->addLayout(d->buttonLayout, 3, 0, 1, 3); layout->setMargin(0); layout->setSpacing(0); updateView(); updateButtonState(); showTaggingBar(false); activated(d->view->model()->index(0, 0)); } KisResourceItemChooser::~KisResourceItemChooser() { disconnect(); delete d; } void KisResourceItemChooser::slotButtonClicked(int button) { if (button == Button_Import) { QStringList mimeTypes = KisResourceLoaderRegistry::instance()->mimeTypes(d->resourceType); KoFileDialog dialog(0, KoFileDialog::OpenFiles, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); Q_FOREACH(const QString &filename, dialog.filenames()) { if (QFileInfo(filename).exists() && QFileInfo(filename).isReadable()) { d->tagFilterProxyModel->importResourceFile(filename); } } } else if (button == Button_Remove) { QModelIndex index = d->view->currentIndex(); if (index.isValid()) { - d->tagFilterProxyModel->removeResource(index); + d->tagFilterProxyModel->setResourceInactive(index); } int row = index.row(); int rowMin = --row; row = qBound(0, rowMin, row); setCurrentItem(row); activated(d->tagFilterProxyModel->index(row, index.column())); } updateButtonState(); } void KisResourceItemChooser::showButtons(bool show) { foreach (QAbstractButton * button, d->buttonGroup->buttons()) { show ? button->show() : button->hide(); } Q_FOREACH (QAbstractButton *button, d->customButtons) { show ? button->show() : button->hide(); } } void KisResourceItemChooser::addCustomButton(QAbstractButton *button, int cell) { d->buttonLayout->addWidget(button, 0, cell); d->buttonLayout->setColumnStretch(2, 1); d->buttonLayout->setColumnStretch(3, 1); } void KisResourceItemChooser::showTaggingBar(bool show) { d->tagManager->showTaggingBar(show); } int KisResourceItemChooser::rowCount() const { return d->view->model()->rowCount(); } void KisResourceItemChooser::setRowHeight(int rowHeight) { d->view->setItemSize(QSize(d->view->gridSize().width(), rowHeight)); } void KisResourceItemChooser::setColumnWidth(int columnWidth) { d->view->setItemSize(QSize(columnWidth, d->view->gridSize().height())); } void KisResourceItemChooser::setItemDelegate(QAbstractItemDelegate *delegate) { d->view->setItemDelegate(delegate); } KoResourceSP KisResourceItemChooser::currentResource() const { QModelIndex index = d->view->currentIndex(); if (index.isValid()) { return resourceFromModelIndex(index); } return 0; } void KisResourceItemChooser::setCurrentResource(KoResourceSP resource) { // don't update if the change came from the same chooser if (d->updatesBlocked) { return; } QModelIndex index = d->resourceModel->indexFromResource(resource); d->view->setCurrentIndex(index); updatePreview(index); } -void KisResourceItemChooser::slotBeforeResourcesLayoutReset(QModelIndex activateAfterReset) -{ - QModelIndex proxyIndex = d->tagFilterProxyModel->mapFromSource(d->tagFilterProxyModel->mapFromSource(activateAfterReset)); - d->savedResourceWhileReset = proxyIndex.isValid() ? proxyIndex : d->view->currentIndex(); -} - -void KisResourceItemChooser::slotAfterResourcesLayoutReset() -{ - if (d->savedResourceWhileReset.isValid()) { - this->blockSignals(true); - setCurrentItem(d->savedResourceWhileReset.row()); - this->blockSignals(false); - } - d->savedResourceWhileReset = QModelIndex(); -} - void KisResourceItemChooser::setPreviewOrientation(Qt::Orientation orientation) { d->splitter->setOrientation(orientation); } void KisResourceItemChooser::setPreviewTiled(bool tiled) { d->tiledPreview = tiled; } void KisResourceItemChooser::setGrayscalePreview(bool grayscale) { d->grayscalePreview = grayscale; } void KisResourceItemChooser::setCurrentItem(int row) { QModelIndex index = d->view->model()->index(row, 0); if (!index.isValid()) return; d->view->setCurrentIndex(index); if (index.isValid()) { updatePreview(index); } } void KisResourceItemChooser::activated(const QModelIndex &index) { if (!index.isValid()) return; KoResourceSP resource = 0; if (index.isValid()) { resource = resourceFromModelIndex(index); } if (resource && resource->valid()) { d->updatesBlocked = true; emit resourceSelected(resource); d->updatesBlocked = false; updatePreview(index); updateButtonState(); } } void KisResourceItemChooser::clicked(const QModelIndex &index) { Q_UNUSED(index); KoResourceSP resource = currentResource(); if (resource) { emit resourceClicked(resource); } } void KisResourceItemChooser::updateButtonState() { QAbstractButton *removeButton = d->buttonGroup->button(Button_Remove); if (! removeButton) return; KoResourceSP resource = currentResource(); if (resource) { removeButton->setEnabled(!resource->permanent()); return; } removeButton->setEnabled(false); } void KisResourceItemChooser::updatePreview(const QModelIndex &idx) { if (!d->usePreview) return; if (!idx.isValid()) { d->previewLabel->setPixmap(QPixmap()); return; } - QImage image = idx.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); + QImage image = idx.data(Qt::UserRole + KisAbstractResourceModel::Thumbnail).value(); if (image.format() != QImage::Format_RGB32 && image.format() != QImage::Format_ARGB32 && image.format() != QImage::Format_ARGB32_Premultiplied) { image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); } if (d->tiledPreview) { int width = d->previewScroller->width() * 4; int height = d->previewScroller->height() * 4; QImage img(width, height, image.format()); QPainter gc(&img); gc.fillRect(img.rect(), Qt::white); gc.setPen(Qt::NoPen); gc.setBrush(QBrush(image)); gc.drawRect(img.rect()); image = img; } // Only convert to grayscale if it is rgb. Otherwise, it's gray already. if (d->grayscalePreview && !image.isGrayscale()) { QRgb *pixel = reinterpret_cast(image.bits()); for (int row = 0; row < image.height(); ++row) { for (int col = 0; col < image.width(); ++col) { const QRgb currentPixel = pixel[row * image.width() + col]; const int red = qRed(currentPixel); const int green = qGreen(currentPixel); const int blue = qBlue(currentPixel); const int grayValue = (red * 11 + green * 16 + blue * 5) / 32; pixel[row * image.width() + col] = qRgb(grayValue, grayValue, grayValue); } } } d->previewLabel->setPixmap(QPixmap::fromImage(image)); } KoResourceSP KisResourceItemChooser::resourceFromModelIndex(const QModelIndex &index) const { if (!index.isValid()) { return 0; } KoResourceSP r = d->tagFilterProxyModel->resourceForIndex(index); return r; } QSize KisResourceItemChooser::viewSize() const { return d->view->size(); } KisResourceItemListView *KisResourceItemChooser::itemView() const { return d->view; } void KisResourceItemChooser::contextMenuRequested(const QPoint &pos) { d->tagManager->contextMenuRequested(currentResource(), pos); } void KisResourceItemChooser::setViewModeButtonVisible(bool visible) { d->viewModeButton->setVisible(visible); } QToolButton *KisResourceItemChooser::viewModeButton() const { return d->viewModeButton; } void KisResourceItemChooser::setSynced(bool sync) { if (d->synced == sync) return; d->synced = sync; KisResourceItemChooserSync *chooserSync = KisResourceItemChooserSync::instance(); if (sync) { connect(chooserSync, SIGNAL(baseLengthChanged(int)), SLOT(baseLengthChanged(int))); baseLengthChanged(chooserSync->baseLength()); } else { chooserSync->disconnect(this); } } void KisResourceItemChooser::baseLengthChanged(int length) { if (d->synced) { d->view->setItemSize(QSize(length, length)); } } bool KisResourceItemChooser::eventFilter(QObject *object, QEvent *event) { if (d->synced && event->type() == QEvent::Wheel) { KisResourceItemChooserSync *chooserSync = KisResourceItemChooserSync::instance(); QWheelEvent *qwheel = static_cast(event); if (qwheel->modifiers() & Qt::ControlModifier) { int degrees = qwheel->delta() / 8; int newBaseLength = chooserSync->baseLength() + degrees / 15 * 10; chooserSync->setBaseLength(newBaseLength); return true; } } return QObject::eventFilter(object, event); } void KisResourceItemChooser::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); updateView(); } void KisResourceItemChooser::showEvent(QShowEvent *event) { QWidget::showEvent(event); updateView(); } void KisResourceItemChooser::updateView() { if (d->synced) { KisResourceItemChooserSync *chooserSync = KisResourceItemChooserSync::instance(); baseLengthChanged(chooserSync->baseLength()); } /// helps to set icons here in case the theme is changed d->viewModeButton->setIcon(koIcon("view-choose")); d->importButton->setIcon(koIcon("document-open")); d->deleteButton->setIcon(koIcon("trash-empty")); d->storagePopupButton->setIcon(koIcon("bundle_archive")); } diff --git a/libs/resourcewidgets/KisResourceItemChooser.h b/libs/resourcewidgets/KisResourceItemChooser.h index 61bd21d4a9..13a4bb9539 100644 --- a/libs/resourcewidgets/KisResourceItemChooser.h +++ b/libs/resourcewidgets/KisResourceItemChooser.h @@ -1,153 +1,153 @@ /* This file is part of the KDE project Copyright (c) 2002 Patrick Julien Copyright (c) 2007 Jan Hambrecht Copyright (c) 2007 Sven Langkamp Copyright (c) 2010 Boudewijn Rempt Copyright (C) 2011 Srikanth Tiyyagura Copyright (c) 2011 José Luis Vergara Copyright (c) 2013 Sascha Suelzer Copyright (c) 2019 Boudewijn Rempt 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 KIS_RESOURCE_ITEM_CHOOSER #define KIS_RESOURCE_ITEM_CHOOSER #include #include #include #include #include class QAbstractProxyModel; class QAbstractItemDelegate; class QAbstractButton; class QToolButton; class QSortFilterProxyModel; class KisResourceItemListView; #include "kritaresourcewidgets_export.h" /** * A widget that contains a KoResourceChooser as well * as an import/export button */ class KRITARESOURCEWIDGETS_EXPORT KisResourceItemChooser : public QWidget { Q_OBJECT public: enum Buttons { Button_Import, Button_Remove }; /// \p usePreview shows the aside preview with the resource's image /// \p extraFilterProxy is an extra filter proxy model for additional filtering. KisResourceItemChooser will take over ownership explicit KisResourceItemChooser(const QString &resourceType, bool usePreview = false, QWidget *parent = 0, QSortFilterProxyModel *extraFilterProxy = 0); ~KisResourceItemChooser() override; /// return the number of rows in the view int rowCount() const; /// Sets the height of the view rows void setRowHeight(int rowHeight); /// Sets the width of the view columns void setColumnWidth(int columnWidth); /// Sets a custom delegate for the view void setItemDelegate(QAbstractItemDelegate *delegate); /// Gets the currently selected resource /// @returns the selected resource, 0 is no resource is selected KoResourceSP currentResource() const; /// Sets the item representing the resource as selected void setCurrentResource(KoResourceSP resource); /** * Sets the selected resource, does nothing if there is no valid item * @param row row of the item * @param column column of the item */ void setCurrentItem(int row); void showButtons(bool show); void addCustomButton(QAbstractButton *button, int cell); /// determines whether the preview right or below the splitter void setPreviewOrientation(Qt::Orientation orientation); + /// determines whether the preview should tile the resource's image or not void setPreviewTiled(bool tiled); + /// shows the preview converted to grayscale void setGrayscalePreview(bool grayscale); /// sets the visibility of tagging KlineEdits. void showTaggingBar(bool show); QSize viewSize() const; KisResourceItemListView *itemView() const; void setViewModeButtonVisible(bool visible); QToolButton *viewModeButton() const; void setSynced(bool sync); bool eventFilter(QObject *object, QEvent *event) override; Q_SIGNALS: /// Emitted when a resource was selected void resourceSelected(KoResourceSP resource); /// Emitted when an *already selected* resource is clicked /// again void resourceClicked(KoResourceSP resource); void splitterMoved(); public Q_SLOTS: void slotButtonClicked(int button); void slotScrollerStateChanged(QScroller::State state){ KisKineticScroller::updateCursor(this, state); } private Q_SLOTS: void activated(const QModelIndex &index); void clicked(const QModelIndex &index); void contextMenuRequested(const QPoint &pos); void baseLengthChanged(int length); void updateView(); - void slotBeforeResourcesLayoutReset(QModelIndex activateAfterReset); - void slotAfterResourcesLayoutReset(); protected: void showEvent(QShowEvent *event) override; void resizeEvent(QResizeEvent *event) override; private: void updateButtonState(); void updatePreview(const QModelIndex &idx); /// Resource for a given model index /// @returns the resource pointer, 0 is index not valid KoResourceSP resourceFromModelIndex(const QModelIndex &index) const; class Private; Private *const d; }; #endif // KO_RESOURCE_ITEM_CHOOSER diff --git a/libs/resourcewidgets/KisResourceItemDelegate.cpp b/libs/resourcewidgets/KisResourceItemDelegate.cpp index e6980f0ba4..2d2039f976 100644 --- a/libs/resourcewidgets/KisResourceItemDelegate.cpp +++ b/libs/resourcewidgets/KisResourceItemDelegate.cpp @@ -1,98 +1,98 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * Copyright (C) 2018 Boudewijn Rempt * * 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 "KisResourceItemDelegate.h" #include #include #include "KisResourceModel.h" KisResourceItemDelegate::KisResourceItemDelegate(QObject *parent) : QAbstractItemDelegate(parent) , m_checkerPainter(4) { } void KisResourceItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & option, const QModelIndex &index) const { if (!index.isValid()) return; painter->save(); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); } QRect innerRect = option.rect.adjusted(2, 2, -2, -2); - QImage thumbnail = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); + QImage thumbnail = index.data(Qt::UserRole + KisAbstractResourceModel::Thumbnail).value(); QSize imageSize = thumbnail.size(); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); - QString resourceType = index.data(Qt::UserRole + KisResourceModel::ResourceType).toString(); + QString resourceType = index.data(Qt::UserRole + KisAbstractResourceModel::ResourceType).toString(); // XXX: don't use a hardcoded string here to identify the resource type if (resourceType == ResourceType::Gradients) { m_checkerPainter.paint(*painter, innerRect); thumbnail = thumbnail.scaled(innerRect.width(), innerRect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter->drawImage(innerRect.topLeft(), thumbnail); } else if (resourceType == ResourceType::Patterns) { painter->fillRect(innerRect, Qt::white); // no checkers, they are confusing with patterns. if (imageSize.height() > innerRect.height() || imageSize.width() > innerRect.width()) { thumbnail = thumbnail.scaled(innerRect.width(), innerRect.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); } painter->fillRect(innerRect, QBrush(thumbnail)); } else if (resourceType == ResourceType::Workspaces || resourceType == ResourceType::WindowLayouts) { // TODO: thumbnails for workspaces and window layouts? painter->fillRect(innerRect, Qt::white); - QString name = index.data(Qt::UserRole + KisResourceModel::Name).toString(); + QString name = index.data(Qt::UserRole + KisAbstractResourceModel::Name).toString(); QPen before = painter->pen(); painter->setPen(Qt::black); painter->drawText(innerRect, Qt::TextWordWrap, name.split("_").join(" ")); painter->setPen(before); } else { painter->fillRect(innerRect, Qt::white); // no checkers, they are confusing with patterns. if (imageSize.height() > innerRect.height() || imageSize.width() > innerRect.width()) { thumbnail = thumbnail.scaled(innerRect.width(), innerRect.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); } QPoint topleft(innerRect.topLeft()); if (thumbnail.width() < innerRect.width()) { topleft.setX(topleft.x() + (innerRect.width() - thumbnail.width()) / 2); } if (thumbnail.height() < innerRect.height()) { topleft.setY(topleft.y() + (innerRect.height() - thumbnail.height()) / 2); } painter->drawImage(topleft, thumbnail); } painter->restore(); } QSize KisResourceItemDelegate::sizeHint(const QStyleOptionViewItem &optionItem, const QModelIndex &) const { return optionItem.decorationSize; } diff --git a/libs/resourcewidgets/dbexplorer/DlgDbExplorer.cpp b/libs/resourcewidgets/dbexplorer/DlgDbExplorer.cpp index 3ed09bd6e7..b15dbeb7d8 100644 --- a/libs/resourcewidgets/dbexplorer/DlgDbExplorer.cpp +++ b/libs/resourcewidgets/dbexplorer/DlgDbExplorer.cpp @@ -1,210 +1,210 @@ /* * Copyright (c) 2018 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "DlgDbExplorer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include DlgDbExplorer::DlgDbExplorer(QWidget *parent) : KoDialog(parent) { setCaption(i18n("Resources Cache Database Explorer")); setButtons(Ok); m_page = new WdgDbExplorer(this); Q_CHECK_PTR(m_page); setMainWidget(m_page); m_resourceTypeModel = new KisResourceTypeModel(this); m_tagModel = new KisTagModel("", this); { m_page->tableStorages->setModel(new KisStorageModel(this)); m_page->tableStorages->hideColumn(0); m_page->tableStorages->setSelectionMode(QAbstractItemView::SingleSelection); m_page->tableStorages->resizeColumnsToContents(); } { KisResourceModel *resourcesModel = KisResourceModelProvider::resourceModel(ResourceType::Brushes); m_page->tableResources->setModel(resourcesModel); m_page->tableResources->hideColumn(0); m_page->tableResources->setSelectionMode(QAbstractItemView::SingleSelection); m_page->cmbResourceTypes->setModel(m_resourceTypeModel); m_page->cmbResourceTypes->setModelColumn(KisResourceTypeModel::Name); connect(m_page->cmbResourceTypes, SIGNAL(activated(int)), SLOT(slotTbResourceTypeSelected(int))); connect(m_page->tableResources, SIGNAL(clicked(QModelIndex)), SLOT(slotTbResourceItemSelected())); } { TableModel *tagsModel = new TableModel(this, QSqlDatabase::database()); TableDelegate *tagsDelegate = new TableDelegate(m_page->tableStorages); tagsDelegate->setEditable(true); tagsModel->setTable("tags"); tagsModel->setHeaderData(0, Qt::Horizontal, i18n("Id")); tagsModel->setHeaderData(1, Qt::Horizontal, i18n("Type")); tagsModel->setRelation(1, QSqlRelation("resource_types", "id", "name")); tagsModel->setHeaderData(2, Qt::Horizontal, i18n("Tag")); tagsModel->setHeaderData(3, Qt::Horizontal, i18n("Name")); tagsModel->setHeaderData(4, Qt::Horizontal, i18n("Comment")); tagsModel->setHeaderData(5, Qt::Horizontal, i18n("Active")); tagsModel->setHeaderData(6, Qt::Horizontal, i18n("Thumbnail")); tagsModel->setHeaderData(7, Qt::Horizontal, i18n("Display Name")); tagsModel->addBooleanColumn(5); tagsDelegate->addBooleanColumn(5); tagsModel->select(); connect(tagsModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(slotResetTagModel(QModelIndex, QModelIndex))); m_page->tableTags->setModel(tagsModel); m_page->tableTags->hideColumn(0); m_page->tableTags->setItemDelegate(tagsDelegate); m_page->tableTags->setSelectionMode(QAbstractItemView::SingleSelection); m_page->tableTags->resizeColumnsToContents(); } { QSqlTableModel *versionModel = new QSqlTableModel(this, QSqlDatabase::database()); versionModel->setTable("version_information"); versionModel->setHeaderData(0, Qt::Horizontal, "id"); versionModel->setHeaderData(1, Qt::Horizontal, "database_version"); versionModel->setHeaderData(2, Qt::Horizontal, "krita_version"); versionModel->setHeaderData(3, Qt::Horizontal, "creation_date"); versionModel->select(); QSqlRecord r = versionModel->record(0); m_page->lblDatabaseVersion->setText(r.value("database_version").toString()); m_page->lblKritaVersion->setText(r.value("krita_version").toString()); m_page->lblCreationDate->setText(r.value("creation_date").toString()); } { m_page->cmbRvResourceTypes->setModel(m_resourceTypeModel); m_page->cmbRvResourceTypes->setModelColumn(KisResourceTypeModel::Name); connect(m_page->cmbRvResourceTypes, SIGNAL(activated(int)), SLOT(slotRvResourceTypeSelected(int))); m_page->cmbRvTags->setModelColumn(KisTagModel::Name); m_page->cmbRvTags->setModel(m_tagModel); connect(m_page->cmbRvTags, SIGNAL(activated(int)), SLOT(slotRvTagSelected(int))); m_page->cmbRvResourceTypes->setCurrentIndex(0); slotRvResourceTypeSelected(0); m_page->resourceItemView->setItemDelegate(new KisResourceItemDelegate(this)); m_page->resourceItemView->setSelectionMode(QAbstractItemView::SingleSelection); } } DlgDbExplorer::~DlgDbExplorer() { } void DlgDbExplorer::updateTagModel(const QString& resourceType) { m_tagModel = KisTagModelProvider::tagModel(resourceType); m_page->cmbRvTags->setModelColumn(KisTagModel::Name); m_page->cmbRvTags->setModel(m_tagModel); m_page->cmbRvTags->update(); qDebug() << "number of tags in " << resourceType << " tag model: " << m_tagModel->rowCount(); } void DlgDbExplorer::slotResetTagModel(QModelIndex topLeft, QModelIndex bottomRight) { KisTagModelProvider::resetModels(); } void DlgDbExplorer::slotRvResourceTypeSelected(int index) { QModelIndex idx = m_page->cmbResourceTypes->model()->index(index, KisResourceTypeModel::ResourceType); QString resourceType = idx.data(Qt::DisplayRole).toString(); qDebug() << resourceType; updateTagModel(resourceType); KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(resourceType); KisTagFilterResourceProxyModel *tagFilterModel = new KisTagFilterResourceProxyModel(KisTagModelProvider::tagModel(resourceType), this); tagFilterModel->setSourceModel(resourceModel); m_filterProxyModel = tagFilterModel; m_page->resourceItemView->setModel(tagFilterModel); } void DlgDbExplorer::slotTbResourceTypeSelected(int index) { QModelIndex idx = m_page->cmbRvResourceTypes->model()->index(index, KisResourceTypeModel::ResourceType); QString resourceType = idx.data(Qt::DisplayRole).toString(); qDebug() << resourceType; m_tagModel = KisTagModelProvider::tagModel(resourceType); KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(resourceType); m_page->tableResources->setModel(resourceModel); m_page->tableResources->setCurrentIndex(m_page->tableResources->model()->index(0, 0)); slotTbResourceItemSelected(); m_page->tableResources->resizeColumnsToContents(); } void DlgDbExplorer::slotTbResourceItemSelected() { QModelIndex idx = m_page->tableResources->selectionModel()->selectedIndexes().first(); - QImage thumb = idx.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); + QImage thumb = idx.data(Qt::UserRole + KisAbstractResourceModel::Thumbnail).value(); Qt::TransformationMode mode = Qt::SmoothTransformation; if (thumb.size().width() < 100 && thumb.size().height() < 100) { mode = Qt::FastTransformation; } m_page->lblThumbnail->setPixmap(QPixmap::fromImage(thumb.scaled(100, 100, Qt::KeepAspectRatio, mode))); //If we could get a list of versions for a given resource, this would be the moment to add them... } void DlgDbExplorer::slotRvTagSelected(int index) { qDebug() << "selected tag" << index; QModelIndex idx = m_tagModel->index(index, 0); KisTagSP tag = m_tagModel->tagForIndex(idx); if (m_filterProxyModel && !tag.isNull() && tag->valid()) { m_filterProxyModel->setTag(tag); } } diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 5b7708105b..2fd3468306 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,2343 +1,2343 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * 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 "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" #include "kis_selection_mask.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include "dialogs/KisRecoverNamedAutosaveDialog.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" #include "KisCloneDocumentStroke.h" #include #include #include "kis_simple_stroke_strategy.h" // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *_q) : q(_q) , docInfo(new KoDocumentInfo(_q)) // deleted by QObject , importExportManager(new KisImportExportManager(_q)) // deleted manually , autoSaveTimer(new QTimer(_q)) , undoStack(new UndoStack(_q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , globalAssistantsColor(KisConfig(true).defaultAssistantsColor()) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } connect(&imageIdleWatcher, SIGNAL(startedIdleMode()), q, SLOT(slotPerformIdleRoutines())); } Private(const Private &rhs, KisDocument *_q) : q(_q) , docInfo(new KoDocumentInfo(*rhs.docInfo, _q)) , importExportManager(new KisImportExportManager(_q)) , autoSaveTimer(new QTimer(_q)) , undoStack(new UndoStack(_q)) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , savingLock(&savingMutex) { copyFromImpl(rhs, _q, CONSTRUCT); connect(&imageIdleWatcher, SIGNAL(startedIdleMode()), q, SLOT(slotPerformIdleRoutines())); } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KisDocument *q = 0; KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; KisMirrorAxisConfig mirrorAxisConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; QColor globalAssistantsColor; KisSharedPtr referenceImagesLayer; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; QString documentStorageID {QUuid::createUuid().toString()}; KisResourceStorageSP documentResourceStorage; void syncDecorationsWrapperLayerState(); void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); } void copyFrom(const Private &rhs, KisDocument *q); void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy); /// clones the palette list oldList /// the ownership of the returned KoColorSet * belongs to the caller class StrippedSafeSavingLocker; }; void KisDocument::Private::syncDecorationsWrapperLayerState() { if (!this->image) return; KisImageSP image = this->image; KisDecorationsWrapperLayerSP decorationsLayer = KisLayerUtils::findNodeByType(image->root()); const bool needsDecorationsWrapper = gridConfig.showGrid() || (guidesConfig.showGuides() && guidesConfig.hasGuides()) || !assistants.isEmpty(); struct SyncDecorationsWrapperStroke : public KisSimpleStrokeStrategy { SyncDecorationsWrapperStroke(KisDocument *document, bool needsDecorationsWrapper) : KisSimpleStrokeStrategy(QLatin1String("sync-decorations-wrapper"), kundo2_noi18n("start-isolated-mode")), m_document(document), m_needsDecorationsWrapper(needsDecorationsWrapper) { this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() override { KisDecorationsWrapperLayerSP decorationsLayer = KisLayerUtils::findNodeByType(m_document->image()->root()); if (m_needsDecorationsWrapper && !decorationsLayer) { m_document->image()->addNode(new KisDecorationsWrapperLayer(m_document)); } else if (!m_needsDecorationsWrapper && decorationsLayer) { m_document->image()->removeNode(decorationsLayer); } } private: KisDocument *m_document = 0; bool m_needsDecorationsWrapper = false; }; KisStrokeId id = image->startStroke(new SyncDecorationsWrapperStroke(q, needsDecorationsWrapper)); image->endStroke(id); } void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q) { copyFromImpl(rhs, q, KisDocument::REPLACE); } void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy) { if (policy == REPLACE) { delete docInfo; } docInfo = (new KoDocumentInfo(*rhs.docInfo, q)); unit = rhs.unit; mimeType = rhs.mimeType; outputMimeType = rhs.outputMimeType; if (policy == REPLACE) { q->setGuidesConfig(rhs.guidesConfig); q->setMirrorAxisConfig(rhs.mirrorAxisConfig); q->setModified(rhs.modified); q->setAssistants(KisPaintingAssistant::cloneAssistantList(rhs.assistants)); q->setGridConfig(rhs.gridConfig); } else { // in CONSTRUCT mode, we cannot use the functions of KisDocument // because KisDocument does not yet have a pointer to us. guidesConfig = rhs.guidesConfig; mirrorAxisConfig = rhs.mirrorAxisConfig; modified = rhs.modified; assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants); gridConfig = rhs.gridConfig; } m_bAutoDetectedMime = rhs.m_bAutoDetectedMime; m_url = rhs.m_url; m_file = rhs.m_file; readwrite = rhs.readwrite; firstMod = rhs.firstMod; lastMod = rhs.lastMod; // XXX: the display properties will be shared between different snapshots globalAssistantsColor = rhs.globalAssistantsColor; batchMode = rhs.batchMode; // CHECK THIS! This is what happened to the palette list -- but is it correct here as well? Ask Dmitry!!! // if (policy == REPLACE) { // QList newPaletteList = clonePaletteList(rhs.paletteList); // q->setPaletteList(newPaletteList, /* emitSignal = */ true); // // we still do not own palettes if we did not // } else { // paletteList = rhs.paletteList; // } if (rhs.documentResourceStorage) { if (policy == REPLACE) { // Clone the resources, but don't add them to the database, only the editable // version of the document should have those resources in the database. documentResourceStorage = rhs.documentResourceStorage->clone(); } else { documentResourceStorage = rhs.documentResourceStorage; } } } class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument(bool addStorage) : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); if (addStorage) { d->documentResourceStorage.reset(new KisResourceStorage(d->documentStorageID)); KisResourceLocator::instance()->addStorage(d->documentStorageID, d->documentResourceStorage); } // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver); d->koShapeController = new KoShapeController(0, d->shapeController); d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { copyFromDocumentImpl(rhs, CONSTRUCT); } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } if (KisResourceLocator::instance()->hasStorage(d->documentStorageID)) { KisResourceLocator::instance()->removeStorage(d->documentStorageID); } delete d; } QString KisDocument::uniqueID() const { return d->documentStorageID; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, ImportExportCodes::NoAccessToWrite, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); //return ImportExportCodes::NoAccessToWrite; return false; } KisConfig cfg(true); if (cfg.backupFile() && filePathInfo.exists()) { QString backupDir; switch(cfg.readEntry("backupfilelocation", 0)) { case 1: backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); break; case 2: backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); break; default: // Do nothing: the empty string is user file location break; } int numOfBackupsKept = cfg.readEntry("numberofbackupfiles", 1); QString suffix = cfg.readEntry("backupfilesuffix", "~"); if (numOfBackupsKept == 1) { if (!KBackup::simpleBackupFile(job.filePath, backupDir, suffix)) { qWarning() << "Failed to create simple backup file!" << job.filePath << backupDir << suffix; KisUsageLogger::log(QString("Failed to create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); return false; } else { KisUsageLogger::log(QString("Create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); } } else if (numOfBackupsKept > 1) { if (!KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept)) { qWarning() << "Failed to create numbered backup file!" << job.filePath << backupDir << suffix; KisUsageLogger::log(QString("Failed to create a numbered backup for %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); return false; } else { KisUsageLogger::log(QString("Create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); } } } //KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); if (job.mimeType.isEmpty()) { KisImportExportErrorCode error = ImportExportCodes::FileFormatIncorrect; slotCompleteSavingDocument(job, error, error.errorMessage()); return false; } const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode ,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8") .arg(url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")); return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, %7 framerate. Export configuration: %8") .arg(_url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration") .arg(url().toLocalFile())); return exportDocumentImpl(ExportFileJob(_url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (!filter->convert(this, &buffer).isOk()) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { if (status.isCancelled()) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (!status.isOk()) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage))); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { const QString existingAutoSaveBaseName = localFilePath(); const bool wasRecovered = isRecovered(); setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { /** * If undo stack is already clean/empty, it doesn't emit any * signals, so we might forget update document modified state * (which was set, e.g. while recovering an autosave file) */ if (d->undoStack->isClean()) { setModified(false); } else { d->undoStack->setClean(); } } setRecovered(false); removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } KisDocument *KisDocument::lockAndCreateSnapshot() { KisDocument *doc = lockAndCloneForSaving(); if (doc) { // clone the local resource storage and its contents -- that is, the old palette list if (doc->d->documentResourceStorage) { doc->d->documentResourceStorage = doc->d->documentResourceStorage->clone(); } } return doc; } void KisDocument::copyFromDocument(const KisDocument &rhs) { copyFromDocumentImpl(rhs, REPLACE); } void KisDocument::copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy) { if (policy == REPLACE) { d->copyFrom(*(rhs.d), this); d->undoStack->clear(); } else { // in CONSTRUCT mode, d should be already initialized connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); d->shapeController = new KisShapeController(this, d->nserver); d->koShapeController = new KoShapeController(0, d->shapeController); d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); } setObjectName(rhs.objectName()); slotConfigChanged(); if (rhs.d->image) { if (policy == REPLACE) { d->image->barrierLock(/* readOnly = */ false); rhs.d->image->barrierLock(/* readOnly = */ true); d->image->copyFromImage(*(rhs.d->image)); d->image->unlock(); rhs.d->image->unlock(); setCurrentImage(d->image, /* forceInitialUpdate = */ true); } else { // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false); } } if (rhs.d->preActivatedNode) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(d->image->root(), [&linearizedNodes, &rhs, this](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); if (rhs.d->preActivatedNode.data() == refNode.data()) { d->preActivatedNode = node; } }); } // reinitialize references' signal connection KisReferenceImagesLayerSP referencesLayer = this->referenceImagesLayer(); setReferenceImagesLayer(referencesLayer, false); KisDecorationsWrapperLayerSP decorationsLayer = KisLayerUtils::findNodeByType(d->image->root()); if (decorationsLayer) { decorationsLayer->setDocument(this); } if (policy == REPLACE) { setModified(true); } } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { { /** * The caller guarantees that no one else uses the document (usually, * it is a temporary document created specifically for exporting), so * we don't need to copy or lock the document. Instead we should just * ensure the barrier lock is synced and then released. */ Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportErrorCode status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status.isOk(); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } auto waitForImage = [] (KisImageSP image) { KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { window->viewManager()->blockUntilOperationsFinishedForced(image); } } }; { KisNodeSP newRoot = clonedDocument->image()->root(); KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) { KisLayerUtils::forceAllDelayedNodesUpdate(newRoot); waitForImage(clonedDocument->image()); } } if (clonedDocument->image()->hasOverlaySelectionMask()) { clonedDocument->image()->setOverlaySelectionMask(0); waitForImage(clonedDocument->image()); } KisConfig cfg(true); if (cfg.trimKra()) { clonedDocument->image()->cropImage(clonedDocument->image()->bounds()); clonedDocument->image()->purgeUnusedData(false); waitForImage(clonedDocument->image()); } KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) { waitForImage(clonedDocument->image()); } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportErrorCode, QString)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportErrorCode, QString))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage) { KIS_ASSERT_RECOVER_RETURN(isSaving()); KIS_ASSERT_RECOVER(d->backgroundSaveDocument) { d->savingMutex.unlock(); return; } if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); KIS_ASSERT_RECOVER(d->backgroundSaveJob.isValid()) { d->savingMutex.unlock(); return; } const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); // unlock at the very end d->savingMutex.unlock(); QFileInfo fi(job.filePath); KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3. Size: %4. MD5 Hash: %5") .arg(job.filePath) .arg(QString::fromLatin1(job.mimeType)) .arg(!status.isOk() ? exportErrorToUserMessage(status, errorMessage) : "OK") .arg(fi.size()) .arg(fi.size() > 10000000 ? "FILE_BIGGER_10MB" : QString::fromLatin1(KoMD5Generator().generateHash(job.filePath).toHex()))); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); KisUsageLogger::log(QString("Autosaving: %1").arg(autoSaveFileName)); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } void KisDocument::slotPerformIdleRoutines() { d->image->explicitRegenerateLevelOfDetail(); /// TODO: automatical purging is disabled for now: it modifies /// data managers without creating a transaction, which breaks /// undo. // d->image->purgeUnusedData(true); } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (!status.isOk()) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg(true); d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } KisImportExportErrorCode initializationStatus(ImportExportCodes::OK); d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (!initializationStatus.isOk()) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, initializationStatus.errorMessage()); return false; } typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(ImportExportCodes::InternalError, ""); return; } KisImportExportErrorCode status = d->childSavingFuture.result(); const QString errorMessage = status.errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QString prefix = KisConfig(true).readEntry("autosavefileshidden") ? QString(".") : QString(); QRegularExpression autosavePattern1("^\\..+-autosave.kra$"); QRegularExpression autosavePattern2("^.+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg('/').arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::homePath()).arg('/').arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #endif } else { retval = QString("%1%2%5%3-autosave%4").arg(dir).arg('/').arg(filename).arg(extension).arg(prefix); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); QString original = ""; bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //qDebug() <<"asf=" << asf; // ## TODO compare timestamps ? KisRecoverNamedAutosaveDialog dlg(0, file, asf); dlg.exec(); int res = dlg.result(); switch (res) { case KisRecoverNamedAutosaveDialog::OpenAutosave : original = file; url.setPath(asf); autosaveOpened = true; break; case KisRecoverNamedAutosaveDialog::OpenMainFile : KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); setUrl(QUrl::fromLocalFile(original)); // since it was an autosave, it will be a local file setLocalFilePath(original); } else { if (ret) { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

"; if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath()) && !fileBatchMode()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); KoUpdaterPtr updater; if (window && window->viewManager()) { updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName); if (!status.isOk()) { if (window && window->viewManager()) { updater->cancel(); } QString msg = status.errorMessage(); if (!msg.isEmpty() && !fileBatchMode()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty() && !fileBatchMode()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); d->syncDecorationsWrapperLayerState(); emit sigLoadingFinished(); undoStack()->clear(); return true; } void KisDocument::autoSaveOnPause() { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); QUrl url("file:/" + autoSaveFileName); bool started = exportDocumentSync(url, nativeFormatMimeType()); if (started) { d->modifiedAfterAutosave = false; dbgAndroid << "autoSaveOnPause successful"; } else { qWarning() << "Could not auto-save when paused"; } } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered) { // Eliminate any auto-save file QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir if (QFile::exists(asf)) { KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME if (QFile::exists(asf)) { KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); QFile::remove(asf); } QList expressions; expressions << QRegularExpression("^\\..+-autosave.kra$") << QRegularExpression("^.+-autosave.kra$"); Q_FOREACH(const QRegularExpression &rex, expressions) { if (wasRecovered && !autosaveBaseName.isEmpty() && rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() && QFile::exists(autosaveBaseName)) { KisUsageLogger::log(QString("Removing autosave file: %1").arg(autosaveBaseName)); QFile::remove(autosaveBaseName); } } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg(true); if (d->undoStack->undoLimit() != cfg.undoStackLimit()) { if (!d->undoStack->isClean()) { d->undoStack->clear(); } d->undoStack->setUndoLimit(cfg.undoStackLimit()); } d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::slotImageRootChanged() { d->syncDecorationsWrapperLayerState(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { if (d->gridConfig != config) { d->gridConfig = config; d->syncDecorationsWrapperLayerState(); emit sigGridConfigChanged(config); } } QList KisDocument::paletteList() { qDebug() << "PALETTELIST storage" << d->documentResourceStorage; QList _paletteList; if (d->documentResourceStorage.isNull()) { qWarning() << "No documentstorage for palettes"; return _paletteList; } QSharedPointer iter = d->documentResourceStorage->resources(ResourceType::Palettes); while (iter->hasNext()) { iter->next(); KoResourceSP resource = iter->resource(); if (resource && resource->valid()) { _paletteList << resource.dynamicCast(); } } return _paletteList; } void KisDocument::setPaletteList(const QList &paletteList, bool emitSignal) { qDebug() << "SET PALETTE LIST" << paletteList.size() << "storage" << d->documentResourceStorage; QList oldPaletteList; if (d->documentResourceStorage) { QSharedPointer iter = d->documentResourceStorage->resources(ResourceType::Palettes); while (iter->hasNext()) { iter->next(); KoResourceSP resource = iter->resource(); if (resource && resource->valid()) { oldPaletteList << resource.dynamicCast(); } } if (oldPaletteList != paletteList) { KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(ResourceType::Palettes); Q_FOREACH(KoColorSetSP palette, oldPaletteList) { - resourceModel->removeResource(palette); + resourceModel->setResourceInactive(palette); } Q_FOREACH(KoColorSetSP palette, paletteList) { qDebug()<< "loading palette into document" << palette->filename(); resourceModel->addResource(palette, d->documentStorageID); } if (emitSignal) { emit sigPaletteListChanged(oldPaletteList, paletteList); } } } } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; d->syncDecorationsWrapperLayerState(); emit sigGuidesConfigChanged(d->guidesConfig); } const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const { return d->mirrorAxisConfig; } void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config) { if (d->mirrorAxisConfig == config) { return; } d->mirrorAxisConfig = config; setModified(true); emit sigMirrorAxisConfigChanged(); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) { return false; } if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) { return false; } d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisImageSP image; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); image->waitForDone(); documentInfo()->setAboutInfo("title", name); documentInfo()->setAboutInfo("abstract", description); KisConfig cfg(false); cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); bool autopin = cfg.autoPinLayersToTimeline(); KisLayerSP bgLayer; if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) { KoColor strippedAlpha = bgColor; strippedAlpha.setOpacity(OPACITY_OPAQUE_U8); if (bgStyle == KisConfig::RASTER_LAYER) { bgLayer = new KisPaintLayer(image.data(), "Background", OPACITY_OPAQUE_U8, cs);; bgLayer->paintDevice()->setDefaultPixel(strippedAlpha); bgLayer->setPinnedToTimeline(autopin); } else if (bgStyle == KisConfig::FILL_LAYER) { KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration(KisGlobalResourcesInterface::instance()); filter_config->setProperty("color", strippedAlpha.toQColor()); filter_config->createLocalResourcesSnapshot(); bgLayer = new KisGeneratorLayer(image.data(), "Background Fill", filter_config, image->globalSelection()); } bgLayer->setOpacity(bgColor.opacityU8()); if (numberOfLayers > 1) { //Lock bg layer if others are present. bgLayer->setUserLocked(true); } } else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer). image->setDefaultProjectionColor(bgColor); bgLayer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); } Q_CHECK_PTR(bgLayer); image->addNode(bgLayer.data(), image->rootLayer().data()); bgLayer->setDirty(QRect(0, 0, width, height)); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); layer->setPinnedToTimeline(autopin); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } KisUsageLogger::log(QString("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8") .arg(name) .arg(width).arg(height) .arg(imageResolution * 72.0) .arg(image->colorSpace()->colorModelId().name()) .arg(image->colorSpace()->colorDepthId().name()) .arg(image->colorSpace()->profile()->name()) .arg(numberOfLayers)); QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { if (isSaving()) { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } } KoShapeControllerBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList &value) { if (d->assistants != value) { d->assistants = value; d->syncDecorationsWrapperLayerState(); emit sigAssistantsChanged(); } } KisReferenceImagesLayerSP KisDocument::referenceImagesLayer() const { if (!d->image) return KisReferenceImagesLayerSP(); KisReferenceImagesLayerSP referencesLayer = KisLayerUtils::findNodeByType(d->image->root()); return referencesLayer; } void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage) { KisReferenceImagesLayerSP currentReferenceLayer = referenceImagesLayer(); if (currentReferenceLayer == layer) { return; } if (currentReferenceLayer) { currentReferenceLayer->disconnect(this); } if (updateImage) { if (currentReferenceLayer) { d->image->removeNode(currentReferenceLayer); } if (layer) { d->image->addNode(layer); } } currentReferenceLayer = layer; if (currentReferenceLayer) { connect(currentReferenceLayer, SIGNAL(sigUpdateCanvas(QRectF)), this, SIGNAL(sigReferenceImagesChanged())); } emit sigReferenceImagesLayerChanged(layer); } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) { if (d->image) { // Disconnect existing sig/slot connections d->image->setUndoStore(new KisDumbUndoStore()); d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; if (d->documentResourceStorage){ d->documentResourceStorage->setMetaData(KisResourceStorage::s_meta_name, image->objectName()); } d->setImageAndInitIdleWatcher(image); d->image->setUndoStore(new KisDocumentUndoStore(this)); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); connect(d->image, SIGNAL(sigLayersChangedAsync()), this, SLOT(slotImageRootChanged())); if (forceInitialUpdate) { d->image->initialRefreshGraph(); } } void KisDocument::hackPreliminarySetImage(KisImageSP image) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); // we set image without connecting idle-watcher, because loading // hasn't been finished yet d->image = image; d->shapeController->setImage(image); } void KisDocument::setImageModified() { // we only set as modified if undo stack is not at clean state setModified(!d->undoStack->isClean()); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage) { return errorMessage.isEmpty() ? status.errorMessage() : errorMessage; } void KisDocument::setAssistantsGlobalColor(QColor color) { d->globalAssistantsColor = color; } QColor KisDocument::assistantsGlobalColor() { return d->globalAssistantsColor; } QRectF KisDocument::documentBounds() const { QRectF bounds = d->image->bounds(); KisReferenceImagesLayerSP referenceImagesLayer = this->referenceImagesLayer(); if (referenceImagesLayer) { bounds |= referenceImagesLayer->boundingImageRect(); } return bounds; } diff --git a/libs/ui/KisViewManager.cpp b/libs/ui/KisViewManager.cpp index 5d782b0906..8831a55c35 100644 --- a/libs/ui/KisViewManager.cpp +++ b/libs/ui/KisViewManager.cpp @@ -1,1452 +1,1452 @@ /* * This file is part of KimageShop^WKrayon^WKrita * * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 1999 Carsten Pfeiffer * 2002 Patrick Julien * 2003-2011 Boudewijn Rempt * 2004 Clarence Dang * 2011 José Luis Vergara * 2017 L. E. Segovia * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "KisViewManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "input/kis_input_manager.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_canvas_controller.h" #include "canvas/kis_grid_manager.h" #include "input/kis_input_profile_manager.h" #include "kis_action_manager.h" #include "kis_action.h" #include "kis_canvas_controls_manager.h" #include "kis_canvas_resource_provider.h" #include "kis_composite_progress_proxy.h" #include #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_control_frame.h" #include "kis_coordinates_converter.h" #include "KisDocument.h" #include "kis_favorite_resource_manager.h" #include "kis_filter_manager.h" #include "kis_group_layer.h" #include #include #include "kis_image_manager.h" #include #include "kis_mainwindow_observer.h" #include "kis_mask_manager.h" #include "kis_mimedata.h" #include "kis_mirror_manager.h" #include "kis_node_commands_adapter.h" #include "kis_node.h" #include "kis_node_manager.h" #include "KisDecorationsManager.h" #include #include "kis_paintop_box.h" #include #include "KisPart.h" #include #include "KisResourceServerProvider.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_selection_manager.h" #include "kis_shape_controller.h" #include "kis_shape_layer.h" #include #include "kis_statusbar.h" #include #include #include "kis_tooltip_manager.h" #include #include "KisView.h" #include "kis_zoom_manager.h" #include "widgets/kis_floating_message.h" #include "kis_signal_auto_connection.h" #include "kis_icon_utils.h" #include "kis_guides_manager.h" #include "kis_derived_resources.h" #include "dialogs/kis_delayed_save_dialog.h" #include #include "kis_signals_blocker.h" class BlockingUserInputEventFilter : public QObject { bool eventFilter(QObject *watched, QEvent *event) override { Q_UNUSED(watched); if(dynamic_cast(event) || dynamic_cast(event) || dynamic_cast(event)) { return true; } else { return false; } } }; class KisViewManager::KisViewManagerPrivate { public: KisViewManagerPrivate(KisViewManager *_q, KActionCollection *_actionCollection, QWidget *_q_parent) : filterManager(_q) , createTemplate(0) , saveIncremental(0) , saveIncrementalBackup(0) , openResourcesDirectory(0) , rotateCanvasRight(0) , rotateCanvasLeft(0) , resetCanvasRotation(0) , wrapAroundAction(0) , levelOfDetailAction(0) , showRulersAction(0) , rulersTrackMouseAction(0) , zoomTo100pct(0) , zoomIn(0) , zoomOut(0) , selectionManager(_q) , statusBar(_q) , controlFrame(_q, _q_parent) , nodeManager(_q) , imageManager(_q) , gridManager(_q) , canvasControlsManager(_q) , paintingAssistantsManager(_q) , actionManager(_q, _actionCollection) , mainWindow(0) , showFloatingMessage(true) , currentImageView(0) , canvasResourceProvider(_q) , canvasResourceManager() , guiUpdateCompressor(30, KisSignalCompressor::POSTPONE, _q) , actionCollection(_actionCollection) , mirrorManager(_q) , inputManager(_q) , actionAuthor(0) , showPixelGrid(0) { KisViewManager::initializeResourceManager(&canvasResourceManager); } public: KisFilterManager filterManager; KisAction *createTemplate; KisAction *createCopy; KisAction *saveIncremental; KisAction *saveIncrementalBackup; KisAction *openResourcesDirectory; KisAction *rotateCanvasRight; KisAction *rotateCanvasLeft; KisAction *resetCanvasRotation; KisAction *wrapAroundAction; KisAction *levelOfDetailAction; KisAction *showRulersAction; KisAction *rulersTrackMouseAction; KisAction *zoomTo100pct; KisAction *zoomIn; KisAction *zoomOut; KisAction *softProof; KisAction *gamutCheck; KisAction *toggleFgBg; KisAction *resetFgBg; KisSelectionManager selectionManager; KisGuidesManager guidesManager; KisStatusBar statusBar; QPointer persistentImageProgressUpdater; QScopedPointer persistentUnthreadedProgressUpdaterRouter; QPointer persistentUnthreadedProgressUpdater; KisControlFrame controlFrame; KisNodeManager nodeManager; KisImageManager imageManager; KisGridManager gridManager; KisCanvasControlsManager canvasControlsManager; KisDecorationsManager paintingAssistantsManager; BlockingUserInputEventFilter blockingEventFilter; KisActionManager actionManager; QMainWindow* mainWindow; QPointer savedFloatingMessage; bool showFloatingMessage; QPointer currentImageView; KisCanvasResourceProvider canvasResourceProvider; KoCanvasResourceProvider canvasResourceManager; KisSignalCompressor guiUpdateCompressor; KActionCollection *actionCollection; KisMirrorManager mirrorManager; KisInputManager inputManager; KisSignalAutoConnectionsStore viewConnections; KSelectAction *actionAuthor; // Select action for author profile. KisAction *showPixelGrid; QByteArray canvasState; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QFlags windowFlags; #endif bool blockUntilOperationsFinishedImpl(KisImageSP image, bool force); }; KisViewManager::KisViewManager(QWidget *parent, KActionCollection *_actionCollection) : d(new KisViewManagerPrivate(this, _actionCollection, parent)) { d->actionCollection = _actionCollection; d->mainWindow = dynamic_cast(parent); d->canvasResourceProvider.setResourceManager(&d->canvasResourceManager); connect(&d->guiUpdateCompressor, SIGNAL(timeout()), this, SLOT(guiUpdateTimeout())); createActions(); setupManagers(); // These initialization functions must wait until KisViewManager ctor is complete. d->statusBar.setup(); d->persistentImageProgressUpdater = d->statusBar.progressUpdater()->startSubtask(1, "", true); // reset state to "completed" d->persistentImageProgressUpdater->setRange(0,100); d->persistentImageProgressUpdater->setValue(100); d->persistentUnthreadedProgressUpdater = d->statusBar.progressUpdater()->startSubtask(1, "", true); // reset state to "completed" d->persistentUnthreadedProgressUpdater->setRange(0,100); d->persistentUnthreadedProgressUpdater->setValue(100); d->persistentUnthreadedProgressUpdaterRouter.reset( new KoProgressUpdater(d->persistentUnthreadedProgressUpdater, KoProgressUpdater::Unthreaded)); d->persistentUnthreadedProgressUpdaterRouter->setAutoNestNames(true); d->controlFrame.setup(parent); //Check to draw scrollbars after "Canvas only mode" toggle is created. this->showHideScrollbars(); QScopedPointer dummy(new KoDummyCanvasController(actionCollection())); KoToolManager::instance()->registerToolActions(actionCollection(), dummy.data()); QTimer::singleShot(0, this, SLOT(initializeStatusBarVisibility())); connect(KoToolManager::instance(), SIGNAL(inputDeviceChanged(KoInputDevice)), d->controlFrame.paintopBox(), SLOT(slotInputDeviceChanged(KoInputDevice))); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), d->controlFrame.paintopBox(), SLOT(slotToolChanged(KoCanvasController*,int))); connect(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), canvasResourceProvider(), SLOT(slotNodeActivated(KisNodeSP))); connect(KisPart::instance(), SIGNAL(sigViewAdded(KisView*)), SLOT(slotViewAdded(KisView*))); connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)), SLOT(slotViewRemoved(KisView*))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotUpdateAuthorProfileActions())); connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotUpdatePixelGridAction())); KisInputProfileManager::instance()->loadProfiles(); KisConfig cfg(true); d->showFloatingMessage = cfg.showCanvasMessages(); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor foreground(Qt::black, cs); d->canvasResourceProvider.setFGColor(cfg.readKoColor("LastForeGroundColor",foreground)); KoColor background(Qt::white, cs); d->canvasResourceProvider.setBGColor(cfg.readKoColor("LastBackGroundColor",background)); } KisViewManager::~KisViewManager() { KisConfig cfg(false); if (canvasResourceProvider() && canvasResourceProvider()->currentPreset()) { cfg.writeKoColor("LastForeGroundColor",canvasResourceProvider()->fgColor()); cfg.writeKoColor("LastBackGroundColor",canvasResourceProvider()->bgColor()); } cfg.writeEntry("baseLength", KisResourceItemChooserSync::instance()->baseLength()); cfg.writeEntry("CanvasOnlyActive", false); // We never restart in CavnasOnlyMode delete d; } void KisViewManager::initializeResourceManager(KoCanvasResourceProvider *resourceManager) { resourceManager->addDerivedResourceConverter(toQShared(new KisCompositeOpResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisEffectiveCompositeOpResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisOpacityResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisFlowResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisSizeResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisLodAvailabilityResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisLodSizeThresholdResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisLodSizeThresholdSupportedResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisEraserModeResourceConverter)); resourceManager->addResourceUpdateMediator(toQShared(new KisPresetUpdateMediator)); } KActionCollection *KisViewManager::actionCollection() const { return d->actionCollection; } void KisViewManager::slotViewAdded(KisView *view) { // WARNING: this slot is called even when a view from another main windows is added! // Don't expect \p view be a child of this view manager! Q_UNUSED(view); if (viewCount() == 0) { d->statusBar.showAllStatusBarItems(); } } void KisViewManager::slotViewRemoved(KisView *view) { // WARNING: this slot is called even when a view from another main windows is removed! // Don't expect \p view be a child of this view manager! Q_UNUSED(view); if (viewCount() == 0) { d->statusBar.hideAllStatusBarItems(); } KisConfig cfg(false); if (canvasResourceProvider() && canvasResourceProvider()->currentPreset()) { cfg.writeEntry("LastPreset", canvasResourceProvider()->currentPreset()->name()); } } void KisViewManager::setCurrentView(KisView *view) { bool first = true; if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(false); d->currentImageView->canvasBase()->setCursor(QCursor(Qt::ArrowCursor)); first = false; KisDocument* doc = d->currentImageView->document(); if (doc) { doc->image()->compositeProgressProxy()->removeProxy(d->persistentImageProgressUpdater); doc->disconnect(this); } d->currentImageView->canvasController()->proxyObject->disconnect(&d->statusBar); d->viewConnections.clear(); } QPointer imageView = qobject_cast(view); d->currentImageView = imageView; if (imageView) { d->softProof->setChecked(imageView->softProofing()); d->gamutCheck->setChecked(imageView->gamutCheck()); // Wait for the async image to have loaded KisDocument* doc = imageView->document(); if (KisConfig(true).readEntry("EnablePositionLabel", false)) { connect(d->currentImageView->canvasController()->proxyObject, SIGNAL(documentMousePositionChanged(QPointF)), &d->statusBar, SLOT(documentMousePositionChanged(QPointF))); } // Restore the last used brush preset, color and background color. if (first) { KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); KisResourceModel *resourceModel = rserver->resourceModel(); QString defaultPresetName = "basic_tip_default"; for (int i = 0; i < resourceModel->rowCount(); i++) { QModelIndex idx = resourceModel->index(i, 0); - QString resourceName = idx.data(Qt::UserRole + KisResourceModel::Name).toString().toLower(); - QString fileName = idx.data(Qt::UserRole + KisResourceModel::Filename).toString().toLower(); + QString resourceName = idx.data(Qt::UserRole + KisAbstractResourceModel::Name).toString().toLower(); + QString fileName = idx.data(Qt::UserRole + KisAbstractResourceModel::Filename).toString().toLower(); if (resourceName.contains("basic_tip_default")) { defaultPresetName = resourceName; } else if (resourceName.contains("default") || fileName.contains("default")) { defaultPresetName = resourceName; } } KisConfig cfg(true); QString lastPreset = cfg.readEntry("LastPreset", defaultPresetName); KisPaintOpPresetSP preset = rserver->resourceByName(lastPreset); if (!preset) { preset = rserver->resourceByName(defaultPresetName); } if (!preset && rserver->resourceCount() > 0) { preset = rserver->firstResource(); } if (preset) { paintOpBox()->restoreResource(preset); canvasResourceProvider()->setCurrentCompositeOp(preset->settings()->paintOpCompositeOp()); } } KisCanvasController *canvasController = dynamic_cast(d->currentImageView->canvasController()); d->viewConnections.addUniqueConnection(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), doc->image(), SLOT(requestStrokeEndActiveNode())); d->viewConnections.addUniqueConnection(d->rotateCanvasRight, SIGNAL(triggered()), canvasController, SLOT(rotateCanvasRight15())); d->viewConnections.addUniqueConnection(d->rotateCanvasLeft, SIGNAL(triggered()),canvasController, SLOT(rotateCanvasLeft15())); d->viewConnections.addUniqueConnection(d->resetCanvasRotation, SIGNAL(triggered()),canvasController, SLOT(resetCanvasRotation())); d->viewConnections.addUniqueConnection(d->wrapAroundAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleWrapAroundMode(bool))); d->wrapAroundAction->setChecked(canvasController->wrapAroundMode()); d->viewConnections.addUniqueConnection(d->levelOfDetailAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleLevelOfDetailMode(bool))); d->levelOfDetailAction->setChecked(canvasController->levelOfDetailMode()); d->viewConnections.addUniqueConnection(d->currentImageView->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), d->controlFrame.paintopBox(), SLOT(slotColorSpaceChanged(const KoColorSpace*))); d->viewConnections.addUniqueConnection(d->showRulersAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setShowRulers(bool))); d->viewConnections.addUniqueConnection(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setRulersTrackMouse(bool))); d->viewConnections.addUniqueConnection(d->zoomTo100pct, SIGNAL(triggered()), imageView->zoomManager(), SLOT(zoomTo100())); d->viewConnections.addUniqueConnection(d->zoomIn, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomIn())); d->viewConnections.addUniqueConnection(d->zoomOut, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomOut())); d->viewConnections.addUniqueConnection(d->softProof, SIGNAL(toggled(bool)), view, SLOT(slotSoftProofing(bool)) ); d->viewConnections.addUniqueConnection(d->gamutCheck, SIGNAL(toggled(bool)), view, SLOT(slotGamutCheck(bool)) ); // set up progrress reporting doc->image()->compositeProgressProxy()->addProxy(d->persistentImageProgressUpdater); d->viewConnections.addUniqueConnection(&d->statusBar, SIGNAL(sigCancellationRequested()), doc->image(), SLOT(requestStrokeCancellation())); d->viewConnections.addUniqueConnection(d->showPixelGrid, SIGNAL(toggled(bool)), canvasController, SLOT(slotTogglePixelGrid(bool))); imageView->zoomManager()->setShowRulers(d->showRulersAction->isChecked()); imageView->zoomManager()->setRulersTrackMouse(d->rulersTrackMouseAction->isChecked()); showHideScrollbars(); } d->filterManager.setView(imageView); d->selectionManager.setView(imageView); d->guidesManager.setView(imageView); d->nodeManager.setView(imageView); d->imageManager.setView(imageView); d->canvasControlsManager.setView(imageView); d->actionManager.setView(imageView); d->gridManager.setView(imageView); d->statusBar.setView(imageView); d->paintingAssistantsManager.setView(imageView); d->mirrorManager.setView(imageView); if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(true); d->currentImageView->canvasController()->activate(); d->currentImageView->canvasController()->setFocus(); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), canvasResourceProvider(), SLOT(slotImageSizeChanged())); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigResolutionChanged(double,double)), canvasResourceProvider(), SLOT(slotOnScreenResolutionChanged())); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(updateGUI())); d->viewConnections.addUniqueConnection( d->currentImageView->zoomManager()->zoomController(), SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)), canvasResourceProvider(), SLOT(slotOnScreenResolutionChanged())); } d->actionManager.updateGUI(); canvasResourceProvider()->slotImageSizeChanged(); canvasResourceProvider()->slotOnScreenResolutionChanged(); Q_EMIT viewChanged(); } KoZoomController *KisViewManager::zoomController() const { if (d->currentImageView) { return d->currentImageView->zoomController(); } return 0; } KisImageWSP KisViewManager::image() const { if (document()) { return document()->image(); } return 0; } KisCanvasResourceProvider * KisViewManager::canvasResourceProvider() { return &d->canvasResourceProvider; } KisCanvas2 * KisViewManager::canvasBase() const { if (d && d->currentImageView) { return d->currentImageView->canvasBase(); } return 0; } QWidget* KisViewManager::canvas() const { if (d && d->currentImageView && d->currentImageView->canvasBase()->canvasWidget()) { return d->currentImageView->canvasBase()->canvasWidget(); } return 0; } KisStatusBar * KisViewManager::statusBar() const { return &d->statusBar; } KisPaintopBox* KisViewManager::paintOpBox() const { return d->controlFrame.paintopBox(); } QPointer KisViewManager::createUnthreadedUpdater(const QString &name) { return d->persistentUnthreadedProgressUpdaterRouter->startSubtask(1, name, false); } QPointer KisViewManager::createThreadedUpdater(const QString &name) { return d->statusBar.progressUpdater()->startSubtask(1, name, false); } KisSelectionManager * KisViewManager::selectionManager() { return &d->selectionManager; } KisNodeSP KisViewManager::activeNode() { return d->nodeManager.activeNode(); } KisLayerSP KisViewManager::activeLayer() { return d->nodeManager.activeLayer(); } KisPaintDeviceSP KisViewManager::activeDevice() { return d->nodeManager.activePaintDevice(); } KisZoomManager * KisViewManager::zoomManager() { if (d->currentImageView) { return d->currentImageView->zoomManager(); } return 0; } KisFilterManager * KisViewManager::filterManager() { return &d->filterManager; } KisImageManager * KisViewManager::imageManager() { return &d->imageManager; } KisInputManager* KisViewManager::inputManager() const { return &d->inputManager; } KisSelectionSP KisViewManager::selection() { if (d->currentImageView) { return d->currentImageView->selection(); } return 0; } bool KisViewManager::selectionEditable() { KisLayerSP layer = activeLayer(); if (layer) { KisSelectionMaskSP mask = layer->selectionMask(); if (mask) { return mask->isEditable(); } } // global selection is always editable return true; } KisUndoAdapter * KisViewManager::undoAdapter() { if (!document()) return 0; KisImageWSP image = document()->image(); Q_ASSERT(image); return image->undoAdapter(); } void KisViewManager::createActions() { KisConfig cfg(true); d->saveIncremental = actionManager()->createAction("save_incremental_version"); connect(d->saveIncremental, SIGNAL(triggered()), this, SLOT(slotSaveIncremental())); d->saveIncrementalBackup = actionManager()->createAction("save_incremental_backup"); connect(d->saveIncrementalBackup, SIGNAL(triggered()), this, SLOT(slotSaveIncrementalBackup())); connect(mainWindow(), SIGNAL(documentSaved()), this, SLOT(slotDocumentSaved())); d->saveIncremental->setEnabled(false); d->saveIncrementalBackup->setEnabled(false); KisAction *tabletDebugger = actionManager()->createAction("tablet_debugger"); connect(tabletDebugger, SIGNAL(triggered()), this, SLOT(toggleTabletLogger())); d->createTemplate = actionManager()->createAction("create_template"); connect(d->createTemplate, SIGNAL(triggered()), this, SLOT(slotCreateTemplate())); d->createCopy = actionManager()->createAction("create_copy"); connect(d->createCopy, SIGNAL(triggered()), this, SLOT(slotCreateCopy())); d->openResourcesDirectory = actionManager()->createAction("open_resources_directory"); connect(d->openResourcesDirectory, SIGNAL(triggered()), SLOT(openResourcesDirectory())); d->rotateCanvasRight = actionManager()->createAction("rotate_canvas_right"); d->rotateCanvasLeft = actionManager()->createAction("rotate_canvas_left"); d->resetCanvasRotation = actionManager()->createAction("reset_canvas_rotation"); d->wrapAroundAction = actionManager()->createAction("wrap_around_mode"); d->levelOfDetailAction = actionManager()->createAction("level_of_detail_mode"); d->softProof = actionManager()->createAction("softProof"); d->gamutCheck = actionManager()->createAction("gamutCheck"); KisAction *tAction = actionManager()->createAction("showStatusBar"); tAction->setChecked(cfg.showStatusBar()); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(showStatusBar(bool))); tAction = actionManager()->createAction("view_show_canvas_only"); tAction->setChecked(false); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(switchCanvasOnly(bool))); //Workaround, by default has the same shortcut as mirrorCanvas KisAction *a = dynamic_cast(actionCollection()->action("format_italic")); if (a) { a->setDefaultShortcut(QKeySequence()); } actionManager()->createAction("ruler_pixel_multiple2"); d->showRulersAction = actionManager()->createAction("view_ruler"); d->showRulersAction->setChecked(cfg.showRulers()); connect(d->showRulersAction, SIGNAL(toggled(bool)), SLOT(slotSaveShowRulersState(bool))); d->rulersTrackMouseAction = actionManager()->createAction("rulers_track_mouse"); d->rulersTrackMouseAction->setChecked(cfg.rulersTrackMouse()); connect(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), SLOT(slotSaveRulersTrackMouseState(bool))); d->zoomTo100pct = actionManager()->createAction("zoom_to_100pct"); d->zoomIn = actionManager()->createStandardAction(KStandardAction::ZoomIn, 0, ""); d->zoomOut = actionManager()->createStandardAction(KStandardAction::ZoomOut, 0, ""); d->actionAuthor = new KSelectAction(KisIconUtils::loadIcon("im-user"), i18n("Active Author Profile"), this); connect(d->actionAuthor, SIGNAL(triggered(QString)), this, SLOT(changeAuthorProfile(QString))); actionCollection()->addAction("settings_active_author", d->actionAuthor); slotUpdateAuthorProfileActions(); d->showPixelGrid = actionManager()->createAction("view_pixel_grid"); slotUpdatePixelGridAction(); d->toggleFgBg = actionManager()->createAction("toggle_fg_bg"); connect(d->toggleFgBg, SIGNAL(triggered(bool)), this, SLOT(slotToggleFgBg())); d->resetFgBg = actionManager()->createAction("reset_fg_bg"); connect(d->resetFgBg, SIGNAL(triggered(bool)), this, SLOT(slotResetFgBg())); } void KisViewManager::setupManagers() { // Create the managers for filters, selections, layers etc. // XXX: When the currentlayer changes, call updateGUI on all // managers d->filterManager.setup(actionCollection(), actionManager()); d->selectionManager.setup(actionManager()); d->guidesManager.setup(actionManager()); d->nodeManager.setup(actionCollection(), actionManager()); d->imageManager.setup(actionManager()); d->gridManager.setup(actionManager()); d->paintingAssistantsManager.setup(actionManager()); d->canvasControlsManager.setup(actionManager()); d->mirrorManager.setup(actionCollection()); } void KisViewManager::updateGUI() { d->guiUpdateCompressor.start(); } KisNodeManager * KisViewManager::nodeManager() const { return &d->nodeManager; } KisActionManager* KisViewManager::actionManager() const { return &d->actionManager; } KisGridManager * KisViewManager::gridManager() const { return &d->gridManager; } KisGuidesManager * KisViewManager::guidesManager() const { return &d->guidesManager; } KisDocument *KisViewManager::document() const { if (d->currentImageView && d->currentImageView->document()) { return d->currentImageView->document(); } return 0; } int KisViewManager::viewCount() const { KisMainWindow *mw = qobject_cast(d->mainWindow); if (mw) { return mw->viewCount(); } return 0; } bool KisViewManager::KisViewManagerPrivate::blockUntilOperationsFinishedImpl(KisImageSP image, bool force) { const int busyWaitDelay = 1000; KisDelayedSaveDialog dialog(image, !force ? KisDelayedSaveDialog::GeneralDialog : KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, mainWindow); dialog.blockIfImageIsBusy(); return dialog.result() == QDialog::Accepted; } bool KisViewManager::blockUntilOperationsFinished(KisImageSP image) { return d->blockUntilOperationsFinishedImpl(image, false); } void KisViewManager::blockUntilOperationsFinishedForced(KisImageSP image) { d->blockUntilOperationsFinishedImpl(image, true); } void KisViewManager::slotCreateTemplate() { if (!document()) return; KisTemplateCreateDia::createTemplate( QStringLiteral("templates/"), ".kra", document(), mainWindow()); } void KisViewManager::slotCreateCopy() { KisDocument *srcDoc = document(); if (!srcDoc) return; if (!this->blockUntilOperationsFinished(srcDoc->image())) return; KisDocument *doc = 0; { KisImageBarrierLocker l(srcDoc->image()); doc = srcDoc->clone(); } KIS_SAFE_ASSERT_RECOVER_RETURN(doc); QString name = srcDoc->documentInfo()->aboutInfo("name"); if (name.isEmpty()) { name = document()->url().toLocalFile(); } name = i18n("%1 (Copy)", name); doc->documentInfo()->setAboutInfo("title", name); KisPart::instance()->addDocument(doc); KisMainWindow *mw = qobject_cast(d->mainWindow); mw->addViewAndNotifyLoadingCompleted(doc); } QMainWindow* KisViewManager::qtMainWindow() const { if (d->mainWindow) return d->mainWindow; //Fallback for when we have not yet set the main window. QMainWindow* w = qobject_cast(qApp->activeWindow()); if(w) return w; return mainWindow(); } void KisViewManager::setQtMainWindow(QMainWindow* newMainWindow) { d->mainWindow = newMainWindow; } void KisViewManager::slotDocumentSaved() { d->saveIncremental->setEnabled(true); d->saveIncrementalBackup->setEnabled(true); } void KisViewManager::slotSaveIncremental() { if (!document()) return; if (document()->url().isEmpty()) { KisMainWindow *mw = qobject_cast(d->mainWindow); mw->saveDocument(document(), true, false); return; } bool foundVersion; bool fileAlreadyExists; bool isBackup; QString version = "000"; QString newVersion; QString letter; QString path = QFileInfo(document()->localFilePath()).canonicalPath(); QString fileName = QFileInfo(document()->localFilePath()).fileName(); // Find current version filenames // v v Regexp to find incremental versions in the filename, taking our backup scheme into account as well // Considering our incremental version and backup scheme, format is filename_001~001.ext QRegExp regex("_\\d{1,4}[.]|_\\d{1,4}[a-z][.]|_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); foundVersion = matches.at(0).isEmpty() ? false : true; // Ensure compatibility with Save Incremental Backup // If this regex is not kept separate, the entire algorithm needs modification; // It's simpler to just add this. QRegExp regexAux("_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regexAux.indexIn(fileName); // Perform the search QStringList matchesAux = regexAux.capturedTexts(); isBackup = matchesAux.at(0).isEmpty() ? false : true; // If the filename has a version, prepare it for incrementation if (foundVersion) { version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "_" } else { // TODO: this will not work with files extensions like jp2 // ...else, simply add a version to it so the next loop works QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(fileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(version); extensionPlusVersion.prepend("_"); fileName.replace(regex2, extensionPlusVersion); } // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("_"); if (!letter.isNull()) newVersion.append(letter); if (isBackup) { newVersion.append("~"); } else { newVersion.append("."); } fileName.replace(regex, newVersion); fileAlreadyExists = QFileInfo(path + '/' + fileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental version"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } QUrl newUrl = QUrl::fromUserInput(path + '/' + fileName); document()->setFileBatchMode(true); document()->saveAs(newUrl, document()->mimeType(), true); document()->setFileBatchMode(false); KisPart::instance()->addRecentURLToAllMainWindows(newUrl, document()->url()); if (mainWindow()) { mainWindow()->updateCaption(); } } void KisViewManager::slotSaveIncrementalBackup() { if (!document()) return; if (document()->url().isEmpty()) { KisMainWindow *mw = qobject_cast(d->mainWindow); mw->saveDocument(document(), true, false); return; } bool workingOnBackup; bool fileAlreadyExists; QString version = "000"; QString newVersion; QString letter; QString path = QFileInfo(document()->localFilePath()).canonicalPath(); QString fileName = QFileInfo(document()->localFilePath()).fileName(); // First, discover if working on a backup file, or a normal file QRegExp regex("~\\d{1,4}[.]|~\\d{1,4}[a-z][.]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); workingOnBackup = matches.at(0).isEmpty() ? false : true; if (workingOnBackup) { // Try to save incremental version (of backup), use letter for alt versions version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "~" // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); QString backupFileName = document()->localFilePath(); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("~"); if (!letter.isNull()) newVersion.append(letter); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental backup"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } QFile::copy(path + '/' + fileName, path + '/' + backupFileName); document()->saveAs(QUrl::fromUserInput(path + '/' + fileName), document()->mimeType(), true); if (mainWindow()) mainWindow()->updateCaption(); } else { // if NOT working on a backup... // Navigate directory searching for latest backup version, ignore letters const quint8 HARDCODED_DIGIT_COUNT = 3; QString baseNewVersion = "000"; QString backupFileName = QFileInfo(document()->localFilePath()).fileName(); QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(backupFileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(baseNewVersion); extensionPlusVersion.prepend("~"); backupFileName.replace(regex2, extensionPlusVersion); // Save version with 1 number higher than the highest version found ignoring letters do { newVersion = baseNewVersion; newVersion.prepend("~"); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { // Prepare the base for new version filename, increment by 1 int intVersion = baseNewVersion.toInt(0); ++intVersion; baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < HARDCODED_DIGIT_COUNT) { baseNewVersion.prepend("0"); } } } while (fileAlreadyExists); // Save both as backup and on current file for interapplication workflow document()->setFileBatchMode(true); QFile::copy(path + '/' + fileName, path + '/' + backupFileName); document()->saveAs(QUrl::fromUserInput(path + '/' + fileName), document()->mimeType(), true); document()->setFileBatchMode(false); if (mainWindow()) mainWindow()->updateCaption(); } } void KisViewManager::disableControls() { // prevents possible crashes, if somebody changes the paintop during dragging by using the mousewheel // this is for Bug 250944 // the solution blocks all wheel, mouse and key event, while dragging with the freehand tool // see KisToolFreehand::initPaint() and endPaint() d->controlFrame.paintopBox()->installEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->installEventFilter(&d->blockingEventFilter); } } void KisViewManager::enableControls() { d->controlFrame.paintopBox()->removeEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->removeEventFilter(&d->blockingEventFilter); } } void KisViewManager::showStatusBar(bool toggled) { KisMainWindow *mw = mainWindow(); if(mw && mw->statusBar()) { mw->statusBar()->setVisible(toggled); KisConfig cfg(false); cfg.setShowStatusBar(toggled); } } void KisViewManager::switchCanvasOnly(bool toggled) { KisConfig cfg(false); KisMainWindow *main = mainWindow(); if(!main) { dbgUI << "Unable to switch to canvas-only mode, main window not found"; return; } cfg.writeEntry("CanvasOnlyActive", toggled); if (toggled) { d->canvasState = qtMainWindow()->saveState(); #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) d->windowFlags = main->windowState(); #endif } if (cfg.hideStatusbarFullscreen()) { if (main->statusBar()) { if (!toggled) { if (main->statusBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->statusBar()->property("wasvisible").toBool()) { main->statusBar()->setVisible(true); } } } else { main->statusBar()->setProperty("wasvisible", main->statusBar()->isVisible()); main->statusBar()->setVisible(false); } } } if (cfg.hideDockersFullscreen()) { KisAction* action = qobject_cast(main->actionCollection()->action("view_toggledockers")); if (action) { action->setCheckable(true); if (toggled) { if (action->isChecked()) { cfg.setShowDockers(action->isChecked()); action->setChecked(false); } else { cfg.setShowDockers(false); } } else { action->setChecked(cfg.showDockers()); } } } // QT in windows does not return to maximized upon 4th tab in a row // https://bugreports.qt.io/browse/QTBUG-57882, https://bugreports.qt.io/browse/QTBUG-52555, https://codereview.qt-project.org/#/c/185016/ if (cfg.hideTitlebarFullscreen() && !cfg.fullscreenMode()) { if(toggled) { main->setWindowState( main->windowState() | Qt::WindowFullScreen); } else { main->setWindowState( main->windowState() & ~Qt::WindowFullScreen); #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) // If window was maximized prior to fullscreen, restore that if (d->windowFlags & Qt::WindowMaximized) { main->setWindowState( main->windowState() | Qt::WindowMaximized); } #endif } } if (cfg.hideMenuFullscreen()) { if (!toggled) { if (main->menuBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->menuBar()->property("wasvisible").toBool()) { main->menuBar()->setVisible(true); } } } else { main->menuBar()->setProperty("wasvisible", main->menuBar()->isVisible()); main->menuBar()->setVisible(false); } } if (cfg.hideToolbarFullscreen()) { QList toolBars = main->findChildren(); Q_FOREACH (QToolBar* toolbar, toolBars) { if (!toggled) { if (toolbar->dynamicPropertyNames().contains("wasvisible")) { if (toolbar->property("wasvisible").toBool()) { toolbar->setVisible(true); } } } else { toolbar->setProperty("wasvisible", toolbar->isVisible()); toolbar->setVisible(false); } } } showHideScrollbars(); if (toggled) { // show a fading heads-up display about the shortcut to go back showFloatingMessage(i18n("Going into Canvas-Only mode.\nPress %1 to go back.", actionCollection()->action("view_show_canvas_only")->shortcut().toString()), QIcon()); } else { main->restoreState(d->canvasState); } } void KisViewManager::toggleTabletLogger() { d->inputManager.toggleTabletLogger(); } void KisViewManager::openResourcesDirectory() { QString dir = KoResourcePaths::locateLocal("data", ""); QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); } void KisViewManager::updateIcons() { if (mainWindow()) { QList dockers = mainWindow()->dockWidgets(); Q_FOREACH (QDockWidget* dock, dockers) { QObjectList objects; objects.append(dock); while (!objects.isEmpty()) { QObject* object = objects.takeFirst(); objects.append(object->children()); KisIconUtils::updateIconCommon(object); } } } } void KisViewManager::initializeStatusBarVisibility() { KisConfig cfg(true); d->mainWindow->statusBar()->setVisible(cfg.showStatusBar()); } void KisViewManager::guiUpdateTimeout() { d->nodeManager.updateGUI(); d->selectionManager.updateGUI(); d->filterManager.updateGUI(); if (zoomManager()) { zoomManager()->updateGuiAfterDocumentSize(); } d->gridManager.updateGUI(); d->actionManager.updateGUI(); } void KisViewManager::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if (!d->currentImageView) return; d->currentImageView->showFloatingMessage(message, icon, timeout, priority, alignment); emit floatingMessageRequested(message, icon.name()); } KisMainWindow *KisViewManager::mainWindow() const { return qobject_cast(d->mainWindow); } void KisViewManager::showHideScrollbars() { if (!d->currentImageView) return; if (!d->currentImageView->canvasController()) return; KisConfig cfg(true); bool toggled = actionCollection()->action("view_show_canvas_only")->isChecked(); if ( (toggled && cfg.hideScrollbarsFullscreen()) || (!toggled && cfg.hideScrollbars()) ) { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } else { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } void KisViewManager::slotSaveShowRulersState(bool value) { KisConfig cfg(false); cfg.setShowRulers(value); } void KisViewManager::slotSaveRulersTrackMouseState(bool value) { KisConfig cfg(false); cfg.setRulersTrackMouse(value); } void KisViewManager::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisViewManager::changeAuthorProfile(const QString &profileName) { KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author"); if (profileName.isEmpty() || profileName == i18nc("choice for author profile", "Anonymous")) { appAuthorGroup.writeEntry("active-profile", ""); } else { appAuthorGroup.writeEntry("active-profile", profileName); } appAuthorGroup.sync(); Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) { doc->documentInfo()->updateParameters(); } } void KisViewManager::slotUpdateAuthorProfileActions() { Q_ASSERT(d->actionAuthor); if (!d->actionAuthor) { return; } d->actionAuthor->clear(); d->actionAuthor->addAction(i18nc("choice for author profile", "Anonymous")); KConfigGroup authorGroup(KSharedConfig::openConfig(), "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); QString authorInfo = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/authorinfo/"; QStringList filters = QStringList() << "*.authorinfo"; QDir dir(authorInfo); Q_FOREACH(QString entry, dir.entryList(filters)) { int ln = QString(".authorinfo").size(); entry.chop(ln); if (!profiles.contains(entry)) { profiles.append(entry); } } Q_FOREACH (const QString &profile , profiles) { d->actionAuthor->addAction(profile); } KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author"); QString profileName = appAuthorGroup.readEntry("active-profile", ""); if (profileName == "anonymous" || profileName.isEmpty()) { d->actionAuthor->setCurrentItem(0); } else if (profiles.contains(profileName)) { d->actionAuthor->setCurrentAction(profileName); } } void KisViewManager::slotUpdatePixelGridAction() { KIS_SAFE_ASSERT_RECOVER_RETURN(d->showPixelGrid); KisSignalsBlocker b(d->showPixelGrid); KisConfig cfg(true); d->showPixelGrid->setChecked(cfg.pixelGridEnabled() && cfg.useOpenGL()); } void KisViewManager::slotActivateTransformTool() { if(KoToolManager::instance()->activeToolId() == "KisToolTransform") { KoToolBase* tool = KoToolManager::instance()->toolById(canvasBase(), "KisToolTransform"); QSet dummy; // Start a new stroke tool->deactivate(); tool->activate(KoToolBase::DefaultActivation, dummy); } KoToolManager::instance()->switchToolRequested("KisToolTransform"); } void KisViewManager::slotToggleFgBg() { KoColor newFg = d->canvasResourceManager.backgroundColor(); KoColor newBg = d->canvasResourceManager.foregroundColor(); /** * NOTE: Some of color selectors do not differentiate foreground * and background colors, so if one wants them to end up * being set up to foreground color, it should be set the * last. */ d->canvasResourceManager.setBackgroundColor(newBg); d->canvasResourceManager.setForegroundColor(newFg); } void KisViewManager::slotResetFgBg() { // see a comment in slotToggleFgBg() d->canvasResourceManager.setBackgroundColor(KoColor(Qt::white, KoColorSpaceRegistry::instance()->rgb8())); d->canvasResourceManager.setForegroundColor(KoColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8())); } void KisViewManager::slotResetRotation() { KisCanvasController *canvasController = d->currentImageView->canvasController(); canvasController->resetCanvasRotation(); } void KisViewManager::slotToggleFullscreen() { KisConfig cfg(false); KisMainWindow *main = mainWindow(); main->viewFullscreen(!main->isFullScreen()); cfg.fullscreenMode(main->isFullScreen()); } diff --git a/libs/ui/dialogs/KisSessionManagerDialog.cpp b/libs/ui/dialogs/KisSessionManagerDialog.cpp index 5303fa9746..51a03ec9c7 100644 --- a/libs/ui/dialogs/KisSessionManagerDialog.cpp +++ b/libs/ui/dialogs/KisSessionManagerDialog.cpp @@ -1,203 +1,203 @@ /* * Copyright (c) 2018 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "KisSessionManagerDialog.h" int KisSessionManagerDialog::refreshEventType = -1; KisSessionManagerDialog::KisSessionManagerDialog(QWidget *parent) : QDialog(parent) { setupUi(this); // Register the custom event type that is used to defer UI updates if (refreshEventType == -1) { refreshEventType = QEvent::registerEventType(); } connect(btnNew, SIGNAL(clicked()), this, SLOT(slotNewSession())); connect(btnRename, SIGNAL(clicked()), this, SLOT(slotRenameSession())); connect(btnSwitchTo, SIGNAL(clicked()), this, SLOT(slotSwitchSession())); connect(btnDelete, SIGNAL(clicked()), this, SLOT(slotDeleteSession())); connect(btnClose, SIGNAL(clicked()), this, SLOT(slotClose())); m_model = KisResourceModelProvider::resourceModel(ResourceType::Sessions); lstSessions->setModel(m_model); - lstSessions->setModelColumn(KisResourceModel::Name); + lstSessions->setModelColumn(KisAbstractResourceModel::Name); connect(m_model, SIGNAL(beforeResourcesLayoutReset(QModelIndex)), this, SLOT(slotModelAboutToBeReset(QModelIndex))); connect(m_model, SIGNAL(afterResourcesLayoutReset()), this, SLOT(slotModelReset())); connect(lstSessions, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotSessionDoubleClicked(QModelIndex))); connect(lstSessions->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(slotModelSelectionChanged(QItemSelection, QItemSelection))); updateButtons(); } bool KisSessionManagerDialog::event(QEvent *event) { if (event->type() == (QEvent::Type) refreshEventType) { // Do the actual work of updating the button state when receiving a custom event bool hasSelectedSession = getSelectedSession() != nullptr; btnDelete->setEnabled(hasSelectedSession); btnSwitchTo->setEnabled(hasSelectedSession); btnRename->setEnabled(hasSelectedSession); return true; } else { return QDialog::event(event); } } void KisSessionManagerDialog::updateButtons() { // Defer updating the buttons by posting a custom event with low priority to avoid locking against // a non-recursive session lock that may be already held by the thread QApplication::postEvent(this, new QEvent((QEvent::Type) refreshEventType), Qt::LowEventPriority); } void KisSessionManagerDialog::slotNewSession() { QString name; KisSessionResourceSP session(new KisSessionResource(QString())); KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); QString saveLocation = server->saveLocation(); QFileInfo fileInfo(saveLocation + name.split(" ").join("_") + session->defaultFileExtension()); bool fileOverwriteAccepted = false; while(!fileOverwriteAccepted) { name = QInputDialog::getText(this, i18n("Create session"), i18n("Session name:"), QLineEdit::Normal, name); if (name.isNull() || name.isEmpty()) { return; } else { fileInfo = QFileInfo(saveLocation + name.split(" ").join("_") + session->defaultFileExtension()); if (fileInfo.exists()) { int res = QMessageBox::warning(this, i18nc("@title:window", "Name Already Exists") , i18n("The name '%1' already exists, do you wish to overwrite it?", name) , QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (res == QMessageBox::Yes) fileOverwriteAccepted = true; } else { fileOverwriteAccepted = true; } } } session->setFilename(fileInfo.fileName()); session->setName(name); session->storeCurrentWindows(); server->addResource(session); KisPart::instance()->setCurrentSession(session); } void KisSessionManagerDialog::slotRenameSession() { QString name = QInputDialog::getText(this, i18n("Rename session"), i18n("New name:"), QLineEdit::Normal ); if (name.isNull() || name.isEmpty()) return; KisSessionResourceSP session = getSelectedSession(); if (!session) return; m_model->renameResource(session, name); } void KisSessionManagerDialog::slotSessionDoubleClicked(QModelIndex /*item*/) { slotSwitchSession(); slotClose(); } void KisSessionManagerDialog::slotSwitchSession() { KisSessionResourceSP session = getSelectedSession(); if (session) { bool closed = KisPart::instance()->closeSession(true); if (closed) { KisPart::instance()->restoreSession(session); } } } KisSessionResourceSP KisSessionManagerDialog::getSelectedSession() const { QModelIndex idx = lstSessions->currentIndex(); if (idx.isValid()) { KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); - QString name = m_model->data(idx, Qt::UserRole + KisResourceModel::Name).toString(); + QString name = m_model->data(idx, Qt::UserRole + KisAbstractResourceModel::Name).toString(); return server->resourceByName(name); } return nullptr; } void KisSessionManagerDialog::slotDeleteSession() { QModelIndex idx = lstSessions->currentIndex(); if (idx.isValid()) { - m_model->removeResource(lstSessions->currentIndex()); + m_model->setResourceInactive(lstSessions->currentIndex()); } } void KisSessionManagerDialog::slotClose() { hide(); } void KisSessionManagerDialog::slotModelAboutToBeReset(QModelIndex) { QModelIndex idx = lstSessions->currentIndex(); if (idx.isValid()) { - m_lastSessionId = m_model->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); + m_lastSessionId = m_model->data(idx, Qt::UserRole + KisAbstractResourceModel::Id).toInt(); } } void KisSessionManagerDialog::slotModelReset() { for (int i = 0; i < m_model->rowCount(); i++) { QModelIndex idx = m_model->index(i, 0); - int id = m_model->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); + int id = m_model->data(idx, Qt::UserRole + KisAbstractResourceModel::Id).toInt(); if (id == m_lastSessionId) { lstSessions->setCurrentIndex(idx); } } updateButtons(); } void KisSessionManagerDialog::slotModelSelectionChanged(QItemSelection selected, QItemSelection deselected) { (void) selected; (void) deselected; updateButtons(); } diff --git a/libs/ui/dialogs/kis_dlg_layer_style.cpp b/libs/ui/dialogs/kis_dlg_layer_style.cpp index f265de6d6c..1c92a90590 100644 --- a/libs/ui/dialogs/kis_dlg_layer_style.cpp +++ b/libs/ui/dialogs/kis_dlg_layer_style.cpp @@ -1,1476 +1,1476 @@ /* * Copyright (c) 2014 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_layer_style.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_cmb_contour.h" #include "kis_cmb_gradient.h" #include "KisResourceServerProvider.h" #include "kis_psd_layer_style.h" #include #include #include "kis_signals_blocker.h" #include "kis_signal_compressor.h" #include "kis_canvas_resource_provider.h" #include #include KoAbstractGradientSP fetchGradientLazy(KoAbstractGradientSP gradient, KisCanvasResourceProvider *resourceProvider) { if (!gradient) { gradient = resourceProvider->currentGradient(); } return gradient; } KisDlgLayerStyle::KisDlgLayerStyle(KisPSDLayerStyleSP layerStyle, KisCanvasResourceProvider *resourceProvider, QWidget *parent) : KoDialog(parent) , m_layerStyle(layerStyle) , m_initialLayerStyle(layerStyle->clone().dynamicCast()) , m_isSwitchingPredefinedStyle(false) , m_sanityLayerStyleDirty(false) { setCaption(i18n("Layer Styles")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_configChangedCompressor = new KisSignalCompressor(1000, KisSignalCompressor::POSTPONE, this); connect(m_configChangedCompressor, SIGNAL(timeout()), SIGNAL(configChanged())); QWidget *page = new QWidget(this); wdgLayerStyles.setupUi(page); setMainWidget(page); wdgLayerStyles.chkPreview->setVisible(false); connect(wdgLayerStyles.lstStyleSelector, SIGNAL(itemChanged(QListWidgetItem*)), SLOT(notifyGuiConfigChanged())); m_stylesSelector = new StylesSelector(this); connect(m_stylesSelector, SIGNAL(styleSelected(KisPSDLayerStyleSP)), SLOT(notifyPredefinedStyleSelected(KisPSDLayerStyleSP))); wdgLayerStyles.stylesStack->addWidget(m_stylesSelector); m_blendingOptions = new BlendingOptions(this); wdgLayerStyles.stylesStack->addWidget(m_blendingOptions); m_dropShadow = new DropShadow(DropShadow::DropShadowMode, this); wdgLayerStyles.stylesStack->addWidget(m_dropShadow); connect(m_dropShadow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_innerShadow = new DropShadow(DropShadow::InnerShadowMode, this); wdgLayerStyles.stylesStack->addWidget(m_innerShadow); connect(m_innerShadow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_outerGlow = new InnerGlow(InnerGlow::OuterGlowMode, resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_outerGlow); connect(m_outerGlow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_innerGlow = new InnerGlow(InnerGlow::InnerGlowMode, resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_innerGlow); connect(m_innerGlow, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); // Contour and Texture are sub-styles of Bevel and Emboss // They are only applied to canvas when Bevel and Emboss is active. m_contour = new Contour(this); m_texture = new Texture(this); m_bevelAndEmboss = new BevelAndEmboss(m_contour, m_texture, this); wdgLayerStyles.stylesStack->addWidget(m_bevelAndEmboss); wdgLayerStyles.stylesStack->addWidget(m_contour); wdgLayerStyles.stylesStack->addWidget(m_texture); // slotBevelAndEmbossChanged(QListWidgetItem*) enables/disables Contour and Texture on "Bevel and Emboss" toggle. connect(wdgLayerStyles.lstStyleSelector, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(slotBevelAndEmbossChanged(QListWidgetItem*))); connect(m_bevelAndEmboss, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_satin = new Satin(this); wdgLayerStyles.stylesStack->addWidget(m_satin); connect(m_satin, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_colorOverlay = new ColorOverlay(this); wdgLayerStyles.stylesStack->addWidget(m_colorOverlay); connect(m_colorOverlay, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_gradientOverlay = new GradientOverlay(resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_gradientOverlay); connect(m_gradientOverlay, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_patternOverlay = new PatternOverlay(this); wdgLayerStyles.stylesStack->addWidget(m_patternOverlay); connect(m_patternOverlay, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); m_stroke = new Stroke(resourceProvider, this); wdgLayerStyles.stylesStack->addWidget(m_stroke); connect(m_stroke, SIGNAL(configChanged()), SLOT(notifyGuiConfigChanged())); KisConfig cfg(true); wdgLayerStyles.stylesStack->setCurrentIndex(cfg.readEntry("KisDlgLayerStyle::current", 1)); wdgLayerStyles.lstStyleSelector->setCurrentRow(cfg.readEntry("KisDlgLayerStyle::current", 1)); connect(wdgLayerStyles.lstStyleSelector, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*))); // improve the checkbox visibility by altering the style sheet list a bit // the dark themes make them hard to see QPalette newPalette = palette(); newPalette.setColor(QPalette::Active, QPalette::Background, palette().text().color() ); wdgLayerStyles.lstStyleSelector->setPalette(newPalette); notifyPredefinedStyleSelected(layerStyle); connect(m_dropShadow, SIGNAL(globalAngleChanged(int)), SLOT(syncGlobalAngle(int))); connect(m_innerShadow, SIGNAL(globalAngleChanged(int)), SLOT(syncGlobalAngle(int))); connect(m_bevelAndEmboss, SIGNAL(globalAngleChanged(int)), SLOT(syncGlobalAngle(int))); connect(wdgLayerStyles.btnNewStyle, SIGNAL(clicked()), SLOT(slotNewStyle())); connect(wdgLayerStyles.btnLoadStyle, SIGNAL(clicked()), SLOT(slotLoadStyle())); connect(wdgLayerStyles.btnSaveStyle, SIGNAL(clicked()), SLOT(slotSaveStyle())); connect(wdgLayerStyles.chkMasterFxSwitch, SIGNAL(toggled(bool)), SLOT(slotMasterFxSwitchChanged(bool))); connect(this, SIGNAL(accepted()), SLOT(slotNotifyOnAccept())); connect(this, SIGNAL(rejected()), SLOT(slotNotifyOnReject())); } KisDlgLayerStyle::~KisDlgLayerStyle() { } void KisDlgLayerStyle::slotMasterFxSwitchChanged(bool value) { wdgLayerStyles.lstStyleSelector->setEnabled(value); wdgLayerStyles.stylesStack->setEnabled(value); wdgLayerStyles.btnNewStyle->setEnabled(value); wdgLayerStyles.btnLoadStyle->setEnabled(value); wdgLayerStyles.btnSaveStyle->setEnabled(value); notifyGuiConfigChanged(); } void KisDlgLayerStyle::notifyGuiConfigChanged() { if (m_isSwitchingPredefinedStyle) return; m_configChangedCompressor->start(); m_layerStyle->setUuid(QUuid::createUuid()); m_sanityLayerStyleDirty = true; m_stylesSelector->notifyExternalStyleChanged(m_layerStyle->name(), m_layerStyle->uuid()); } void KisDlgLayerStyle::notifyPredefinedStyleSelected(KisPSDLayerStyleSP style) { m_isSwitchingPredefinedStyle = true; setStyle(style); m_isSwitchingPredefinedStyle = false; m_configChangedCompressor->start(); } void KisDlgLayerStyle::slotBevelAndEmbossChanged(QListWidgetItem*) { QListWidgetItem *item; if (wdgLayerStyles.lstStyleSelector->item(6)->checkState() == Qt::Checked) { // Enable "Contour" (list item 7) item = wdgLayerStyles.lstStyleSelector->item(7); Qt::ItemFlags currentFlags7 = item->flags(); item->setFlags(currentFlags7 | Qt::ItemIsEnabled); // Enable "Texture" (list item 8) item = wdgLayerStyles.lstStyleSelector->item(8); Qt::ItemFlags currentFlags8 = item->flags(); item->setFlags(currentFlags8 | Qt::ItemIsEnabled); } else { // Disable "Contour" item = wdgLayerStyles.lstStyleSelector->item(7); Qt::ItemFlags currentFlags7 = item->flags(); item->setFlags(currentFlags7 & (~Qt::ItemIsEnabled)); // Disable "Texture" item = wdgLayerStyles.lstStyleSelector->item(8); Qt::ItemFlags currentFlags8 = item->flags(); item->setFlags(currentFlags8 & (~Qt::ItemIsEnabled)); } } void KisDlgLayerStyle::slotNotifyOnAccept() { if (m_configChangedCompressor->isActive()) { m_configChangedCompressor->stop(); emit configChanged(); } } void KisDlgLayerStyle::slotNotifyOnReject() { notifyPredefinedStyleSelected(m_initialLayerStyle); m_configChangedCompressor->stop(); emit configChanged(); } bool checkCustomNameAvailable(const QString &name) { const QString customName = "CustomStyles.asl"; KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleServer(); KoResourceSP resource = server->resourceByName(customName); return !resource; } QString selectAvailableStyleName(const QString &name) { QString finalName = name; if (checkCustomNameAvailable(finalName)) { return finalName; } int i = 0; do { finalName = QString("%1%2").arg(name).arg(i++); } while (!checkCustomNameAvailable(finalName)); return finalName; } void KisDlgLayerStyle::slotNewStyle() { QString styleName = QInputDialog::getText(this, i18nc("@title:window", "Enter new style name"), i18nc("@label:textbox", "Name:"), QLineEdit::Normal, i18nc("Default name for a new style", "New Style")); KisPSDLayerStyleSP style = this->style(); style->setName(selectAvailableStyleName(styleName)); m_stylesSelector->addNewStyle(style->clone().dynamicCast()); } QString createNewAslPath(QString resourceFolderPath, QString filename) { return resourceFolderPath + '/' + "asl" + '/' + filename; } void KisDlgLayerStyle::slotLoadStyle() { QString filename; // default value? KoFileDialog dialog(this, KoFileDialog::OpenFile, "layerstyle"); dialog.setCaption(i18n("Select ASL file")); dialog.setMimeTypeFilters(QStringList() << "application/x-photoshop-style-library", "application/x-photoshop-style-library"); filename = dialog.filename(); QFileInfo oldFileInfo(filename); KisConfig cfg(true); QString newDir = cfg.readEntry(KisResourceLocator::resourceLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); QString newName = oldFileInfo.fileName(); QString newLocation = createNewAslPath(newDir, newName); QFileInfo newFileInfo(newLocation); if (newFileInfo.exists()) { bool done = false; int i = 0; do { // ask for new filename bool ok; newName = QInputDialog::getText(this, i18n("New name for ASL storage"), i18n("The old filename is taken.\nNew name:"), QLineEdit::Normal, newName, &ok); newLocation = createNewAslPath(newDir, newName); newFileInfo.setFile(newLocation); done = !newFileInfo.exists(); i++; } while (!done); } QFile::copy(filename, newLocation); KisResourceStorageSP storage = QSharedPointer::create(newLocation); KIS_ASSERT(!storage.isNull()); KisResourceLocator::instance()->addStorage(newLocation, storage); } void KisDlgLayerStyle::slotSaveStyle() { // TODO RESOURCES: needs figuring out warnKrita << "Layer style cannot be saved; needs figuring out what to do here"; /* QString filename; // default value? KoFileDialog dialog(this, KoFileDialog::SaveFile, "layerstyle"); dialog.setCaption(i18n("Select ASL file")); dialog.setMimeTypeFilters(QStringList() << "application/x-photoshop-style-library", "application/x-photoshop-style-library"); filename = dialog.filename(); QScopedPointer collection( new KisPSDLayerStyleCollectionResource(filename)); KisPSDLayerStyleSP newStyle = style()->clone().dynamicCast(); newStyle->setName(QFileInfo(filename).completeBaseName()); KisPSDLayerStyleCollectionResource::StylesVector vector = collection->layerStyles(); vector << newStyle; collection->setLayerStyles(vector); collection->save(); */ } void KisDlgLayerStyle::changePage(QListWidgetItem *current, QListWidgetItem *previous) { if (!current) { current = previous; } wdgLayerStyles.stylesStack->setCurrentIndex(wdgLayerStyles.lstStyleSelector->row(current)); } void KisDlgLayerStyle::setStyle(KisPSDLayerStyleSP style) { // we may self-assign style is some cases if (style != m_layerStyle) { m_layerStyle = style->clone().dynamicCast(); } m_sanityLayerStyleDirty = false; { KisSignalsBlocker b(m_stylesSelector); m_stylesSelector->notifyExternalStyleChanged(m_layerStyle->name(), m_layerStyle->uuid()); } QListWidgetItem *item; item = wdgLayerStyles.lstStyleSelector->item(2); item->setCheckState(m_layerStyle->dropShadow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(3); item->setCheckState(m_layerStyle->innerShadow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(4); item->setCheckState(m_layerStyle->outerGlow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(5); item->setCheckState(m_layerStyle->innerGlow()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(6); item->setCheckState(m_layerStyle->bevelAndEmboss()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(7); item->setCheckState(m_layerStyle->bevelAndEmboss()->contourEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(8); item->setCheckState(m_layerStyle->bevelAndEmboss()->textureEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(9); item->setCheckState(m_layerStyle->satin()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(10); item->setCheckState(m_layerStyle->colorOverlay()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(11); item->setCheckState(m_layerStyle->gradientOverlay()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(12); item->setCheckState(m_layerStyle->patternOverlay()->effectEnabled() ? Qt::Checked : Qt::Unchecked); item = wdgLayerStyles.lstStyleSelector->item(13); item->setCheckState(m_layerStyle->stroke()->effectEnabled() ? Qt::Checked : Qt::Unchecked); m_dropShadow->setShadow(m_layerStyle->dropShadow()); m_innerShadow->setShadow(m_layerStyle->innerShadow()); m_outerGlow->setConfig(m_layerStyle->outerGlow()); m_innerGlow->setConfig(m_layerStyle->innerGlow()); m_bevelAndEmboss->setBevelAndEmboss(m_layerStyle->bevelAndEmboss()); m_satin->setSatin(m_layerStyle->satin()); m_colorOverlay->setColorOverlay(m_layerStyle->colorOverlay()); m_gradientOverlay->setGradientOverlay(m_layerStyle->gradientOverlay()); m_patternOverlay->setPatternOverlay(m_layerStyle->patternOverlay()); m_stroke->setStroke(m_layerStyle->stroke()); wdgLayerStyles.chkMasterFxSwitch->setChecked(m_layerStyle->isEnabled()); slotMasterFxSwitchChanged(m_layerStyle->isEnabled()); } KisPSDLayerStyleSP KisDlgLayerStyle::style() const { m_layerStyle->setEnabled(wdgLayerStyles.chkMasterFxSwitch->isChecked()); m_layerStyle->dropShadow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(2)->checkState() == Qt::Checked); m_layerStyle->innerShadow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(3)->checkState() == Qt::Checked); m_layerStyle->outerGlow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(4)->checkState() == Qt::Checked); m_layerStyle->innerGlow()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(5)->checkState() == Qt::Checked); m_layerStyle->bevelAndEmboss()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(6)->checkState() == Qt::Checked); m_layerStyle->bevelAndEmboss()->setContourEnabled(wdgLayerStyles.lstStyleSelector->item(7)->checkState() == Qt::Checked); m_layerStyle->bevelAndEmboss()->setTextureEnabled(wdgLayerStyles.lstStyleSelector->item(8)->checkState() == Qt::Checked); m_layerStyle->satin()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(9)->checkState() == Qt::Checked); m_layerStyle->colorOverlay()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(10)->checkState() == Qt::Checked); m_layerStyle->gradientOverlay()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(11)->checkState() == Qt::Checked); m_layerStyle->patternOverlay()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(12)->checkState() == Qt::Checked); m_layerStyle->stroke()->setEffectEnabled(wdgLayerStyles.lstStyleSelector->item(13)->checkState() == Qt::Checked); m_dropShadow->fetchShadow(m_layerStyle->dropShadow()); m_innerShadow->fetchShadow(m_layerStyle->innerShadow()); m_outerGlow->fetchConfig(m_layerStyle->outerGlow()); m_innerGlow->fetchConfig(m_layerStyle->innerGlow()); m_bevelAndEmboss->fetchBevelAndEmboss(m_layerStyle->bevelAndEmboss()); m_satin->fetchSatin(m_layerStyle->satin()); m_colorOverlay->fetchColorOverlay(m_layerStyle->colorOverlay()); m_gradientOverlay->fetchGradientOverlay(m_layerStyle->gradientOverlay()); m_patternOverlay->fetchPatternOverlay(m_layerStyle->patternOverlay()); m_stroke->fetchStroke(m_layerStyle->stroke()); m_sanityLayerStyleDirty = false; m_stylesSelector->notifyExternalStyleChanged(m_layerStyle->name(), m_layerStyle->uuid()); return m_layerStyle; } void KisDlgLayerStyle::syncGlobalAngle(int angle) { KisPSDLayerStyleSP style = this->style(); if (style->dropShadow()->useGlobalLight()) { style->dropShadow()->setAngle(angle); } if (style->innerShadow()->useGlobalLight()) { style->innerShadow()->setAngle(angle); } if (style->bevelAndEmboss()->useGlobalLight()) { style->bevelAndEmboss()->setAngle(angle); } setStyle(style); } /********************************************************************/ /***** Styles Selector **********************************************/ /********************************************************************/ StylesSelector::LocationProxyModel::LocationProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { } void StylesSelector::LocationProxyModel::setEnableFiltering(bool enableFiltering) { m_enableFiltering = enableFiltering; invalidateFilter(); } void StylesSelector::LocationProxyModel::setLocationToFilterBy(QString location) { m_locationToFilter = location; invalidateFilter(); } bool StylesSelector::LocationProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (!m_enableFiltering) { return true; } QModelIndex idx = sourceModel()->index(source_row, 0); - QString location = sourceModel()->data(idx, Qt::UserRole + KisResourceModel::Location).toString(); - qDebug() << sourceModel()->data(idx, Qt::UserRole + KisResourceModel::Location).toString() - << sourceModel()->data(idx, Qt::UserRole + KisResourceModel::Name).toString(); + QString location = sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::Location).toString(); + qDebug() << sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::Location).toString() + << sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::Name).toString(); return location == m_locationToFilter; } class StyleItem : public QListWidgetItem { public: StyleItem(KisPSDLayerStyleSP style) : QListWidgetItem(style->name()) , m_style(style) { } public: KisPSDLayerStyleSP m_style; }; StylesSelector::StylesSelector(QWidget *parent) : QWidget(parent) { ui.setupUi(this); //ui.cmbStyleCollections->setModel(); m_resourceModel = KisResourceModelProvider::resourceModel(ResourceType::LayerStyles); m_locationsProxyModel = new LocationProxyModel(this); m_locationsProxyModel->setSourceModel(m_resourceModel); m_locationsProxyModel->setEnableFiltering(false); ui.listStyles->setModel(m_locationsProxyModel); - ui.listStyles->setModelColumn(KisResourceModel::Name); + ui.listStyles->setModelColumn(KisAbstractResourceModel::Name); connect(ui.cmbStyleCollections, SIGNAL(activated(QString)), this, SLOT(loadStyles(QString))); connect(ui.listStyles, SIGNAL(clicked(QModelIndex)), this, SLOT(selectStyle(QModelIndex))); connect(m_resourceModel, SIGNAL(afterResourcesLayoutReset()), this, SLOT(slotResourceModelReset())); refillCollections(); if (ui.cmbStyleCollections->count()) { ui.cmbStyleCollections->setCurrentIndex(0); m_locationsProxyModel->setEnableFiltering(true); loadStyles(ui.cmbStyleCollections->currentText()); } } void StylesSelector::refillCollections() { QStringList locationsList; for (int i = 0; i < m_resourceModel->rowCount(); i++) { QModelIndex idx = m_resourceModel->index(i, 0); - QString location = m_resourceModel->data(idx, Qt::UserRole + KisResourceModel::Location).toString(); + QString location = m_resourceModel->data(idx, Qt::UserRole + KisAbstractResourceModel::Location).toString(); if (!locationsList.contains(location)) { locationsList << location; } } ui.cmbStyleCollections->clear(); ui.cmbStyleCollections->addItems(locationsList); } void StylesSelector::notifyExternalStyleChanged(const QString &name, const QUuid &uuid) { /* int currentIndex = -1; for (int i = 0; i < ui.listStyles->count(); i++ ) { StyleItem *item = dynamic_cast(ui.listStyles->item(i)); QString itemName = item->m_style->name(); if (itemName == name) { bool isDirty = item->m_style->uuid() != uuid; if (isDirty) { itemName += "*"; } currentIndex = i; } item->setText(itemName); } ui.listStyles->setCurrentRow(currentIndex); */ } void StylesSelector::loadStyles(const QString &name) { m_locationsProxyModel->setLocationToFilterBy(name); } void StylesSelector::selectStyle(QModelIndex current) { // the index is from the proxy model QModelIndex sourceModelIndex = m_locationsProxyModel->mapToSource(current); KoResourceSP resource = m_resourceModel->resourceForIndex(sourceModelIndex); KisPSDLayerStyleSP layerStyle = resource.dynamicCast(); qDebug() << "StylesSelector::selectStyle" << (resource.isNull() ? "(null)" : resource->name()) << (layerStyle.isNull() ? "(null)" : layerStyle->name()); if (layerStyle) { emit styleSelected(layerStyle); } /* StyleItem *item = dynamic_cast(current); if (item) { emit styleSelected(item->m_style); } */ } void StylesSelector::loadCollection(const QString &fileName) { // TODO: RESOURCES: implement or remove warnKrita << "Collection cannot be loaded, because we do not use collections now; please use KisAslStorage instead."; /* if (!QFileInfo(fileName).exists()) { warnKrita << "Loaded style collection doesn't exist!"; return; } KisPSDLayerStyleCollectionResourceSP collection = KisPSDLayerStyleCollectionResourceSP(new KisPSDLayerStyleCollectionResource(fileName)); collection->load(); KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); collection->setFilename(server->saveLocation() + '/' + collection->name()); server->addResource(collection); refillCollections(); int index = ui.cmbStyleCollections->findText(collection->name()); ui.cmbStyleCollections->setCurrentIndex(index); loadStyles(collection->name()); */ } void StylesSelector::slotResourceModelReset() { ENTER_FUNCTION() << "MODEL RESET!!!"; refillCollections(); } void StylesSelector::addNewStyle(KisPSDLayerStyleSP style) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleServer(); server->addResource(style); // TODO: RESOURCES: what about adding only to CustomStyles.asl /* //server->resourceByName(style->name()) // NOTE: not translatable, since it is a key! const QString customName = "CustomStyles.asl"; const QString saveLocation = server->saveLocation(); const QString fullFilename = saveLocation + customName; KoResourceSP resource = server->resourceByName(customName); KisPSDLayerStyleSP style; if (!resource) { collection = KisPSDLayerStyleSP(new KisPSDLayerStyle("")); collection->setName(customName); collection->setFilename(fullFilename); KisPSDLayerStyleCollectionResource::StylesVector vector; vector << style; collection->setLayerStyles(vector); server->addResource(collection); } else { collection = resource.dynamicCast(); //KisPSDLayerStyle::StylesVector vector; vector = collection->layerStyles(); vector << style; collection->setLayerStyles(vector); collection->save(); } */ refillCollections(); // select in gui //int index = ui.cmbStyleCollections->findText(customName); int index = 0; KIS_ASSERT_RECOVER_RETURN(index >= 0); ui.cmbStyleCollections->setCurrentIndex(index); loadStyles(""); notifyExternalStyleChanged(style->name(), style->uuid()); } /********************************************************************/ /***** Bevel and Emboss *********************************************/ /********************************************************************/ BevelAndEmboss::BevelAndEmboss(Contour *contour, Texture *texture, QWidget *parent) : QWidget(parent) , m_contour(contour) , m_texture(texture) { ui.setupUi(this); // Structure ui.intDepth->setRange(0, 100); ui.intDepth->setSuffix(i18n(" %")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); ui.intSoften->setRange(0, 18); ui.intSoften->setSuffix(i18n(" px")); connect(ui.cmbStyle, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.cmbTechnique, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intDepth, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbDirection, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSoften, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // Shading ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intOpacity2->setRange(0, 100); ui.intOpacity2->setSuffix(i18n(" %")); ui.angleSelector->enableGlobalLight(true); connect(ui.angleSelector, SIGNAL(globalAngleChanged(int)), SIGNAL(globalAngleChanged(int))); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); connect(ui.intAltitude, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbHighlightMode, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnHighlightColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbShadowMode, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnShadowColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intOpacity2, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // Contour m_contour->ui.intRange->setRange(1, 100); m_contour->ui.intRange->setSuffix(i18n(" %")); connect(m_contour->ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(m_contour->ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(m_contour->ui.intRange, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // Texture m_texture->ui.intScale->setRange(0, 100); m_texture->ui.intScale->setSuffix(i18n(" %")); m_texture->ui.intDepth->setRange(-1000, 1000); m_texture->ui.intDepth->setSuffix(i18n(" %")); connect(m_texture->ui.patternChooser, SIGNAL(resourceSelected(KoResourceSP )), SIGNAL(configChanged())); connect(m_texture->ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(m_texture->ui.intDepth, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(m_texture->ui.chkInvert, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(m_texture->ui.chkLinkWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); } void BevelAndEmboss::setBevelAndEmboss(const psd_layer_effects_bevel_emboss *bevelAndEmboss) { ui.cmbStyle->setCurrentIndex((int)bevelAndEmboss->style()); ui.cmbTechnique->setCurrentIndex((int)bevelAndEmboss->technique()); ui.intDepth->setValue(bevelAndEmboss->depth()); ui.cmbDirection->setCurrentIndex((int)bevelAndEmboss->direction()); ui.intSize->setValue(bevelAndEmboss->size()); ui.intSoften->setValue(bevelAndEmboss->soften()); ui.angleSelector->setValue(bevelAndEmboss->angle()); ui.angleSelector->setUseGlobalLight(bevelAndEmboss->useGlobalLight()); ui.intAltitude->setValue(bevelAndEmboss->altitude()); // FIXME: curve editing // ui.cmbContour; ui.chkAntiAliased->setChecked(bevelAndEmboss->glossAntiAliased()); ui.cmbHighlightMode->selectCompositeOp(KoID(bevelAndEmboss->highlightBlendMode())); KoColor highlightshadow(KoColorSpaceRegistry::instance()->rgb8()); highlightshadow.fromQColor(bevelAndEmboss->highlightColor()); ui.bnHighlightColor->setColor(highlightshadow); ui.intOpacity->setValue(bevelAndEmboss->highlightOpacity()); ui.cmbShadowMode->selectCompositeOp(KoID(bevelAndEmboss->shadowBlendMode())); highlightshadow.fromQColor(bevelAndEmboss->shadowColor()); ui.bnShadowColor->setColor(highlightshadow); ui.intOpacity2->setValue(bevelAndEmboss->shadowOpacity()); // FIXME: curve editing // m_contour->ui.cmbContour; m_contour->ui.chkAntiAliased->setChecked(bevelAndEmboss->antiAliased()); m_contour->ui.intRange->setValue(bevelAndEmboss->contourRange()); m_texture->ui.patternChooser->setCurrentPattern(bevelAndEmboss->texturePattern()); m_texture->ui.intScale->setValue(bevelAndEmboss->textureScale()); m_texture->ui.intDepth->setValue(bevelAndEmboss->textureDepth()); m_texture->ui.chkInvert->setChecked(bevelAndEmboss->textureInvert()); m_texture->ui.chkLinkWithLayer->setChecked(bevelAndEmboss->textureAlignWithLayer()); } void BevelAndEmboss::fetchBevelAndEmboss(psd_layer_effects_bevel_emboss *bevelAndEmboss) const { bevelAndEmboss->setStyle((psd_bevel_style)ui.cmbStyle->currentIndex()); bevelAndEmboss->setTechnique((psd_technique_type)ui.cmbTechnique->currentIndex()); bevelAndEmboss->setDepth(ui.intDepth->value()); bevelAndEmboss->setDirection((psd_direction)ui.cmbDirection->currentIndex()); bevelAndEmboss->setSize(ui.intSize->value()); bevelAndEmboss->setSoften(ui.intSoften->value()); bevelAndEmboss->setAngle(ui.angleSelector->value()); bevelAndEmboss->setUseGlobalLight(ui.angleSelector->useGlobalLight()); bevelAndEmboss->setAltitude(ui.intAltitude->value()); bevelAndEmboss->setGlossAntiAliased(ui.chkAntiAliased->isChecked()); bevelAndEmboss->setHighlightBlendMode(ui.cmbHighlightMode->selectedCompositeOp().id()); bevelAndEmboss->setHighlightColor(ui.bnHighlightColor->color().toQColor()); bevelAndEmboss->setHighlightOpacity(ui.intOpacity->value()); bevelAndEmboss->setShadowBlendMode(ui.cmbShadowMode->selectedCompositeOp().id()); bevelAndEmboss->setShadowColor(ui.bnShadowColor->color().toQColor()); bevelAndEmboss->setShadowOpacity(ui.intOpacity2->value()); // FIXME: curve editing bevelAndEmboss->setAntiAliased(m_contour->ui.chkAntiAliased->isChecked()); bevelAndEmboss->setContourRange(m_contour->ui.intRange->value()); bevelAndEmboss->setTexturePattern(m_texture->ui.patternChooser->currentResource().staticCast()); bevelAndEmboss->setTextureScale(m_texture->ui.intScale->value()); bevelAndEmboss->setTextureDepth(m_texture->ui.intDepth->value()); bevelAndEmboss->setTextureInvert(m_texture->ui.chkInvert->isChecked()); bevelAndEmboss->setTextureAlignWithLayer(m_texture->ui.chkLinkWithLayer->isChecked()); } /********************************************************************/ /***** Texture *********************************************/ /********************************************************************/ Texture::Texture(QWidget *parent) : QWidget(parent) { ui.setupUi(this); } /********************************************************************/ /***** Contour *********************************************/ /********************************************************************/ Contour::Contour(QWidget *parent) : QWidget(parent) { ui.setupUi(this); } /********************************************************************/ /***** Blending Options *********************************************/ /********************************************************************/ BlendingOptions::BlendingOptions(QWidget *parent) : QWidget(parent) { ui.setupUi(this); // FIXME: Blend options are not implemented yet ui.grpBlendingOptions->setTitle(QString("%1 (%2)").arg(ui.grpBlendingOptions->title()).arg(i18n("Not Implemented Yet"))); ui.grpBlendingOptions->setEnabled(false); } /********************************************************************/ /***** Color Overlay *********************************************/ /********************************************************************/ ColorOverlay::ColorOverlay(QWidget *parent) : QWidget(parent) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); } void ColorOverlay::setColorOverlay(const psd_layer_effects_color_overlay *colorOverlay) { ui.cmbCompositeOp->selectCompositeOp(KoID(colorOverlay->blendMode())); ui.intOpacity->setValue(colorOverlay->opacity()); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(colorOverlay->color()); ui.bnColor->setColor(color); } void ColorOverlay::fetchColorOverlay(psd_layer_effects_color_overlay *colorOverlay) const { colorOverlay->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); colorOverlay->setOpacity(ui.intOpacity->value()); colorOverlay->setColor(ui.bnColor->color().toQColor()); } /********************************************************************/ /***** Drop Shadow **************************************************/ /********************************************************************/ DropShadow::DropShadow(Mode mode, QWidget *parent) : QWidget(parent), m_mode(mode) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intDistance->setRange(0, 500); ui.intDistance->setSuffix(i18n(" px")); ui.intDistance->setExponentRatio(3.0); ui.intSpread->setRange(0, 100); ui.intSpread->setSuffix(i18n(" %")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); ui.intNoise->setRange(0, 100); ui.intNoise->setSuffix(i18n(" %")); ui.angleSelector->enableGlobalLight(true); connect(ui.angleSelector, SIGNAL(globalAngleChanged(int)), SIGNAL(globalAngleChanged(int))); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); // connect everything to configChanged() signal connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intDistance, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSpread, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intNoise, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.chkLayerKnocksOutDropShadow, SIGNAL(toggled(bool)), SIGNAL(configChanged())); if (m_mode == InnerShadowMode) { ui.chkLayerKnocksOutDropShadow->setVisible(false); ui.grpMain->setTitle(i18n("Inner Shadow")); ui.lblSpread->setText(i18n("Choke:")); } } void DropShadow::setShadow(const psd_layer_effects_shadow_common *shadow) { ui.cmbCompositeOp->selectCompositeOp(KoID(shadow->blendMode())); ui.intOpacity->setValue(shadow->opacity()); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(shadow->color()); ui.bnColor->setColor(color); ui.angleSelector->setValue(shadow->angle()); ui.angleSelector->setUseGlobalLight(shadow->useGlobalLight()); ui.intDistance->setValue(shadow->distance()); ui.intSpread->setValue(shadow->spread()); ui.intSize->setValue(shadow->size()); // FIXME: curve editing // ui.cmbContour; ui.chkAntiAliased->setChecked(shadow->antiAliased()); ui.intNoise->setValue(shadow->noise()); if (m_mode == DropShadowMode) { const psd_layer_effects_drop_shadow *realDropShadow = dynamic_cast(shadow); KIS_ASSERT_RECOVER_NOOP(realDropShadow); ui.chkLayerKnocksOutDropShadow->setChecked(shadow->knocksOut()); } } void DropShadow::fetchShadow(psd_layer_effects_shadow_common *shadow) const { shadow->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); shadow->setOpacity(ui.intOpacity->value()); shadow->setColor(ui.bnColor->color().toQColor()); shadow->setAngle(ui.angleSelector->value()); shadow->setUseGlobalLight(ui.angleSelector->useGlobalLight()); shadow->setDistance(ui.intDistance->value()); shadow->setSpread(ui.intSpread->value()); shadow->setSize(ui.intSize->value()); // FIXME: curve editing // ui.cmbContour; shadow->setAntiAliased(ui.chkAntiAliased->isChecked()); shadow->setNoise(ui.intNoise->value()); if (m_mode == DropShadowMode) { psd_layer_effects_drop_shadow *realDropShadow = dynamic_cast(shadow); KIS_ASSERT_RECOVER_NOOP(realDropShadow); realDropShadow->setKnocksOut(ui.chkLayerKnocksOutDropShadow->isChecked()); } } class GradientPointerConverter { public: static KoAbstractGradientSP resourceToStyle(KoAbstractGradientSP gradient) { return gradient ? KoAbstractGradientSP(gradient->clone().dynamicCast()) : KoAbstractGradientSP(); } static KoAbstractGradientSP styleToResource(KoAbstractGradientSP gradient) { if (!gradient) return 0; KoResourceServer *server = KoResourceServerProvider::instance()->gradientServer(); KoAbstractGradientSP resource = server->resourceByMD5(gradient->md5()); if (!resource) { KoAbstractGradientSP clone = gradient->clone().dynamicCast(); clone->setName(findAvailableName(gradient->name())); server->addResource(clone, false); resource = clone; } return resource; } private: static QString findAvailableName(const QString &name) { KoResourceServer *server = KoResourceServerProvider::instance()->gradientServer(); QString newName = name; int i = 0; while (server->resourceByName(newName)) { newName = QString("%1%2").arg(name).arg(i++); } return newName; } }; /********************************************************************/ /***** Gradient Overlay *********************************************/ /********************************************************************/ GradientOverlay::GradientOverlay(KisCanvasResourceProvider *resourceProvider, QWidget *parent) : QWidget(parent), m_resourceProvider(resourceProvider) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intScale->setRange(0, 100); ui.intScale->setSuffix(i18n(" %")); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbGradient, SIGNAL(gradientChanged(KoAbstractGradient*)), SIGNAL(configChanged())); connect(ui.chkReverse, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbStyle, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAlignWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); } void GradientOverlay::setGradientOverlay(const psd_layer_effects_gradient_overlay *config) { ui.cmbCompositeOp->selectCompositeOp(KoID(config->blendMode())); ui.intOpacity->setValue(config->opacity()); KoAbstractGradientSP gradient = fetchGradientLazy(GradientPointerConverter::styleToResource(config->gradient()), m_resourceProvider); if (gradient) { ui.cmbGradient->setGradient(gradient); } ui.chkReverse->setChecked(config->reverse()); ui.cmbStyle->setCurrentIndex((int)config->style()); ui.chkAlignWithLayer->setCheckable(config->alignWithLayer()); ui.angleSelector->setValue(config->angle()); ui.intScale->setValue(config->scale()); } void GradientOverlay::fetchGradientOverlay(psd_layer_effects_gradient_overlay *config) const { config->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); config->setOpacity(ui.intOpacity->value()); config->setGradient(GradientPointerConverter::resourceToStyle(ui.cmbGradient->gradient())); config->setReverse(ui.chkReverse->isChecked()); config->setStyle((psd_gradient_style)ui.cmbStyle->currentIndex()); config->setAlignWithLayer(ui.chkAlignWithLayer->isChecked()); config->setAngle(ui.angleSelector->value()); config->setScale(ui.intScale->value()); } /********************************************************************/ /***** Innner Glow *********************************************/ /********************************************************************/ InnerGlow::InnerGlow(Mode mode, KisCanvasResourceProvider *resourceProvider, QWidget *parent) : QWidget(parent), m_mode(mode), m_resourceProvider(resourceProvider) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intNoise->setRange(0, 100); ui.intNoise->setSuffix(i18n(" %")); ui.intChoke->setRange(0, 100); ui.intChoke->setSuffix(i18n(" %")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); ui.intRange->setRange(1, 100); ui.intRange->setSuffix(i18n(" %")); ui.intJitter->setRange(0, 100); ui.intJitter->setSuffix(i18n(" %")); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intNoise, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.radioColor, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.radioGradient, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbGradient, SIGNAL(gradientChanged(KoAbstractGradient*)), SIGNAL(configChanged())); connect(ui.cmbTechnique, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.cmbSource, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intChoke, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intRange, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intJitter, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); if (m_mode == OuterGlowMode) { ui.cmbSource->hide(); ui.lblSource->hide(); ui.lblChoke->setText(i18nc("layer styles parameter", "Spread:")); } } void InnerGlow::setConfig(const psd_layer_effects_glow_common *config) { ui.cmbCompositeOp->selectCompositeOp(KoID(config->blendMode())); ui.intOpacity->setValue(config->opacity()); ui.intNoise->setValue(config->noise()); ui.radioColor->setChecked(config->fillType() == psd_fill_solid_color); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(config->color()); ui.bnColor->setColor(color); ui.radioGradient->setChecked(config->fillType() == psd_fill_gradient); KoAbstractGradientSP gradient = fetchGradientLazy(GradientPointerConverter::styleToResource(config->gradient()), m_resourceProvider); if (gradient) { ui.cmbGradient->setGradient(gradient); } ui.cmbTechnique->setCurrentIndex((int)config->technique()); ui.intChoke->setValue(config->spread()); ui.intSize->setValue(config->size()); if (m_mode == InnerGlowMode) { const psd_layer_effects_inner_glow *iglow = dynamic_cast(config); KIS_ASSERT_RECOVER_RETURN(iglow); ui.cmbSource->setCurrentIndex(iglow->source() == psd_glow_edge); } // FIXME: Curve editing //ui.cmbContour; ui.chkAntiAliased->setChecked(config->antiAliased()); ui.intRange->setValue(config->range()); ui.intJitter->setValue(config->jitter()); } void InnerGlow::fetchConfig(psd_layer_effects_glow_common *config) const { config->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); config->setOpacity(ui.intOpacity->value()); config->setNoise(ui.intNoise->value()); if (ui.radioColor->isChecked()) { config->setFillType(psd_fill_solid_color); } else { config->setFillType(psd_fill_gradient); } config->setColor(ui.bnColor->color().toQColor()); config->setGradient(GradientPointerConverter::resourceToStyle(ui.cmbGradient->gradient())); config->setTechnique((psd_technique_type)ui.cmbTechnique->currentIndex()); config->setSpread(ui.intChoke->value()); config->setSize(ui.intSize->value()); if (m_mode == InnerGlowMode) { psd_layer_effects_inner_glow *iglow = dynamic_cast(config); KIS_ASSERT_RECOVER_RETURN(iglow); iglow->setSource((psd_glow_source)ui.cmbSource->currentIndex()); } // FIXME: Curve editing //ui.cmbContour; config->setAntiAliased(ui.chkAntiAliased->isChecked()); config->setRange(ui.intRange->value()); config->setJitter(ui.intJitter->value()); } /********************************************************************/ /***** Pattern Overlay *********************************************/ /********************************************************************/ PatternOverlay::PatternOverlay(QWidget *parent) : QWidget(parent) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intScale->setRange(0, 100); ui.intScale->setSuffix(i18n(" %")); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.patternChooser, SIGNAL(resourceSelected(KoResourceSP )), SIGNAL(configChanged())); connect(ui.chkLinkWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); } void PatternOverlay::setPatternOverlay(const psd_layer_effects_pattern_overlay *pattern) { ui.cmbCompositeOp->selectCompositeOp(KoID(pattern->blendMode())); ui.intOpacity->setValue(pattern->opacity()); ui.patternChooser->setCurrentPattern(pattern->pattern()); ui.chkLinkWithLayer->setChecked(pattern->alignWithLayer()); ui.intScale->setValue(pattern->scale()); } void PatternOverlay::fetchPatternOverlay(psd_layer_effects_pattern_overlay *pattern) const { pattern->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); pattern->setOpacity(ui.intOpacity->value()); pattern->setPattern(ui.patternChooser->currentResource().staticCast()); pattern->setAlignWithLayer(ui.chkLinkWithLayer->isChecked()); pattern->setScale(ui.intScale->value()); } /********************************************************************/ /***** Satin *********************************************/ /********************************************************************/ Satin::Satin(QWidget *parent) : QWidget(parent) { ui.setupUi(this); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intDistance->setRange(0, 250); ui.intDistance->setSuffix(i18n(" px")); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); connect(ui.intDistance, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbContour, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAntiAliased, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.chkInvert, SIGNAL(toggled(bool)), SIGNAL(configChanged())); } void Satin::setSatin(const psd_layer_effects_satin *satin) { ui.cmbCompositeOp->selectCompositeOp(KoID(satin->blendMode())); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(satin->color()); ui.bnColor->setColor(color); ui.intOpacity->setValue(satin->opacity()); ui.angleSelector->setValue(satin->angle()); ui.intDistance->setValue(satin->distance()); ui.intSize->setValue(satin->size()); // FIXME: Curve editing //ui.cmbContour; ui.chkAntiAliased->setChecked(satin->antiAliased()); ui.chkInvert->setChecked(satin->invert()); } void Satin::fetchSatin(psd_layer_effects_satin *satin) const { satin->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); satin->setOpacity(ui.intOpacity->value()); satin->setColor(ui.bnColor->color().toQColor()); satin->setAngle(ui.angleSelector->value()); satin->setDistance(ui.intDistance->value()); satin->setSize(ui.intSize->value()); // FIXME: curve editing // ui.cmbContour; satin->setAntiAliased(ui.chkAntiAliased->isChecked()); satin->setInvert(ui.chkInvert->isChecked()); } /********************************************************************/ /***** Stroke *********************************************/ /********************************************************************/ Stroke::Stroke(KisCanvasResourceProvider *resourceProvider, QWidget *parent) : QWidget(parent), m_resourceProvider(resourceProvider) { ui.setupUi(this); ui.intSize->setRange(0, 250); ui.intSize->setSuffix(i18n(" px")); ui.intSize->setExponentRatio(2.0); ui.intOpacity->setRange(0, 100); ui.intOpacity->setSuffix(i18n(" %")); ui.intScale->setRange(0, 100); ui.intScale->setSuffix(i18n(" %")); ui.intScale_2->setRange(0, 100); ui.intScale_2->setSuffix(i18n(" %")); connect(ui.cmbFillType, SIGNAL(currentIndexChanged(int)), ui.fillStack, SLOT(setCurrentIndex(int))); connect(ui.intSize, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbPosition, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.cmbCompositeOp, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.intOpacity, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.cmbFillType, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.bnColor, SIGNAL(changed(KoColor)), SIGNAL(configChanged())); connect(ui.cmbGradient, SIGNAL(gradientChanged(KoAbstractGradient*)), SIGNAL(configChanged())); connect(ui.chkReverse, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.cmbStyle, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged())); connect(ui.chkAlignWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); connect(ui.patternChooser, SIGNAL(resourceSelected(KoResourceSP )), SIGNAL(configChanged())); connect(ui.angleSelector, SIGNAL(configChanged()), SIGNAL(configChanged())); connect(ui.chkLinkWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged())); connect(ui.intScale_2, SIGNAL(valueChanged(int)), SIGNAL(configChanged())); // cold initialization ui.fillStack->setCurrentIndex(ui.cmbFillType->currentIndex()); } void Stroke::setStroke(const psd_layer_effects_stroke *stroke) { ui.intSize->setValue(stroke->size()); ui.cmbPosition->setCurrentIndex((int)stroke->position()); ui.cmbCompositeOp->selectCompositeOp(KoID(stroke->blendMode())); ui.intOpacity->setValue(stroke->opacity()); ui.cmbFillType->setCurrentIndex((int)stroke->fillType()); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(stroke->color()); ui.bnColor->setColor(color); KoAbstractGradientSP gradient = fetchGradientLazy(GradientPointerConverter::styleToResource(stroke->gradient()), m_resourceProvider); if (gradient) { ui.cmbGradient->setGradient(gradient); } ui.chkReverse->setChecked(stroke->antiAliased()); ui.cmbStyle->setCurrentIndex((int)stroke->style()); ui.chkAlignWithLayer->setCheckable(stroke->alignWithLayer()); ui.angleSelector->setValue(stroke->angle()); ui.intScale->setValue(stroke->scale()); ui.patternChooser->setCurrentPattern(stroke->pattern()); ui.chkLinkWithLayer->setChecked(stroke->alignWithLayer()); ui.intScale_2->setValue(stroke->scale()); } void Stroke::fetchStroke(psd_layer_effects_stroke *stroke) const { stroke->setSize(ui.intSize->value()); stroke->setPosition((psd_stroke_position)ui.cmbPosition->currentIndex()); stroke->setBlendMode(ui.cmbCompositeOp->selectedCompositeOp().id()); stroke->setOpacity(ui.intOpacity->value()); stroke->setFillType((psd_fill_type)ui.cmbFillType->currentIndex()); stroke->setColor(ui.bnColor->color().toQColor()); stroke->setGradient(GradientPointerConverter::resourceToStyle(ui.cmbGradient->gradient())); stroke->setReverse(ui.chkReverse->isChecked()); stroke->setStyle((psd_gradient_style)ui.cmbStyle->currentIndex()); stroke->setAlignWithLayer(ui.chkAlignWithLayer->isChecked()); stroke->setAngle(ui.angleSelector->value()); stroke->setScale(ui.intScale->value()); stroke->setPattern(ui.patternChooser->currentResource().staticCast()); stroke->setAlignWithLayer(ui.chkLinkWithLayer->isChecked()); stroke->setScale(ui.intScale->value()); } diff --git a/libs/ui/kis_favorite_resource_manager.cpp b/libs/ui/kis_favorite_resource_manager.cpp index cd9edc39a6..029ea5cbae 100644 --- a/libs/ui/kis_favorite_resource_manager.cpp +++ b/libs/ui/kis_favorite_resource_manager.cpp @@ -1,368 +1,368 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 #include #include #include #include #include #include #include #include #include "kis_favorite_resource_manager.h" #include "kis_popup_palette.h" #include "kis_paintop_box.h" #include "KisViewManager.h" #include "KisResourceServerProvider.h" #include "kis_min_heap.h" #include "kis_config.h" #include "kis_config_notifier.h" #include class KisFavoriteResourceManager::ColorDataList { public: static const int MAX_RECENT_COLOR = 12; ColorDataList() { m_key = 0; } ~ColorDataList() { qDeleteAll(m_guiList); } int size() { return m_guiList.size(); } int leastUsedGuiPos() { return findPos(m_priorityList.valueAt(0)); } const KoColor& guiColor(int pos) { Q_ASSERT_X(pos < size(), "ColorDataList::guiColor", "index out of bound"); Q_ASSERT_X(pos >= 0, "ColorDataList::guiColor", "negative index"); return m_guiList.at(pos)->data; } void append(const KoColor& data) { int pos = findPos(data); if (pos > -1) updateKey(pos); else appendNew(data); } void appendNew(const KoColor& data) { if (size() >= ColorDataList::MAX_RECENT_COLOR) removeLeastUsed(); PriorityNode * node; node = new PriorityNode (); node->data = data; node->key = m_key++; m_priorityList.append(node); int pos = guiInsertPos(data); pos >= m_guiList.size() ? m_guiList.append(node) : m_guiList.insert(pos, node); node = 0; } void removeLeastUsed() { Q_ASSERT_X(size() >= 0, "ColorDataList::removeLeastUsed", "index out of bound"); if (size() <= 0) return; int pos = findPos(m_priorityList.valueAt(0)); m_guiList.removeAt(pos); m_priorityList.remove(0); } void updateKey(int guiPos) { if (m_guiList.at(guiPos)->key == m_key - 1) return; m_priorityList.changeKey(m_guiList.at(guiPos)->pos, m_key++); } /*find position of the color on the gui list*/ int findPos(const KoColor& color) { int low = 0, high = size(), mid = 0; while (low < high) { mid = (low + high) / 2; if (hsvComparison(color, m_guiList.at(mid)->data) == 0) return mid; else if (hsvComparison(color, m_guiList.at(mid)->data) < 0) high = mid; else low = mid + 1; } return -1; } private: int m_key; int guiInsertPos(const KoColor& color) { int low = 0, high = size() - 1, mid = (low + high) / 2; while (low < high) { hsvComparison(color, m_guiList[mid]->data) == -1 ? high = mid : low = mid + 1; mid = (low + high) / 2; } if (m_guiList.size() > 0) { if (hsvComparison(color, m_guiList[mid]->data) == 1) ++mid; } return mid; } /*compares c1 and c2 based on HSV. c1 < c2, returns -1 c1 = c2, returns 0 c1 > c2, returns 1 */ int hsvComparison(const KoColor& c1, const KoColor& c2) { QColor qc1 = c1.toQColor(); QColor qc2 = c2.toQColor(); if (qc1.hue() < qc2.hue()) return -1; if (qc1.hue() > qc2.hue()) return 1; // hue is the same, ok let's compare saturation if (qc1.saturation() < qc2.saturation()) return -1; if (qc1.saturation() > qc2.saturation()) return 1; // oh, also saturation is same? if (qc1.value() < qc2.value()) return -1; if (qc1.value() > qc2.value()) return 1; // user selected two similar colors return 0; } KisMinHeap m_priorityList; QList *> m_guiList; }; KisFavoriteResourceManager::KisFavoriteResourceManager(KisPaintopBox *paintopBox) : m_paintopBox(paintopBox) , m_colorList(0) , m_initialized(false) { KisConfig cfg(true); m_maxPresets = cfg.favoritePresets(); m_colorList = new ColorDataList(); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(configChanged())); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); rServer->addObserver(this); } KisFavoriteResourceManager::~KisFavoriteResourceManager() { KisPaintOpPresetResourceServer *rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); rServer->removeObserver(this); delete m_colorList; } void KisFavoriteResourceManager::unsetResourceServer() { // ... } QVector KisFavoriteResourceManager::favoritePresetNamesList() { init(); QVector names; for (int i = 0; i < m_maxPresets; i++) { QModelIndex index = m_resourcesProxyModel->index(i, 0); if (index.isValid()) { - QString name = m_resourcesProxyModel->data(index, Qt::UserRole + KisResourceModel::Name).toString(); + QString name = m_resourcesProxyModel->data(index, Qt::UserRole + KisAbstractResourceModel::Name).toString(); names << name; } else { break; // no more valid indices } } return names; } QList KisFavoriteResourceManager::favoritePresetImages() { init(); QList images; for (int i = 0; i < m_maxPresets; i++) { QModelIndex index = m_resourcesProxyModel->index(i, 0); if (index.isValid()) { - QVariant tmp = m_resourcesProxyModel->data(index, Qt::UserRole + KisResourceModel::Thumbnail); + QVariant tmp = m_resourcesProxyModel->data(index, Qt::UserRole + KisAbstractResourceModel::Thumbnail); QImage image = tmp.value(); images << image; } else { break; // no more valid indices } } return images; } void KisFavoriteResourceManager::setCurrentTag(const KisTagSP tag) { m_currentTag = tag; m_resourcesProxyModel->setTag(tag); KisConfig(false).writeEntry("favoritePresetsTag", tag->url()); updateFavoritePresets(); } void KisFavoriteResourceManager::slotChangeActivePaintop(int pos) { ENTER_FUNCTION() << ppVar(pos) << ppVar(numFavoritePresets()); if (pos < 0 || pos >= numFavoritePresets()) return; QModelIndex index = m_resourcesProxyModel->index(pos, 0); KoResourceSP resource = m_resourcesProxyModel->resourceForIndex(index); m_paintopBox->resourceSelected(resource); emit hidePalettes(); } int KisFavoriteResourceManager::numFavoritePresets() { init(); return favoritePresetNamesList().size(); } //Recent Colors void KisFavoriteResourceManager::slotUpdateRecentColor(int pos) { // Do not update the key, the colour might be selected but it is not used yet. So we are not supposed // to update the colour priority when we select it. m_colorList->updateKey(pos); emit setSelectedColor(pos); emit sigSetFGColor(m_colorList->guiColor(pos)); emit hidePalettes(); } void KisFavoriteResourceManager::slotAddRecentColor(const KoColor& color) { m_colorList->append(color); int pos = m_colorList->findPos(color); emit setSelectedColor(pos); } void KisFavoriteResourceManager::slotChangeFGColorSelector(KoColor c) { emit sigChangeFGColorSelector(c); } void KisFavoriteResourceManager::removingResource(QSharedPointer /*resource*/) { updateFavoritePresets(); } void KisFavoriteResourceManager::resourceAdded(QSharedPointer /*resource*/) { updateFavoritePresets(); } void KisFavoriteResourceManager::resourceChanged(QSharedPointer /*resource*/) { updateFavoritePresets(); } void KisFavoriteResourceManager::syncTaggedResourceView() { updateFavoritePresets(); } void KisFavoriteResourceManager::syncTagAddition(const QString& /*tag*/) {} void KisFavoriteResourceManager::syncTagRemoval(const QString& /*tag*/) {} int KisFavoriteResourceManager::recentColorsTotal() { return m_colorList->size(); } const KoColor& KisFavoriteResourceManager::recentColorAt(int pos) { return m_colorList->guiColor(pos); } void KisFavoriteResourceManager::slotSetBGColor(const KoColor c) { m_bgColor = c; } KoColor KisFavoriteResourceManager::bgColor() const { return m_bgColor; } bool sortPresetByName(KisPaintOpPresetSP preset1, KisPaintOpPresetSP preset2) { return preset1->name() < preset2->name(); } void KisFavoriteResourceManager::updateFavoritePresets() { emit updatePalettes(); } void KisFavoriteResourceManager::configChanged() { KisConfig cfg(true); m_maxPresets = cfg.favoritePresets(); updateFavoritePresets(); } void KisFavoriteResourceManager::init() { if (!m_initialized) { m_initialized = true; m_tagModel = KisTagModelProvider::tagModel(ResourceType::PaintOpPresets); m_resourcesProxyModel = new KisTagFilterResourceProxyModel(m_tagModel, this); m_resourcesProxyModel->setSourceModel(KisResourceModelProvider::resourceModel(ResourceType::PaintOpPresets)); m_resourceModel = KisResourceModelProvider::resourceModel(ResourceType::PaintOpPresets); KisResourceServerProvider::instance()->paintOpPresetServer(); QString currentTag = KisConfig(true).readEntry("favoritePresetsTag", "★ My Favorites"); // TODO: RESOURCES: tag by url? KisTagModel* tagModel = KisTagModelProvider::tagModel(ResourceType::PaintOpPresets); for (int i = 0; i < tagModel->rowCount(); i++) { QModelIndex index = tagModel->index(i, 0); KisTagSP tag = tagModel->tagForIndex(index); if (!tag.isNull() && tag->url() == currentTag) { m_currentTag = tag; break; } } m_resourcesProxyModel->setTag(m_currentTag); updateFavoritePresets(); } } diff --git a/libs/ui/kis_paintop_box.cc b/libs/ui/kis_paintop_box.cc index bc652e205b..bcee03dbcf 100644 --- a/libs/ui/kis_paintop_box.cc +++ b/libs/ui/kis_paintop_box.cc @@ -1,1398 +1,1398 @@ /* * kis_paintop_box.cc - part of KImageShop/Krayon/Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2009-2011 Sven Langkamp (sven.langkamp@gmail.com) * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2014 Mohit Goyal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paintop_box.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_node_manager.h" #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_favorite_resource_manager.h" #include "kis_config.h" #include "KisPopupButton.h" #include "widgets/kis_iconwidget.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_popup.h" #include "widgets/kis_paintop_presets_chooser_popup.h" #include "widgets/kis_workspace_chooser.h" #include "widgets/kis_paintop_list_widget.h" #include "kis_slider_spin_box.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_widget_chooser.h" #include "tool/kis_tool.h" #include "kis_signals_blocker.h" #include "kis_action_manager.h" #include "KisHighlightedToolButton.h" #include KisPaintopBox::KisPaintopBox(KisViewManager *view, QWidget *parent, const char *name) : QWidget(parent) , m_resourceProvider(view->canvasResourceProvider()) , m_optionWidget(0) , m_toolOptionsPopupButton(0) , m_brushEditorPopupButton(0) , m_presetSelectorPopupButton(0) , m_toolOptionsPopup(0) , m_viewManager(view) , m_previousNode(0) , m_currTabletToolID(KoInputDevice::invalid()) , m_presetsEnabled(true) , m_blockUpdate(false) , m_dirtyPresetsEnabled(false) , m_eraserBrushSizeEnabled(false) , m_eraserBrushOpacityEnabled(false) { Q_ASSERT(view != 0); setObjectName(name); KisConfig cfg(true); m_dirtyPresetsEnabled = cfg.useDirtyPresets(); m_eraserBrushSizeEnabled = cfg.useEraserBrushSize(); m_eraserBrushOpacityEnabled = cfg.useEraserBrushOpacity(); KAcceleratorManager::setNoAccel(this); setWindowTitle(i18n("Painter's Toolchest")); m_favoriteResourceManager = new KisFavoriteResourceManager(this); KConfigGroup grp = KSharedConfig::openConfig()->group("krita").group("Toolbar BrushesAndStuff"); int iconsize = grp.readEntry("IconSize", 32); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopupButton = new KisPopupButton(this); m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); m_toolOptionsPopupButton->setToolTip(i18n("Tool Settings")); m_toolOptionsPopupButton->setFixedSize(iconsize, iconsize); } m_brushEditorPopupButton = new KisIconWidget(this); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_brushEditorPopupButton->setToolTip(i18n("Edit brush settings")); m_brushEditorPopupButton->setFixedSize(iconsize, iconsize); m_presetSelectorPopupButton = new KisPopupButton(this); m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_presetSelectorPopupButton->setToolTip(i18n("Choose brush preset")); m_presetSelectorPopupButton->setFixedSize(iconsize, iconsize); m_eraseModeButton = new KisHighlightedToolButton(this); m_eraseModeButton->setFixedSize(iconsize, iconsize); m_eraseModeButton->setCheckable(true); m_eraseAction = m_viewManager->actionManager()->createAction("erase_action"); m_eraseModeButton->setDefaultAction(m_eraseAction); m_reloadButton = new QToolButton(this); m_reloadButton->setFixedSize(iconsize, iconsize); m_reloadAction = m_viewManager->actionManager()->createAction("reload_preset_action"); m_reloadButton->setDefaultAction(m_reloadAction); m_alphaLockButton = new KisHighlightedToolButton(this); m_alphaLockButton->setFixedSize(iconsize, iconsize); m_alphaLockButton->setCheckable(true); KisAction* alphaLockAction = m_viewManager->actionManager()->createAction("preserve_alpha"); m_alphaLockButton->setDefaultAction(alphaLockAction); // horizontal and vertical mirror toolbar buttons // mirror tool options for the X Mirror QMenu *toolbarMenuXMirror = new QMenu(); hideCanvasDecorationsX = m_viewManager->actionManager()->createAction("mirrorX-hideDecorations"); toolbarMenuXMirror->addAction(hideCanvasDecorationsX); lockActionX = m_viewManager->actionManager()->createAction("mirrorX-lock"); toolbarMenuXMirror->addAction(lockActionX); moveToCenterActionX = m_viewManager->actionManager()->createAction("mirrorX-moveToCenter"); toolbarMenuXMirror->addAction(moveToCenterActionX); // mirror tool options for the Y Mirror QMenu *toolbarMenuYMirror = new QMenu(); hideCanvasDecorationsY = m_viewManager->actionManager()->createAction("mirrorY-hideDecorations"); toolbarMenuYMirror->addAction(hideCanvasDecorationsY); lockActionY = m_viewManager->actionManager()->createAction("mirrorY-lock"); toolbarMenuYMirror->addAction(lockActionY); moveToCenterActionY = m_viewManager->actionManager()->createAction("mirrorY-moveToCenter"); toolbarMenuYMirror->addAction(moveToCenterActionY); // create horizontal and vertical mirror buttons m_hMirrorButton = new KisHighlightedToolButton(this); int menuPadding = 10; m_hMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_hMirrorButton->setCheckable(true); m_hMirrorAction = m_viewManager->actionManager()->createAction("hmirror_action"); m_hMirrorButton->setDefaultAction(m_hMirrorAction); m_hMirrorButton->setMenu(toolbarMenuXMirror); m_hMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); m_vMirrorButton = new KisHighlightedToolButton(this); m_vMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_vMirrorButton->setCheckable(true); m_vMirrorAction = m_viewManager->actionManager()->createAction("vmirror_action"); m_vMirrorButton->setDefaultAction(m_vMirrorAction); m_vMirrorButton->setMenu(toolbarMenuYMirror); m_vMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); // add connections for horizontal and mirrror buttons connect(lockActionX, SIGNAL(toggled(bool)), this, SLOT(slotLockXMirrorToggle(bool))); connect(lockActionY, SIGNAL(toggled(bool)), this, SLOT(slotLockYMirrorToggle(bool))); connect(moveToCenterActionX, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorX())); connect(moveToCenterActionY, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorY())); connect(hideCanvasDecorationsX, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorX(bool))); connect(hideCanvasDecorationsY, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorY(bool))); const bool sliderLabels = cfg.sliderLabels(); int sliderWidth; if (sliderLabels) { sliderWidth = 150 * logicalDpiX() / 96; } else { sliderWidth = 120 * logicalDpiX() / 96; } for (int i = 0; i < 3; ++i) { m_sliderChooser[i] = new KisWidgetChooser(i + 1); KisDoubleSliderSpinBox* slOpacity; KisDoubleSliderSpinBox* slFlow; KisDoubleSliderSpinBox* slSize; if (sliderLabels) { slOpacity = m_sliderChooser[i]->addWidget("opacity"); slFlow = m_sliderChooser[i]->addWidget("flow"); slSize = m_sliderChooser[i]->addWidget("size"); slOpacity->setPrefix(QString("%1 ").arg(i18n("Opacity:"))); slFlow->setPrefix(QString("%1 ").arg(i18n("Flow:"))); slSize->setPrefix(QString("%1 ").arg(i18n("Size:"))); } else { slOpacity = m_sliderChooser[i]->addWidget("opacity", i18n("Opacity:")); slFlow = m_sliderChooser[i]->addWidget("flow", i18n("Flow:")); slSize = m_sliderChooser[i]->addWidget("size", i18n("Size:")); } slOpacity->setRange(0, 100, 0); slOpacity->setValue(100); slOpacity->setSingleStep(5); slOpacity->setSuffix(i18n("%")); slOpacity->setMinimumWidth(qMax(sliderWidth, slOpacity->sizeHint().width())); slOpacity->setFixedHeight(iconsize); slOpacity->setBlockUpdateSignalOnDrag(true); slFlow->setRange(0, 100, 0); slFlow->setValue(100); slFlow->setSingleStep(5); slFlow->setSuffix(i18n("%")); slFlow->setMinimumWidth(qMax(sliderWidth, slFlow->sizeHint().width())); slFlow->setFixedHeight(iconsize); slFlow->setBlockUpdateSignalOnDrag(true); slSize->setRange(0.01, cfg.readEntry("maximumBrushSize", 1000), 2); slSize->setValue(100); slSize->setSingleStep(1); slSize->setExponentRatio(3.0); slSize->setSuffix(i18n(" px")); slSize->setMinimumWidth(qMax(sliderWidth, slSize->sizeHint().width())); slSize->setFixedHeight(iconsize); slSize->setBlockUpdateSignalOnDrag(true); m_sliderChooser[i]->chooseWidget(cfg.toolbarSlider(i + 1)); } m_cmbCompositeOp = new KisCompositeOpComboBox(); m_cmbCompositeOp->setFixedHeight(iconsize); Q_FOREACH (KisAction * a, m_cmbCompositeOp->createBlendmodeActions()) { m_viewManager->actionManager()->addAction(a->text(), a); } m_workspaceWidget = new KisPopupButton(this); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_workspaceWidget->setToolTip(i18n("Choose workspace")); m_workspaceWidget->setFixedSize(iconsize, iconsize); m_workspaceWidget->setPopupWidget(new KisWorkspaceChooser(view)); QHBoxLayout* baseLayout = new QHBoxLayout(this); m_paintopWidget = new QWidget(this); baseLayout->addWidget(m_paintopWidget); baseLayout->setSpacing(4); baseLayout->setContentsMargins(0, 0, 0, 0); m_layout = new QHBoxLayout(m_paintopWidget); if (!cfg.toolOptionsInDocker()) { m_layout->addWidget(m_toolOptionsPopupButton); } m_layout->addWidget(m_brushEditorPopupButton); m_layout->addWidget(m_presetSelectorPopupButton); m_layout->setSpacing(4); m_layout->setContentsMargins(0, 0, 0, 0); QWidget* compositeActions = new QWidget(this); QHBoxLayout* compositeLayout = new QHBoxLayout(compositeActions); compositeLayout->addWidget(m_cmbCompositeOp); compositeLayout->addWidget(m_eraseModeButton); compositeLayout->addWidget(m_alphaLockButton); compositeLayout->setSpacing(4); compositeLayout->setContentsMargins(0, 0, 0, 0); compositeLayout->addWidget(m_reloadButton); QWidgetAction * action; action = new QWidgetAction(this); view->actionCollection()->addAction("composite_actions", action); action->setText(i18n("Brush composite")); action->setDefaultWidget(compositeActions); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider1", action); view->actionCollection()->addAction("brushslider1", action); action->setDefaultWidget(m_sliderChooser[0]); connect(action, SIGNAL(triggered()), m_sliderChooser[0], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[0], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider2", action); view->actionCollection()->addAction("brushslider2", action); action->setDefaultWidget(m_sliderChooser[1]); connect(action, SIGNAL(triggered()), m_sliderChooser[1], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[1], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider3", action); view->actionCollection()->addAction("brushslider3", action); action->setDefaultWidget(m_sliderChooser[2]); connect(action, SIGNAL(triggered()), m_sliderChooser[2], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[2], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("next_favorite_preset", action); view->actionCollection()->addAction("next_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotNextFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_favorite_preset", action); view->actionCollection()->addAction("previous_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotPreviousFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_preset", action); view->actionCollection()->addAction("previous_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotSwitchToPreviousPreset())); if (!cfg.toolOptionsInDocker()) { action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_tool_options", action); view->actionCollection()->addAction("show_tool_options", action); connect(action, SIGNAL(triggered()), m_toolOptionsPopupButton, SLOT(showPopupWidget())); } action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_editor", action); view->actionCollection()->addAction("show_brush_editor", action); connect(action, SIGNAL(triggered()), m_brushEditorPopupButton, SLOT(showPopupWidget())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_presets", action); view->actionCollection()->addAction("show_brush_presets", action); connect(action, SIGNAL(triggered()), m_presetSelectorPopupButton, SLOT(showPopupWidget())); QWidget* mirrorActions = new QWidget(this); QHBoxLayout* mirrorLayout = new QHBoxLayout(mirrorActions); mirrorLayout->addWidget(m_hMirrorButton); mirrorLayout->addWidget(m_vMirrorButton); mirrorLayout->setSpacing(4); mirrorLayout->setContentsMargins(0, 0, 0, 0); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("mirror_actions", action); action->setDefaultWidget(mirrorActions); view->actionCollection()->addAction("mirror_actions", action); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction(ResourceType::Workspaces, action); view->actionCollection()->addAction(ResourceType::Workspaces, action); action->setDefaultWidget(m_workspaceWidget); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopup = new KisToolOptionsPopup(); m_toolOptionsPopupButton->setPopupWidget(m_toolOptionsPopup); m_toolOptionsPopup->switchDetached(false); } m_savePresetWidget = new KisPresetSaveWidget(this); m_presetsPopup = new KisPaintOpPresetsPopup(m_resourceProvider, m_favoriteResourceManager, m_savePresetWidget); m_brushEditorPopupButton->setPopupWidget(m_presetsPopup); m_presetsPopup->parentWidget()->setWindowTitle(i18n("Brush Editor")); connect(m_presetsPopup, SIGNAL(brushEditorShown()), SLOT(slotUpdateOptionsWidgetPopup())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_presetsPopup, SLOT(updateThemedIcons())); m_presetsChooserPopup = new KisPaintOpPresetsChooserPopup(); m_presetsChooserPopup->setMinimumHeight(550); m_presetsChooserPopup->setMinimumWidth(450); m_presetSelectorPopupButton->setPopupWidget(m_presetsChooserPopup); m_currCompositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); slotNodeChanged(view->activeNode()); // Get all the paintops QList keys = KisPaintOpRegistry::instance()->keys(); QList factoryList; Q_FOREACH (const QString & paintopId, keys) { factoryList.append(KisPaintOpRegistry::instance()->get(paintopId)); } m_presetsPopup->setPaintOpList(factoryList); connect(m_presetsPopup , SIGNAL(paintopActivated(QString)) , SLOT(slotSetPaintop(QString))); connect(m_presetsPopup , SIGNAL(defaultPresetClicked()) , SLOT(slotSetupDefaultPreset())); connect(m_presetsPopup , SIGNAL(signalResourceSelected(KoResourceSP )), SLOT(resourceSelected(KoResourceSP ))); connect(m_presetsPopup , SIGNAL(reloadPresetClicked()) , SLOT(slotReloadPreset())); connect(m_presetsPopup , SIGNAL(dirtyPresetToggled(bool)) , SLOT(slotDirtyPresetToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushSizeToggled(bool)) , SLOT(slotEraserBrushSizeToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushOpacityToggled(bool)) , SLOT(slotEraserBrushOpacityToggled(bool))); connect(m_presetsPopup, SIGNAL(createPresetFromScratch(QString)), this, SLOT(slotCreatePresetFromScratch(QString))); connect(m_presetsChooserPopup, SIGNAL(resourceSelected(KoResourceSP )) , SLOT(resourceSelected(KoResourceSP ))); connect(m_presetsChooserPopup, SIGNAL(resourceClicked(KoResourceSP )) , SLOT(resourceSelected(KoResourceSP ))); connect(m_resourceProvider , SIGNAL(sigNodeChanged(KisNodeSP)) , SLOT(slotNodeChanged(KisNodeSP))); connect(m_cmbCompositeOp , SIGNAL(currentIndexChanged(int)) , SLOT(slotSetCompositeMode(int))); connect(m_eraseAction , SIGNAL(toggled(bool)) , SLOT(slotToggleEraseMode(bool))); connect(alphaLockAction , SIGNAL(toggled(bool)) , SLOT(slotToggleAlphaLockMode(bool))); m_disablePressureAction = m_viewManager->actionManager()->createAction("disable_pressure"); connect(m_disablePressureAction , SIGNAL(toggled(bool)) , SLOT(slotDisablePressureMode(bool))); m_disablePressureAction->setChecked(true); connect(m_hMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotHorizontalMirrorChanged(bool))); connect(m_vMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotVerticalMirrorChanged(bool))); connect(m_reloadAction , SIGNAL(triggered()) , SLOT(slotReloadPreset())); connect(m_sliderChooser[0]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[1]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[2]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_resourceProvider, SIGNAL(sigFGColorUsed(KoColor)), m_favoriteResourceManager, SLOT(slotAddRecentColor(KoColor))); connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotChangeFGColorSelector(KoColor))); connect(m_resourceProvider, SIGNAL(sigBGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotSetBGColor(KoColor))); // cold initialization m_favoriteResourceManager->slotChangeFGColorSelector(m_resourceProvider->fgColor()); m_favoriteResourceManager->slotSetBGColor(m_resourceProvider->bgColor()); connect(m_favoriteResourceManager, SIGNAL(sigSetFGColor(KoColor)), m_resourceProvider, SLOT(slotSetFGColor(KoColor))); connect(m_favoriteResourceManager, SIGNAL(sigSetBGColor(KoColor)), m_resourceProvider, SLOT(slotSetBGColor(KoColor))); connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateSelectionIcon())); connect(m_resourceProvider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(slotCanvasResourceChanged(int,QVariant))); connect(m_resourceProvider->resourceManager(), SIGNAL(canvasResourceChangeAttempted(int,QVariant)), this, SLOT(slotCanvasResourceChangeAttempted(int,QVariant))); slotInputDeviceChanged(KoToolManager::instance()->currentInputDevice()); findDefaultPresets(); } KisPaintopBox::~KisPaintopBox() { KisConfig cfg(false); QMapIterator iter(m_tabletToolMap); while (iter.hasNext()) { iter.next(); if ((iter.key().pointer) == QTabletEvent::Eraser) { cfg.writeEntry(QString("LastEraser") , iter.value().preset->name()); } else { cfg.writeEntry(QString("LastPreset"), iter.value().preset->name()); } } // Do not delete the widget, since it is global to the application, not owned by the view m_presetsPopup->setPaintOpSettingsWidget(0); qDeleteAll(m_paintopOptionWidgets); delete m_favoriteResourceManager; for (int i = 0; i < 3; ++i) { delete m_sliderChooser[i]; } } void KisPaintopBox::restoreResource(KoResourceSP resource) { KisPaintOpPresetSP preset = resource.dynamicCast(); if (preset) { setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::newOptionWidgets(const QList > &optionWidgetList) { if (m_toolOptionsPopup) { m_toolOptionsPopup->newOptionWidgets(optionWidgetList); } } void KisPaintopBox::resourceSelected(KoResourceSP resource) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget); m_presetsPopup->setCreatingBrushFromScratch(false); // show normal UI elements when we are not creating // qDebug() << ">>>>>>>>>>>>>>>" << resource // << (resource ? resource->name() : "") // << (resource ? QString("%1").arg(resource->valid()) : "") // << (resource ? QString("%1").arg(resource->filename()) : ""); KisPaintOpPresetSP preset = resource.dynamicCast(); if (preset && preset->valid() && preset != m_resourceProvider->currentPreset()) { qWarning() << "Preset reloading if presets are dirty is broken"; // if (!preset->settings()->isLoadable()) { // return; // } // if (!m_dirtyPresetsEnabled) { // KisSignalsBlocker blocker(m_optionWidget); // Q_UNUSED(blocker) // if (!preset->load()) { // qWarning() << "failed to load the preset."; // } // } dbgResources << "resourceSelected: preset" << preset << (preset ? QString("%1").arg(preset->valid()) : ""); setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::setCurrentPaintop(const KoID& paintop) { KisPaintOpPresetSP preset = activePreset(paintop); Q_ASSERT(preset && preset->settings()); //qDebug() << "setCurrentPaintop();" << paintop << preset; setCurrentPaintop(preset); } void KisPaintopBox::setCurrentPaintop(KisPaintOpPresetSP preset) { //qDebug() << "setCurrentPaintop(); " << preset->name(); if (preset == m_resourceProvider->currentPreset()) { if (preset == m_tabletToolMap[m_currTabletToolID].preset) { return; } } Q_ASSERT(preset); const KoID& paintop = preset->paintOp(); m_presetConnections.clear(); if (m_resourceProvider->currentPreset()) { m_resourceProvider->setPreviousPaintOpPreset(m_resourceProvider->currentPreset()); if (m_optionWidget) { m_optionWidget->hide(); } } if (!m_paintopOptionWidgets.contains(paintop)) m_paintopOptionWidgets[paintop] = KisPaintOpRegistry::instance()->get(paintop.id())->createConfigWidget(this); m_optionWidget = m_paintopOptionWidgets[paintop]; KisSignalsBlocker b(m_optionWidget); preset->setOptionsWidget(m_optionWidget); m_optionWidget->setImage(m_viewManager->image()); m_optionWidget->setNode(m_viewManager->activeNode()); m_presetsPopup->setPaintOpSettingsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); Q_ASSERT(m_optionWidget && m_presetSelectorPopupButton); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigConfigurationUpdated()), this, SLOT(slotGuiChangedCurrentPreset())); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigSaveLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP))); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigDropLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotDropLockedOption(KisPropertiesConfigurationSP))); // load the current brush engine icon for the brush editor toolbar button m_brushEditorPopupButton->setThumbnail(preset->image()); m_presetsPopup->setCurrentPaintOpId(paintop.id()); ////qDebug() << "\tsetting the new preset for" << m_currTabletToolID.uniqueID << "to" << preset->name(); m_paintOpPresetMap[m_resourceProvider->currentPreset()->paintOp()] = preset; m_tabletToolMap[m_currTabletToolID].preset = preset; m_tabletToolMap[m_currTabletToolID].paintOpID = preset->paintOp(); if (m_presetsPopup->currentPaintOpId() != paintop.id()) { // Must change the paintop as the current one is not supported // by the new colorspace. dbgKrita << "current paintop " << paintop.name() << " was not set, not supported by colorspace"; } m_currCompositeOpID = preset->settings()->paintOpCompositeOp(); updateCompositeOp(m_currCompositeOpID); } void KisPaintopBox::slotUpdateOptionsWidgetPopup() { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); // This happens when we have a new brush engine for which no default preset exists yet. if (!preset) return; KIS_SAFE_ASSERT_RECOVER_RETURN(preset); KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget); m_optionWidget->setConfigurationSafe(preset->settings()); m_presetsPopup->resourceSelected(preset); m_presetsPopup->updateViewSettings(); // the m_viewManager->image() is set earlier, but the reference will be missing when the stamp button is pressed // need to later do some research on how and when we should be using weak shared pointers (WSP) that creates this situation m_optionWidget->setImage(m_viewManager->image()); } KisPaintOpPresetSP KisPaintopBox::defaultPreset(const KoID& paintOp) { QString defaultName = paintOp.id() + ".kpp"; QString path = KoResourcePaths::findResource("kis_defaultpresets", defaultName); KisPaintOpPresetSP preset(new KisPaintOpPreset(path)); if (!preset->load(KisGlobalResourcesInterface::instance())) { preset = KisPaintOpRegistry::instance()->defaultPreset(paintOp, KisGlobalResourcesInterface::instance()); } Q_ASSERT(preset); Q_ASSERT(preset->valid()); return preset; } KisPaintOpPresetSP KisPaintopBox::activePreset(const KoID& paintOp) { if (m_paintOpPresetMap[paintOp] == 0) { m_paintOpPresetMap[paintOp] = defaultPreset(paintOp); } return m_paintOpPresetMap[paintOp]; } void KisPaintopBox::updateCompositeOp(QString compositeOpID) { if (!m_optionWidget) return; KisSignalsBlocker blocker(m_optionWidget); KisNodeSP node = m_resourceProvider->currentNode(); if (node && node->paintDevice()) { if (!node->paintDevice()->colorSpace()->hasCompositeOp(compositeOpID)) compositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); { KisSignalsBlocker b1(m_cmbCompositeOp); m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID)); } if (compositeOpID != m_currCompositeOpID) { m_currCompositeOpID = compositeOpID; } if (compositeOpID == COMPOSITE_ERASE || m_resourceProvider->eraserMode()) { m_eraseModeButton->setChecked(true); } else { m_eraseModeButton->setChecked(false); } } else if (!node) { KisSignalsBlocker b1(m_cmbCompositeOp); m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID)); m_currCompositeOpID = compositeOpID; } } void KisPaintopBox::setWidgetState(int flags) { if (flags & (ENABLE_COMPOSITEOP | DISABLE_COMPOSITEOP)) { m_cmbCompositeOp->setEnabled(flags & ENABLE_COMPOSITEOP); m_eraseModeButton->setEnabled(flags & ENABLE_COMPOSITEOP); } if (flags & (ENABLE_PRESETS | DISABLE_PRESETS)) { m_presetSelectorPopupButton->setEnabled(flags & ENABLE_PRESETS); m_brushEditorPopupButton->setEnabled(flags & ENABLE_PRESETS); } for (int i = 0; i < 3; ++i) { if (flags & (ENABLE_OPACITY | DISABLE_OPACITY)) m_sliderChooser[i]->getWidget("opacity")->setEnabled(flags & ENABLE_OPACITY); if (flags & (ENABLE_FLOW | DISABLE_FLOW)) m_sliderChooser[i]->getWidget("flow")->setEnabled(flags & ENABLE_FLOW); if (flags & (ENABLE_SIZE | DISABLE_SIZE)) m_sliderChooser[i]->getWidget("size")->setEnabled(flags & ENABLE_SIZE); } } void KisPaintopBox::setSliderValue(const QString& sliderID, qreal value) { for (int i = 0; i < 3; ++i) { KisDoubleSliderSpinBox* slider = m_sliderChooser[i]->getWidget(sliderID); KisSignalsBlocker b(slider); if (sliderID == "opacity" || sliderID == "flow") { // opacity and flows UI stored at 0-100% slider->setValue(value*100); } else { slider->setValue(value); // brush size } } } void KisPaintopBox::slotSetPaintop(const QString& paintOpId) { if (KisPaintOpRegistry::instance()->get(paintOpId) != 0) { KoID id(paintOpId, KisPaintOpRegistry::instance()->get(paintOpId)->name()); //qDebug() << "slotsetpaintop" << id; setCurrentPaintop(id); } } void KisPaintopBox::slotInputDeviceChanged(const KoInputDevice& inputDevice) { TabletToolMap::iterator toolData = m_tabletToolMap.find(inputDevice); //qDebug() << "slotInputDeviceChanged()" << inputDevice.device() << inputDevice.uniqueTabletId(); m_currTabletToolID = TabletToolID(inputDevice); if (toolData == m_tabletToolMap.end()) { KisConfig cfg(true); KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); KisPaintOpPresetSP preset; if (inputDevice.pointer() == QTabletEvent::Eraser) { preset = rserver->resourceByName(cfg.readEntry(QString("LastEraser_%1").arg(inputDevice.uniqueTabletId()), m_eraserName)); } else { preset = rserver->resourceByName(cfg.readEntry(QString("LastPreset_%1").arg(inputDevice.uniqueTabletId()), m_defaultPresetName)); //if (preset) //qDebug() << "found stored preset " << preset->name() << "for" << inputDevice.uniqueTabletId(); //else //qDebug() << "no preset found for" << inputDevice.uniqueTabletId(); } if (!preset) { preset = rserver->resourceByName(m_defaultPresetName); } if (preset) { //qDebug() << "inputdevicechanged 1" << preset; setCurrentPaintop(preset); } } else { if (toolData->preset) { //qDebug() << "inputdevicechanged 2" << toolData->preset; setCurrentPaintop(toolData->preset); } else { //qDebug() << "inputdevicechanged 3" << toolData->paintOpID; setCurrentPaintop(toolData->paintOpID); } } } void KisPaintopBox::slotCreatePresetFromScratch(QString paintop) { //First try to select an available default preset for that engine. If it doesn't exist, then //manually set the engine to use a new preset. KoID id(paintop, KisPaintOpRegistry::instance()->get(paintop)->name()); KisPaintOpPresetSP preset = defaultPreset(id); slotSetPaintop(paintop); // change the paintop settings area and update the UI if (!preset) { m_presetsPopup->setCreatingBrushFromScratch(true); // disable UI elements while creating from scratch preset = m_resourceProvider->currentPreset(); } else { m_resourceProvider->setPaintOpPreset(preset); preset->setOptionsWidget(m_optionWidget); } m_presetsPopup->resourceSelected(preset); // this helps update the UI on the brush editor } void KisPaintopBox::slotCanvasResourceChangeAttempted(int key, const QVariant &value) { Q_UNUSED(value); if (key == KoCanvasResourceProvider::ForegroundColor) { slotUnsetEraseMode(); } } void KisPaintopBox::slotCanvasResourceChanged(int key, const QVariant &value) { if (m_viewManager) { sender()->blockSignals(true); KisPaintOpPresetSP preset = m_viewManager->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && m_resourceProvider->currentPreset()->name() != preset->name()) { QString compositeOp = preset->settings()->getString("CompositeOp"); updateCompositeOp(compositeOp); resourceSelected(preset); } if (key == KisCanvasResourceProvider::CurrentPaintOpPreset) { /** * Update currently selected preset in both the popup widgets */ m_presetsChooserPopup->canvasResourceChanged(preset); m_presetsPopup->currentPresetChanged(preset); } if (key == KisCanvasResourceProvider::CurrentCompositeOp) { if (m_resourceProvider->currentCompositeOp() != m_currCompositeOpID) { updateCompositeOp(m_resourceProvider->currentCompositeOp()); } } if (key == KisCanvasResourceProvider::Size) { setSliderValue("size", m_resourceProvider->size()); } if (key == KisCanvasResourceProvider::Opacity) { setSliderValue("opacity", m_resourceProvider->opacity()); } if (key == KisCanvasResourceProvider::Flow) { setSliderValue("flow", m_resourceProvider->flow()); } if (key == KisCanvasResourceProvider::EraserMode) { m_eraseAction->setChecked(value.toBool()); } if (key == KisCanvasResourceProvider::DisablePressure) { m_disablePressureAction->setChecked(value.toBool()); } if (key == KisCanvasResourceProvider::MirrorHorizontal) { m_hMirrorAction->setChecked(value.toBool()); } if (key == KisCanvasResourceProvider::MirrorVertical) { m_vMirrorAction->setChecked(value.toBool()); } sender()->blockSignals(false); } } void KisPaintopBox::slotSetupDefaultPreset() { KisPaintOpPresetSP preset = defaultPreset(m_resourceProvider->currentPreset()->paintOp()); preset->setOptionsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); // tell the brush editor that the resource has changed // so it can update everything m_presetsPopup->resourceSelected(preset); } void KisPaintopBox::slotNodeChanged(const KisNodeSP node) { if (m_previousNode.isValid() && m_previousNode->paintDevice()) disconnect(m_previousNode->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); // Reconnect colorspace change of node if (node && node->paintDevice()) { connect(node->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); m_resourceProvider->setCurrentCompositeOp(m_currCompositeOpID); m_previousNode = node; slotColorSpaceChanged(node->colorSpace()); } if (m_optionWidget) { m_optionWidget->setNode(node); } } void KisPaintopBox::slotColorSpaceChanged(const KoColorSpace* colorSpace) { m_cmbCompositeOp->validate(colorSpace); } void KisPaintopBox::slotToggleEraseMode(bool checked) { const bool oldEraserMode = m_resourceProvider->eraserMode(); m_resourceProvider->setEraserMode(checked); if (oldEraserMode != checked && m_eraserBrushSizeEnabled) { const qreal currentSize = m_resourceProvider->size(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush size. set the eraser size to the normal brush size if not set if (checked) { settings->setSavedBrushSize(currentSize); if (qFuzzyIsNull(settings->savedEraserSize())) { settings->setSavedEraserSize(currentSize); } } else { settings->setSavedEraserSize(currentSize); if (qFuzzyIsNull(settings->savedBrushSize())) { settings->setSavedBrushSize(currentSize); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newSize = checked ? settings->savedEraserSize() : settings->savedBrushSize(); m_resourceProvider->setSize(newSize); } if (oldEraserMode != checked && m_eraserBrushOpacityEnabled) { const qreal currentOpacity = m_resourceProvider->opacity(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush opacity. set the eraser opacity to the normal brush opacity if not set if (checked) { settings->setSavedBrushOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedEraserOpacity())) { settings->setSavedEraserOpacity(currentOpacity); } } else { settings->setSavedEraserOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedBrushOpacity())) { settings->setSavedBrushOpacity(currentOpacity); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newOpacity = checked ? settings->savedEraserOpacity() : settings->savedBrushOpacity(); m_resourceProvider->setOpacity(newOpacity); } } void KisPaintopBox::slotSetCompositeMode(int index) { Q_UNUSED(index); QString compositeOp = m_cmbCompositeOp->selectedCompositeOp().id(); m_resourceProvider->setCurrentCompositeOp(compositeOp); } void KisPaintopBox::slotHorizontalMirrorChanged(bool value) { m_resourceProvider->setMirrorHorizontal(value); } void KisPaintopBox::slotVerticalMirrorChanged(bool value) { m_resourceProvider->setMirrorVertical(value); } void KisPaintopBox::sliderChanged(int n) { if (!m_optionWidget) // widget will not exist if the are no documents open return; KisSignalsBlocker blocker(m_optionWidget); // flow and opacity are shown as 0-100% on the UI, but their data is actually 0-1. Convert those two values // back for further work qreal opacity = m_sliderChooser[n]->getWidget("opacity")->value()/100; qreal flow = m_sliderChooser[n]->getWidget("flow")->value()/100; qreal size = m_sliderChooser[n]->getWidget("size")->value(); setSliderValue("opacity", opacity); setSliderValue("flow" , flow); setSliderValue("size" , size); if (m_presetsEnabled) { // IMPORTANT: set the PaintOp size before setting the other properties // it won't work the other way // TODO: why?! m_resourceProvider->setSize(size); m_resourceProvider->setOpacity(opacity); m_resourceProvider->setFlow(flow); KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(m_resourceProvider->currentPreset()->settings()); propertiesProxy->setProperty("OpacityValue", opacity); propertiesProxy->setProperty("FlowValue", flow); m_optionWidget->setConfigurationSafe(m_resourceProvider->currentPreset()->settings().data()); } else { m_resourceProvider->setOpacity(opacity); } m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset()); } void KisPaintopBox::slotSlider1Changed() { sliderChanged(0); } void KisPaintopBox::slotSlider2Changed() { sliderChanged(1); } void KisPaintopBox::slotSlider3Changed() { sliderChanged(2); } void KisPaintopBox::slotToolChanged(KoCanvasController* canvas, int toolId) { Q_UNUSED(canvas); Q_UNUSED(toolId); if (!m_viewManager->canvasBase()) return; QString id = KoToolManager::instance()->activeToolId(); KisTool* tool = dynamic_cast(KoToolManager::instance()->toolById(m_viewManager->canvasBase(), id)); if (tool) { int flags = tool->flags(); if (flags & KisTool::FLAG_USES_CUSTOM_COMPOSITEOP) { setWidgetState(ENABLE_COMPOSITEOP | ENABLE_OPACITY); } else { setWidgetState(DISABLE_COMPOSITEOP | DISABLE_OPACITY); } if (flags & KisTool::FLAG_USES_CUSTOM_PRESET) { setWidgetState(ENABLE_PRESETS); if (!m_resourceProvider->currentPreset()) return; // block updates of avoid some over updating of the option widget m_blockUpdate = true; setSliderValue("size", m_resourceProvider->size()); { qreal opacity = m_resourceProvider->currentPreset()->settings()->paintOpOpacity(); m_resourceProvider->setOpacity(opacity); setSliderValue("opacity", opacity); setWidgetState(ENABLE_OPACITY); } { setSliderValue("flow", m_resourceProvider->currentPreset()->settings()->paintOpFlow()); setWidgetState(ENABLE_FLOW); } { updateCompositeOp(m_resourceProvider->currentPreset()->settings()->paintOpCompositeOp()); setWidgetState(ENABLE_COMPOSITEOP); } m_blockUpdate = false; m_presetsEnabled = true; } else { setWidgetState(DISABLE_PRESETS); m_presetsEnabled = false; } if (flags & KisTool::FLAG_USES_CUSTOM_SIZE) { setWidgetState(ENABLE_SIZE | ENABLE_FLOW); } else { setWidgetState(DISABLE_SIZE | DISABLE_FLOW); } } else setWidgetState(DISABLE_ALL); } void KisPaintopBox::slotPreviousFavoritePreset() { if (!m_favoriteResourceManager) return; QVector presets = m_favoriteResourceManager->favoritePresetNamesList(); for (int i=0; i < presets.size(); ++i) { if (m_resourceProvider->currentPreset() && m_resourceProvider->currentPreset()->name() == presets[i]) { if (i > 0) { m_favoriteResourceManager->slotChangeActivePaintop(i - 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(m_favoriteResourceManager->numFavoritePresets() - 1); } //floating message should have least 2 lines, otherwise //preset thumbnail will be too small to distinguish //(because size of image on floating message depends on amount of lines in msg) m_viewManager->showFloatingMessage( i18n("%1\nselected", m_resourceProvider->currentPreset()->name()), QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image()))); return; } } } void KisPaintopBox::slotNextFavoritePreset() { if (!m_favoriteResourceManager) return; QVector presets = m_favoriteResourceManager->favoritePresetNamesList(); for(int i = 0; i < presets.size(); ++i) { if (m_resourceProvider->currentPreset()->name() == presets[i]) { if (i < m_favoriteResourceManager->numFavoritePresets() - 1) { m_favoriteResourceManager->slotChangeActivePaintop(i + 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(0); } m_viewManager->showFloatingMessage( i18n("%1\nselected", m_resourceProvider->currentPreset()->name()), QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image()))); return; } } } void KisPaintopBox::slotSwitchToPreviousPreset() { if (m_resourceProvider->previousPreset()) { //qDebug() << "slotSwitchToPreviousPreset();" << m_resourceProvider->previousPreset(); setCurrentPaintop(m_resourceProvider->previousPreset()); m_viewManager->showFloatingMessage( i18n("%1\nselected", m_resourceProvider->currentPreset()->name()), QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image()))); } } void KisPaintopBox::slotUnsetEraseMode() { m_eraseAction->setChecked(false); } void KisPaintopBox::slotToggleAlphaLockMode(bool checked) { if (checked) { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-locked")); } else { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-unlocked")); } m_resourceProvider->setGlobalAlphaLock(checked); } void KisPaintopBox::slotDisablePressureMode(bool checked) { if (checked) { m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } m_resourceProvider->setDisablePressure(checked); } void KisPaintopBox::slotReloadPreset() { KisSignalsBlocker blocker(m_optionWidget); // Here using the name and fetching the preset from the server was the only way the load was working. Otherwise it was not loading. KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); QSharedPointer preset = rserver->resourceByName(m_resourceProvider->currentPreset()->name()); if (preset) { preset->load(KisGlobalResourcesInterface::instance()); } if (m_resourceProvider->currentPreset() != preset) { m_resourceProvider->setPaintOpPreset(preset); } else { /** * HACK ALERT: here we emit a private signal from the resource manager to * ensure that all the subscribers of resource-changed signal got the * notification. That is especially important for * KisPaintopTransformationConnector. See bug 392622. */ emit m_resourceProvider->resourceManager()->canvasResourceChanged( KisCanvasResourceProvider::CurrentPaintOpPreset, QVariant::fromValue(preset)); } } void KisPaintopBox::slotGuiChangedCurrentPreset() // Called only when UI is changed and not when preset is changed { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { /** * Here we postpone all the settings updates events until the entire writing * operation will be finished. As soon as it is finished, the updates will be * emitted happily (if there were any). */ KisPaintOpPreset::UpdatedPostponer postponer(preset); QStringList preserveProperties; preserveProperties << "lodUserAllowed"; preserveProperties << "lodSizeThreshold"; // clear all the properties before dumping the stuff into the preset, // some of the options add the values incrementally // (e.g. KisPaintOpUtils::RequiredBrushFilesListTag), therefore they // may add up if we pass the same preset multiple times preset->settings()->resetSettings(preserveProperties); m_optionWidget->writeConfigurationSafe(const_cast(preset->settings().data())); } // we should also update the preset strip to update the status of the "dirty" mark m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset()); // TODO!!!!!!!! //m_presetsPopup->updateViewSettings(); } void KisPaintopBox::slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p) { QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); m_resourceProvider->currentPreset()->settings()->setProperty(i.key(), QVariant(i.value())); if (m_resourceProvider->currentPreset()->settings()->hasProperty(i.key() + "_previous")) { m_resourceProvider->currentPreset()->settings()->removeProperty(i.key() + "_previous"); } } slotGuiChangedCurrentPreset(); } void KisPaintopBox::slotDropLockedOption(KisPropertiesConfigurationSP p) { KisSignalsBlocker blocker(m_optionWidget); KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { KisResourceDirtyStateSaver dirtySaver(preset); QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); if (preset->settings()->hasProperty(i.key() + "_previous")) { preset->settings()->setProperty(i.key(), preset->settings()->getProperty(i.key() + "_previous")); preset->settings()->removeProperty(i.key() + "_previous"); } } } } void KisPaintopBox::slotDirtyPresetToggled(bool value) { if (!value) { slotReloadPreset(); m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset()); m_presetsPopup->updateViewSettings(); } m_dirtyPresetsEnabled = value; KisConfig cfg(false); cfg.setUseDirtyPresets(m_dirtyPresetsEnabled); } void KisPaintopBox::slotEraserBrushSizeToggled(bool value) { m_eraserBrushSizeEnabled = value; KisConfig cfg(false); cfg.setUseEraserBrushSize(m_eraserBrushSizeEnabled); } void KisPaintopBox::slotEraserBrushOpacityToggled(bool value) { m_eraserBrushOpacityEnabled = value; KisConfig cfg(false); cfg.setUseEraserBrushOpacity(m_eraserBrushOpacityEnabled); } void KisPaintopBox::slotUpdateSelectionIcon() { m_hMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-horizontal")); m_vMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-vertical")); KisConfig cfg(true); if (!cfg.toolOptionsInDocker() && m_toolOptionsPopupButton) { m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); } m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_eraseAction->setIcon(KisIconUtils::loadIcon("draw-eraser")); m_reloadAction->setIcon(KisIconUtils::loadIcon("view-refresh")); if (m_disablePressureAction->isChecked()) { m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureAction->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } } void KisPaintopBox::slotLockXMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorHorizontalLock(toggleLock); } void KisPaintopBox::slotLockYMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorVerticalLock(toggleLock); } void KisPaintopBox::slotHideDecorationMirrorX(bool toggled) { m_resourceProvider->setMirrorHorizontalHideDecorations(toggled); } void KisPaintopBox::slotHideDecorationMirrorY(bool toggled) { m_resourceProvider->setMirrorVerticalHideDecorations(toggled); } void KisPaintopBox::slotMoveToCenterMirrorX() { m_resourceProvider->mirrorHorizontalMoveCanvasToCenter(); } void KisPaintopBox::slotMoveToCenterMirrorY() { m_resourceProvider->mirrorVerticalMoveCanvasToCenter(); } void KisPaintopBox::findDefaultPresets() { KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); m_eraserName = "eraser_circle"; m_defaultPresetName = "basic_tip_default"; KisResourceModel *resourceModel = rserver->resourceModel(); for (int i = 0; i < resourceModel->rowCount(); i++) { QModelIndex idx = resourceModel->index(i, 0); - QString resourceName = idx.data(Qt::UserRole + KisResourceModel::Name).toString().toLower(); - QString fileName = idx.data(Qt::UserRole + KisResourceModel::Filename).toString().toLower(); + QString resourceName = idx.data(Qt::UserRole + KisAbstractResourceModel::Name).toString().toLower(); + QString fileName = idx.data(Qt::UserRole + KisAbstractResourceModel::Filename).toString().toLower(); if (resourceName.contains("eraser_circle")) { m_eraserName = resourceName; } else if (resourceName.contains("eraser") || fileName.contains("eraser")) { m_eraserName = resourceName; } if (resourceName.contains("basic_tip_default")) { m_defaultPresetName = resourceName; } else if (resourceName.contains("default") || fileName.contains("default")) { m_defaultPresetName = resourceName; } } } diff --git a/libs/ui/widgets/kis_preset_chooser.cpp b/libs/ui/widgets/kis_preset_chooser.cpp index 434de80461..8d2306cd6c 100644 --- a/libs/ui/widgets/kis_preset_chooser.cpp +++ b/libs/ui/widgets/kis_preset_chooser.cpp @@ -1,486 +1,486 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2009 Sven Langkamp * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_preset_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "kis_global.h" #include "kis_slider_spin_box.h" #include "kis_config_notifier.h" #include /// The resource item delegate for rendering the resource preview class KisPresetDelegate : public QAbstractItemDelegate { public: KisPresetDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent), m_showText(false), m_useDirtyPresets(false) {} ~KisPresetDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } void setShowText(bool showText) { m_showText = showText; } void setUseDirtyPresets(bool value) { m_useDirtyPresets = value; } private: bool m_showText; bool m_useDirtyPresets; }; void KisPresetDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { painter->save(); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); if (!index.isValid()) { qDebug() << "KisPresetDelegate::paint: index is invalid"; painter->restore(); return; } - bool dirty = index.data(Qt::UserRole + KisResourceModel::Dirty).toBool(); + bool dirty = index.data(Qt::UserRole + KisAbstractResourceModel::Dirty).toBool(); - QImage preview = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); + QImage preview = index.data(Qt::UserRole + KisAbstractResourceModel::Thumbnail).value(); if (preview.isNull()) { qDebug() << "KisPresetDelegate::paint: Preview is null"; painter->restore(); return; } - QMap metaData = index.data(Qt::UserRole + KisResourceModel::MetaData).value>(); + QMap metaData = index.data(Qt::UserRole + KisAbstractResourceModel::MetaData).value>(); QRect paintRect = option.rect.adjusted(1, 1, -1, -1); if (!m_showText) { painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(paintRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else { QSize pixSize(paintRect.height(), paintRect.height()); painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(pixSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); // Put an asterisk after the preset if it is dirty. This will help in case the pixmap icon is too small QString dirtyPresetIndicator = QString(""); if (m_useDirtyPresets && dirty) { dirtyPresetIndicator = QString("*"); } // qreal brushSize = metaData["paintopSize"].toReal(); // QString brushSizeText; // // Disable displayed decimal precision beyond a certain brush size // if (brushSize < 100) { // brushSizeText = QString::number(brushSize, 'g', 3); // } else { // brushSizeText = QString::number(brushSize, 'f', 0); // } // painter->drawText(pixSize.width() + 10, option.rect.y() + option.rect.height() - 10, brushSizeText); // brush size - QString presetDisplayName = index.data(Qt::UserRole + KisResourceModel::Name).toString().replace("_", " "); // don't need underscores that might be part of the file name + QString presetDisplayName = index.data(Qt::UserRole + KisAbstractResourceModel::Name).toString().replace("_", " "); // don't need underscores that might be part of the file name painter->drawText(pixSize.width() + 40, option.rect.y() + option.rect.height() - 10, presetDisplayName.append(dirtyPresetIndicator)); } if (m_useDirtyPresets && dirty) { const QIcon icon = KisIconUtils::loadIcon(koIconName("dirty-preset")); QPixmap pixmap = icon.pixmap(QSize(15,15)); painter->drawPixmap(paintRect.x() + 3, paintRect.y() + 3, pixmap); } // if (!preset->settings() || !preset->settings()->isValid()) { // const QIcon icon = KisIconUtils::loadIcon("broken-preset"); // icon.paint(painter, QRect(paintRect.x() + paintRect.height() - 25, paintRect.y() + paintRect.height() - 25, 25, 25)); // } if (option.state & QStyle::State_Selected) { painter->setCompositionMode(QPainter::CompositionMode_HardLight); painter->setOpacity(1.0); painter->fillRect(option.rect, option.palette.highlight()); // highlight is not strong enough to pick out preset. draw border around it. painter->setCompositionMode(QPainter::CompositionMode_SourceOver); painter->setPen(QPen(option.palette.highlight(), 4, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); QRect selectedBorder = option.rect.adjusted(2 , 2, -2, -2); // constrict the rectangle so it doesn't bleed into other presets painter->drawRect(selectedBorder); } painter->restore(); } class KisPresetChooser::PaintOpFilterModel : public QSortFilterProxyModel, public KisAbstractResourceModel { Q_OBJECT public: PaintOpFilterModel(QObject *parent = 0) : QSortFilterProxyModel(parent) { } ~PaintOpFilterModel() override { } void setPaintOpId(const QString &id) { m_id = id; } QString currentPaintOpId() const { return m_id; } // KisAbstractResourceModel interface public: KoResourceSP resourceForIndex(QModelIndex index) const override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->resourceForIndex(mapToSource(index)); } return 0; } QModelIndex indexFromResource(KoResourceSP resource) const override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return mapFromSource(source->indexFromResource(resource)); } return QModelIndex(); } - bool removeResource(const QModelIndex &index) override + bool setResourceInactive(const QModelIndex &index) override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { - return source->removeResource(mapToSource(index)); + return source->setResourceInactive(mapToSource(index)); } return false; } bool importResourceFile(const QString &filename) override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->importResourceFile(filename); } return false; } bool addResource(KoResourceSP resource, const QString &storageId = QString()) override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->addResource(resource, storageId); } return false; } bool updateResource(KoResourceSP resource) override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->updateResource(resource); } return false; } bool renameResource(KoResourceSP resource, const QString &name) override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->renameResource(resource, name); } return false; } - bool removeResource(KoResourceSP resource) override + bool setResourceInactive(KoResourceSP resource) override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { - return source->removeResource(resource); + return source->setResourceInactive(resource); } return false; } bool setResourceMetaData(KoResourceSP resource, QMap metadata) override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->setResourceMetaData(resource, metadata); } return false; } // QSortFilterProxyModel interface protected: QVariant data(const QModelIndex &index, int role) const override { return sourceModel()->data(mapToSource(index), role); } bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { if (m_id.isEmpty()) return true; QModelIndex idx = sourceModel()->index(source_row, 0, source_parent); - QMap metadata = sourceModel()->data(idx, Qt::UserRole + KisResourceModel::MetaData).toMap(); + QMap metadata = sourceModel()->data(idx, Qt::UserRole + KisAbstractResourceModel::MetaData).toMap(); if (metadata.contains("paintopid")) { return (metadata["paintopid"].toString() == m_id); } return false; } bool filterAcceptsColumn(int /*source_column*/, const QModelIndex &/*source_parent*/) const override { return true; } bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override { - QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisResourceModel::Name).toString(); - QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisResourceModel::Name).toString(); + QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisAbstractResourceModel::Name).toString(); + QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisAbstractResourceModel::Name).toString(); return nameLeft < nameRight; } private: QString m_id; }; KisPresetChooser::KisPresetChooser(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QVBoxLayout * layout = new QVBoxLayout(this); layout->setMargin(0); m_paintOpFilterModel = new PaintOpFilterModel(); m_chooser = new KisResourceItemChooser(ResourceType::PaintOpPresets, false, this, m_paintOpFilterModel); m_chooser->setObjectName("ResourceChooser"); m_chooser->setRowHeight(50); m_delegate = new KisPresetDelegate(this); m_chooser->setItemDelegate(m_delegate); m_chooser->setSynced(true); layout->addWidget(m_chooser); { QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this->itemChooser()->itemView()); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } } connect(m_chooser, SIGNAL(resourceSelected(KoResourceSP )), this, SLOT(slotResourceWasSelected(KoResourceSP ))); connect(m_chooser, SIGNAL(resourceSelected(KoResourceSP )), this, SIGNAL(resourceSelected(KoResourceSP ))); connect(m_chooser, SIGNAL(resourceClicked(KoResourceSP )), this, SIGNAL(resourceClicked(KoResourceSP ))); m_mode = THUMBNAIL; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(notifyConfigChanged())); notifyConfigChanged(); } KisPresetChooser::~KisPresetChooser() { } void KisPresetChooser::showButtons(bool show) { m_chooser->showButtons(show); } void KisPresetChooser::setViewMode(KisPresetChooser::ViewMode mode) { m_mode = mode; updateViewSettings(); } void KisPresetChooser::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); updateViewSettings(); } void KisPresetChooser::notifyConfigChanged() { KisConfig cfg(true); m_delegate->setUseDirtyPresets(cfg.useDirtyPresets()); setIconSize(cfg.presetIconSize()); updateViewSettings(); } void KisPresetChooser::slotResourceWasSelected(KoResourceSP resource) { m_currentPresetConnections.clear(); if (!resource) return; KisPaintOpPresetSP preset = resource.dynamicCast(); KIS_SAFE_ASSERT_RECOVER_RETURN(preset); m_currentPresetConnections.addUniqueConnection( preset->updateProxy(), SIGNAL(sigSettingsChanged()), this, SLOT(slotCurrentPresetChanged())); } void KisPresetChooser::slotCurrentPresetChanged() { KoResourceSP currentResource = m_chooser->currentResource(); if (!currentResource) return; QModelIndex index = m_paintOpFilterModel->indexFromResource(currentResource); emit m_paintOpFilterModel->dataChanged(index, index, - {Qt::UserRole + KisResourceModel::Thumbnail}); + {Qt::UserRole + KisAbstractResourceModel::Thumbnail}); } void KisPresetChooser::updateViewSettings() { if (m_mode == THUMBNAIL) { m_chooser->setSynced(true); m_delegate->setShowText(false); m_chooser->itemView()->setViewMode(QListView::IconMode); m_chooser->itemView()->setFlow(QListView::LeftToRight); } else if (m_mode == DETAIL) { m_chooser->setSynced(false); m_chooser->itemView()->setViewMode(QListView::ListMode); m_chooser->itemView()->setFlow(QListView::TopToBottom); m_chooser->setColumnWidth(m_chooser->width()); KisResourceItemChooserSync* chooserSync = KisResourceItemChooserSync::instance(); m_chooser->setRowHeight(chooserSync->baseLength()); m_delegate->setShowText(true); } else if (m_mode == STRIP) { m_chooser->setSynced(false); m_chooser->itemView()->setViewMode(QListView::ListMode); m_chooser->itemView()->setFlow(QListView::LeftToRight); // An offset of 7 keeps the cell exactly square, TODO: use constants, not hardcoded numbers m_chooser->setColumnWidth(m_chooser->viewSize().height() - 7); m_delegate->setShowText(false); } } void KisPresetChooser::setCurrentResource(KoResourceSP resource) { m_chooser->setCurrentResource(resource); } KoResourceSP KisPresetChooser::currentResource() const { return m_chooser->currentResource(); } void KisPresetChooser::showTaggingBar(bool show) { m_chooser->showTaggingBar(show); } KisResourceItemChooser *KisPresetChooser::itemChooser() { return m_chooser; } void KisPresetChooser::setPresetFilter(const QString& paintOpId) { if (m_paintOpFilterModel && m_paintOpFilterModel->currentPaintOpId() != paintOpId) { m_paintOpFilterModel->setPaintOpId(paintOpId); updateViewSettings(); } } void KisPresetChooser::setIconSize(int newSize) { KisResourceItemChooserSync* chooserSync = KisResourceItemChooserSync::instance(); chooserSync->setBaseLength(newSize); updateViewSettings(); } int KisPresetChooser::iconSize() { KisResourceItemChooserSync* chooserSync = KisResourceItemChooserSync::instance(); return chooserSync->baseLength(); } void KisPresetChooser::saveIconSize() { // save icon size KisConfig cfg(false); cfg.setPresetIconSize(iconSize()); } void KisPresetChooser::slotScrollerStateChanged(QScroller::State state) { KisKineticScroller::updateCursor(this, state); } #include "kis_preset_chooser.moc" diff --git a/libs/ui/widgets/kis_workspace_chooser.cpp b/libs/ui/widgets/kis_workspace_chooser.cpp index c72247de3a..b1f9e91124 100644 --- a/libs/ui/widgets/kis_workspace_chooser.cpp +++ b/libs/ui/widgets/kis_workspace_chooser.cpp @@ -1,231 +1,231 @@ /* This file is part of the KDE project * Copyright (C) 2011 Sven Langkamp * * 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 "kis_workspace_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_workspace_resource.h" #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "KisMainWindow.h" #include "KisPart.h" #include "KisWindowLayoutManager.h" #include "dialogs/KisNewWindowLayoutDialog.h" #include "kis_config.h" #include class KisWorkspaceDelegate : public QAbstractItemDelegate { public: KisWorkspaceDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {} ~KisWorkspaceDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } }; void KisWorkspaceDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { if (!index.isValid()) return; QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Active : QPalette::Disabled; QPalette::ColorRole cr = (option.state & QStyle::State_Selected) ? QPalette::HighlightedText : QPalette::Text; painter->setPen(option.palette.color(cg, cr)); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); } else { painter->fillRect(option.rect, option.palette.base()); } - QString name = index.data(Qt::UserRole + KisResourceModel::Name).toString(); + QString name = index.data(Qt::UserRole + KisAbstractResourceModel::Name).toString(); painter->drawText(option.rect.x() + 5, option.rect.y() + painter->fontMetrics().ascent() + 5, name); } KisWorkspaceChooser::KisWorkspaceChooser(KisViewManager * view, QWidget* parent): QWidget(parent), m_view(view) { m_layout = new QGridLayout(this); m_workspaceWidgets = createChooserWidgets(ResourceType::Workspaces, i18n("Workspaces")); m_windowLayoutWidgets = createChooserWidgets(ResourceType::WindowLayouts, i18n("Window layouts")); connect(m_workspaceWidgets.itemChooser, SIGNAL(resourceSelected(KoResourceSP )), this, SLOT(workspaceSelected(KoResourceSP ))); connect(m_workspaceWidgets.saveButton, SIGNAL(clicked(bool)), this, SLOT(slotSaveWorkspace())); connect(m_workspaceWidgets.nameEdit, SIGNAL(textEdited(const QString&)), this, SLOT(slotUpdateWorkspaceSaveButton())); connect(m_windowLayoutWidgets.itemChooser, SIGNAL(resourceSelected(KoResourceSP )), this, SLOT(windowLayoutSelected(KoResourceSP ))); connect(m_windowLayoutWidgets.saveButton, SIGNAL(clicked(bool)), this, SLOT(slotSaveWindowLayout())); connect(m_windowLayoutWidgets.nameEdit, SIGNAL(textEdited(const QString&)), this, SLOT(slotUpdateWindowLayoutSaveButton())); } KisWorkspaceChooser::ChooserWidgets KisWorkspaceChooser::createChooserWidgets(const QString &resourceType, const QString &title) { ChooserWidgets widgets; QLabel *titleLabel = new QLabel(this); QFont titleFont; titleFont.setBold(true); titleLabel->setFont(titleFont); titleLabel->setText(title); m_workspaceSaveLocation = KisResourceServerProvider::instance()->workspaceServer()->saveLocation(); m_windowLayoutSaveLocation = KisResourceServerProvider::instance()->windowLayoutServer()->saveLocation(); widgets.itemChooser = new KisResourceItemChooser(resourceType, false, this); widgets.itemChooser->setItemDelegate(new KisWorkspaceDelegate(this)); widgets.itemChooser->setFixedSize(250, 250); widgets.itemChooser->setRowHeight(30); widgets.itemChooser->itemView()->setViewMode(QListView::ListMode); widgets.itemChooser->showTaggingBar(false); widgets.saveButton = new QPushButton(i18n("Save")); widgets.nameEdit = new QLineEdit(this); widgets.nameEdit->setPlaceholderText(i18n("Insert name")); widgets.nameEdit->setClearButtonEnabled(true); int firstRow = m_layout->rowCount(); m_layout->addWidget(titleLabel, firstRow, 0, 1, 2); m_layout->addWidget(widgets.itemChooser, firstRow + 1, 0, 1, 2); m_layout->addWidget(widgets.nameEdit, firstRow + 2, 0, 1, 1); m_layout->addWidget(widgets.saveButton, firstRow + 2, 1, 1, 1); return widgets; } KisWorkspaceChooser::~KisWorkspaceChooser() { } void KisWorkspaceChooser::slotSaveWorkspace() { if (!m_view->qtMainWindow()) { return; } KisWorkspaceResourceSP workspace(new KisWorkspaceResource(QString())); workspace->setDockerState(m_view->qtMainWindow()->saveState()); workspace->setImage(m_view->mainWindow()->layoutThumbnail()); m_view->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString name = m_workspaceWidgets.nameEdit->text(); workspace->setName(name); workspace->setFilename(name.split(" ").join("_")+workspace->defaultFileExtension()); KisResourceModelProvider::resourceModel(ResourceType::Workspaces)->addResource(workspace); } void KisWorkspaceChooser::slotUpdateWorkspaceSaveButton() { if (QFileInfo(m_workspaceSaveLocation + m_workspaceWidgets.nameEdit->text().split(" ").join("_") + ".kws").exists()) { m_workspaceWidgets.saveButton->setIcon(KisIconUtils::loadIcon("warning")); m_workspaceWidgets.saveButton->setToolTip(i18n("File name already in use. Saving will overwrite the original Workspace.")); //m_workspaceWidgets.saveButton->setText(i18n("Overwrite")); } else { m_workspaceWidgets.saveButton->setIcon(QIcon()); m_workspaceWidgets.saveButton->setToolTip(i18n("Save current workspace.")); //m_workspaceWidgets.saveButton->setText(i18n("Save")); } } void KisWorkspaceChooser::workspaceSelected(KoResourceSP resource) { if (!m_view->qtMainWindow()) { return; } KisConfig cfg(false); cfg.writeEntry("CurrentWorkspace", resource->name()); KisWorkspaceResourceSP workspace = resource.staticCast(); KisMainWindow *mainWindow = qobject_cast(m_view->qtMainWindow()); mainWindow->restoreWorkspace(workspace->resourceId()); } void KisWorkspaceChooser::slotSaveWindowLayout() { KisMainWindow *thisWindow = qobject_cast(m_view->qtMainWindow()); if (!thisWindow) return; KisNewWindowLayoutDialog dlg; dlg.setName(m_windowLayoutWidgets.nameEdit->text()); dlg.exec(); if (dlg.result() != QDialog::Accepted) return; QString name = dlg.name(); bool showImageInAllWindows = dlg.showImageInAllWindows(); bool primaryWorkspaceFollowsFocus = dlg.primaryWorkspaceFollowsFocus(); KisWindowLayoutResourceSP layout = KisWindowLayoutResource::fromCurrentWindows(name, KisPart::instance()->mainWindows(), showImageInAllWindows, primaryWorkspaceFollowsFocus, thisWindow); layout->setValid(true); KisWindowLayoutManager::instance()->setShowImageInAllWindowsEnabled(showImageInAllWindows); KisWindowLayoutManager::instance()->setPrimaryWorkspaceFollowsFocus(primaryWorkspaceFollowsFocus, thisWindow->id()); if (name.isEmpty()) { name = i18n("Window Layout"); } layout->setName(name); layout->setFilename(name.split(" ").join("_") + layout->defaultFileExtension()); KisResourceModelProvider::resourceModel(ResourceType::WindowLayouts)->addResource(layout); } void KisWorkspaceChooser::slotUpdateWindowLayoutSaveButton() { if (QFileInfo(m_windowLayoutSaveLocation + m_windowLayoutWidgets.nameEdit->text().split(" ").join("_") + ".kwl").exists()) { m_windowLayoutWidgets.saveButton->setIcon(KisIconUtils::loadIcon("warning")); m_workspaceWidgets.saveButton->setToolTip(i18n("File name already in use. Saving will overwrite the original window layout.")); } else { m_windowLayoutWidgets.saveButton->setIcon(QIcon()); m_workspaceWidgets.saveButton->setToolTip(i18n("Save current window layout.")); } } void KisWorkspaceChooser::windowLayoutSelected(KoResourceSP resource) { KisWindowLayoutResourceSP layout = resource.dynamicCast(); layout->applyLayout(); } diff --git a/libs/widgets/KisPaletteChooser.cpp b/libs/widgets/KisPaletteChooser.cpp index 905a20744e..e72477303e 100644 --- a/libs/widgets/KisPaletteChooser.cpp +++ b/libs/widgets/KisPaletteChooser.cpp @@ -1,194 +1,194 @@ /* * Copyright (c) 2013 Sven Langkamp * Copyright (c) 2018 Michael Zhou * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisPaletteChooser.h" #include "KisPaletteChooser_p.h" KisPaletteChooser::KisPaletteChooser(QWidget *parent) : QWidget(parent) , m_ui(new Ui_WdgPaletteListWidget) , m_d(new KisPaletteChooserPrivate(this)) { m_d->allowModification = false; m_d->actAdd.reset(new QAction(KisIconUtils::loadIcon("list-add"), i18n("Add a new palette"))); m_d->actRemove.reset(new QAction(KisIconUtils::loadIcon("list-remove"), i18n("Remove current palette"))); m_d->actImport.reset(new QAction(KisIconUtils::loadIcon("document-import"), i18n("Import a new palette from file"))); m_d->actExport.reset(new QAction(KisIconUtils::loadIcon("document-export"), i18n("Export current palette to file"))); m_ui->setupUi(this); m_ui->bnAdd->setDefaultAction(m_d->actAdd.data()); m_ui->bnRemove->setDefaultAction(m_d->actRemove.data()); m_ui->bnImport->setDefaultAction(m_d->actImport.data()); m_ui->bnExport->setDefaultAction(m_d->actExport.data()); m_ui->bnAdd->setEnabled(false); m_ui->bnRemove->setEnabled(false); m_ui->bnImport->setEnabled(false); m_ui->bnExport->setEnabled(false); connect(m_d->actAdd.data(), SIGNAL(triggered()), SLOT(slotAdd())); connect(m_d->actRemove.data(), SIGNAL(triggered()), SLOT(slotRemove())); connect(m_d->actImport.data(), SIGNAL(triggered()), SLOT(slotImport())); connect(m_d->actExport.data(), SIGNAL(triggered()), SLOT(slotExport())); m_d->itemChooser->setItemDelegate(m_d->delegate.data()); m_d->itemChooser->setRowHeight(40); m_d->itemChooser->itemView()->setViewMode(QListView::ListMode); m_d->itemChooser->showButtons(false); m_d->itemChooser->showTaggingBar(true); m_ui->viewPalette->setLayout(new QHBoxLayout(m_ui->viewPalette)); m_ui->viewPalette->layout()->addWidget(m_d->itemChooser.data()); connect(m_d->itemChooser.data(), SIGNAL(resourceSelected(KoResourceSP )), SLOT(slotPaletteResourceSelected(KoResourceSP ))); } KisPaletteChooser::~KisPaletteChooser() { } void KisPaletteChooser::slotPaletteResourceSelected(KoResourceSP r) { KoColorSetSP g = r.staticCast(); emit sigPaletteSelected(g); if (!m_d->allowModification) { return; } if (g->isEditable()) { m_ui->bnRemove->setEnabled(true); } else { m_ui->bnRemove->setEnabled(false); } } void KisPaletteChooser::slotAdd() { if (!m_d->allowModification) { return; } emit sigAddPalette(); m_d->itemChooser->setCurrentItem(m_d->itemChooser->rowCount() - 1); } void KisPaletteChooser::slotRemove() { if (!m_d->allowModification) { return; } if (m_d->itemChooser->currentResource()) { KoColorSetSP cs = m_d->itemChooser->currentResource().staticCast(); emit sigRemovePalette(cs); } m_d->itemChooser->setCurrentItem(0); } void KisPaletteChooser::slotImport() { if (!m_d->allowModification) { return; } emit sigImportPalette(); m_d->itemChooser->setCurrentItem(m_d->itemChooser->rowCount() - 1); } void KisPaletteChooser::slotExport() { if (!m_d->allowModification) { return; } emit sigExportPalette(m_d->itemChooser->currentResource().staticCast()); } void KisPaletteChooser::setAllowModification(bool allowModification) { m_d->allowModification = allowModification; m_ui->bnAdd->setEnabled(allowModification); m_ui->bnImport->setEnabled(allowModification); m_ui->bnExport->setEnabled(allowModification); KoColorSetSP cs = m_d->itemChooser->currentResource().staticCast(); m_ui->bnRemove->setEnabled(allowModification && cs && cs->isEditable()); } /************************* KisPaletteChooserPrivate **********************/ KisPaletteChooserPrivate::KisPaletteChooserPrivate(KisPaletteChooser *a_c) : c(a_c) , itemChooser(new KisResourceItemChooser(ResourceType::Palettes, false, a_c)) , delegate(new Delegate(a_c)) { } KisPaletteChooserPrivate::~KisPaletteChooserPrivate() { } /******************* KisPaletteChooserPrivate::Delegate ******************/ KisPaletteChooserPrivate::Delegate::Delegate(QObject *parent) : QAbstractItemDelegate(parent) { } KisPaletteChooserPrivate::Delegate::~Delegate() { } void KisPaletteChooserPrivate::Delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); if (!index.isValid()) return; - QImage preview = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); - QString name = index.data(Qt::UserRole + KisResourceModel::Name).toString(); + QImage preview = index.data(Qt::UserRole + KisAbstractResourceModel::Thumbnail).value(); + QString name = index.data(Qt::UserRole + KisAbstractResourceModel::Name).toString(); QRect previewRect(option.rect.x() + 2, option.rect.y() + 2, option.rect.height() - 4, option.rect.height() - 4); painter->drawImage(previewRect, preview); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); painter->drawImage(previewRect, preview); painter->setPen(option.palette.highlightedText().color()); } else { painter->setBrush(option.palette.text().color()); } painter->drawText(option.rect.x() + previewRect.width() + 10, option.rect.y() + painter->fontMetrics().ascent() + 5, name); painter->restore(); } inline QSize KisPaletteChooserPrivate::Delegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const { return option.decorationSize; } diff --git a/libs/widgetutils/KoItemToolTip.h b/libs/widgetutils/KoItemToolTip.h index 174daa2449..46761559a7 100644 --- a/libs/widgetutils/KoItemToolTip.h +++ b/libs/widgetutils/KoItemToolTip.h @@ -1,84 +1,84 @@ /* Copyright (c) 2006 Gábor Lehel 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 KO_ITEM_TOOLTIP_H #define KO_ITEM_TOOLTIP_H #include #include "kritawidgetutils_export.h" class QStyleOptionViewItem; class QModelIndex; class QTextDocument; /** * Base class for tooltips that can show extensive information about * the contents of the data pointed to by something that contains a * QModelIndex. Subclasses need to use this data to create a * QTextDocument that is formatted to provide the complete tooltip. */ class KRITAWIDGETUTILS_EXPORT KoItemToolTip : public QFrame { Q_OBJECT public: KoItemToolTip(); ~KoItemToolTip() override; void showTip(QWidget *widget, const QPoint &pos, const QStyleOptionViewItem &option, const QModelIndex &index); protected: /** * Re-implement this to provide the actual tooltip contents. * For instance: * @code * QTextDocument *doc = new QTextDocument(this); * - * QImage thumb = index.data(Qt::UserRole + KisResourceModel::LargeThumbnail).value(); + * QImage thumb = index.data(Qt::UserRole + KisAbstractResourceModel::LargeThumbnail).value(); * doc->addResource(QTextDocument::ImageResource, QUrl("data:thumbnail"), thumb); * * QString name = index.data(Qt::DisplayRole).toString(); * * const QString image = QString(""); * const QString body = QString("

%1

").arg(name) + image; * const QString html = QString("%1").arg(body); * * doc->setHtml(html); * doc->setTextWidth(qMin(doc->size().width(), 500.0)); * * return doc; * @endcode */ virtual QTextDocument *createDocument(const QModelIndex &index) = 0; private: class Private; Private* const d; void updatePosition(QWidget *widget, const QPoint &pos, const QStyleOptionViewItem &option); public: QSize sizeHint() const override; protected: void paintEvent(QPaintEvent *e) override; void timerEvent(QTimerEvent *e) override; bool eventFilter(QObject *object, QEvent *event) override; }; #endif diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp index 45da4f5656..4c2bc92487 100644 --- a/plugins/dockers/animation/timeline_frames_model.cpp +++ b/plugins/dockers/animation/timeline_frames_model.cpp @@ -1,1021 +1,1021 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_frames_model.h" #include #include #include #include #include #include #include "kis_layer.h" #include "kis_config.h" #include "kis_global.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_undo_adapter.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" #include "KisNodeDisplayModeAdapter.h" #include "kis_signal_compressor.h" #include "kis_signal_compressor_with_param.h" #include "kis_keyframe_channel.h" #include "kundo2command.h" #include "kis_post_execution_undo_adapter.h" #include #include #include "kis_animation_utils.h" #include "timeline_color_scheme.h" #include "kis_node_model.h" #include "kis_projection_leaf.h" #include "kis_time_range.h" #include "kis_node_view_color_scheme.h" #include "krita_utils.h" #include "KisPart.h" #include #include "KisDocument.h" #include "KisViewManager.h" #include "kis_processing_applicator.h" #include #include "kis_node_uuid_info.h" struct TimelineFramesModel::Private { Private() : activeLayerIndex(0), dummiesFacade(0), needFinishInsertRows(false), needFinishRemoveRows(false), updateTimer(200, KisSignalCompressor::FIRST_INACTIVE), parentOfRemovedNode(0) {} int activeLayerIndex; QPointer dummiesFacade; KisImageWSP image; bool needFinishInsertRows; bool needFinishRemoveRows; QList updateQueue; KisSignalCompressor updateTimer; KisNodeDummy* parentOfRemovedNode; QScopedPointer converter; QScopedPointer nodeInterface; QPersistentModelIndex lastClickedIndex; QVariant layerName(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); return dummy->node()->name(); } bool layerEditable(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return true; return dummy->node()->visible() && !dummy->node()->userLocked(); } bool frameExists(int row, int column) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); return (primaryChannel && primaryChannel->keyframeAt(column)); } bool frameHasContent(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return false; // first check if we are a key frame KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); if (!frame) return false; return frame->hasContent(); } bool specialKeyframeExists(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; Q_FOREACH(KisKeyframeChannel *channel, dummy->node()->keyframeChannels()) { if (channel->id() != KisKeyframeChannel::Content.id() && channel->keyframeAt(column)) { return true; } } return false; } int frameColorLabel(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return -1; KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); if (!frame) return -1; return frame->colorLabel(); } void setFrameColorLabel(int row, int column, int color) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return; frame->setColorLabel(color); } int layerColorLabel(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; return dummy->node()->colorLabelIndex(); } QVariant layerProperties(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); PropertyList props = dummy->node()->sectionModelProperties(); return QVariant::fromValue(props); } bool setLayerProperties(int row, PropertyList props) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; nodeInterface->setNodeProperties(dummy->node(), image, props); return true; } bool addKeyframe(int row, int column, bool copy) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) return false; KisAnimationUtils::createKeyframeLazy(image, node, KisKeyframeChannel::Content.id(), column, copy); return true; } bool addNewLayer(int row) { Q_UNUSED(row); if (nodeInterface) { KisLayerSP layer = nodeInterface->addPaintLayer(); layer->setPinnedToTimeline(true); } return true; } bool removeLayer(int row) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; if (nodeInterface) { nodeInterface->removeNode(dummy->node()); } return true; } }; TimelineFramesModel::TimelineFramesModel(QObject *parent) : ModelWithExternalNotifications(parent), m_d(new Private) { connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue())); } TimelineFramesModel::~TimelineFramesModel() { } bool TimelineFramesModel::hasConnectionToCanvas() const { return m_d->dummiesFacade; } void TimelineFramesModel::setNodeManipulationInterface(NodeManipulationInterface *iface) { m_d->nodeInterface.reset(iface); } KisNodeSP TimelineFramesModel::nodeAt(QModelIndex index) const { /** * The dummy might not exist because the user could (quickly) change * active layer and the list of the nodes in m_d->converter will change. */ KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); return dummy ? dummy->node() : 0; } QMap TimelineFramesModel::channelsAt(QModelIndex index) const { KisNodeDummy *srcDummy = m_d->converter->dummyFromRow(index.row()); return srcDummy->node()->keyframeChannels(); } void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image, KisNodeDisplayModeAdapter *displayModeAdapter) { KisDummiesFacadeBase *oldDummiesFacade = m_d->dummiesFacade; if (m_d->dummiesFacade && m_d->image) { m_d->image->animationInterface()->disconnect(this); m_d->image->disconnect(this); m_d->dummiesFacade->disconnect(this); } m_d->image = image; KisTimeBasedItemModel::setImage(image); m_d->dummiesFacade = dummiesFacade; m_d->converter.reset(); if (m_d->dummiesFacade) { m_d->converter.reset(new TimelineNodeListKeeper(this, m_d->dummiesFacade, displayModeAdapter)); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); connect(m_d->image->animationInterface(), SIGNAL(sigFullClipRangeChanged()), SIGNAL(sigInfiniteTimelineUpdateNeeded())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioChannelChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image, SIGNAL(sigImageModified()), SLOT(slotImageContentChanged())); } if (m_d->dummiesFacade != oldDummiesFacade) { beginResetModel(); endResetModel(); } if (m_d->dummiesFacade) { emit sigInfiniteTimelineUpdateNeeded(); emit sigAudioChannelChanged(); slotCurrentTimeChanged(m_d->image->animationInterface()->currentUITime()); } } void TimelineFramesModel::slotDummyChanged(KisNodeDummy *dummy) { if (!m_d->updateQueue.contains(dummy)) { m_d->updateQueue.append(dummy); } m_d->updateTimer.start(); } void TimelineFramesModel::slotImageContentChanged() { if (m_d->activeLayerIndex < 0) return; KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); if (!dummy) return; slotDummyChanged(dummy); } void TimelineFramesModel::processUpdateQueue() { if (!m_d->converter) return; Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) { int row = m_d->converter->rowForDummy(dummy); if (row >= 0) { emit headerDataChanged (Qt::Vertical, row, row); emit dataChanged(this->index(row, 0), this->index(row, columnCount() - 1)); } } m_d->updateQueue.clear(); } void TimelineFramesModel::slotCurrentNodeChanged(KisNodeSP node) { if (!node) { m_d->activeLayerIndex = -1; return; } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node); if (!dummy) { // It's perfectly normal that dummyForNode returns 0; that happens // when views get activated while Krita is closing down. return; } m_d->converter->updateActiveDummy(dummy); const int row = m_d->converter->rowForDummy(dummy); if (row < 0) { qWarning() << "WARNING: TimelineFramesModel::slotCurrentNodeChanged: node not found!"; } if (row >= 0 && m_d->activeLayerIndex != row) { setData(index(row, 0), true, ActiveLayerRole); } } int TimelineFramesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if(!m_d->dummiesFacade) return 0; return m_d->converter->rowCount(); } QVariant TimelineFramesModel::data(const QModelIndex &index, int role) const { if(!m_d->dummiesFacade) return QVariant(); switch (role) { case ActiveLayerRole: { return index.row() == m_d->activeLayerIndex; } case FrameEditableRole: { return m_d->layerEditable(index.row()); } case FrameHasContent: { return m_d->frameHasContent(index.row(), index.column()); } case FrameExistsRole: { return m_d->frameExists(index.row(), index.column()); } case SpecialKeyframeExists: { return m_d->specialKeyframeExists(index.row(), index.column()); } case FrameColorLabelIndexRole: { int label = m_d->frameColorLabel(index.row(), index.column()); return label > 0 ? label : QVariant(); } case Qt::DisplayRole: { return m_d->layerName(index.row()); } case Qt::TextAlignmentRole: { return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } - case Qt::UserRole + KisResourceModel::LargeThumbnail: { + case Qt::UserRole + KisAbstractResourceModel::LargeThumbnail: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); if (!dummy) { return QVariant(); } const int maxSize = 200; QImage image(dummy->node()->createThumbnailForFrame(maxSize, maxSize, index.column(), Qt::KeepAspectRatio)); return image; } } return ModelWithExternalNotifications::data(index, role); } bool TimelineFramesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || !m_d->dummiesFacade) return false; switch (role) { case ActiveLayerRole: { if (value.toBool() && index.row() != m_d->activeLayerIndex) { int prevLayer = m_d->activeLayerIndex; m_d->activeLayerIndex = index.row(); emit dataChanged(this->index(prevLayer, 0), this->index(prevLayer, columnCount() - 1)); emit dataChanged(this->index(m_d->activeLayerIndex, 0), this->index(m_d->activeLayerIndex, columnCount() - 1)); emit headerDataChanged(Qt::Vertical, prevLayer, prevLayer); emit headerDataChanged(Qt::Vertical, m_d->activeLayerIndex, m_d->activeLayerIndex); KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); KIS_ASSERT_RECOVER(dummy) { return true; } emit requestCurrentNodeChanged(dummy->node()); emit sigEnsureRowVisible(m_d->activeLayerIndex); } break; } case FrameColorLabelIndexRole: { m_d->setFrameColorLabel(index.row(), index.column(), value.toInt()); } break; } return ModelWithExternalNotifications::setData(index, value, role); } QVariant TimelineFramesModel::headerData(int section, Qt::Orientation orientation, int role) const { if(!m_d->dummiesFacade) return QVariant(); if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: return section == m_d->activeLayerIndex; case Qt::DisplayRole: { QVariant value = headerData(section, orientation, Qt::ToolTipRole); if (!value.isValid()) return value; QString name = value.toString(); const int maxNameSize = 13; if (name.size() > maxNameSize) { name = QString("%1...").arg(name.left(maxNameSize)); } return name; } case Qt::TextColorRole: { // WARNING: this role doesn't work for header views! Use // bold font to show isolated mode instead! return QVariant(); } case Qt::FontRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); KisNodeSP node = dummy->node(); QFont baseFont; if (node->projectionLeaf()->isDroppedNode()) { baseFont.setStrikeOut(true); } else if (m_d->image && m_d->image->isolationRootNode() && KisNodeModel::belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade)) { baseFont.setBold(true); } return baseFont; } case Qt::ToolTipRole: { return m_d->layerName(section); } case TimelinePropertiesRole: { return QVariant::fromValue(m_d->layerProperties(section)); } case OtherLayersRole: { TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); return QVariant::fromValue(list); } case PinnedToTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); return dummy->node()->isPinnedToTimeline(); } case Qt::BackgroundRole: { int label = m_d->layerColorLabel(section); if (label > 0) { KisNodeViewColorScheme scm; QColor color = scm.colorLabel(label); QPalette pal = qApp->palette(); color = KritaUtils::blendColors(color, pal.color(QPalette::Button), 0.3); return QBrush(color); } else { return QVariant(); } } } } return ModelWithExternalNotifications::headerData(section, orientation, role); } bool TimelineFramesModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (!m_d->dummiesFacade) return false; if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: { setData(index(section, 0), value, role); break; } case TimelinePropertiesRole: { TimelineFramesModel::PropertyList props = value.value(); int result = m_d->setLayerProperties(section, props); emit headerDataChanged (Qt::Vertical, section, section); return result; } case PinnedToTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return false; dummy->node()->setPinnedToTimeline(value.toBool()); return true; } } } return ModelWithExternalNotifications::setHeaderData(section, orientation, value, role); } Qt::DropActions TimelineFramesModel::supportedDragActions() const { return Qt::MoveAction | Qt::CopyAction; } Qt::DropActions TimelineFramesModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } QStringList TimelineFramesModel::mimeTypes() const { QStringList types; types << QLatin1String("application/x-krita-frame"); return types; } void TimelineFramesModel::setLastClickedIndex(const QModelIndex &index) { m_d->lastClickedIndex = index; } QMimeData* TimelineFramesModel::mimeData(const QModelIndexList &indexes) const { return mimeDataExtended(indexes, m_d->lastClickedIndex, UndefinedPolicy); } QMimeData *TimelineFramesModel::mimeDataExtended(const QModelIndexList &indexes, const QModelIndex &baseIndex, TimelineFramesModel::MimeCopyPolicy copyPolicy) const { QMimeData *data = new QMimeData(); QByteArray encoded; QDataStream stream(&encoded, QIODevice::WriteOnly); const int baseRow = baseIndex.row(); const int baseColumn = baseIndex.column(); const QByteArray uuidDataRoot = m_d->image->root()->uuid().toRfc4122(); stream << int(uuidDataRoot.size()); stream.writeRawData(uuidDataRoot.data(), uuidDataRoot.size()); stream << indexes.size(); stream << baseRow << baseColumn; Q_FOREACH (const QModelIndex &index, indexes) { KisNodeSP node = nodeAt(index); KIS_SAFE_ASSERT_RECOVER(node) { continue; } stream << index.row() - baseRow << index.column() - baseColumn; const QByteArray uuidData = node->uuid().toRfc4122(); stream << int(uuidData.size()); stream.writeRawData(uuidData.data(), uuidData.size()); } stream << int(copyPolicy); data->setData("application/x-krita-frame", encoded); return data; } inline void decodeBaseIndex(QByteArray *encoded, int *row, int *col) { int size_UNUSED = 0; QDataStream stream(encoded, QIODevice::ReadOnly); stream >> size_UNUSED >> *row >> *col; } bool TimelineFramesModel::canDropFrameData(const QMimeData */*data*/, const QModelIndex &index) { if (!index.isValid()) return false; /** * Now we support D&D around any layer, so just return 'true' all * the time. */ return true; } bool TimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); return dropMimeDataExtended(data, action, parent); } bool TimelineFramesModel::dropMimeDataExtended(const QMimeData *data, Qt::DropAction action, const QModelIndex &parent, bool *dataMoved) { bool result = false; if ((action != Qt::MoveAction && action != Qt::CopyAction) || !parent.isValid()) return result; QByteArray encoded = data->data("application/x-krita-frame"); QDataStream stream(&encoded, QIODevice::ReadOnly); int uuidLenRoot = 0; stream >> uuidLenRoot; QByteArray uuidDataRoot(uuidLenRoot, '\0'); stream.readRawData(uuidDataRoot.data(), uuidLenRoot); QUuid nodeUuidRoot = QUuid::fromRfc4122(uuidDataRoot); KisPart *partInstance = KisPart::instance(); QList> documents = partInstance->documents(); KisImageSP srcImage = 0; Q_FOREACH(KisDocument *doc, documents) { KisImageSP tmpSrcImage = doc->image(); if (tmpSrcImage->root()->uuid() == nodeUuidRoot) { srcImage = tmpSrcImage; break; } } if (!srcImage) { KisPart *kisPartInstance = KisPart::instance(); kisPartInstance->currentMainwindow()->viewManager()->showFloatingMessage( i18n("Dropped frames are not available in this Krita instance") , QIcon()); return false; } int size, baseRow, baseColumn; stream >> size >> baseRow >> baseColumn; const QPoint offset(parent.column() - baseColumn, parent.row() - baseRow); KisAnimationUtils::FrameMovePairList frameMoves; for (int i = 0; i < size; i++) { int relRow, relColumn; stream >> relRow >> relColumn; const int srcRow = baseRow + relRow; const int srcColumn = baseColumn + relColumn; int uuidLen = 0; stream >> uuidLen; QByteArray uuidData(uuidLen, '\0'); stream.readRawData(uuidData.data(), uuidLen); QUuid nodeUuid = QUuid::fromRfc4122(uuidData); KisNodeSP srcNode; if (!nodeUuid.isNull()) { KisNodeUuidInfo nodeInfo(nodeUuid); srcNode = nodeInfo.findNode(srcImage->root()); } else { QModelIndex index = this->index(srcRow, srcColumn); srcNode = nodeAt(index); } KIS_SAFE_ASSERT_RECOVER(srcNode) { continue; } const QModelIndex dstRowIndex = this->index(srcRow + offset.y(), 0); if (!dstRowIndex.isValid()) continue; KisNodeSP dstNode = nodeAt(dstRowIndex); KIS_SAFE_ASSERT_RECOVER(dstNode) { continue; } Q_FOREACH (KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) { KisAnimationUtils::FrameItem srcItem(srcNode, channel->id(), srcColumn); KisAnimationUtils::FrameItem dstItem(dstNode, channel->id(), srcColumn + offset.x()); frameMoves << std::make_pair(srcItem, dstItem); } } MimeCopyPolicy copyPolicy = UndefinedPolicy; if (!stream.atEnd()) { int value = 0; stream >> value; copyPolicy = MimeCopyPolicy(value); } const bool copyFrames = copyPolicy == UndefinedPolicy ? action == Qt::CopyAction : copyPolicy == CopyFramesPolicy; if (dataMoved) { *dataMoved = !copyFrames; } KUndo2Command *cmd = 0; if (!frameMoves.isEmpty()) { KisImageBarrierLockerWithFeedback locker(m_d->image); cmd = KisAnimationUtils::createMoveKeyframesCommand(frameMoves, copyFrames, false, 0); } if (cmd) { KisProcessingApplicator::runSingleCommandStroke(m_d->image, cmd, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); } return cmd; } Qt::ItemFlags TimelineFramesModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = ModelWithExternalNotifications::flags(index); if (!index.isValid()) return flags; if (m_d->frameExists(index.row(), index.column()) || m_d->specialKeyframeExists(index.row(), index.column())) { if (data(index, FrameEditableRole).toBool()) { flags |= Qt::ItemIsDragEnabled; } } /** * Basically we should forbid overrides only if we D&D a single frame * and allow it when we D&D multiple frames. But we cannot distinguish * it here... So allow all the time. */ flags |= Qt::ItemIsDropEnabled; return flags; } bool TimelineFramesModel::insertRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row > rowCount()) return false; bool result = m_d->addNewLayer(row); return result; } bool TimelineFramesModel::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row >= rowCount()) return false; bool result = m_d->removeLayer(row); return result; } bool TimelineFramesModel::insertOtherLayer(int index, int dstRow) { Q_UNUSED(dstRow); TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); if (index < 0 || index >= list.size()) return false; list[index].dummy->node()->setPinnedToTimeline(true); dstRow = m_d->converter->rowForDummy(list[index].dummy); setData(this->index(dstRow, 0), true, ActiveLayerRole); return true; } int TimelineFramesModel::activeLayerRow() const { return m_d->activeLayerIndex; } bool TimelineFramesModel::createFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), false); } bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true); } bool TimelineFramesModel::insertFrames(int dstColumn, const QList &dstRows, int count, int timing) { if (dstRows.isEmpty() || count <= 0) return true; timing = qMax(timing, 1); KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count)); { KisImageBarrierLockerWithFeedback locker(m_d->image); QModelIndexList indexes; Q_FOREACH (int row, dstRows) { for (int column = dstColumn; column < columnCount(); column++) { indexes << index(row, column); } } setLastVisibleFrame(columnCount() + (count * timing) - 1); createOffsetFramesCommand(indexes, QPoint((count * timing), 0), false, false, parentCommand); Q_FOREACH (int row, dstRows) { KisNodeDummy *dummy = m_d->converter->dummyFromRow(row); if (!dummy) continue; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) continue; for (int column = dstColumn; column < dstColumn + (count * timing); column += timing) { KisAnimationUtils::createKeyframeCommand(m_d->image, node, KisKeyframeChannel::Content.id(), column, false, parentCommand); } } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = dstColumn > oldTime ? dstColumn : dstColumn + (count * timing) - 1; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, newTime, parentCommand); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } bool TimelineFramesModel::insertHoldFrames(QModelIndexList selectedIndexes, int count) { if (selectedIndexes.isEmpty() || count == 0) return true; QScopedPointer parentCommand(new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count))); { KisImageBarrierLockerWithFeedback locker(m_d->image); QSet uniqueKeyframesInSelection; int minSelectedTime = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, selectedIndexes) { KisNodeSP node = nodeAt(index); KIS_SAFE_ASSERT_RECOVER(node) { continue; } KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) continue; minSelectedTime = qMin(minSelectedTime, index.column()); KisKeyframeSP keyFrame = channel->activeKeyframeAt(index.column()); if (keyFrame) { uniqueKeyframesInSelection.insert(keyFrame); } } QList keyframesToMove; for (auto it = uniqueKeyframesInSelection.begin(); it != uniqueKeyframesInSelection.end(); ++it) { KisKeyframeSP keyframe = *it; KisKeyframeChannel *channel = keyframe->channel(); KisKeyframeSP nextKeyframe = channel->nextKeyframe(keyframe); if (nextKeyframe) { keyframesToMove << nextKeyframe; } } std::sort(keyframesToMove.begin(), keyframesToMove.end(), [] (KisKeyframeSP lhs, KisKeyframeSP rhs) { return lhs->time() > rhs->time(); }); if (keyframesToMove.isEmpty()) return true; const int maxColumn = columnCount(); if (count > 0) { setLastVisibleFrame(columnCount() + count); } Q_FOREACH (KisKeyframeSP keyframe, keyframesToMove) { int plannedFrameMove = count; if (count < 0) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(keyframe->time() > 0, false); KisKeyframeSP prevFrame = keyframe->channel()->previousKeyframe(keyframe); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(prevFrame, false); plannedFrameMove = qMax(count, prevFrame->time() - keyframe->time() + 1); minSelectedTime = qMin(minSelectedTime, prevFrame->time()); } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(keyframe->channel()->node()); KIS_SAFE_ASSERT_RECOVER(dummy) { continue; } const int row = m_d->converter->rowForDummy(dummy); KIS_SAFE_ASSERT_RECOVER(row >= 0) { continue; } QModelIndexList indexes; for (int column = keyframe->time(); column < maxColumn; column++) { indexes << index(row, column); } createOffsetFramesCommand(indexes, QPoint(plannedFrameMove, 0), false, true, parentCommand.data()); } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = minSelectedTime; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, newTime, parentCommand.data()); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand.take(), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } QString TimelineFramesModel::audioChannelFileName() const { return m_d->image ? m_d->image->animationInterface()->audioChannelFileName() : QString(); } void TimelineFramesModel::setAudioChannelFileName(const QString &fileName) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioChannelFileName(fileName); } bool TimelineFramesModel::isAudioMuted() const { return m_d->image ? m_d->image->animationInterface()->isAudioMuted() : false; } void TimelineFramesModel::setAudioMuted(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioMuted(value); } qreal TimelineFramesModel::audioVolume() const { return m_d->image ? m_d->image->animationInterface()->audioVolume() : 0.5; } void TimelineFramesModel::setAudioVolume(qreal value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioVolume(value); } void TimelineFramesModel::setFullClipRangeStart(int column) { m_d->image->animationInterface()->setFullClipRangeStartTime(column); } void TimelineFramesModel::setFullClipRangeEnd(int column) { m_d->image->animationInterface()->setFullClipRangeEndTime(column); } diff --git a/plugins/dockers/gamutmask/KisGamutMaskChooser.cpp b/plugins/dockers/gamutmask/KisGamutMaskChooser.cpp index 088a6be379..fe5dd5e60b 100644 --- a/plugins/dockers/gamutmask/KisGamutMaskChooser.cpp +++ b/plugins/dockers/gamutmask/KisGamutMaskChooser.cpp @@ -1,243 +1,243 @@ /* * Copyright (c) 2018 Anna Medonosova * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisGamutMaskChooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /// The resource item delegate for rendering the resource preview class KisGamutMaskDelegate: public QAbstractItemDelegate { public: KisGamutMaskDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) , m_mode(KisGamutMaskChooser::ViewMode::THUMBNAIL) {} ~KisGamutMaskDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } void setViewMode(KisGamutMaskChooser::ViewMode mode) { m_mode = mode; } private: KisGamutMaskChooser::ViewMode m_mode; }; void KisGamutMaskDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { painter->save(); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); if (!index.isValid()) return; - QImage preview = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); - QString name = index.data(Qt::UserRole + KisResourceModel::Name).value(); + QImage preview = index.data(Qt::UserRole + KisAbstractResourceModel::Thumbnail).value(); + QString name = index.data(Qt::UserRole + KisAbstractResourceModel::Name).value(); if(preview.isNull()) { return; } QRect paintRect = option.rect.adjusted(1, 1, -1, -1); if (m_mode == KisGamutMaskChooser::ViewMode::THUMBNAIL) { painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(paintRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); if (option.state & QStyle::State_Selected) { painter->setCompositionMode(QPainter::CompositionMode_Overlay); painter->setOpacity(0.5); painter->fillRect(paintRect, Qt::white); painter->setCompositionMode(QPainter::CompositionMode_SourceOver); painter->setOpacity(1.0); painter->setPen(QPen(option.palette.highlight(), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); QRect selectedBorder = option.rect.adjusted(1, 1, -1, -1); painter->drawRect(selectedBorder); } } else { QSize previewSize(paintRect.height(), paintRect.height()); painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(previewSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); int leftMargin = 8; int rightMargin = 7; int vertMargin = 4; int descOffset = 7; QFont font = option.font; font.setBold(true); painter->setFont(font); QRectF titleRect(QPointF(previewSize.width() + leftMargin, paintRect.y() + vertMargin), QPointF(paintRect.width() - rightMargin, paintRect.y() + descOffset + painter->fontMetrics().lineSpacing())); painter->drawText(titleRect, Qt::AlignLeft, painter->fontMetrics().elidedText( name, Qt::ElideRight, titleRect.width() ) ); /* * We currently cannot actually get the mask description, so lets stop this for now. if (!mask->description().isEmpty() && !mask->description().isNull()) { font.setPointSize(font.pointSize()-1); font.setBold(false); font.setStyle(QFont::StyleItalic); painter->setFont(font); QRectF descRect(QPointF(previewSize.width() + leftMargin, paintRect.y() + descOffset + painter->fontMetrics().lineSpacing()), QPointF(paintRect.right() - rightMargin, paintRect.bottom() - vertMargin)); int numLines = floor(((float)descRect.height() / (float)painter->fontMetrics().lineSpacing())); if (numLines > 0) { int elideWidth = 0; QTextLayout textLayout(mask->description()); textLayout.beginLayout(); for (int i = 0; i < numLines; i++) { QTextLine line = textLayout.createLine(); if (line.isValid()) { line.setLineWidth(descRect.width()); elideWidth += line.naturalTextWidth(); } } QString elidedText = painter->fontMetrics().elidedText(mask->description(), Qt::ElideRight, elideWidth); painter->drawText(descRect, Qt::AlignLeft|Qt::TextWordWrap, elidedText); } }*/ } painter->restore(); } KisGamutMaskChooser::KisGamutMaskChooser(QWidget *parent) : QWidget(parent) { m_delegate = new KisGamutMaskDelegate(this); m_itemChooser = new KisResourceItemChooser(ResourceType::GamutMasks, false, this); m_itemChooser->setItemDelegate(m_delegate); m_itemChooser->showTaggingBar(true); m_itemChooser->showButtons(false); m_itemChooser->setSynced(true); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0,0,0,0); // TODO: menu for view mode change QMenu* menu = new QMenu(this); menu->setStyleSheet("margin: 6px"); menu->addSection(i18nc("@title Which elements to display (e.g., thumbnails or details)", "Display")); QActionGroup *actionGroup = new QActionGroup(this); KisConfig cfg(true); m_mode = KisGamutMaskChooser::ViewMode(cfg.readEntry("GamutMasks.viewMode", KisGamutMaskChooser::THUMBNAIL)); QAction* action = menu->addAction(KisIconUtils::loadIcon("view-preview"), i18n("Thumbnails"), this, SLOT(slotSetModeThumbnail())); action->setCheckable(true); action->setChecked(m_mode == KisGamutMaskChooser::THUMBNAIL); action->setActionGroup(actionGroup); action = menu->addAction(KisIconUtils::loadIcon("view-list-details"), i18n("Details"), this, SLOT(slotSetModeDetail())); action->setCheckable(true); action->setChecked(m_mode == KisGamutMaskChooser::DETAIL); action->setActionGroup(actionGroup); // setting the view mode setViewMode(m_mode); m_itemChooser->setViewModeButtonVisible(true); QToolButton* viewModeButton = m_itemChooser->viewModeButton(); viewModeButton->setMenu(menu); layout->addWidget(m_itemChooser); setLayout(layout); connect(m_itemChooser, SIGNAL(resourceSelected(KoResourceSP )), this, SLOT(resourceSelected(KoResourceSP ))); } KisGamutMaskChooser::~KisGamutMaskChooser() { } void KisGamutMaskChooser::setCurrentResource(KoResourceSP resource) { m_itemChooser->setCurrentResource(resource); } void KisGamutMaskChooser::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); updateViewSettings(); } void KisGamutMaskChooser::setViewMode(KisGamutMaskChooser::ViewMode mode) { m_mode = mode; updateViewSettings(); } void KisGamutMaskChooser::updateViewSettings() { KisConfig cfg(false); cfg.writeEntry("GamutMasks.viewMode", qint32(m_mode)); if (m_mode == KisGamutMaskChooser::THUMBNAIL) { m_itemChooser->setSynced(true); m_delegate->setViewMode(m_mode); } else if (m_mode == KisGamutMaskChooser::DETAIL) { m_itemChooser->setSynced(false); m_itemChooser->itemView()->setViewMode(QListView::ListMode); m_itemChooser->setRowHeight(this->fontMetrics().lineSpacing()*4); m_itemChooser->setColumnWidth(m_itemChooser->width()); m_delegate->setViewMode(m_mode); } } void KisGamutMaskChooser::resourceSelected(KoResourceSP resource) { emit sigGamutMaskSelected(resource.staticCast()); } void KisGamutMaskChooser::slotSetModeThumbnail() { setViewMode(KisGamutMaskChooser::THUMBNAIL); } void KisGamutMaskChooser::slotSetModeDetail() { setViewMode(KisGamutMaskChooser::DETAIL); } diff --git a/plugins/dockers/svgcollectiondocker/SvgSymbolCollectionDocker.cpp b/plugins/dockers/svgcollectiondocker/SvgSymbolCollectionDocker.cpp index 6a731caa3e..320c3d60fb 100644 --- a/plugins/dockers/svgcollectiondocker/SvgSymbolCollectionDocker.cpp +++ b/plugins/dockers/svgcollectiondocker/SvgSymbolCollectionDocker.cpp @@ -1,313 +1,313 @@ /* This file is part of the KDE project * Copyright (C) 2008 Peter Simonsson * * 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 "SvgSymbolCollectionDocker.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_icon_utils.h" #include #include #include "ui_WdgSvgCollection.h" #include // // SvgCollectionModel // SvgCollectionModel::SvgCollectionModel(QObject *parent) : QAbstractListModel(parent) { } QVariant SvgCollectionModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() > m_symbolCollection->symbols().count()) { return QVariant(); } switch (role) { case Qt::ToolTipRole: return m_symbolCollection->symbols()[index.row()]->title; case Qt::DecorationRole: { QPixmap px = QPixmap::fromImage(m_symbolCollection->symbols()[index.row()]->icon()); QIcon icon(px); return icon; } case Qt::UserRole: return m_symbolCollection->symbols()[index.row()]->id; case Qt::DisplayRole: return m_symbolCollection->symbols()[index.row()]->title; default: return QVariant(); } return QVariant(); } int SvgCollectionModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_symbolCollection->symbols().count(); } QMimeData *SvgCollectionModel::mimeData(const QModelIndexList &indexes) const { if (indexes.isEmpty()) { return 0; } QModelIndex index = indexes.first(); if (!index.isValid()) { return 0; } if (m_symbolCollection->symbols().isEmpty()) { return 0; } QList shapes; shapes.append(m_symbolCollection->symbols()[index.row()]->shape); KoDrag drag; drag.setSvg(shapes); QMimeData *mimeData = drag.mimeData(); return mimeData; } QStringList SvgCollectionModel::mimeTypes() const { return QStringList() << SHAPETEMPLATE_MIMETYPE << "image/svg+xml"; } Qt::ItemFlags SvgCollectionModel::flags(const QModelIndex &index) const { if (index.isValid()) { return QAbstractListModel::flags(index) | Qt::ItemIsDragEnabled; } return QAbstractListModel::flags(index); } Qt::DropActions SvgCollectionModel::supportedDragActions() const { return Qt::CopyAction; } void SvgCollectionModel::setSvgSymbolCollectionResource(QSharedPointer resource) { m_symbolCollection = resource; } // // SvgSymbolCollectionDockerFactory // SvgSymbolCollectionDockerFactory::SvgSymbolCollectionDockerFactory() : KoDockFactoryBase() { } QString SvgSymbolCollectionDockerFactory::id() const { return QString("SvgSymbolCollectionDocker"); } QDockWidget *SvgSymbolCollectionDockerFactory::createDockWidget() { SvgSymbolCollectionDocker *docker = new SvgSymbolCollectionDocker(); return docker; } // // SvgSymbolCollectionDocker // SvgSymbolCollectionDocker::SvgSymbolCollectionDocker(QWidget *parent) : QDockWidget(parent) , m_wdgSvgCollection(new Ui_WdgSvgCollection()) { setWindowTitle(i18n("Vector Libraries")); QWidget* mainWidget = new QWidget(this); setWidget(mainWidget); m_wdgSvgCollection->setupUi(mainWidget); connect(m_wdgSvgCollection->cmbCollections, SIGNAL(activated(int)), SLOT(collectionActivated(int))); m_resourceModel = KisResourceModelProvider::resourceModel(ResourceType::Symbols); m_wdgSvgCollection->cmbCollections->setModel(m_resourceModel); - m_wdgSvgCollection->cmbCollections->setModelColumn(KisResourceModel::Name); + m_wdgSvgCollection->cmbCollections->setModelColumn(KisAbstractResourceModel::Name); m_wdgSvgCollection->listCollection->setDragEnabled(true); m_wdgSvgCollection->listCollection->setDragDropMode(QAbstractItemView::DragOnly); m_wdgSvgCollection->listCollection->setSelectionMode(QListView::SingleSelection); QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(m_wdgSvgCollection->listCollection); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } // thumbnail icon changer QMenu* configureMenu = new QMenu(this); configureMenu->setStyleSheet("margin: 6px"); m_wdgSvgCollection->vectorPresetsConfigureButton->setIcon(KisIconUtils::loadIcon("configure")); m_wdgSvgCollection->vectorPresetsConfigureButton->setPopupMode(QToolButton::InstantPopup); // add horizontal slider for changing the icon size m_iconSizeSlider = new QSlider(this); m_iconSizeSlider->setOrientation(Qt::Horizontal); m_iconSizeSlider->setRange(20, 80); m_iconSizeSlider->setValue(20); // defaults to small icon size m_iconSizeSlider->setMinimumHeight(20); m_iconSizeSlider->setMinimumWidth(40); m_iconSizeSlider->setTickInterval(10); QWidgetAction *sliderAction= new QWidgetAction(this); sliderAction->setDefaultWidget(m_iconSizeSlider); configureMenu->addSection(i18n("Icon Size")); configureMenu->addAction(sliderAction); m_wdgSvgCollection->vectorPresetsConfigureButton->setMenu(configureMenu); connect(m_iconSizeSlider, SIGNAL(sliderReleased()), this, SLOT(slotSetIconSize())); // resizing while sliding is too heavy of an operation KConfigGroup cfg = KSharedConfig::openConfig()->group("SvgSymbolCollection"); int i = cfg.readEntry("currentCollection", 0); if (i > m_wdgSvgCollection->cmbCollections->count()) { i = 0; } m_wdgSvgCollection->cmbCollections->setCurrentIndex(i); collectionActivated(i); connect(m_resourceModel, SIGNAL(modelAboutToBeReset()), this, SLOT(slotResourceModelAboutToBeReset())); connect(m_resourceModel, SIGNAL(modelReset()), this, SLOT(slotResourceModelReset())); } SvgSymbolCollectionDocker::~SvgSymbolCollectionDocker() { clearModels(); } void SvgSymbolCollectionDocker::slotSetIconSize() { m_wdgSvgCollection->listCollection->setIconSize(QSize(m_iconSizeSlider->value(),m_iconSizeSlider->value())); } void SvgSymbolCollectionDocker::slotResourceModelAboutToBeReset() { int index = m_wdgSvgCollection->cmbCollections->currentIndex(); QModelIndex idx = m_resourceModel->index(index, 0); - int id = m_resourceModel->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); + int id = m_resourceModel->data(idx, Qt::UserRole + KisAbstractResourceModel::Id).toInt(); m_rememberedSvgCollectionId = id; } void SvgSymbolCollectionDocker::slotResourceModelReset() { int indexToSet = 0; if (m_rememberedSvgCollectionId < 0) { indexToSet = 0; } else { for (int i = 0; i < m_resourceModel->rowCount(); i++) { QModelIndex idx = m_resourceModel->index(i, 0); - int id = m_resourceModel->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); + int id = m_resourceModel->data(idx, Qt::UserRole + KisAbstractResourceModel::Id).toInt(); if (id == m_rememberedSvgCollectionId) { indexToSet = i; break; } } } // remove the current model from the view m_wdgSvgCollection->listCollection->setModel(0); // delete all models clearModels(); // setting current index will create and set the model m_wdgSvgCollection->cmbCollections->setCurrentIndex(indexToSet); collectionActivated(indexToSet); m_rememberedSvgCollectionId = -1; } void SvgSymbolCollectionDocker::setCanvas(KoCanvasBase *canvas) { setEnabled(canvas != 0); } void SvgSymbolCollectionDocker::unsetCanvas() { setEnabled(false); } void SvgSymbolCollectionDocker::collectionActivated(int index) { if (index < m_resourceModel->rowCount()) { SvgCollectionModel *model; if (m_collectionsModelsCache.contains(index)) { model = m_collectionsModelsCache.value(index); } else { QModelIndex idx = m_resourceModel->index(index, 0); QSharedPointer r = m_resourceModel->resourceForIndex(idx).dynamicCast(); model = new SvgCollectionModel(); model->setSvgSymbolCollectionResource(r); m_collectionsModelsCache.insert(index, model); } KConfigGroup cfg = KSharedConfig::openConfig()->group("SvgSymbolCollection"); cfg.writeEntry("currentCollection", index); m_wdgSvgCollection->listCollection->setModel(model); } } void SvgSymbolCollectionDocker::clearModels() { Q_FOREACH(int key, m_collectionsModelsCache.keys()) { delete m_collectionsModelsCache.value(key); } m_collectionsModelsCache.clear(); } diff --git a/plugins/dockers/tasksetdocker/tasksetdocker_dock.cpp b/plugins/dockers/tasksetdocker/tasksetdocker_dock.cpp index 165d39d7dc..7089446ba1 100644 --- a/plugins/dockers/tasksetdocker/tasksetdocker_dock.cpp +++ b/plugins/dockers/tasksetdocker/tasksetdocker_dock.cpp @@ -1,241 +1,241 @@ /* * Copyright (c) 2011 Sven Langkamp * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "tasksetdocker_dock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tasksetmodel.h" class KisTasksetDelegate : public QStyledItemDelegate { public: KisTasksetDelegate(QObject * parent = 0) : QStyledItemDelegate(parent) {} ~KisTasksetDelegate() override {} /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const override { return QSize(QStyledItemDelegate::sizeHint(option, index).width(), qMin(QStyledItemDelegate::sizeHint(option, index).width(), 25)); } }; class KisTasksetResourceDelegate : public QStyledItemDelegate { public: KisTasksetResourceDelegate(QObject * parent = 0) : QStyledItemDelegate(parent) {} ~KisTasksetResourceDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; }; void KisTasksetResourceDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { if (! index.isValid()) return; - QString name = index.data(Qt::UserRole + KisResourceModel::Name).toString(); + QString name = index.data(Qt::UserRole + KisAbstractResourceModel::Name).toString(); if (option.state & QStyle::State_Selected) { painter->setPen(QPen(option.palette.highlight(), 2.0)); painter->fillRect(option.rect, option.palette.highlight()); painter->setBrush(option.palette.highlightedText()); } else { painter->setBrush(option.palette.text()); } painter->drawText(option.rect.x() + 5, option.rect.y() + painter->fontMetrics().ascent() + 5, name); } TasksetDockerDock::TasksetDockerDock( ) : QDockWidget(i18n("Task Sets")), m_canvas(0), m_blocked(false) { QWidget* widget = new QWidget(this); setupUi(widget); m_model = new TasksetModel(this); tasksetView->setModel(m_model); tasksetView->setItemDelegate(new KisTasksetDelegate(this)); recordButton->setIcon(KisIconUtils::loadIcon("media-record")); recordButton->setCheckable(true); clearButton->setIcon(KisIconUtils::loadIcon("edit-delete")); saveButton->setIcon(KisIconUtils::loadIcon("document-save")); saveButton->setEnabled(false); chooserButton->setIcon(KisIconUtils::loadIcon("edit-copy")); m_rserver = new KoResourceServer(ResourceType::TaskSets); KisResourceLoaderRegistry::instance()->registerLoader(new KisResourceLoader(ResourceType::TaskSets, ResourceType::TaskSets, i18n("Task sets"), QStringList() << "application/x-krita-taskset")); KisResourceItemChooser *itemChooser = new KisResourceItemChooser(ResourceType::TaskSets, false, this); itemChooser->setItemDelegate(new KisTasksetResourceDelegate(this)); itemChooser->setFixedSize(500, 250); itemChooser->setRowHeight(30); itemChooser->itemView()->setViewMode(QListView::ListMode); itemChooser->showTaggingBar(true); chooserButton->setPopupWidget(itemChooser); connect(itemChooser, SIGNAL(resourceSelected(KoResourceSP )), this, SLOT(resourceSelected(KoResourceSP ))); setWidget(widget); connect( tasksetView, SIGNAL(clicked(QModelIndex)), this, SLOT(activated(QModelIndex)) ); connect( recordButton, SIGNAL(toggled(bool)), this, SLOT(recordClicked())); connect( clearButton, SIGNAL(clicked(bool)), this, SLOT(clearClicked())); connect( saveButton, SIGNAL(clicked(bool)), this, SLOT(saveClicked())); } TasksetDockerDock::~TasksetDockerDock() { delete m_rserver; } void TasksetDockerDock::setCanvas(KoCanvasBase * canvas) { if (m_canvas && m_canvas->viewManager()) { m_canvas->viewManager()->actionCollection()->disconnect(this); Q_FOREACH (KXMLGUIClient* client, m_canvas->viewManager()->mainWindow()->childClients()) { client->actionCollection()->disconnect(this); } } m_canvas = dynamic_cast(canvas); } void TasksetDockerDock::unsetCanvas() { m_canvas = 0; m_model->clear(); } void TasksetDockerDock::actionTriggered(QAction* action) { if(action && !action->objectName().isEmpty() && !m_blocked && recordButton->isChecked()) { m_model->addAction(action); saveButton->setEnabled(true); } } void TasksetDockerDock::activated(const QModelIndex& index) { QAction* action = m_model->actionFromIndex(index); m_blocked = true; action->trigger(); m_blocked = false; } void TasksetDockerDock::recordClicked() { if(m_canvas) { KisViewManager* view = m_canvas->viewManager(); connect(view->actionCollection(), SIGNAL(actionTriggered(QAction*)), this, SLOT(actionTriggered(QAction*)), Qt::UniqueConnection); Q_FOREACH (KXMLGUIClient* client, view->mainWindow()->childClients()) { connect(client->actionCollection(), SIGNAL(actionTriggered(QAction*)), this, SLOT(actionTriggered(QAction*)), Qt::UniqueConnection); } } } void TasksetDockerDock::saveClicked() { QString name; TasksetResourceSP taskset(new TasksetResource(QString())); QStringList actionNames; Q_FOREACH (QAction* action, m_model->actions()) { actionNames.append(action->objectName()); } taskset->setActionList(actionNames); taskset->setValid(true); QString saveLocation = m_rserver->saveLocation(); if (name.isEmpty()) { name = i18n("Taskset"); } QFileInfo fileInfo(saveLocation + name + taskset->defaultFileExtension()); bool fileOverwriteAccepted = false; bool ok = false; while(!fileOverwriteAccepted) { name = QInputDialog::getText(this, i18n("Taskset Name"), i18n("Name:"), QLineEdit::Normal, QString(), &ok); if (name.isNull() || name.isEmpty()) { return; } else { fileInfo = QFileInfo(saveLocation + name.split(" ").join("_") + taskset->defaultFileExtension()); if (fileInfo.exists()) { int res = QMessageBox::warning(this, i18nc("@title:window", "Name Already Exists") , i18n("The name '%1' already exists, do you wish to overwrite it?", name) , QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (res == QMessageBox::Yes) fileOverwriteAccepted = true; } else { fileOverwriteAccepted = true; } } } taskset->setName(name); taskset->setFilename(fileInfo.fileName()); m_rserver->addResource(taskset); } void TasksetDockerDock::clearClicked() { saveButton->setEnabled(false); m_model->clear(); } void TasksetDockerDock::resourceSelected(KoResourceSP resource) { if(!m_canvas) { return; } m_model->clear(); saveButton->setEnabled(true); Q_FOREACH (const QString& actionName, resource.staticCast()->actionList()) { QAction* action = m_canvas->viewManager()->actionCollection()->action(actionName); if(action) { m_model->addAction(action); } } } diff --git a/plugins/extensions/resourcemanager/dlg_create_bundle.cpp b/plugins/extensions/resourcemanager/dlg_create_bundle.cpp index 3cf4ad6477..ec6f4444ed 100644 --- a/plugins/extensions/resourcemanager/dlg_create_bundle.cpp +++ b/plugins/extensions/resourcemanager/dlg_create_bundle.cpp @@ -1,453 +1,453 @@ /* * Copyright (c) 2014 Victor Lafon metabolic.ewilan@hotmail.fr * Copyright (c) 2020 Agata Cacko cacko.azh@gmail.com * * 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 "dlg_create_bundle.h" #include "ui_wdgdlgcreatebundle.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ICON_SIZE 48 DlgCreateBundle::DlgCreateBundle(KoResourceBundleSP bundle, QWidget *parent) : KoDialog(parent) , m_ui(new Ui::WdgDlgCreateBundle) , m_bundle(bundle) { m_page = new QWidget(); m_ui->setupUi(m_page); setMainWidget(m_page); setFixedSize(m_page->sizeHint()); setButtons(Ok | Cancel); setDefaultButton(Ok); setButtonText(Ok, i18n("Save")); connect(m_ui->bnSelectSaveLocation, SIGNAL(clicked()), SLOT(selectSaveLocation())); KoDocumentInfo info; info.updateParameters(); if (bundle) { setCaption(i18n("Edit Resource Bundle")); #if 0 /* m_ui->lblSaveLocation->setText(QFileInfo(bundle->filename()).absolutePath()); m_ui->editBundleName->setText(bundle->name()); m_ui->editAuthor->setText(bundle->getMeta("author")); m_ui->editEmail->setText(bundle->getMeta("email")); m_ui->editLicense->setText(bundle->getMeta("license")); m_ui->editWebsite->setText(bundle->getMeta("website")); m_ui->editDescription->document()->setPlainText(bundle->getMeta("description")); m_ui->lblPreview->setPixmap(QPixmap::fromImage(bundle->image().scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation))); Q_FOREACH (const QString & resType, bundle->resourceTypes()) { if (resType == ResourceType::Gradients) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedGradients << res->filename(); } } } else if (resType == ResourceType::Patterns) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedPatterns << res->filename(); } } } else if (resType == ResourceType::Brushes) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedBrushes << res->filename(); } } } else if (resType == ResourceType::Palettes) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedPalettes << res->filename(); } } } else if (resType == ResourceType::Workspaces) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedWorkspaces << res->filename(); } } } else if (resType == ResourceType::PaintOpPresets) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedPresets << res->filename(); } } } else if (resType == ResourceType::GamutMasks) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedGamutMasks << res->filename(); } } } } */ #endif } else { setCaption(i18n("Create Resource Bundle")); KisConfig cfg(true); m_ui->editAuthor->setText(cfg.readEntry("BundleAuthorName", info.authorInfo("creator"))); m_ui->editEmail->setText(cfg.readEntry("BundleAuthorEmail", info.authorInfo("email"))); m_ui->editWebsite->setText(cfg.readEntry("BundleWebsite", "http://")); m_ui->editLicense->setText(cfg.readEntry("BundleLicense", "CC-BY-SA")); m_ui->editBundleName->setText(cfg.readEntry("BundleName", "New Bundle")); m_ui->editDescription->document()->setPlainText(cfg.readEntry("BundleDescription", "New Bundle")); m_previewImage = cfg.readEntry("BundleImage", ""); if (!m_previewImage.isEmpty()) { QImage img(m_previewImage); img = img.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation); m_ui->lblPreview->setPixmap(QPixmap::fromImage(img)); } m_ui->lblSaveLocation->setText(cfg.readEntry("BundleExportLocation", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation))); } m_ui->bnAdd->setIcon(KisIconUtils::loadIcon("arrow-right")); connect(m_ui->bnAdd, SIGNAL(clicked()), SLOT(addSelected())); m_ui->bnRemove->setIcon(KisIconUtils::loadIcon("arrow-left")); connect(m_ui->bnRemove, SIGNAL(clicked()), SLOT(removeSelected())); m_ui->cmbResourceTypes->addItem(i18n("Brushes"), ResourceType::Brushes); m_ui->cmbResourceTypes->addItem(i18n("Brush Presets"), ResourceType::PaintOpPresets); m_ui->cmbResourceTypes->addItem(i18n("Gradients"), ResourceType::Gradients); m_ui->cmbResourceTypes->addItem(i18n("Gamut Masks"), ResourceType::GamutMasks); m_ui->cmbResourceTypes->addItem(i18n("Patterns"), ResourceType::Patterns); m_ui->cmbResourceTypes->addItem(i18n("Palettes"), ResourceType::Palettes); m_ui->cmbResourceTypes->addItem(i18n("Workspaces"), ResourceType::Workspaces); connect(m_ui->cmbResourceTypes, SIGNAL(activated(int)), SLOT(resourceTypeSelected(int))); m_ui->tableAvailable->setIconSize(QSize(ICON_SIZE, ICON_SIZE)); m_ui->tableAvailable->setSelectionMode(QAbstractItemView::ExtendedSelection); m_ui->tableSelected->setIconSize(QSize(ICON_SIZE, ICON_SIZE)); m_ui->tableSelected->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(m_ui->bnGetPreview, SIGNAL(clicked()), SLOT(getPreviewImage())); connect(m_ui->bnEmbedTags, SIGNAL(clicked()), SLOT(slotEmbedTags())); resourceTypeSelected(0); } DlgCreateBundle::~DlgCreateBundle() { delete m_ui; } QString DlgCreateBundle::bundleName() const { return m_ui->editBundleName->text().replace(" ", "_"); } QString DlgCreateBundle::authorName() const { return m_ui->editAuthor->text(); } QString DlgCreateBundle::email() const { return m_ui->editEmail->text(); } QString DlgCreateBundle::website() const { return m_ui->editWebsite->text(); } QString DlgCreateBundle::license() const { return m_ui->editLicense->text(); } QString DlgCreateBundle::description() const { return m_ui->editDescription->document()->toPlainText(); } QString DlgCreateBundle::saveLocation() const { return m_ui->lblSaveLocation->text(); } QString DlgCreateBundle::previewImage() const { return m_previewImage; } QVector DlgCreateBundle::getTagsForEmbeddingInResource(QVector resourceTags) const { QVector tagsToEmbed; Q_FOREACH(KisTagSP tag, resourceTags) { if (m_selectedTagIds.contains(tag->id())) { tagsToEmbed << tag; } } return tagsToEmbed; } void DlgCreateBundle::putResourcesInTheBundle() const { KisResourceModel* emptyModel = KisResourceModelProvider::resourceModel(""); QStack allResourcesIds; Q_FOREACH(int id, m_selectedResourcesIds) { allResourcesIds << id; } // note: if there are repetitions, it's fine; the bundle will filter them out while(!allResourcesIds.isEmpty()) { int id = allResourcesIds.takeFirst(); KoResourceSP res = emptyModel->resourceForId(id); if (!res) { warnKrita << "No resource for id " << id; continue; } KisResourceModel* resModel = KisResourceModelProvider::resourceModel(res->resourceType().first); QVector tags = getTagsForEmbeddingInResource(resModel->tagsForResource(id)); m_bundle->addResource(res->resourceType().first, res->filename(), tags, res->md5()); QList linkedResources = res->linkedResources(KisGlobalResourcesInterface::instance()); if (!linkedResources.isEmpty()) { Q_FOREACH(KoResourceSP resource, linkedResources) { if (!allResourcesIds.contains(resource->resourceId())) { allResourcesIds.append(resource->resourceId()); } } } } } void DlgCreateBundle::accept() { QString name = bundleName(); QString filename = m_ui->lblSaveLocation->text() + "/" + name + ".bundle"; if (name.isEmpty()) { m_ui->editBundleName->setStyleSheet(QString(" border: 1px solid red")); QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The resource bundle name cannot be empty.")); return; } else { QFileInfo fileInfo(filename); if (fileInfo.exists() && !m_bundle) { m_ui->editBundleName->setStyleSheet("border: 1px solid red"); QMessageBox msgBox; msgBox.setText(i18nc("In a dialog asking whether to overwrite a bundle (resource pack)", "A bundle with this name already exists.")); msgBox.setInformativeText(i18nc("In a dialog regarding overwriting a bundle (resource pack)", "Do you want to overwrite the existing bundle?")); msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Cancel); int ret = msgBox.exec(); if (ret == QMessageBox::Cancel) { return; } } if (!m_bundle) { saveToConfiguration(false); m_bundle.reset(new KoResourceBundle(filename)); putResourcesInTheBundle(); m_bundle->save(); } else { KIS_SAFE_ASSERT_RECOVER(!m_bundle) { warnKrita << "Updating a bundle is not implemented yet"; }; } KoDialog::accept(); } } void DlgCreateBundle::saveToConfiguration(bool full) { KisConfig cfg(false); if (full) { cfg.writeEntry("BundleName", bundleName()); cfg.writeEntry("BundleDescription", description()); cfg.writeEntry("BundleImage", previewImage()); } else { cfg.writeEntry("BundleName", ""); cfg.writeEntry("BundleDescription", ""); cfg.writeEntry("BundleImage", ""); } cfg.writeEntry("BundleExportLocation", saveLocation()); cfg.writeEntry("BundleAuthorName", authorName()); cfg.writeEntry("BundleAuthorEmail", email()); cfg.writeEntry("BundleWebsite", website()); cfg.writeEntry("BundleLicense", license()); } void DlgCreateBundle::slotEmbedTags() { DlgEmbedTags* dlg = new DlgEmbedTags(m_selectedTagIds); int response = dlg->exec(); if (response == KoDialog::Accepted) { m_selectedTagIds = dlg->selectedTagIds(); } } void DlgCreateBundle::reject() { saveToConfiguration(true); KoDialog::reject(); } void DlgCreateBundle::selectSaveLocation() { KoFileDialog dialog(this, KoFileDialog::OpenDirectory, "resourcebundlesavelocation"); dialog.setDefaultDir(m_ui->lblSaveLocation->text()); dialog.setCaption(i18n("Select a directory to save the bundle")); QString location = dialog.filename(); m_ui->lblSaveLocation->setText(location); } void DlgCreateBundle::addSelected() { int row = m_ui->tableAvailable->currentRow(); Q_FOREACH (QListWidgetItem *item, m_ui->tableAvailable->selectedItems()) { m_ui->tableSelected->addItem(m_ui->tableAvailable->takeItem(m_ui->tableAvailable->row(item))); m_selectedResourcesIds.append(item->data(Qt::UserRole).toInt()); } m_ui->tableAvailable->setCurrentRow(row); } void DlgCreateBundle::removeSelected() { int row = m_ui->tableSelected->currentRow(); Q_FOREACH (QListWidgetItem *item, m_ui->tableSelected->selectedItems()) { m_ui->tableAvailable->addItem(m_ui->tableSelected->takeItem(m_ui->tableSelected->row(item))); m_selectedResourcesIds.removeAll(item->data(Qt::UserRole).toInt()); } m_ui->tableSelected->setCurrentRow(row); } QPixmap imageToIcon(const QImage &img) { QPixmap pixmap(ICON_SIZE, ICON_SIZE); pixmap.fill(); QImage scaled = img.scaled(ICON_SIZE, ICON_SIZE, Qt::KeepAspectRatio, Qt::SmoothTransformation); int x = (ICON_SIZE - scaled.width()) / 2; int y = (ICON_SIZE - scaled.height()) / 2; QPainter gc(&pixmap); gc.drawImage(x, y, scaled); gc.end(); return pixmap; } void DlgCreateBundle::resourceTypeSelected(int idx) { QString resourceType = m_ui->cmbResourceTypes->itemData(idx).toString(); m_ui->tableAvailable->clear(); m_ui->tableSelected->clear(); QString standarizedResourceType = (resourceType == "presets" ? ResourceType::PaintOpPresets : resourceType); KisResourceModel* model = KisResourceModelProvider::resourceModel(standarizedResourceType); for (int i = 0; i < model->rowCount(); i++) { QModelIndex idx = model->index(i, 0); - QString filename = model->data(idx, Qt::UserRole + KisResourceModel::Filename).toString(); - int id = model->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); + QString filename = model->data(idx, Qt::UserRole + KisAbstractResourceModel::Filename).toString(); + int id = model->data(idx, Qt::UserRole + KisAbstractResourceModel::Id).toInt(); if (resourceType == ResourceType::Gradients) { if (filename == "Foreground to Transparent" || filename == "Foreground to Background") { continue; } } - QImage image = (model->data(idx, Qt::UserRole + KisResourceModel::Thumbnail)).value(); - QString name = model->data(idx, Qt::UserRole + KisResourceModel::Name).toString(); + QImage image = (model->data(idx, Qt::UserRole + KisAbstractResourceModel::Thumbnail)).value(); + QString name = model->data(idx, Qt::UserRole + KisAbstractResourceModel::Name).toString(); // Function imageToIcon(QImage()) returns a square white pixmap and a warning "QImage::scaled: Image is a null image" // while QPixmap() returns an empty pixmap. // The difference between them is relevant in case of Workspaces which has no images. // Using QPixmap() makes them appear in a dense list without icons, while imageToIcon(QImage()) // would give a list with big white rectangles and names of the workspaces. QListWidgetItem *item = new QListWidgetItem(image.isNull() ? QPixmap() : imageToIcon(image), name); item->setData(Qt::UserRole, id); if (m_selectedResourcesIds.contains(id)) { m_ui->tableSelected->addItem(item); } else { m_ui->tableAvailable->addItem(item); } } } void DlgCreateBundle::getPreviewImage() { KoFileDialog dialog(this, KoFileDialog::OpenFile, "BundlePreviewImage"); dialog.setCaption(i18n("Select file to use as bundle icon")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); m_previewImage = dialog.filename(); QImage img(m_previewImage); img = img.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation); m_ui->lblPreview->setPixmap(QPixmap::fromImage(img)); } diff --git a/plugins/paintops/libpaintop/kis_brush_chooser.cpp b/plugins/paintops/libpaintop/kis_brush_chooser.cpp index cc3daf0d3d..0382070694 100644 --- a/plugins/paintops/libpaintop/kis_brush_chooser.cpp +++ b/plugins/paintops/libpaintop/kis_brush_chooser.cpp @@ -1,585 +1,585 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2009 Sven Langkamp * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Srikanth Tiyyagura * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisBrushServerProvider.h" #include "kis_slider_spin_box.h" #include "widgets/kis_multipliers_double_slider_spinbox.h" #include "kis_spacing_selection_widget.h" #include "kis_signals_blocker.h" #include "kis_imagepipe_brush.h" #include "kis_custom_brush_widget.h" #include "kis_clipboard_brush_widget.h" #include #include "kis_global.h" #include "kis_gbr_brush.h" #include "kis_png_brush.h" #include "kis_debug.h" #include "kis_image.h" #include /// The resource item delegate for rendering the resource preview class KisBrushDelegate : public QAbstractItemDelegate { public: KisBrushDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {} ~KisBrushDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } }; void KisBrushDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { if (! index.isValid()) return; - QImage thumbnail = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); + QImage thumbnail = index.data(Qt::UserRole + KisAbstractResourceModel::Thumbnail).value(); QRect itemRect = option.rect; if (thumbnail.height() > itemRect.height() || thumbnail.width() > itemRect.width()) { thumbnail = thumbnail.scaled(itemRect.size() , Qt::KeepAspectRatio, Qt::SmoothTransformation); } painter->save(); int dx = (itemRect.width() - thumbnail.width()) / 2; int dy = (itemRect.height() - thumbnail.height()) / 2; painter->drawImage(itemRect.x() + dx, itemRect.y() + dy, thumbnail); if (option.state & QStyle::State_Selected) { painter->setPen(QPen(option.palette.highlight(), 2.0)); painter->drawRect(option.rect); painter->setCompositionMode(QPainter::CompositionMode_HardLight); painter->setOpacity(0.65); painter->fillRect(option.rect, option.palette.highlight()); } painter->restore(); } KisPredefinedBrushChooser::KisPredefinedBrushChooser(QWidget *parent, const char *name) : QWidget(parent), m_stampBrushWidget(0), m_clipboardBrushWidget(0) { setObjectName(name); setupUi(this); brushSizeSpinBox->setRange(0, KSharedConfig::openConfig()->group("").readEntry("maximumBrushSize", 1000), 2); brushSizeSpinBox->setValue(5); brushSizeSpinBox->setExponentRatio(3.0); brushSizeSpinBox->setSuffix(i18n(" px")); brushSizeSpinBox->setExponentRatio(3.0); QObject::connect(brushSizeSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemSize(qreal))); brushRotationSpinBox->setRange(0, 360, 0); brushRotationSpinBox->setValue(0); brushRotationSpinBox->setSuffix(QChar(Qt::Key_degree)); QObject::connect(brushRotationSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemRotation(qreal))); brushSpacingSelectionWidget->setSpacing(true, 1.0); connect(brushSpacingSelectionWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged())); m_itemChooser = new KisResourceItemChooser(ResourceType::Brushes, false, this); m_itemChooser->setObjectName("brush_selector"); m_itemChooser->showTaggingBar(true); m_itemChooser->setRowHeight(30); m_itemChooser->setItemDelegate(new KisBrushDelegate(this)); m_itemChooser->setCurrentItem(0); m_itemChooser->setSynced(true); m_itemChooser->setMinimumWidth(100); m_itemChooser->setMinimumHeight(150); m_itemChooser->showButtons(false); // turn the import and delete buttons since we want control over them addPresetButton->setIcon(KisIconUtils::loadIcon("list-add")); deleteBrushTipButton->setIcon(KisIconUtils::loadIcon("trash-empty")); connect(addPresetButton, SIGNAL(clicked(bool)), this, SLOT(slotImportNewBrushResource())); connect(deleteBrushTipButton, SIGNAL(clicked(bool)), this, SLOT(slotDeleteBrushResource())); presetsLayout->addWidget(m_itemChooser); connect(m_itemChooser, SIGNAL(resourceSelected(KoResourceSP )), this, SLOT(updateBrushTip(KoResourceSP ))); stampButton->setIcon(KisIconUtils::loadIcon("list-add")); stampButton->setToolTip(i18n("Creates a brush tip from the current image selection." "\n If no selection is present the whole image will be used.")); clipboardButton->setIcon(KisIconUtils::loadIcon("list-add")); clipboardButton->setToolTip(i18n("Creates a brush tip from the image in the clipboard.")); connect(stampButton, SIGNAL(clicked()), this, SLOT(slotOpenStampBrush())); connect(clipboardButton, SIGNAL(clicked()), SLOT(slotOpenClipboardBrush())); QGridLayout *spacingLayout = new QGridLayout(); spacingLayout->setObjectName("spacing grid layout"); resetBrushButton->setToolTip(i18n("Reloads Spacing from file\nSets Scale to 1.0\nSets Rotation to 0.0")); connect(resetBrushButton, SIGNAL(clicked()), SLOT(slotResetBrush())); intAdjustmentMidPoint->setRange(0, 255); intAdjustmentMidPoint->setPageStep(10); intAdjustmentMidPoint->setSingleStep(1); intAdjustmentMidPoint->setPrefix(i18nc("@label:slider", "Neutral point: ")); intBrightnessAdjustment->setRange(-100, 100); intBrightnessAdjustment->setPageStep(10); intBrightnessAdjustment->setSingleStep(1); intBrightnessAdjustment->setSuffix("%"); intBrightnessAdjustment->setPrefix(i18nc("@label:slider", "Brightness: ")); intContrastAdjustment->setRange(-100, 100); intContrastAdjustment->setPageStep(10); intContrastAdjustment->setSingleStep(1); intContrastAdjustment->setSuffix("%"); intContrastAdjustment->setPrefix(i18nc("@label:slider", "Contrast: ")); btnResetAdjustments->setToolTip(i18nc("@info:tooltip", "Resets all the adjustments to default values:\n Neutral Point: 127\n Brightness: 0%\n Contrast: 0%")); connect(btnResetAdjustments, SIGNAL(clicked()), SLOT(slotResetAdjustments())); connect(btnMaskMode, SIGNAL(toggled(bool)), SLOT(slotUpdateBrushAdjustmentsState())); connect(btnColorMode, SIGNAL(toggled(bool)), SLOT(slotUpdateBrushAdjustmentsState())); connect(btnLightnessMode, SIGNAL(toggled(bool)), SLOT(slotUpdateBrushAdjustmentsState())); connect(btnMaskMode, SIGNAL(toggled(bool)), SLOT(slotWriteBrushMode())); connect(btnColorMode, SIGNAL(toggled(bool)), SLOT(slotWriteBrushMode())); connect(btnLightnessMode, SIGNAL(toggled(bool)), SLOT(slotWriteBrushMode())); connect(btnMaskMode, SIGNAL(toggled(bool)), SLOT(slotUpdateResetBrushAdjustmentsButtonState())); connect(btnColorMode, SIGNAL(toggled(bool)), SLOT(slotUpdateResetBrushAdjustmentsButtonState())); connect(btnLightnessMode, SIGNAL(toggled(bool)), SLOT(slotUpdateResetBrushAdjustmentsButtonState())); connect(intAdjustmentMidPoint, SIGNAL(valueChanged(int)), SLOT(slotWriteBrushAdjustments())); connect(intBrightnessAdjustment, SIGNAL(valueChanged(int)), SLOT(slotWriteBrushAdjustments())); connect(intContrastAdjustment, SIGNAL(valueChanged(int)), SLOT(slotWriteBrushAdjustments())); connect(intAdjustmentMidPoint, SIGNAL(valueChanged(int)), SLOT(slotUpdateResetBrushAdjustmentsButtonState())); connect(intBrightnessAdjustment, SIGNAL(valueChanged(int)), SLOT(slotUpdateResetBrushAdjustmentsButtonState())); connect(intContrastAdjustment, SIGNAL(valueChanged(int)), SLOT(slotUpdateResetBrushAdjustmentsButtonState())); updateBrushTip(m_itemChooser->currentResource()); } KisPredefinedBrushChooser::~KisPredefinedBrushChooser() { } void KisPredefinedBrushChooser::setBrush(KisBrushSP brush) { /** * Warning: since the brushes are always cloned after loading from XML or * fetching from the server, we cannot just ask for that brush explicitly. * Instead, we should search for the brush with the same filename and/or name * and load it. Please take it into account that after selecting the brush * explicitly in the chooser, m_itemChooser->currentResource() might be * **not** the same as the value in m_brush. * * Ideally, if the resource is not found on the server, we should add it, but * it might lead to a set of weird consequences. So for now we just * select nothing. */ KoResourceServer* server = KisBrushServerProvider::instance()->brushServer(); KoResourceSP resource = server->resourceByFilename(brush->filename()); if (!resource) { resource = server->resourceByName(brush->name()); } if (!resource) { resource = brush; } m_itemChooser->setCurrentResource(resource); updateBrushTip(brush, true); } void KisPredefinedBrushChooser::slotResetBrush() { /** * The slot also resets the brush on the server * * TODO: technically, after we refactored all the brushes to be forked, * we can just re-update the brush from the server without reloading. * But it needs testing. */ KisBrushSP brush = m_itemChooser->currentResource().dynamicCast(); if (brush) { brush->load(KisGlobalResourcesInterface::instance()); brush->setScale(1.0); brush->setAngle(0.0); if (KisColorfulBrush *colorfulBrush = dynamic_cast(m_brush.data())) { colorfulBrush->setUseColorAsMask(false); colorfulBrush->setPreserveLightness(false); colorfulBrush->setAdjustmentMidPoint(127); colorfulBrush->setBrightnessAdjustment(0.0); colorfulBrush->setContrastAdjustment(0.0); } updateBrushTip(brush); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSetItemSize(qreal sizeValue) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { int brushWidth = m_brush->width(); m_brush->setScale(sizeValue / qreal(brushWidth)); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSetItemRotation(qreal rotationValue) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { m_brush->setAngle(rotationValue / 180.0 * M_PI); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSpacingChanged() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { m_brush->setSpacing(brushSpacingSelectionWidget->spacing()); m_brush->setAutoSpacing(brushSpacingSelectionWidget->autoSpacingActive(), brushSpacingSelectionWidget->autoSpacingCoeff()); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotOpenStampBrush() { if(!m_stampBrushWidget) { m_stampBrushWidget = new KisCustomBrushWidget(this, i18n("Stamp"), m_image); m_stampBrushWidget->setModal(false); connect(m_stampBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResourceSP )), SLOT(slotNewPredefinedBrush(KoResourceSP ))); } else { m_stampBrushWidget->setImage(m_image); } QDialog::DialogCode result = (QDialog::DialogCode)m_stampBrushWidget->exec(); if(result) { updateBrushTip(m_itemChooser->currentResource()); } } void KisPredefinedBrushChooser::slotOpenClipboardBrush() { if(!m_clipboardBrushWidget) { m_clipboardBrushWidget = new KisClipboardBrushWidget(this, i18n("Clipboard"), m_image); m_clipboardBrushWidget->setModal(true); connect(m_clipboardBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResourceSP )), SLOT(slotNewPredefinedBrush(KoResourceSP ))); } QDialog::DialogCode result = (QDialog::DialogCode)m_clipboardBrushWidget->exec(); if(result) { updateBrushTip(m_itemChooser->currentResource()); } } void KisPredefinedBrushChooser::updateBrushTip(KoResourceSP resource, bool isChangingBrushPresets) { QString animatedBrushTipSelectionMode; // incremental, random, etc { KisBrushSP brush = resource.dynamicCast(); m_brush = brush ? brush->clone().dynamicCast() : 0; } if (m_brush) { brushTipNameLabel->setText(i18n(m_brush->name().toUtf8().data())); QString brushTypeString = ""; if (m_brush->brushType() == INVALID) { brushTypeString = i18n("Invalid"); } else if (m_brush->brushType() == MASK) { brushTypeString = i18n("Mask"); } else if (m_brush->brushType() == IMAGE) { brushTypeString = i18n("GBR"); } else if (m_brush->brushType() == PIPE_MASK ) { brushTypeString = i18n("Animated Mask"); // GIH brush // cast to GIH brush and grab parasite name //m_brush KisImagePipeBrushSP pipeBrush = resource.dynamicCast(); animatedBrushTipSelectionMode = pipeBrush->parasiteSelection(); } else if (m_brush->brushType() == PIPE_IMAGE ) { brushTypeString = i18n("Animated Image"); } QString brushDetailsText = QString("%1 (%2 x %3) %4") .arg(brushTypeString) .arg(m_brush->width()) .arg(m_brush->height()) .arg(animatedBrushTipSelectionMode); brushDetailsLabel->setText(brushDetailsText); // keep the current preset's tip settings if we are preserving it // this will set the brush's model data to keep what it currently has for size, spacing, etc. if (preserveBrushPresetSettings->isChecked() && !isChangingBrushPresets) { m_brush->setAutoSpacing(brushSpacingSelectionWidget->autoSpacingActive(), brushSpacingSelectionWidget->autoSpacingCoeff()); m_brush->setAngle(brushRotationSpinBox->value() * M_PI / 180); m_brush->setSpacing(brushSpacingSelectionWidget->spacing()); m_brush->setUserEffectiveSize(brushSizeSpinBox->value()); } brushSpacingSelectionWidget->setSpacing(m_brush->autoSpacingActive(), m_brush->autoSpacingActive() ? m_brush->autoSpacingCoeff() : m_brush->spacing()); brushRotationSpinBox->setValue(m_brush->angle() * 180 / M_PI); brushSizeSpinBox->setValue(m_brush->width() * m_brush->scale()); emit sigBrushChanged(); } slotUpdateBrushModeButtonsState(); } #include "kis_scaling_size_brush.h" void KisPredefinedBrushChooser::slotUpdateBrushModeButtonsState() { KisColorfulBrush *colorfulBrush = dynamic_cast(m_brush.data()); const bool modeSwitchEnabled = m_hslBrushTipEnabled && colorfulBrush && colorfulBrush->hasColor(); if (modeSwitchEnabled) { if (colorfulBrush->useColorAsMask() && colorfulBrush->preserveLightness()) { btnLightnessMode->setChecked(true); } else if (colorfulBrush->useColorAsMask()) { btnMaskMode->setChecked(true); } else { btnColorMode->setChecked(true); } { // sliders emit update signals when modified from the code KisSignalsBlocker b(intAdjustmentMidPoint, intBrightnessAdjustment, intContrastAdjustment); intAdjustmentMidPoint->setValue(colorfulBrush->adjustmentMidPoint()); intBrightnessAdjustment->setValue(qRound(colorfulBrush->brightnessAdjustment() * 100.0)); intContrastAdjustment->setValue(qRound(colorfulBrush->contrastAdjustment() * 100.0)); } btnMaskMode->setToolTip(i18nc("@info:tooltip", "Luminosity of the brush tip image is used as alpha channel for the stroke")); btnColorMode->setToolTip(i18nc("@info:tooltip", "The brush tip image is painted as it is")); btnLightnessMode->setToolTip(i18nc("@info:tooltip", "Luminosity of the brush tip image is used as lightness correction for the painting color. Alpha channel of the brush tip image is used as alpha for the final stroke")); intAdjustmentMidPoint->setToolTip(i18nc("@info:tooltip", "Luminosity value of the brush that will not change the painting color. All brush pixels darker than neutral point will paint with darker color, pixels lighter than neutral point — lighter.")); intBrightnessAdjustment->setToolTip(i18nc("@info:tooltip", "Brightness correction for the brush")); intContrastAdjustment->setToolTip(i18nc("@info:tooltip", "Contrast correction for the brush")); grpBrushMode->setToolTip(""); } else { { // sliders emit update signals when modified from the code KisSignalsBlocker b(intAdjustmentMidPoint, intBrightnessAdjustment, intContrastAdjustment); intAdjustmentMidPoint->setValue(127); intBrightnessAdjustment->setValue(0); intContrastAdjustment->setValue(0); } btnMaskMode->setChecked(true); btnMaskMode->setToolTip(""); btnColorMode->setToolTip(""); btnLightnessMode->setToolTip(""); intAdjustmentMidPoint->setToolTip(""); intBrightnessAdjustment->setToolTip(""); intContrastAdjustment->setToolTip(""); if (m_hslBrushTipEnabled) { grpBrushMode->setToolTip(i18nc("@info:tooltip", "The selected brush tip does not have color channels. The brush will work in \"Mask\" mode.")); } else { grpBrushMode->setToolTip(i18nc("@info:tooltip", "The selected brush engine does not support \"Color\" or \"Lightness\" modes. The brush will work in \"Mask\" mode.")); } } grpBrushMode->setEnabled(modeSwitchEnabled); slotUpdateBrushAdjustmentsState(); slotUpdateResetBrushAdjustmentsButtonState(); } void KisPredefinedBrushChooser::slotUpdateBrushAdjustmentsState() { const bool adjustmentsEnabled = btnLightnessMode->isEnabled() && btnLightnessMode->isChecked(); intAdjustmentMidPoint->setEnabled(adjustmentsEnabled); intBrightnessAdjustment->setEnabled(adjustmentsEnabled); intContrastAdjustment->setEnabled(adjustmentsEnabled); } void KisPredefinedBrushChooser::slotUpdateResetBrushAdjustmentsButtonState() { const bool adjustmentsEnabled = btnLightnessMode->isEnabled() && btnLightnessMode->isChecked(); const bool adjustmentsDefault = intAdjustmentMidPoint->value() == 127 && intBrightnessAdjustment->value() == 0 && intContrastAdjustment->value() == 0; btnResetAdjustments->setEnabled(!adjustmentsDefault && adjustmentsEnabled); } void KisPredefinedBrushChooser::slotWriteBrushMode() { KisColorfulBrush *colorfulBrush = dynamic_cast(m_brush.data()); if (!colorfulBrush) return; if (btnLightnessMode->isChecked()) { colorfulBrush->setUseColorAsMask(true); colorfulBrush->setPreserveLightness(true); } else if (btnMaskMode->isChecked()) { colorfulBrush->setUseColorAsMask(true); colorfulBrush->setPreserveLightness(false); } else { colorfulBrush->setUseColorAsMask(false); colorfulBrush->setPreserveLightness(false); } emit sigBrushChanged(); } void KisPredefinedBrushChooser::slotWriteBrushAdjustments() { KisColorfulBrush *colorfulBrush = dynamic_cast(m_brush.data()); if (!colorfulBrush) return; { // sliders emit update signals when modified from the code KisSignalsBlocker b(intAdjustmentMidPoint, intBrightnessAdjustment, intContrastAdjustment); colorfulBrush->setAdjustmentMidPoint(quint8(intAdjustmentMidPoint->value())); colorfulBrush->setBrightnessAdjustment(intBrightnessAdjustment->value() / 100.0); colorfulBrush->setContrastAdjustment(intContrastAdjustment->value() / 100.0); } emit sigBrushChanged(); } void KisPredefinedBrushChooser::slotResetAdjustments() { intAdjustmentMidPoint->setValue(127); intBrightnessAdjustment->setValue(0); intContrastAdjustment->setValue(0); slotWriteBrushAdjustments(); } void KisPredefinedBrushChooser::slotNewPredefinedBrush(KoResourceSP resource) { m_itemChooser->setCurrentResource(resource); updateBrushTip(resource); } void KisPredefinedBrushChooser::setBrushSize(qreal xPixels, qreal yPixels) { Q_UNUSED(yPixels); qreal oldWidth = m_brush->width() * m_brush->scale(); qreal newWidth = oldWidth + xPixels; newWidth = qMax(newWidth, qreal(0.1)); brushSizeSpinBox->setValue(newWidth); } void KisPredefinedBrushChooser::setImage(KisImageWSP image) { m_image = image; } void KisPredefinedBrushChooser::setHSLBrusTipEnabled(bool value) { m_hslBrushTipEnabled = value; } bool KisPredefinedBrushChooser::hslBrushTipEnabled() const { return m_hslBrushTipEnabled; } void KisPredefinedBrushChooser::slotImportNewBrushResource() { m_itemChooser->slotButtonClicked(KisResourceItemChooser::Button_Import); } void KisPredefinedBrushChooser::slotDeleteBrushResource() { m_itemChooser->slotButtonClicked(KisResourceItemChooser::Button_Remove); } #include "moc_kis_brush_chooser.cpp" diff --git a/plugins/tools/basictools/kis_tool_colorpicker.cc b/plugins/tools/basictools/kis_tool_colorpicker.cc index d3fffa660b..62d46cf376 100644 --- a/plugins/tools/basictools/kis_tool_colorpicker.cc +++ b/plugins/tools/basictools/kis_tool_colorpicker.cc @@ -1,368 +1,368 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2018 Emmet & Eoin O'Neill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_colorpicker.h" #include #include #include "kis_cursor.h" #include "KisDocument.h" #include "kis_canvas2.h" #include "KisReferenceImagesLayer.h" #include "KoCanvasBase.h" #include "kis_random_accessor_ng.h" #include "KoResourceServerProvider.h" #include #include "kis_wrapped_rect.h" #include "kis_tool_utils.h" namespace { // GUI ComboBox index constants const int SAMPLE_MERGED = 0; } KisToolColorPicker::KisToolColorPicker(KoCanvasBase *canvas) : KisTool(canvas, KisCursor::pickerCursor()), m_config(new KisToolUtils::ColorPickerConfig) { setObjectName("tool_colorpicker"); m_isActivated = false; m_optionsWidget = 0; m_pickedColor = KoColor(); } KisToolColorPicker::~KisToolColorPicker() { if (m_isActivated) { m_config->save(m_toolActivationSource == KisTool::DefaultActivation); } } void KisToolColorPicker::paint(QPainter &gc, const KoViewConverter &converter) { Q_UNUSED(gc); Q_UNUSED(converter); } void KisToolColorPicker::activate(ToolActivation activation, const QSet &shapes) { m_isActivated = true; m_toolActivationSource = activation; m_config->load(m_toolActivationSource == KisTool::DefaultActivation); updateOptionWidget(); KisTool::activate(activation, shapes); } void KisToolColorPicker::deactivate() { m_config->save(m_toolActivationSource == KisTool::DefaultActivation); m_isActivated = false; KisTool::deactivate(); } bool KisToolColorPicker::pickColor(const QPointF &pos) { // Timer check. if (m_colorPickerDelayTimer.isActive()) { return false; } else { m_colorPickerDelayTimer.setSingleShot(true); m_colorPickerDelayTimer.start(100); } QScopedPointer> imageLocker; m_pickedColor.setOpacity(0.0); // Pick from reference images. if (m_optionsWidget->cmbSources->currentIndex() == SAMPLE_MERGED) { auto *kisCanvas = dynamic_cast(canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(kisCanvas, false); KisSharedPtr referenceImageLayer = kisCanvas->imageView()->document()->referenceImagesLayer(); if (referenceImageLayer && kisCanvas->referenceImagesDecoration()->visible()) { QColor color = referenceImageLayer->getPixel(pos); if (color.isValid()) { m_pickedColor.fromQColor(color); } } } if (m_pickedColor.opacityU8() == OPACITY_TRANSPARENT_U8) { if (!currentImage()->bounds().contains(pos.toPoint()) && !currentImage()->wrapAroundModePermitted()) { return false; } KisPaintDeviceSP dev; if (m_optionsWidget->cmbSources->currentIndex() != SAMPLE_MERGED && currentNode() && currentNode()->colorPickSourceDevice()) { dev = currentNode()->colorPickSourceDevice(); } else { imageLocker.reset(new boost::lock_guard(*currentImage())); dev = currentImage()->projection(); } KoColor previousColor = canvas()->resourceManager()->foregroundColor(); KisToolUtils::pickColor(m_pickedColor, dev, pos.toPoint(), &previousColor, m_config->radius, m_config->blend); } if (m_config->updateColor && m_pickedColor.opacityU8() != OPACITY_TRANSPARENT_U8) { KoColor publicColor = m_pickedColor; publicColor.setOpacity(OPACITY_OPAQUE_U8); // Alpha is unwanted for FG and BG colors. if (m_config->toForegroundColor) { canvas()->resourceManager()->setResource(KoCanvasResourceProvider::ForegroundColor, publicColor); } else { canvas()->resourceManager()->setResource(KoCanvasResourceProvider::BackgroundColor, publicColor); } } return true; } void KisToolColorPicker::beginPrimaryAction(KoPointerEvent *event) { bool sampleMerged = m_optionsWidget->cmbSources->currentIndex() == SAMPLE_MERGED; if (!sampleMerged) { if (!currentNode()) { QMessageBox::information(0, i18nc("@title:window", "Krita"), i18n("Cannot pick a color as no layer is active.")); event->ignore(); return; } if (!currentNode()->visible()) { QMessageBox::information(0, i18nc("@title:window", "Krita"), i18n("Cannot pick a color as the active layer is not visible.")); event->ignore(); return; } } QPoint pos = convertToImagePixelCoordFloored(event); setMode(KisTool::PAINT_MODE); bool picked = pickColor(pos); if (!picked) { // Color picking has to start in the visible part of the layer event->ignore(); return; } displayPickedColor(); } void KisToolColorPicker::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); QPoint pos = convertToImagePixelCoordFloored(event); pickColor(pos); displayPickedColor(); } #include "kis_display_color_converter.h" void KisToolColorPicker::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (m_config->addColorToCurrentPalette) { KisSwatch swatch; swatch.setColor(m_pickedColor); // We don't ask for a name, too intrusive here KoColorSetSP palette = m_palettes.at(m_optionsWidget->cmbPalette->currentIndex()); palette->add(swatch); KoResourceServerProvider::instance()->paletteServer()->updateResource(palette); if (!palette->save()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Cannot write to palette file %1. Maybe it is read-only.", palette->filename())); } } } struct PickedChannel { QString name; QString valueText; }; void KisToolColorPicker::displayPickedColor() { if (m_pickedColor.data() && m_optionsWidget) { QList channels = m_pickedColor.colorSpace()->channels(); m_optionsWidget->listViewChannels->clear(); QVector pickedChannels; for (int i = 0; i < channels.count(); ++i) { pickedChannels.append(PickedChannel()); } for (int i = 0; i < channels.count(); ++i) { PickedChannel pc; pc.name = channels[i]->name(); if (m_config->normaliseValues) { pc.valueText = m_pickedColor.colorSpace()->normalisedChannelValueText(m_pickedColor.data(), i); } else { pc.valueText = m_pickedColor.colorSpace()->channelValueText(m_pickedColor.data(), i); } pickedChannels[channels[i]->displayPosition()] = pc; } Q_FOREACH (const PickedChannel &pc, pickedChannels) { QTreeWidgetItem *item = new QTreeWidgetItem(m_optionsWidget->listViewChannels); item->setText(0, pc.name); item->setText(1, pc.valueText); } KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); KoColor newColor = kritaCanvas->displayColorConverter()->applyDisplayFiltering(m_pickedColor, Float32BitsColorDepthID); QVector values(4); newColor.colorSpace()->normalisedChannelsValue(newColor.data(), values); for (int i = 0; i < values.size(); i++) { QTreeWidgetItem *item = new QTreeWidgetItem(m_optionsWidget->listViewChannels); item->setText(0, QString("DisplayCh%1").arg(i)); item->setText(1, QString::number(values[i])); } } } QWidget* KisToolColorPicker::createOptionWidget() { m_optionsWidget = new ColorPickerOptionsWidget(0); m_optionsWidget->setObjectName(toolId() + " option widget"); m_optionsWidget->listViewChannels->setSortingEnabled(false); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); // Initialize blend KisSliderSpinBox m_optionsWidget->blend->setRange(0,100); m_optionsWidget->blend->setSuffix(i18n("%")); updateOptionWidget(); connect(m_optionsWidget->cbUpdateCurrentColor, SIGNAL(toggled(bool)), SLOT(slotSetUpdateColor(bool))); connect(m_optionsWidget->cbNormaliseValues, SIGNAL(toggled(bool)), SLOT(slotSetNormaliseValues(bool))); connect(m_optionsWidget->cbPalette, SIGNAL(toggled(bool)), SLOT(slotSetAddPalette(bool))); connect(m_optionsWidget->radius, SIGNAL(valueChanged(int)), SLOT(slotChangeRadius(int))); connect(m_optionsWidget->blend, SIGNAL(valueChanged(int)), SLOT(slotChangeBlend(int))); connect(m_optionsWidget->cmbSources, SIGNAL(currentIndexChanged(int)), SLOT(slotSetColorSource(int))); KoResourceServer *srv = KoResourceServerProvider::instance()->paletteServer(); if (!srv) { return m_optionsWidget; } m_optionsWidget->cmbPalette->setModel(srv->resourceModel()); - m_optionsWidget->cmbPalette->setModelColumn(KisResourceModel::Name); + m_optionsWidget->cmbPalette->setModelColumn(KisAbstractResourceModel::Name); return m_optionsWidget; } void KisToolColorPicker::updateOptionWidget() { if (!m_optionsWidget) return; m_optionsWidget->cbNormaliseValues->setChecked(m_config->normaliseValues); m_optionsWidget->cbUpdateCurrentColor->setChecked(m_config->updateColor); m_optionsWidget->cmbSources->setCurrentIndex(SAMPLE_MERGED + !m_config->sampleMerged); m_optionsWidget->cbPalette->setChecked(m_config->addColorToCurrentPalette); m_optionsWidget->radius->setValue(m_config->radius); m_optionsWidget->blend->setValue(m_config->blend); } void KisToolColorPicker::setToForeground(bool newValue) { m_config->toForegroundColor = newValue; emit toForegroundChanged(); } bool KisToolColorPicker::toForeground() const { return m_config->toForegroundColor; } void KisToolColorPicker::slotSetUpdateColor(bool state) { m_config->updateColor = state; } void KisToolColorPicker::slotSetNormaliseValues(bool state) { m_config->normaliseValues = state; displayPickedColor(); } void KisToolColorPicker::slotSetAddPalette(bool state) { m_config->addColorToCurrentPalette = state; } void KisToolColorPicker::slotChangeRadius(int value) { m_config->radius = value; } void KisToolColorPicker::slotChangeBlend(int value) { m_config->blend = value; } void KisToolColorPicker::slotSetColorSource(int value) { m_config->sampleMerged = value == SAMPLE_MERGED; } void KisToolColorPicker::slotAddPalette(KoResourceSP resource) { KoColorSetSP palette = resource.dynamicCast(); if (palette) { m_optionsWidget->cmbPalette->addSqueezedItem(palette->name()); m_palettes.append(palette); } }