diff --git a/libs/image/3rdparty/lock_free_map/concurrent_map.h b/libs/image/3rdparty/lock_free_map/concurrent_map.h index 6671927710..a7d5f8fa65 100644 --- a/libs/image/3rdparty/lock_free_map/concurrent_map.h +++ b/libs/image/3rdparty/lock_free_map/concurrent_map.h @@ -1,363 +1,366 @@ /*------------------------------------------------------------------------ Junction: Concurrent data structures in C++ Copyright (c) 2016 Jeff Preshing Distributed under the Simplified BSD License. Original location: https://github.com/preshing/junction This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the LICENSE file for more information. ------------------------------------------------------------------------*/ #ifndef CONCURRENTMAP_H #define CONCURRENTMAP_H #include "leapfrog.h" #include "qsbr.h" template , class VT = DefaultValueTraits > class ConcurrentMap { public: typedef K Key; typedef V Value; typedef KT KeyTraits; typedef VT ValueTraits; typedef quint32 Hash; typedef Leapfrog Details; private: Atomic m_root; QSBR m_gc; public: ConcurrentMap(quint64 capacity = Details::InitialSize) : m_root(Details::Table::create(capacity)) { } ~ConcurrentMap() { typename Details::Table* table = m_root.loadNonatomic(); table->destroy(); m_gc.flush(); } QSBR &getGC() { return m_gc; } bool migrationInProcess() { - return (quint64) m_root.loadNonatomic()->jobCoordinator.loadConsume() != 1; + return quint64(m_root.loadNonatomic()->jobCoordinator.loadConsume()) > 1; } // publishTableMigration() is called by exactly one thread from Details::TableMigration::run() // after all the threads participating in the migration have completed their work. void publishTableMigration(typename Details::TableMigration* migration) { m_root.store(migration->m_destination, Release); // Caller will GC the TableMigration and the source table. } // A Mutator represents a known cell in the hash table. // It's meant for manipulations within a temporary function scope. // Obviously you must not call QSBR::Update while holding a Mutator. // Any operation that modifies the table (exchangeValue, eraseValue) // may be forced to follow a redirected cell, which changes the Mutator itself. // Note that even if the Mutator was constructed from an existing cell, // exchangeValue() can still trigger a resize if the existing cell was previously marked deleted, // or if another thread deletes the key between the two steps. class Mutator { private: friend class ConcurrentMap; ConcurrentMap& m_map; typename Details::Table* m_table; typename Details::Cell* m_cell; Value m_value; // Constructor: Find existing cell Mutator(ConcurrentMap& map, Key key, bool) : m_map(map), m_value(Value(ValueTraits::NullValue)) { Hash hash = KeyTraits::hash(key); for (;;) { m_table = m_map.m_root.load(Consume); m_cell = Details::find(hash, m_table); if (!m_cell) { return; } Value value = m_cell->value.load(Consume); if (value != Value(ValueTraits::Redirect)) { // Found an existing value m_value = value; return; } // We've encountered a Redirect value. Help finish the migration. m_table->jobCoordinator.participate(); // Try again using the latest root. } } // Constructor: Insert or find cell Mutator(ConcurrentMap& map, Key key) : m_map(map), m_value(Value(ValueTraits::NullValue)) { Hash hash = KeyTraits::hash(key); for (;;) { m_table = m_map.m_root.load(Consume); quint64 overflowIdx; switch (Details::insertOrFind(hash, m_table, m_cell, overflowIdx)) { // Modifies m_cell case Details::InsertResult_InsertedNew: { // We've inserted a new cell. Don't load m_cell->value. return; } case Details::InsertResult_AlreadyFound: { // The hash was already found in the table. Value value = m_cell->value.load(Consume); if (value == Value(ValueTraits::Redirect)) { // We've encountered a Redirect value. break; // Help finish the migration. } // Found an existing value m_value = value; return; } case Details::InsertResult_Overflow: { // Unlike ConcurrentMap_Linear, we don't need to keep track of & pass a "mustDouble" flag. // Passing overflowIdx is sufficient to prevent an infinite loop here. // It defines the start of the range of cells to check while estimating total cells in use. // After the first migration, deleted keys are purged, so if we hit this line during the // second loop iteration, every cell in the range will be in use, thus the estimate will be 100%. // (Concurrent deletes could result in further iterations, but it will eventually settle.) Details::beginTableMigration(m_map, m_table, overflowIdx); break; } } // A migration has been started (either by us, or another thread). Participate until it's complete. m_table->jobCoordinator.participate(); // Try again using the latest root. } } public: Value getValue() const { // Return previously loaded value. Don't load it again. return Value(m_value); } Value exchangeValue(Value desired) { for (;;) { Value oldValue = m_value; if (m_cell->value.compareExchangeStrong(m_value, desired, ConsumeRelease)) { // Exchange was successful. Return previous value. Value result = m_value; m_value = desired; // Leave the mutator in a valid state return result; } // The CAS failed and m_value has been updated with the latest value. if (m_value != Value(ValueTraits::Redirect)) { if (oldValue == Value(ValueTraits::NullValue) && m_value != Value(ValueTraits::NullValue)) { // racing write inserted new value } // There was a racing write (or erase) to this cell. // Pretend we exchanged with ourselves, and just let the racing write win. return desired; } // We've encountered a Redirect value. Help finish the migration. Hash hash = m_cell->hash.load(Relaxed); for (;;) { // Help complete the migration. m_table->jobCoordinator.participate(); // Try again in the new table. m_table = m_map.m_root.load(Consume); m_value = Value(ValueTraits::NullValue); quint64 overflowIdx; switch (Details::insertOrFind(hash, m_table, m_cell, overflowIdx)) { // Modifies m_cell case Details::InsertResult_AlreadyFound: m_value = m_cell->value.load(Consume); if (m_value == Value(ValueTraits::Redirect)) { break; } goto breakOuter; case Details::InsertResult_InsertedNew: goto breakOuter; case Details::InsertResult_Overflow: Details::beginTableMigration(m_map, m_table, overflowIdx); break; } // We were redirected... again } breakOuter:; // Try again in the new table. } } void assignValue(Value desired) { exchangeValue(desired); } Value eraseValue() { for (;;) { if (m_value == Value(ValueTraits::NullValue)) { return Value(m_value); } if (m_cell->value.compareExchangeStrong(m_value, Value(ValueTraits::NullValue), Consume)) { // Exchange was successful and a non-NULL value was erased and returned by reference in m_value. Value result = m_value; m_value = Value(ValueTraits::NullValue); // Leave the mutator in a valid state return result; } // The CAS failed and m_value has been updated with the latest value. if (m_value != Value(ValueTraits::Redirect)) { // There was a racing write (or erase) to this cell. // Pretend we erased nothing, and just let the racing write win. return Value(ValueTraits::NullValue); } // We've been redirected to a new table. Hash hash = m_cell->hash.load(Relaxed); // Re-fetch hash for (;;) { // Help complete the migration. m_table->jobCoordinator.participate(); // Try again in the new table. m_table = m_map.m_root.load(Consume); m_cell = Details::find(hash, m_table); if (!m_cell) { m_value = Value(ValueTraits::NullValue); return m_value; } m_value = m_cell->value.load(Relaxed); if (m_value != Value(ValueTraits::Redirect)) { break; } } } } }; Mutator insertOrFind(Key key) { return Mutator(*this, key); } Mutator find(Key key) { return Mutator(*this, key, false); } // Lookup without creating a temporary Mutator. Value get(Key key) { Hash hash = KeyTraits::hash(key); for (;;) { typename Details::Table* table = m_root.load(Consume); typename Details::Cell* cell = Details::find(hash, table); if (!cell) { return Value(ValueTraits::NullValue); } Value value = cell->value.load(Consume); if (value != Value(ValueTraits::Redirect)) { return value; // Found an existing value } // We've been redirected to a new table. Help with the migration. table->jobCoordinator.participate(); // Try again in the new table. } } Value assign(Key key, Value desired) { Mutator iter(*this, key); return iter.exchangeValue(desired); } Value exchange(Key key, Value desired) { Mutator iter(*this, key); return iter.exchangeValue(desired); } Value erase(Key key) { Mutator iter(*this, key, false); return iter.eraseValue(); } // The easiest way to implement an Iterator is to prevent all Redirects. // The currrent Iterator does that by forbidding concurrent inserts. // To make it work with concurrent inserts, we'd need a way to block TableMigrations. class Iterator { private: typename Details::Table* m_table; quint64 m_idx; Key m_hash; Value m_value; public: Iterator() = default; Iterator(ConcurrentMap& map) { // Since we've forbidden concurrent inserts (for now), nonatomic would suffice here, but let's plan ahead: m_table = map.m_root.load(Consume); m_idx = -1; next(); } void setMap(ConcurrentMap& map) { m_table = map.m_root.load(Consume); m_idx = -1; next(); } void next() { while (++m_idx <= m_table->sizeMask) { // Index still inside range of table. typename Details::CellGroup* group = m_table->getCellGroups() + (m_idx >> 2); typename Details::Cell* cell = group->cells + (m_idx & 3); m_hash = cell->hash.load(Relaxed); if (m_hash != KeyTraits::NullHash) { // Cell has been reserved. m_value = cell->value.load(Relaxed); if (m_value != Value(ValueTraits::NullValue)) return; // Yield this cell. } } // That's the end of the map. m_hash = KeyTraits::NullHash; m_value = Value(ValueTraits::NullValue); } bool isValid() const { +#ifdef SANITY_CHECK + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_value != Value(ValueTraits::Redirect), false); +#endif return m_value != Value(ValueTraits::NullValue); } Key getKey() const { // Since we've forbidden concurrent inserts (for now), nonatomic would suffice here, but let's plan ahead: return KeyTraits::dehash(m_hash); } Value getValue() const { return m_value; } }; }; #endif // CONCURRENTMAP_LEAPFROG_H diff --git a/libs/image/tiles3/kis_tile_data_store.cc b/libs/image/tiles3/kis_tile_data_store.cc index 493756b2ec..3cd2d8d94f 100644 --- a/libs/image/tiles3/kis_tile_data_store.cc +++ b/libs/image/tiles3/kis_tile_data_store.cc @@ -1,377 +1,374 @@ /* * Copyright (c) 2009 Dmitry Kazakov * Copyright (c) 2018 Andrey Kamakin * * 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. */ // to disable assert when the leak tracker is active #include "config-memory-leak-tracker.h" #include #include "kis_tile_data_store.h" #include "kis_tile_data.h" #include "kis_debug.h" #include "kis_tile_data_store_iterators.h" Q_GLOBAL_STATIC(KisTileDataStore, s_instance) //#define DEBUG_PRECLONE #ifdef DEBUG_PRECLONE #include #define DEBUG_PRECLONE_ACTION(action, oldTD, newTD) \ printf("!!! %s:\t\t\t 0x%X -> 0x%X \t\t!!!\n", \ action, (quintptr)oldTD, (quintptr) newTD) #define DEBUG_FREE_ACTION(td) \ printf("Tile data free'd \t(0x%X)\n", td) #else #define DEBUG_PRECLONE_ACTION(action, oldTD, newTD) #define DEBUG_FREE_ACTION(td) #endif #ifdef DEBUG_HIT_MISS qint64 __preclone_miss = 0; qint64 __preclone_hit = 0; qint64 __preclone_miss_user_count = 0; qint64 __preclone_miss_age = 0; #define DEBUG_COUNT_PRECLONE_HIT(td) __preclone_hit++ #define DEBUG_COUNT_PRECLONE_MISS(td) __preclone_miss++; __preclone_miss_user_count+=td->numUsers(); __preclone_miss_age+=td->age() #define DEBUG_REPORT_PRECLONE_EFFICIENCY() \ dbgKrita << "Hits:" << __preclone_hit \ << "of" << __preclone_hit + __preclone_miss \ << "(" \ << qreal(__preclone_hit) / (__preclone_hit + __preclone_miss) \ << ")" \ << "miss users" << qreal(__preclone_miss_user_count) / __preclone_miss \ << "miss age" << qreal(__preclone_miss_age) / __preclone_miss #else #define DEBUG_COUNT_PRECLONE_HIT(td) #define DEBUG_COUNT_PRECLONE_MISS(td) #define DEBUG_REPORT_PRECLONE_EFFICIENCY() #endif KisTileDataStore::KisTileDataStore() : m_pooler(this), m_swapper(this), m_numTiles(0), m_memoryMetric(0), m_counter(1), m_clockIndex(1) { m_pooler.start(); m_swapper.start(); } KisTileDataStore::~KisTileDataStore() { m_pooler.terminatePooler(); m_swapper.terminateSwapper(); if (numTiles() > 0) { errKrita << "Warning: some tiles have leaked:"; errKrita << "\tTiles in memory:" << numTilesInMemory() << "\n" << "\tTotal tiles:" << numTiles(); } } KisTileDataStore* KisTileDataStore::instance() { return s_instance; } KisTileDataStore::MemoryStatistics KisTileDataStore::memoryStatistics() { // in case the pooler is disabled, we should force it // to update the stats if (!m_pooler.isRunning()) { m_pooler.forceUpdateMemoryStats(); } QReadLocker lock(&m_iteratorLock); MemoryStatistics stats; const qint64 metricCoeff = KisTileData::WIDTH * KisTileData::HEIGHT; stats.realMemorySize = m_pooler.lastRealMemoryMetric() * metricCoeff; stats.historicalMemorySize = m_pooler.lastHistoricalMemoryMetric() * metricCoeff; stats.poolSize = m_pooler.lastPoolMemoryMetric() * metricCoeff; stats.totalMemorySize = memoryMetric() * metricCoeff + stats.poolSize; stats.swapSize = m_swappedStore.totalMemoryMetric() * metricCoeff; return stats; } inline void KisTileDataStore::registerTileDataImp(KisTileData *td) { int index = m_counter.fetchAndAddOrdered(1); td->m_tileNumber = index; m_tileDataMap.assign(index, td); m_numTiles.ref(); m_memoryMetric += td->pixelSize(); } void KisTileDataStore::registerTileData(KisTileData *td) { QReadLocker lock(&m_iteratorLock); registerTileDataImp(td); } inline void KisTileDataStore::unregisterTileDataImp(KisTileData *td) { if (m_clockIndex == td->m_tileNumber) { do { m_clockIndex.ref(); } while (!m_tileDataMap.get(m_clockIndex.loadAcquire()) && m_clockIndex < m_counter); } int index = td->m_tileNumber; td->m_tileNumber = -1; m_tileDataMap.erase(index); m_numTiles.deref(); m_memoryMetric -= td->pixelSize(); } void KisTileDataStore::unregisterTileData(KisTileData *td) { QReadLocker lock(&m_iteratorLock); unregisterTileDataImp(td); } KisTileData *KisTileDataStore::allocTileData(qint32 pixelSize, const quint8 *defPixel) { KisTileData *td = new KisTileData(pixelSize, defPixel, this); registerTileData(td); return td; } KisTileData *KisTileDataStore::duplicateTileData(KisTileData *rhs) { KisTileData *td = 0; if (rhs->m_clonesStack.pop(td)) { DEBUG_PRECLONE_ACTION("+ Pre-clone HIT", rhs, td); DEBUG_COUNT_PRECLONE_HIT(rhs); } else { rhs->blockSwapping(); td = new KisTileData(*rhs); rhs->unblockSwapping(); DEBUG_PRECLONE_ACTION("- Pre-clone #MISS#", rhs, td); DEBUG_COUNT_PRECLONE_MISS(rhs); } registerTileData(td); return td; } void KisTileDataStore::freeTileData(KisTileData *td) { Q_ASSERT(td->m_store == this); DEBUG_FREE_ACTION(td); m_iteratorLock.lockForRead(); td->m_swapLock.lockForWrite(); if (!td->data()) { m_swappedStore.forgetTileData(td); } else { unregisterTileDataImp(td); } td->m_swapLock.unlock(); m_iteratorLock.unlock(); delete td; } void KisTileDataStore::ensureTileDataLoaded(KisTileData *td) { // dbgKrita << "#### SWAP MISS! ####" << td << ppVar(td->mementoed()) << ppVar(td->age()) << ppVar(td->numUsers()); checkFreeMemory(); td->m_swapLock.lockForRead(); while (!td->data()) { td->m_swapLock.unlock(); /** * The order of this heavy locking is very important. * Change it only in case, you really know what you are doing. */ m_iteratorLock.lockForWrite(); /** * If someone has managed to load the td from swap, then, most * probably, they have already taken the swap lock. This may * lead to a deadlock, because COW mechanism breaks lock * ordering rules in duplicateTileData() (it takes m_listLock * while the swap lock is held). In our case it is enough just * to check whether the other thread has already fetched the * data. Please notice that we do not take both of the locks * while checking this, because holding m_listLock is * enough. Nothing can happen to the tile while we hold * m_listLock. */ if (!td->data()) { td->m_swapLock.lockForWrite(); m_swappedStore.swapInTileData(td); registerTileDataImp(td); td->m_swapLock.unlock(); } m_iteratorLock.unlock(); /** * <-- In theory, livelock is possible here... */ td->m_swapLock.lockForRead(); } } bool KisTileDataStore::trySwapTileData(KisTileData *td) { /** * This function is called with m_listLock acquired */ bool result = false; if (!td->m_swapLock.tryLockForWrite()) return result; if (td->data()) { - unregisterTileDataImp(td); if (m_swappedStore.trySwapOutTileData(td)) { + unregisterTileDataImp(td); result = true; - } else { - result = false; - registerTileDataImp(td); } } td->m_swapLock.unlock(); return result; } KisTileDataStoreIterator* KisTileDataStore::beginIteration() { m_iteratorLock.lockForWrite(); return new KisTileDataStoreIterator(m_tileDataMap, this); } void KisTileDataStore::endIteration(KisTileDataStoreIterator* iterator) { delete iterator; m_iteratorLock.unlock(); } KisTileDataStoreReverseIterator* KisTileDataStore::beginReverseIteration() { m_iteratorLock.lockForWrite(); return new KisTileDataStoreReverseIterator(m_tileDataMap, this); } void KisTileDataStore::endIteration(KisTileDataStoreReverseIterator* iterator) { delete iterator; m_iteratorLock.unlock(); DEBUG_REPORT_PRECLONE_EFFICIENCY(); } KisTileDataStoreClockIterator* KisTileDataStore::beginClockIteration() { m_iteratorLock.lockForWrite(); return new KisTileDataStoreClockIterator(m_tileDataMap, m_clockIndex.loadAcquire(), this); } void KisTileDataStore::endIteration(KisTileDataStoreClockIterator* iterator) { m_clockIndex = iterator->getFinalPosition(); delete iterator; m_iteratorLock.unlock(); } void KisTileDataStore::debugPrintList() { KisTileDataStoreIterator* iter = beginIteration(); KisTileData *item = 0; while (iter->hasNext()) { item = iter->next(); dbgTiles << "-------------------------\n" << "TileData:\t\t\t" << item << "\n refCount:\t" << item->m_refCount; } endIteration(iter); } void KisTileDataStore::debugSwapAll() { KisTileDataStoreIterator* iter = beginIteration(); KisTileData *item = 0; while (iter->hasNext()) { item = iter->next(); iter->trySwapOut(item); } endIteration(iter); // dbgKrita << "Number of tiles:" << numTiles(); // dbgKrita << "Tiles in memory:" << numTilesInMemory(); // m_swappedStore.debugStatistics(); } void KisTileDataStore::debugClear() { QWriteLocker l(&m_iteratorLock); ConcurrentMap::Iterator iter(m_tileDataMap); while (iter.isValid()) { delete iter.getValue(); iter.next(); } m_counter = 1; m_clockIndex = 1; m_numTiles = 0; m_memoryMetric = 0; } void KisTileDataStore::testingRereadConfig() { m_pooler.testingRereadConfig(); m_swapper.testingRereadConfig(); kickPooler(); } void KisTileDataStore::testingSuspendPooler() { m_pooler.terminatePooler(); } void KisTileDataStore::testingResumePooler() { m_pooler.start(); } diff --git a/libs/ui/input/kis_input_manager.cpp b/libs/ui/input/kis_input_manager.cpp index 9362690bb3..8d06413f5e 100644 --- a/libs/ui/input/kis_input_manager.cpp +++ b/libs/ui/input/kis_input_manager.cpp @@ -1,836 +1,838 @@ /* This file is part of the KDE project * * Copyright (C) 2012 Arjen Hiemstra * Copyright (C) 2015 Michael Abrahams * * 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_input_manager.h" #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include #include #include #include #include #include #include "kis_abstract_input_action.h" #include "kis_tool_invocation_action.h" #include "kis_pan_action.h" #include "kis_alternate_invocation_action.h" #include "kis_rotate_canvas_action.h" #include "kis_zoom_action.h" #include "kis_show_palette_action.h" #include "kis_change_primary_setting_action.h" #include "kis_shortcut_matcher.h" #include "kis_stroke_shortcut.h" #include "kis_single_action_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_input_profile.h" #include "kis_input_profile_manager.h" #include "kis_shortcut_configuration.h" #include #include #include "kis_extended_modifiers_mapper.h" #include "kis_input_manager_p.h" #include "kis_algebra_2d.h" template uint qHash(QPointer value) { return reinterpret_cast(value.data()); } KisInputManager::KisInputManager(QObject *parent) : QObject(parent), d(new Private(this)) { d->setupActions(); connect(KoToolManager::instance(), SIGNAL(aboutToChangeTool(KoCanvasController*)), SLOT(slotAboutToChangeTool())); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), SLOT(slotToolChanged())); connect(&d->moveEventCompressor, SIGNAL(timeout()), SLOT(slotCompressedMoveEvent())); QApplication::instance()-> installEventFilter(new Private::ProximityNotifier(d, this)); } KisInputManager::~KisInputManager() { delete d; } void KisInputManager::addTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.addCanvas(canvas); } void KisInputManager::removeTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.removeCanvas(canvas); } void KisInputManager::toggleTabletLogger() { KisTabletDebugger::instance()->toggleDebugging(); } void KisInputManager::attachPriorityEventFilter(QObject *filter, int priority) { Private::PriorityList::iterator begin = d->priorityEventFilter.begin(); Private::PriorityList::iterator it = begin; Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(begin, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) return; it = std::find_if(begin, end, [priority] (const Private::PriorityPair &a) { return a.first > priority; }); d->priorityEventFilter.insert(it, qMakePair(priority, filter)); d->priorityEventFilterSeqNo++; } void KisInputManager::detachPriorityEventFilter(QObject *filter) { Private::PriorityList::iterator it = d->priorityEventFilter.begin(); Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(it, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) { d->priorityEventFilter.erase(it); } } void KisInputManager::setupAsEventFilter(QObject *receiver) { if (d->eventsReceiver) { d->eventsReceiver->removeEventFilter(this); } d->eventsReceiver = receiver; if (d->eventsReceiver) { d->eventsReceiver->installEventFilter(this); } } void KisInputManager::stopIgnoringEvents() { d->allowMouseEvents(); } #if defined (__clang__) #pragma GCC diagnostic ignored "-Wswitch" #endif bool KisInputManager::eventFilter(QObject* object, QEvent* event) { if (object != d->eventsReceiver) return false; if (d->eventEater.eventFilter(object, event)) return false; if (!d->matcher.hasRunningShortcut()) { int savedPriorityEventFilterSeqNo = d->priorityEventFilterSeqNo; for (auto it = d->priorityEventFilter.begin(); it != d->priorityEventFilter.end(); /*noop*/) { const QPointer &filter = it->second; if (filter.isNull()) { it = d->priorityEventFilter.erase(it); d->priorityEventFilterSeqNo++; savedPriorityEventFilterSeqNo++; continue; } if (filter->eventFilter(object, event)) return true; /** * If the filter removed itself from the filters list or * added something there, just exit the loop */ if (d->priorityEventFilterSeqNo != savedPriorityEventFilterSeqNo) { return true; } ++it; } // KoToolProxy needs to pre-process some events to ensure the // global shortcuts (not the input manager's ones) are not // executed, in particular, this line will accept events when the // tool is in text editing, preventing shortcut triggering if (d->toolProxy) { d->toolProxy->processEvent(event); } } // Continue with the actual switch statement... return eventFilterImpl(event); } // Qt's events do not have copy-ctors yes, so we should emulate them // See https://bugreports.qt.io/browse/QTBUG-72488 template void copyEventHack(Event *src, QScopedPointer &dst); template<> void copyEventHack(QMouseEvent *src, QScopedPointer &dst) { QMouseEvent *tmp = new QMouseEvent(src->type(), src->localPos(), src->windowPos(), src->screenPos(), src->button(), src->buttons(), src->modifiers(), src->source()); tmp->setTimestamp(src->timestamp()); dst.reset(tmp); } template<> void copyEventHack(QTabletEvent *src, QScopedPointer &dst) { QTabletEvent *tmp = new QTabletEvent(src->type(), src->posF(), src->globalPosF(), src->device(), src->pointerType(), src->pressure(), src->xTilt(), src->yTilt(), src->tangentialPressure(), src->rotation(), src->z(), src->modifiers(), src->uniqueId(), src->button(), src->buttons()); tmp->setTimestamp(src->timestamp()); dst.reset(tmp); } template<> void copyEventHack(QTouchEvent *src, QScopedPointer &dst) { QTouchEvent *tmp = new QTouchEvent(src->type(), src->device(), src->modifiers(), src->touchPointStates(), src->touchPoints()); tmp->setTimestamp(src->timestamp()); dst.reset(tmp); } template bool KisInputManager::compressMoveEventCommon(Event *event) { /** * We construct a copy of this event object, so we must ensure it * has a correct type. */ static_assert(std::is_same::value || std::is_same::value || std::is_same::value, "event should be a mouse or a tablet event"); bool retval = false; /** * Compress the events if the tool doesn't need high resolution input */ if ((event->type() == QEvent::MouseMove || event->type() == QEvent::TabletMove || event->type() == QEvent::TouchUpdate) && (!d->matcher.supportsHiResInputEvents() || d->testingCompressBrushEvents)) { copyEventHack(event, d->compressedMoveEvent); d->moveEventCompressor.start(); /** * On Linux Qt eats the rest of unneeded events if we * ignore the first of the chunk of tablet events. So * generally we should never activate this feature. Only * for testing purposes! */ if (d->testingAcceptCompressedTabletEvents) { event->setAccepted(true); } retval = true; } else { slotCompressedMoveEvent(); retval = d->handleCompressedTabletEvent(event); } return retval; } bool shouldResetWheelDelta(QEvent * event) { return event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut || event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonDblClick || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease || event->type() == QEvent::Enter || event->type() == QEvent::Leave || event->type() == QEvent::TouchBegin || event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel || event->type() == QEvent::NativeGesture; } bool KisInputManager::eventFilterImpl(QEvent * event) { bool retval = false; if (shouldResetWheelDelta(event)) { d->accumulatedScrollDelta = 0; } switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: { d->debugEvent(event); if (d->touchHasBlockedPressEvents) break; QMouseEvent *mouseEvent = static_cast(event); if (d->tryHidePopupPalette()) { retval = true; } else { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.buttonPressed(mouseEvent->button(), mouseEvent); } //Reset signal compressor to prevent processing events before press late d->resetCompressor(); event->setAccepted(retval); break; } case QEvent::MouseButtonRelease: { d->debugEvent(event); if (d->touchHasBlockedPressEvents) break; QMouseEvent *mouseEvent = static_cast(event); retval = d->matcher.buttonReleased(mouseEvent->button(), mouseEvent); event->setAccepted(retval); break; } case QEvent::ShortcutOverride: { d->debugEvent(event); QKeyEvent *keyEvent = static_cast(event); Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent); if (!keyEvent->isAutoRepeat()) { retval = d->matcher.keyPressed(key); } else { retval = d->matcher.autoRepeatedKeyPressed(key); } /** * Workaround for temporary switching of tools by * KoCanvasControllerWidget. We don't need this switch because * we handle it ourselves. */ retval |= !d->forwardAllEventsToTool && (keyEvent->key() == Qt::Key_Space || keyEvent->key() == Qt::Key_Escape); break; } case QEvent::KeyRelease: { d->debugEvent(event); QKeyEvent *keyEvent = static_cast(event); if (!keyEvent->isAutoRepeat()) { Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent); retval = d->matcher.keyReleased(key); } break; } case QEvent::MouseMove: { d->debugEvent(event); QMouseEvent *mouseEvent = static_cast(event); retval = compressMoveEventCommon(mouseEvent); break; } case QEvent::Wheel: { d->debugEvent(event); QWheelEvent *wheelEvent = static_cast(event); #ifdef Q_OS_MACOS // Some QT wheel events are actually touch pad pan events. From the QT docs: // "Wheel events are generated for both mouse wheels and trackpad scroll gestures." // We differentiate between touchpad events and real mouse wheels by inspecting the // event source. if (wheelEvent->source() == Qt::MouseEventSource::MouseEventSynthesizedBySystem) { KisAbstractInputAction::setInputManager(this); retval = d->matcher.wheelEvent(KisSingleActionShortcut::WheelTrackpad, wheelEvent); break; } #endif d->accumulatedScrollDelta += wheelEvent->delta(); KisSingleActionShortcut::WheelAction action; /** * Ignore delta 0 events on OSX, since they are triggered by tablet * proximity when using Wacom devices. */ #ifdef Q_OS_MACOS if(wheelEvent->delta() == 0) { retval = true; break; } #endif if (wheelEvent->orientation() == Qt::Horizontal) { if(wheelEvent->delta() < 0) { action = KisSingleActionShortcut::WheelRight; } else { action = KisSingleActionShortcut::WheelLeft; } } else { if(wheelEvent->delta() > 0) { action = KisSingleActionShortcut::WheelUp; } else { action = KisSingleActionShortcut::WheelDown; } } bool wasScrolled = false; while (qAbs(d->accumulatedScrollDelta) >= QWheelEvent::DefaultDeltasPerStep) { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.wheelEvent(action, wheelEvent); - d->accumulatedScrollDelta -= KisAlgebra2D::signPZ(d->accumulatedScrollDelta); + d->accumulatedScrollDelta -= + KisAlgebra2D::signPZ(d->accumulatedScrollDelta) * + QWheelEvent::DefaultDeltasPerStep; wasScrolled = true; } if (wasScrolled) { d->accumulatedScrollDelta = 0; } retval = !wasScrolled; break; } case QEvent::Enter: d->debugEvent(event); //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); if (!d->containsPointer) { d->containsPointer = true; d->allowMouseEvents(); d->touchHasBlockedPressEvents = false; } d->matcher.enterEvent(); break; case QEvent::Leave: d->debugEvent(event); d->containsPointer = false; /** * We won't get a TabletProximityLeave event when the tablet * is hovering above some other widget, so restore cursor * events processing right now. */ d->allowMouseEvents(); d->touchHasBlockedPressEvents = false; d->matcher.leaveEvent(); break; case QEvent::FocusIn: d->debugEvent(event); KisAbstractInputAction::setInputManager(this); //Clear all state so we don't have half-matched shortcuts dangling around. d->matcher.reinitialize(); { // Emulate pressing of the key that are already pressed KisExtendedModifiersMapper mapper; Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers(); Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) { QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers); eventFilterImpl(&kevent); } } d->allowMouseEvents(); break; case QEvent::FocusOut: { d->debugEvent(event); KisAbstractInputAction::setInputManager(this); QPointF currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); d->matcher.lostFocusEvent(currentLocalPos); break; } case QEvent::TabletPress: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); if (d->tryHidePopupPalette()) { retval = true; } else { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.buttonPressed(tabletEvent->button(), tabletEvent); if (!d->containsPointer) { d->containsPointer = true; d->touchHasBlockedPressEvents = false; } } event->setAccepted(true); retval = true; d->blockMouseEvents(); //Reset signal compressor to prevent processing events before press late d->resetCompressor(); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); d->eatOneMousePress(); #elif defined Q_OS_WIN32 /** * Windows is the only platform that synthesizes mouse events for * the tablet on OS-level, that is, even when we accept the event */ d->eatOneMousePress(); #endif break; } case QEvent::TabletMove: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); retval = compressMoveEventCommon(tabletEvent); if (d->tabletLatencyTracker) { d->tabletLatencyTracker->push(tabletEvent->timestamp()); } /** * The flow of tablet events means the tablet is in the * proximity area, so activate it even when the * TabletEnterProximity event was missed (may happen when * changing focus of the window with tablet in the proximity * area) */ d->blockMouseEvents(); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); #endif break; } case QEvent::TabletRelease: { #ifdef Q_OS_MAC d->allowMouseEvents(); #endif d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); retval = d->matcher.buttonReleased(tabletEvent->button(), tabletEvent); retval = true; event->setAccepted(true); #if defined Q_OS_LINUX && !defined QT_HAS_ENTER_LEAVE_PATCH // remove this hack when this patch is integrated: // https://codereview.qt-project.org/#/c/255384/ event->setAccepted(false); #endif break; } case QEvent::TouchBegin: { if (startTouch(retval)) { QTouchEvent *touchEvent = static_cast (event); KisAbstractInputAction::setInputManager(this); if (!KisConfig(true).disableTouchOnCanvas() && touchEvent->touchPoints().count() == 1) { d->previousPos = touchEvent->touchPoints().at(0).pos(); d->buttonPressed = false; } else { retval = d->matcher.touchBeginEvent(touchEvent); } event->accept(); } // if the event isn't handled, Qt starts to send MouseEvents if (!KisConfig(true).disableTouchOnCanvas()) retval = true; break; } case QEvent::TouchUpdate: { QTouchEvent *touchEvent = static_cast(event); #ifdef Q_OS_MAC int count = 0; Q_FOREACH (const QTouchEvent::TouchPoint &point, touchEvent->touchPoints()) { if (point.state() != Qt::TouchPointReleased) { count++; } } if (count < 2 && touchEvent->touchPoints().length() > count) { d->touchHasBlockedPressEvents = false; retval = d->matcher.touchEndEvent(touchEvent); } else { #endif QPointF currentPos = touchEvent->touchPoints().at(0).pos(); if (d->touchStrokeStarted || (!KisConfig(true).disableTouchOnCanvas() && !d->touchHasBlockedPressEvents && touchEvent->touchPoints().count() == 1 && touchEvent->touchPointStates() != Qt::TouchPointStationary && (qAbs(currentPos.x() - d->previousPos.x()) > 1 // stop wobbiliness which Qt sends us || qAbs(currentPos.y() - d->previousPos.y()) > 1))) { d->previousPos = currentPos; if (!d->buttonPressed) { // we start it here not in TouchBegin, because Qt::TouchPointStationary doesn't work with hpdi devices. retval = d->matcher.buttonPressed(Qt::LeftButton, touchEvent); d->buttonPressed = true; break; } // if it is a full-fledged stroke, then ignore (currentPos.x - previousPos.x) d->touchStrokeStarted = true; retval = compressMoveEventCommon(touchEvent); d->blockMouseEvents(); d->resetCompressor(); } else if (!d->touchStrokeStarted){ KisAbstractInputAction::setInputManager(this); retval = d->matcher.touchUpdateEvent(touchEvent); d->touchHasBlockedPressEvents = retval; } #ifdef Q_OS_MACOS } #endif // if the event isn't handled, Qt starts to send MouseEvents if (!KisConfig(true).disableTouchOnCanvas()) retval = true; event->accept(); break; } case QEvent::TouchEnd: { endTouch(); QTouchEvent *touchEvent = static_cast(event); retval = d->matcher.touchEndEvent(touchEvent); if (d->touchStrokeStarted) { retval = d->matcher.buttonReleased(Qt::LeftButton, touchEvent); d->previousPos = {0, 0}; d->touchStrokeStarted = false; // stroke ended } // if the event isn't handled, Qt starts to send MouseEvents if (!KisConfig(true).disableTouchOnCanvas()) retval = true; event->accept(); break; } case QEvent::NativeGesture: { QNativeGestureEvent *gevent = static_cast(event); switch (gevent->gestureType()) { case Qt::BeginNativeGesture: { if (startTouch(retval)) { KisAbstractInputAction::setInputManager(this); retval = d->matcher.nativeGestureBeginEvent(gevent); event->accept(); } break; } case Qt::EndNativeGesture: { endTouch(); retval = d->matcher.nativeGestureEndEvent(gevent); event->accept(); break; } default: { KisAbstractInputAction::setInputManager(this); retval = d->matcher.nativeGestureEvent(gevent); event->accept(); break; } } break; } default: break; } return !retval ? d->processUnhandledEvent(event) : true; } bool KisInputManager::startTouch(bool &retval) { // Touch rejection: if touch is disabled on canvas, no need to block mouse press events if (KisConfig(true).disableTouchOnCanvas()) { d->eatOneMousePress(); } if (d->tryHidePopupPalette()) { retval = true; return false; } else { return true; } } void KisInputManager::endTouch() { d->touchHasBlockedPressEvents = false; } void KisInputManager::slotCompressedMoveEvent() { if (d->compressedMoveEvent) { // d->touchHasBlockedPressEvents = false; (void) d->handleCompressedTabletEvent(d->compressedMoveEvent.data()); d->compressedMoveEvent.reset(); //dbgInput << "Compressed move event received."; } else { //dbgInput << "Unexpected empty move event"; } } KisCanvas2* KisInputManager::canvas() const { return d->canvas; } QPointer KisInputManager::toolProxy() const { return d->toolProxy; } void KisInputManager::slotAboutToChangeTool() { QPointF currentLocalPos; if (canvas() && canvas()->canvasWidget()) { currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); } d->matcher.lostFocusEvent(currentLocalPos); } void KisInputManager::slotToolChanged() { if (!d->canvas) return; KoToolManager *toolManager = KoToolManager::instance(); KoToolBase *tool = toolManager->toolById(canvas(), toolManager->activeToolId()); if (tool) { d->setMaskSyntheticEvents(tool->maskSyntheticEvents()); if (tool->isInTextMode()) { d->forwardAllEventsToTool = true; d->matcher.suppressAllActions(true); } else { d->forwardAllEventsToTool = false; d->matcher.suppressAllActions(false); } } } void KisInputManager::profileChanged() { d->matcher.clearShortcuts(); KisInputProfile *profile = KisInputProfileManager::instance()->currentProfile(); if (profile) { const QList shortcuts = profile->allShortcuts(); for (KisShortcutConfiguration * const shortcut : shortcuts) { dbgUI << "Adding shortcut" << shortcut->keys() << "for action" << shortcut->action()->name(); switch(shortcut->type()) { case KisShortcutConfiguration::KeyCombinationType: d->addKeyShortcut(shortcut->action(), shortcut->mode(), shortcut->keys()); break; case KisShortcutConfiguration::MouseButtonType: d->addStrokeShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->buttons()); break; case KisShortcutConfiguration::MouseWheelType: d->addWheelShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->wheel()); break; case KisShortcutConfiguration::GestureType: if (!d->addNativeGestureShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture())) { d->addTouchShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture()); } break; default: break; } } } else { dbgInput << "No Input Profile Found: canvas interaction will be impossible"; } } diff --git a/libs/ui/kis_paintop_box.cc b/libs/ui/kis_paintop_box.cc index 60647354ae..858b72ad96 100644 --- a/libs/ui/kis_paintop_box.cc +++ b/libs/ui/kis_paintop_box.cc @@ -1,1382 +1,1386 @@ /* * 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 "kis_popup_button.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 "widgets/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" 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->blendmodeActions()) { 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())); //Needed to connect canvas to favorite resource manager connect(m_viewManager->canvasResourceProvider(), SIGNAL(sigFGColorChanged(KoColor)), SLOT(slotUnsetEraseMode())); 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(m_favoriteResourceManager, SIGNAL(sigEnableChangeColor(bool)), m_resourceProvider, SLOT(slotResetEnableFGChange(bool))); connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateSelectionIcon())); connect(m_resourceProvider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(slotCanvasResourceChanged(int,QVariant))); slotInputDeviceChanged(KoToolManager::instance()->currentInputDevice()); KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); m_eraserName = "eraser_circle"; m_defaultPresetName = "basic_tip_default"; bool foundEraser = false; bool foundTip = false; for (int i = 0; i < rserver->resourceCount(); i++) { KisPaintOpPresetSP resource = rserver->resources()[i]; if (resource->name().toLower().contains("eraser_circle")) { m_eraserName = resource->name(); foundEraser = true; } else if (foundEraser == false && (resource->name().toLower().contains("eraser") || resource->filename().toLower().contains("eraser"))) { m_eraserName = resource->name(); foundEraser = true; } if (resource->name().toLower().contains("basic_tip_default")) { m_defaultPresetName = resource->name(); foundTip = true; } else if (foundTip == false && (resource->name().toLower().contains("default") || resource->filename().toLower().contains("default"))) { m_defaultPresetName = resource->name(); foundTip = true; } } } 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()) { preset = KisPaintOpRegistry::instance()->defaultPreset(paintOp); } 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::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); } /** * 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->favoritePresetList(); for (int i=0; i < presets.size(); ++i) { if (m_resourceProvider->currentPreset() && m_resourceProvider->currentPreset()->name() == presets[i]->name()) { 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->favoritePresetList(); for(int i = 0; i < presets.size(); ++i) { if (m_resourceProvider->currentPreset()->name() == presets[i]->name()) { 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(); } 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(); } diff --git a/libs/ui/widgets/kis_iconwidget.cc b/libs/ui/widgets/kis_iconwidget.cc index a1cf560179..ffbb46a639 100644 --- a/libs/ui/widgets/kis_iconwidget.cc +++ b/libs/ui/widgets/kis_iconwidget.cc @@ -1,110 +1,109 @@ /* * Copyright (c) 2000 Matthias Elter * Copyright (c) 2003 Patrick Julien * * 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.g * * 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 "widgets/kis_iconwidget.h" #include #include #include #include KisIconWidget::KisIconWidget(QWidget *parent, const QString &name) : KisPopupButton(parent) { setObjectName(name); m_resource = 0; } void KisIconWidget::KisIconWidget::setThumbnail(const QImage &thumbnail) { m_thumbnail = thumbnail; } void KisIconWidget::setResource(KoResourceSP resource) { m_resource = resource; update(); } void KisIconWidget::paintEvent(QPaintEvent *event) { QPushButton::paintEvent(event); QPainter p; p.begin(this); const qint32 cw = width(); const qint32 ch = height(); const qint32 border = 3; const qint32 iconWidth = cw - (border*2); const qint32 iconHeight = ch - (border*2); // Round off the corners of the preview QRegion clipRegion(border, border, iconWidth, iconHeight); clipRegion -= QRegion(border, border, 1, 1); clipRegion -= QRegion(cw-border-1, border, 1, 1); clipRegion -= QRegion(cw-border-1, ch-border-1, 1, 1); clipRegion -= QRegion(border, ch-border-1, 1, 1); p.setClipRegion(clipRegion); p.setClipping(true); p.setBrush(this->palette().background()); p.drawRect(QRect(0,0,cw,ch)); if (!m_thumbnail.isNull()) { QImage img = QImage(iconWidth, iconHeight, QImage::Format_ARGB32); img.fill(Qt::transparent); if (m_thumbnail.width() < iconWidth || m_thumbnail.height() < iconHeight) { QPainter paint2; paint2.begin(&img); for (int x = 0; x < iconWidth; x += m_thumbnail.width()) { for (int y = 0; y < iconHeight; y+= m_thumbnail.height()) { paint2.drawImage(x, y, m_thumbnail); } } } else { img = m_thumbnail.scaled(iconWidth, iconHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); } p.drawImage(QRect(border, border, iconWidth, iconHeight), img); } else if (m_resource) { QImage img = QImage(iconWidth, iconHeight, QImage::Format_ARGB32); img.fill(Qt::transparent); if (m_resource->image().width()image().height()image().width()) { - for (int y=0; y< iconHeight; y+=m_resource->image().height()) { + for (int x = 0; x < iconWidth; x += m_resource->image().width()) { + for (int y = 0; y < iconHeight; y += m_resource->image().height()) { paint2.drawImage(x, y, m_resource->image()); } } } else { img = m_resource->image().scaled(iconWidth, iconHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); } p.drawImage(QRect(border, border, iconWidth, iconHeight), img); - } - else if (!icon().isNull()) { - int border2 = qRound((cw-16)*0.5); + } else if (!icon().isNull()) { + int border2 = qRound((cw - 16) * 0.5); p.drawImage(QRect(border2, border2, 16, 16), icon().pixmap(16, 16).toImage()); } p.setClipping(false); }