diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c0e862f..2df7b1ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,285 +1,287 @@ enable_testing() project(libkdegames) cmake_minimum_required (VERSION 3.5 FATAL_ERROR) set (QT_MIN_VERSION "5.9.0") set (KF5_MIN_VERSION "5.46.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Widgets Qml Quick QuickWidgets Svg Test) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS CoreAddons Config WidgetsAddons Codecs Archive DBusAddons DNSSD Declarative I18n GuiAddons Service ConfigWidgets ItemViews IconThemes Completion JobWidgets TextWidgets GlobalAccel XmlGui Crash Bookmarks NewStuff Completion) include(FeatureSummary) include(GenerateExportHeader) include(CMakePackageConfigHelpers) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMMarkNonGuiExecutable) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) add_definitions(${QT_DEFINITIONS}) add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) add_definitions(-DMAKE_KDEGAMESPRIVATE_LIB) add_definitions(-DTRANSLATION_DOMAIN="libkdegames5") include_directories(${CMAKE_SOURCE_DIR}/includes) find_package(OpenAL REQUIRED) set_package_properties(OPENAL PROPERTIES URL "http://connect.creativelabs.com/openal" ) find_package(SndFile REQUIRED) set_package_properties(SndFile PROPERTIES URL "http://www.mega-nerd.com/libsndfile/" ) set(HIGHSCORE_DIRECTORY "" CACHE STRING "Where to install system-wide highscores e.g. /var/games") configure_file(highscore/config-highscore.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/highscore/config-highscore.h ) add_subdirectory( carddecks ) add_subdirectory( declarativeimports ) add_subdirectory( highscore ) add_subdirectory( includes ) add_subdirectory( libkdegamesprivate ) add_subdirectory( tests ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/highscore ${CMAKE_CURRENT_BINARY_DIR}/highscore # the following only for libkdegamesprivate, but there aren't # target-specific include directories ${CMAKE_CURRENT_SOURCE_DIR}/libkdegamesprivate/kgame ${CMAKE_CURRENT_SOURCE_DIR}/libkdegamesprivate/.. ) option (USE_OPENAL_SNDFILE "use OpenAL and libsndfile in libkdegames" ON) if (SNDFILE_FOUND AND USE_OPENAL_SNDFILE) message(STATUS "Checking libsndfile capabilities") try_compile(SNDFILE_WORKS ${CMAKE_CURRENT_BINARY_DIR}/audio/check-libsndfile-capabilities ${CMAKE_CURRENT_SOURCE_DIR}/audio/check-libsndfile-capabilities.cpp CMAKE_FLAGS -DINCLUDE_DIRECTORIES=${SNDFILE_INCLUDE_DIR}) if (NOT SNDFILE_WORKS) message(FATAL_ERROR "Your version of libsndfile (found in " ${SNDFILE_LIBRARIES} ") is too old. At least version 0.21 is needed. To skip the optional OpenAL/libsndfile dependency in libkdegames (not recommended), re-run cmake with -DUSE_OPENAL_SNDFILE=OFF.") endif (NOT SNDFILE_WORKS) endif (SNDFILE_FOUND AND USE_OPENAL_SNDFILE) message (STATUS "INCLUDES FOR SOUND: " ${OPENAL_INCLUDE_DIR} " " ${SNDFILE_INCLUDE_DIR}) message (STATUS "LIBRARIES FOR SOUND: " ${OPENAL_LIBRARY} " " ${SNDFILE_LIBRARIES}) include_directories(${OPENAL_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIR}) set(KGAUDIO_LINKLIBS ${OPENAL_LIBRARY} ${SNDFILE_LIBRARIES}) set(KGAUDIO_BACKEND openal) set(KGAUDIO_BACKEND_OPENAL TRUE) # for configure_file() below configure_file(libkdegames_capabilities.h.in ${CMAKE_CURRENT_BINARY_DIR}/libkdegames_capabilities.h) +if (EXISTS "${CMAKE_SOURCE_DIR}/.git") + add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) +endif() ########### next target ############### -#add_definitions( -DQT_DISABLE_DEPRECATED_BEFORE=0x060000 ) #if (${KF5Config_VERSION} STRGREATER "5.56.0") # add_definitions(-DQT_NO_FOREACH) # MESSAGE(STATUS "compile without foreach") #endif() set(kdegames_LIB_SRCS audio/kgaudioscene-${KGAUDIO_BACKEND}.cpp audio/kgsound-${KGAUDIO_BACKEND}.cpp audio/virtualfileqt-${KGAUDIO_BACKEND}.cpp colorproxy_p.cpp # highscore/kconfigrawbackend.cpp highscore/khighscore.cpp highscore/kscoredialog.cpp kgameclock.cpp kgamepopupitem.cpp kgamerendereditem.cpp kgamerenderedobjectitem.cpp kgamerendererclient.cpp kgamerenderer.cpp kgdeclarativeview.cpp kgimageprovider.cpp kgdifficulty.cpp kgtheme.cpp kgthemeprovider.cpp kgthemeselector.cpp kstandardgameaction.cpp ) add_library(KF5KDEGames SHARED ${kdegames_LIB_SRCS}) generate_export_header(KF5KDEGames BASE_NAME libkdegames EXPORT_MACRO_NAME KDEGAMES_EXPORT DEPRECATED_MACRO_NAME KDE_DEPRECATED) target_link_libraries(KF5KDEGames PRIVATE ${KGAUDIO_LINKLIBS} Qt5::Xml Qt5::Svg Qt5::Quick KF5::Declarative KF5::NewStuff KF5::IconThemes KF5::GuiAddons KF5::Completion PUBLIC Qt5::Widgets Qt5::QuickWidgets Qt5::Qml KF5::ConfigCore KF5::I18n KF5::WidgetsAddons KF5::ConfigWidgets ) target_include_directories(KF5KDEGames INTERFACE "$") set(KDEGAMES_VERSION 7.2.0) set(KDEGAMES_SOVERSION 7) set_target_properties(KF5KDEGames PROPERTIES VERSION ${KDEGAMES_VERSION} SOVERSION ${KDEGAMES_SOVERSION} EXPORT_NAME KF5KDEGames ) ecm_setup_version(${KDEGAMES_VERSION} VARIABLE_PREFIX KDEGAMES VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kdegames_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5KDEGamesConfigVersion.cmake" SOVERSION ${KDEGAMES_SOVERSION}) set_target_properties(KF5KDEGames PROPERTIES VERSION ${KDEGAMES_VERSION} SOVERSION ${KDEGAMES_SOVERSION} ) install(TARGETS KF5KDEGames EXPORT KF5KDEGamesLibraryDepends ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) ########### next target ############### # NOTE: The libkdegamesprivate target is compiled in this directory, because # CMake can't cope with exported libraries in two different directories. set(kdegamesprivate_LIB_SRCS libkdegamesprivate/kchatbase.cpp libkdegamesprivate/kchatbaseitemdelegate.cpp libkdegamesprivate/kchatbasemodel.cpp libkdegamesprivate/kgame/kgamechat.cpp libkdegamesprivate/kgame/kgame.cpp libkdegamesprivate/kgame/kgameerror.cpp libkdegamesprivate/kgame/kgameio.cpp libkdegamesprivate/kgame/kgamemessage.cpp libkdegamesprivate/kgame/kgamenetwork.cpp libkdegamesprivate/kgame/kgameproperty.cpp libkdegamesprivate/kgame/kgamepropertyhandler.cpp libkdegamesprivate/kgame/kgamesequence.cpp libkdegamesprivate/kgame/kmessageclient.cpp libkdegamesprivate/kgame/kmessageio.cpp libkdegamesprivate/kgame/kmessageserver.cpp libkdegamesprivate/kgame/kplayer.cpp libkdegamesprivate/kgamecanvas.cpp libkdegamesprivate/kgamedifficulty.cpp libkdegamesprivate/kgamesvgdocument.cpp libkdegamesprivate/kgametheme.cpp libkdegamesprivate/kgamethemeselector.cpp ) ki18n_wrap_ui(kdegamesprivate_LIB_SRCS libkdegamesprivate/kgamethemeselector.ui ) add_library(KF5KDEGamesPrivate SHARED ${kdegamesprivate_LIB_SRCS}) generate_export_header(KF5KDEGamesPrivate BASE_NAME libkdegamesprivate EXPORT_MACRO_NAME KDEGAMESPRIVATE_EXPORT DEPRECATED_MACRO_NAME KDE_DEPRECATED) target_link_libraries(KF5KDEGamesPrivate PRIVATE KF5::DNSSD KF5::NewStuff KF5::Archive PUBLIC Qt5::Xml Qt5::Network KF5::Completion KF5KDEGames ) target_include_directories(KF5KDEGamesPrivate INTERFACE "$" ) set_target_properties(KF5KDEGamesPrivate PROPERTIES VERSION 1.0.0 SOVERSION 1 ) install(TARGETS KF5KDEGamesPrivate EXPORT KF5KDEGamesLibraryDepends ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install(FILES kgthemeprovider-migration.upd DESTINATION ${DATA_INSTALL_DIR}/kconf_update) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libkdegames_export.h ${CMAKE_CURRENT_BINARY_DIR}/libkdegames_capabilities.h audio/kgaudioscene.h audio/kgsound.h kgameclock.h kgamepopupitem.h kgamerendereditem.h kgamerenderedobjectitem.h kgamerendererclient.h kgamerenderer.h kgdeclarativeview.h kgdifficulty.h kgtheme.h kgthemeprovider.h kgthemeselector.h kstandardgameaction.h DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KF5KDEGames COMPONENT Devel) ########### generate exports ############### # add libraries to the build-tree export set export(TARGETS KF5KDEGames KF5KDEGamesPrivate FILE "${PROJECT_BINARY_DIR}/KF5KDEGamesLibraryDepends.cmake") # define the installation directory for the CMake files set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KF5KDEGames") # create the Config.cmake and ConfigVersion.cmake files configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KDEGamesConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5KDEGamesConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5KDEGamesConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5KDEGamesConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) # install the export set for use with the install-tree install(EXPORT KF5KDEGamesLibraryDepends DESTINATION ${CMAKECONFIG_INSTALL_DIR} COMPONENT Devel) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kdegames_version.h DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KF5KDEGames COMPONENT Devel) if (${ECM_VERSION} STRGREATER "5.58.0") install(FILES libkdegames.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) else() install(FILES libkdegames.categories DESTINATION ${KDE_INSTALL_CONFDIR}) endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/kgamerenderer.cpp b/kgamerenderer.cpp index 64c5ef6f..6107ca28 100644 --- a/kgamerenderer.cpp +++ b/kgamerenderer.cpp @@ -1,659 +1,659 @@ /*************************************************************************** * Copyright 2010-2012 Stefan Majewsky * * * * This program 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 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library 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 "kgamerenderer.h" #include "kgamerenderer_p.h" #include "kgamerendererclient.h" #include "colorproxy_p.h" #include "kgtheme.h" #include "kgthemeprovider.h" #include #include #include #include #include #include //TODO: automatically schedule pre-rendering of animation frames //TODO: multithreaded SVG loading? static const QString cacheName(QByteArray theme) { const QString appName = QCoreApplication::instance()->applicationName(); //e.g. "themes/foobar.desktop" -> "themes/foobar" if (theme.endsWith(QByteArray(".desktop"))) theme.chop(8); //8 = strlen(".desktop") return QStringLiteral("kgamerenderer-%1-%2") .arg(appName).arg(QString::fromUtf8(theme)); } KGameRendererPrivate::KGameRendererPrivate(KgThemeProvider* provider, unsigned cacheSize, KGameRenderer* parent) : m_parent(parent) , m_provider(provider) , m_currentTheme(0) //will be loaded on first use , m_frameSuffix(QStringLiteral("_%1")) , m_sizePrefix(QStringLiteral("%1-%2-")) , m_frameCountPrefix(QStringLiteral("fc-")) , m_boundsPrefix(QStringLiteral("br-")) //default cache size: 3 MiB = 3 << 20 bytes , m_cacheSize((cacheSize == 0 ? 3 : cacheSize) << 20) , m_strategies(KGameRenderer::UseDiskCache | KGameRenderer::UseRenderingThreads) , m_frameBaseIndex(0) , m_defaultPrimaryView(0) , m_rendererPool(&m_workerPool) , m_imageCache(0) { qRegisterMetaType(); } KGameRenderer::KGameRenderer(KgThemeProvider* provider, unsigned cacheSize) : d(new KGameRendererPrivate(provider, cacheSize, this)) { if (!provider->parent()) { provider->setParent(this); } connect(provider, SIGNAL(currentThemeChanged(const KgTheme*)), SLOT(_k_setTheme(const KgTheme*))); } static KgThemeProvider* providerForSingleTheme(KgTheme* theme, QObject* parent) { KgThemeProvider* prov = new KgThemeProvider(QByteArray(), parent); prov->addTheme(theme); return prov; } KGameRenderer::KGameRenderer(KgTheme* theme, unsigned cacheSize) : d(new KGameRendererPrivate(providerForSingleTheme(theme, this), cacheSize, this)) { } KGameRenderer::~KGameRenderer() { //cleanup clients while (!d->m_clients.isEmpty()) { delete d->m_clients.constBegin().key(); } //cleanup own stuff d->m_workerPool.waitForDone(); delete d->m_imageCache; delete d; } QGraphicsView* KGameRenderer::defaultPrimaryView() const { return d->m_defaultPrimaryView; } void KGameRenderer::setDefaultPrimaryView(QGraphicsView* view) { d->m_defaultPrimaryView = view; } int KGameRenderer::frameBaseIndex() const { return d->m_frameBaseIndex; } void KGameRenderer::setFrameBaseIndex(int frameBaseIndex) { d->m_frameBaseIndex = frameBaseIndex; } QString KGameRenderer::frameSuffix() const { return d->m_frameSuffix; } void KGameRenderer::setFrameSuffix(const QString& suffix) { d->m_frameSuffix = suffix.contains(QLatin1String("%1")) ? suffix : QStringLiteral("_%1"); } KGameRenderer::Strategies KGameRenderer::strategies() const { return d->m_strategies; } void KGameRenderer::setStrategyEnabled(KGameRenderer::Strategy strategy, bool enabled) { const bool oldEnabled = d->m_strategies & strategy; if (enabled) { d->m_strategies |= strategy; } else { d->m_strategies &= ~strategy; } if (strategy == KGameRenderer::UseDiskCache && oldEnabled != enabled) { //reload theme const KgTheme* theme = d->m_currentTheme; if (theme) { d->m_currentTheme = 0; //or setTheme() will return immediately d->_k_setTheme(theme); } } } void KGameRendererPrivate::_k_setTheme(const KgTheme* theme) { const KgTheme* oldTheme = m_currentTheme; if (oldTheme == theme) { return; } qCDebug(GAMES_LIB) << "Setting theme:" << theme->name(); if (!setTheme(theme)) { const KgTheme* defaultTheme = m_provider->defaultTheme(); if (theme != defaultTheme) { qCDebug(GAMES_LIB) << "Falling back to default theme:" << defaultTheme->name(); setTheme(defaultTheme); m_provider->setCurrentTheme(defaultTheme); } } //announce change to KGameRendererClients QHash::iterator it1 = m_clients.begin(), it2 = m_clients.end(); for (; it1 != it2; ++it1) { it1.value().clear(); //because the pixmap is outdated it1.key()->d->fetchPixmap(); } emit m_parent->themeChanged(m_currentTheme); } bool KGameRendererPrivate::setTheme(const KgTheme* theme) { if (!theme) { return false; } //open cache (and SVG file, if necessary) if (m_strategies & KGameRenderer::UseDiskCache) { QScopedPointer oldCache(m_imageCache); const QString imageCacheName = cacheName(theme->identifier()); m_imageCache = new KImageCache(imageCacheName, m_cacheSize); m_imageCache->setPixmapCaching(false); //see big comment in KGRPrivate class declaration //check timestamp of cache vs. last write access to theme/SVG const uint svgTimestamp = qMax( - QFileInfo(theme->graphicsPath()).lastModified().toTime_t(), - theme->property("_k_themeDescTimestamp").value() + QFileInfo(theme->graphicsPath()).lastModified().toSecsSinceEpoch(), + theme->property("_k_themeDescTimestamp").value() ); QByteArray buffer; if (!m_imageCache->find(QStringLiteral("kgr_timestamp"), &buffer)) buffer = "0"; const uint cacheTimestamp = buffer.toInt(); //try to instantiate renderer immediately if the cache does not exist or is outdated //FIXME: This logic breaks if the cache evicts the "kgr_timestamp" key. We need additional API in KSharedDataCache to make sure that this key does not get evicted. if (cacheTimestamp < svgTimestamp) { qCDebug(GAMES_LIB) << "Theme newer than cache, checking SVG"; QScopedPointer renderer(new QSvgRenderer(theme->graphicsPath())); if (renderer->isValid()) { m_rendererPool.setPath(theme->graphicsPath(), renderer.take()); m_imageCache->clear(); m_imageCache->insert(QStringLiteral("kgr_timestamp"), QByteArray::number(svgTimestamp)); } else { //The SVG file is broken, so we deny to change the theme without //breaking the previous theme. delete m_imageCache; KSharedDataCache::deleteCache(imageCacheName); m_imageCache = oldCache.take(); qCDebug(GAMES_LIB) << "Theme change failed: SVG file broken"; return false; } } //theme is cached - just delete the old renderer after making sure that no worker threads are using it anymore else if (m_currentTheme != theme) m_rendererPool.setPath(theme->graphicsPath()); } else // !(m_strategies & KGameRenderer::UseDiskCache) -> no cache is used { //load SVG file QScopedPointer renderer(new QSvgRenderer(theme->graphicsPath())); if (renderer->isValid()) { m_rendererPool.setPath(theme->graphicsPath(), renderer.take()); } else { qCDebug(GAMES_LIB) << "Theme change failed: SVG file broken"; return false; } //disconnect from disk cache (only needed if changing strategy) delete m_imageCache; m_imageCache = 0; } //clear in-process caches m_pixmapCache.clear(); m_frameCountCache.clear(); m_boundsCache.clear(); //done m_currentTheme = theme; return true; } const KgTheme* KGameRenderer::theme() const { //ensure that some theme is loaded if (!d->m_currentTheme) { d->_k_setTheme(d->m_provider->currentTheme()); } return d->m_currentTheme; } KgThemeProvider* KGameRenderer::themeProvider() const { return d->m_provider; } QString KGameRendererPrivate::spriteFrameKey(const QString& key, int frame, bool normalizeFrameNo) const { //fast path for non-animated sprites if (frame < 0) { return key; } //normalize frame number if (normalizeFrameNo) { const int frameCount = m_parent->frameCount(key); if (frameCount <= 0) { //non-animated sprite return key; } else { frame = (frame - m_frameBaseIndex) % frameCount + m_frameBaseIndex; } } return key + m_frameSuffix.arg(frame); } int KGameRenderer::frameCount(const QString& key) const { //ensure that some theme is loaded if (!d->m_currentTheme) { d->_k_setTheme(d->m_provider->currentTheme()); } //look up in in-process cache QHash::const_iterator it = d->m_frameCountCache.constFind(key); if (it != d->m_frameCountCache.constEnd()) { return it.value(); } //look up in shared cache (if SVG is not yet loaded) int count = -1; bool countFound = false; const QString cacheKey = d->m_frameCountPrefix + key; if (d->m_rendererPool.hasAvailableRenderers() && (d->m_strategies & KGameRenderer::UseDiskCache)) { QByteArray buffer; if (d->m_imageCache->find(cacheKey, &buffer)) { count = buffer.toInt(); countFound = true; } } //determine from SVG if (!countFound) { QSvgRenderer* renderer = d->m_rendererPool.allocRenderer(); //look for animated sprite first count = d->m_frameBaseIndex; while (renderer->elementExists(d->spriteFrameKey(key, count, false))) { ++count; } count -= d->m_frameBaseIndex; //look for non-animated sprite instead if (count == 0) { if (!renderer->elementExists(key)) { count = -1; } } d->m_rendererPool.freeRenderer(renderer); //save in shared cache for following requests if (d->m_strategies & KGameRenderer::UseDiskCache) { d->m_imageCache->insert(cacheKey, QByteArray::number(count)); } } d->m_frameCountCache.insert(key, count); return count; } QRectF KGameRenderer::boundsOnSprite(const QString& key, int frame) const { const QString elementKey = d->spriteFrameKey(key, frame); //ensure that some theme is loaded if (!d->m_currentTheme) { d->_k_setTheme(d->m_provider->currentTheme()); } //look up in in-process cache QHash::const_iterator it = d->m_boundsCache.constFind(elementKey); if (it != d->m_boundsCache.constEnd()) { return it.value(); } //look up in shared cache (if SVG is not yet loaded) QRectF bounds; bool boundsFound = false; const QString cacheKey = d->m_boundsPrefix + elementKey; if (!d->m_rendererPool.hasAvailableRenderers() && (d->m_strategies & KGameRenderer::UseDiskCache)) { QByteArray buffer; if (d->m_imageCache->find(cacheKey, &buffer)) { QDataStream stream(buffer); stream >> bounds; boundsFound = true; } } //determine from SVG if (!boundsFound) { QSvgRenderer* renderer = d->m_rendererPool.allocRenderer(); bounds = renderer->boundsOnElement(elementKey); d->m_rendererPool.freeRenderer(renderer); //save in shared cache for following requests if (d->m_strategies & KGameRenderer::UseDiskCache) { QByteArray buffer; { QDataStream stream(&buffer, QIODevice::WriteOnly); stream << bounds; } d->m_imageCache->insert(cacheKey, buffer); } } d->m_boundsCache.insert(elementKey, bounds); return bounds; } bool KGameRenderer::spriteExists(const QString& key) const { return this->frameCount(key) >= 0; } QPixmap KGameRenderer::spritePixmap(const QString& key, const QSize& size, int frame, const QHash& customColors) const { QPixmap result; d->requestPixmap(KGRInternal::ClientSpec(key, frame, size, customColors), 0, &result); return result; } //Helper function for KGameRendererPrivate::requestPixmap. void KGameRendererPrivate::requestPixmap__propagateResult(const QPixmap& pixmap, KGameRendererClient* client, QPixmap* synchronousResult) { if (client) { client->receivePixmap(pixmap); } if (synchronousResult) { *synchronousResult = pixmap; } } void KGameRendererPrivate::requestPixmap(const KGRInternal::ClientSpec& spec, KGameRendererClient* client, QPixmap* synchronousResult) { //NOTE: If client == 0, the request is synchronous and must be finished when this method returns. This behavior is used by KGR::spritePixmap(). Instead of KGameRendererClient::receivePixmap, the QPixmap* argument is then used to return the result. //parse request if (spec.size.isEmpty()) { requestPixmap__propagateResult(QPixmap(), client, synchronousResult); return; } const QString elementKey = spriteFrameKey(spec.spriteKey, spec.frame); QString cacheKey = m_sizePrefix.arg(spec.size.width()).arg(spec.size.height()) + elementKey; QHash::const_iterator it1 = spec.customColors.constBegin(), it2 = spec.customColors.constEnd(); static const QString colorSuffix(QStringLiteral( "-%1-%2" )); for (; it1 != it2; ++it1) { cacheKey += colorSuffix.arg(it1.key().rgba()).arg(it1.value().rgba()); } //check if update is needed if (client) { if (m_clients.value(client) == cacheKey) { return; } m_clients[client] = cacheKey; } //ensure that some theme is loaded if (!m_currentTheme) { _k_setTheme(m_provider->currentTheme()); } //try to serve from high-speed cache QHash::const_iterator it = m_pixmapCache.constFind(cacheKey); if (it != m_pixmapCache.constEnd()) { requestPixmap__propagateResult(it.value(), client, synchronousResult); return; } //try to serve from low-speed cache if (m_strategies & KGameRenderer::UseDiskCache) { QPixmap pix; if (m_imageCache->findPixmap(cacheKey, &pix)) { m_pixmapCache.insert(cacheKey, pix); requestPixmap__propagateResult(pix, client, synchronousResult); return; } } //if asynchronous request, is such a rendering job already running? if (client && m_pendingRequests.contains(cacheKey)) { return; } //create job KGRInternal::Job* job = new KGRInternal::Job; job->rendererPool = &m_rendererPool; job->cacheKey = cacheKey; job->elementKey = elementKey; job->spec = spec; const bool synchronous = !client; if (synchronous || !(m_strategies & KGameRenderer::UseRenderingThreads)) { KGRInternal::Worker worker(job, true, this); worker.run(); //if everything worked fine, result is in high-speed cache now const QPixmap result = m_pixmapCache.value(cacheKey); requestPixmap__propagateResult(result, client, synchronousResult); } else { m_workerPool.start(new KGRInternal::Worker(job, !client, this)); m_pendingRequests << cacheKey; } } void KGameRendererPrivate::jobFinished(KGRInternal::Job* job, bool isSynchronous) { //read job const QString cacheKey = job->cacheKey; const QImage result = job->result; delete job; //check who wanted this pixmap m_pendingRequests.removeAll(cacheKey); const QList requesters = m_clients.keys(cacheKey); //put result into image cache if (m_strategies & KGameRenderer::UseDiskCache) { m_imageCache->insertImage(cacheKey, result); //convert result to pixmap (and put into pixmap cache) only if it is needed now //This optimization saves the image-pixmap conversion for intermediate sizes which occur during smooth resize events or window initializations. if (!isSynchronous && requesters.isEmpty()) { return; } } const QPixmap pixmap = QPixmap::fromImage(result); m_pixmapCache.insert(cacheKey, pixmap); foreach (KGameRendererClient* requester, requesters) { requester->receivePixmap(pixmap); } } //BEGIN KGRInternal::Job/Worker KGRInternal::Worker::Worker(KGRInternal::Job* job, bool isSynchronous, KGameRendererPrivate* parent) : m_job(job) , m_synchronous(isSynchronous) , m_parent(parent) { } static const uint transparentRgba = QColor(Qt::transparent).rgba(); void KGRInternal::Worker::run() { QImage image(m_job->spec.size, QImage::Format_ARGB32_Premultiplied); image.fill(transparentRgba); QPainter* painter = 0; QPaintDeviceColorProxy* proxy = 0; //if no custom colors requested, paint directly onto image if (m_job->spec.customColors.isEmpty()) { painter = new QPainter(&image); } else { proxy = new QPaintDeviceColorProxy(&image, m_job->spec.customColors); painter = new QPainter(proxy); } //do renderering QSvgRenderer* renderer = m_job->rendererPool->allocRenderer(); renderer->render(painter, m_job->elementKey); m_job->rendererPool->freeRenderer(renderer); delete painter; delete proxy; //talk back to the main thread m_job->result = image; QMetaObject::invokeMethod( m_parent, "jobFinished", Qt::AutoConnection, Q_ARG(KGRInternal::Job*, m_job), Q_ARG(bool, m_synchronous) ); //NOTE: KGR::spritePixmap relies on Qt::DirectConnection when this method is run in the main thread. } //END KGRInternal::Job/Worker //BEGIN KGRInternal::RendererPool KGRInternal::RendererPool::RendererPool(QThreadPool* threadPool) : m_valid(Checked_Invalid) //don't try to allocate renderers until given a valid SVG file , m_threadPool(threadPool) { } KGRInternal::RendererPool::~RendererPool() { //This deletes all renderers. setPath(QString()); } void KGRInternal::RendererPool::setPath(const QString& graphicsPath, QSvgRenderer* renderer) { QMutexLocker locker(&m_mutex); //delete all renderers m_threadPool->waitForDone(); QHash::const_iterator it1 = m_hash.constBegin(), it2 = m_hash.constEnd(); for (; it1 != it2; ++it1) { Q_ASSERT(it1.value() == 0); //nobody may be using our renderers anymore now delete it1.key(); } m_hash.clear(); //set path m_path = graphicsPath; //existence of a renderer instance is evidence for the validity of the SVG file if (renderer) { m_valid = Checked_Valid; m_hash.insert(renderer, 0); } else { m_valid = Unchecked; } } bool KGRInternal::RendererPool::hasAvailableRenderers() const { //look for a renderer which is not associated with a thread QMutexLocker locker(&m_mutex); return m_hash.key(0) != 0; } QSvgRenderer* KGRInternal::RendererPool::allocRenderer() { QThread* thread = QThread::currentThread(); //look for an available renderer QMutexLocker locker(&m_mutex); QSvgRenderer* renderer = m_hash.key(0); if (!renderer) { //instantiate a new renderer (only if the SVG file has not been found to be invalid yet) if (m_valid == Checked_Invalid) { return 0; } renderer = new QSvgRenderer(m_path); m_valid = renderer->isValid() ? Checked_Valid : Checked_Invalid; } //mark renderer as used m_hash.insert(renderer, thread); return renderer; } void KGRInternal::RendererPool::freeRenderer(QSvgRenderer* renderer) { //mark renderer as available QMutexLocker locker(&m_mutex); m_hash.insert(renderer, 0); } //END KGRInternal::RendererPool #include "moc_kgamerenderer.cpp" #include "moc_kgamerenderer_p.cpp" diff --git a/kgtheme.cpp b/kgtheme.cpp index 8d126091..3f6bf1e5 100644 --- a/kgtheme.cpp +++ b/kgtheme.cpp @@ -1,154 +1,154 @@ /*************************************************************************** * Copyright 2012 Stefan Majewsky * * * * This program 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 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library 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 "kgtheme.h" #include #include #include #include #include Q_LOGGING_CATEGORY(GAMES_LIB, "org.kde.games.lib", QtWarningMsg) class KgTheme::Private { public: const QByteArray m_identifier; QString m_name, m_description, m_author, m_authorEmail, m_graphicsPath, m_previewPath; QMap m_customData; Private(const QByteArray& id) : m_identifier(id) {} static QStringList s_configGroupNames; }; /*static*/ QStringList KgTheme::Private::s_configGroupNames; KgTheme::KgTheme(const QByteArray& identifier, QObject* parent) : QObject(parent) , d(new Private(identifier)) { } KgTheme::~KgTheme() { delete d; } QByteArray KgTheme::identifier() const { return d->m_identifier; } #define KGTHEME_STRING_PROPERTY(PROP, SETTER) \ QString KgTheme::PROP() const { return d->m_##PROP; } \ void KgTheme::SETTER(const QString& val) { d->m_##PROP = val; } KGTHEME_STRING_PROPERTY(name, setName) KGTHEME_STRING_PROPERTY(description, setDescription) KGTHEME_STRING_PROPERTY(author, setAuthor) KGTHEME_STRING_PROPERTY(authorEmail, setAuthorEmail) KGTHEME_STRING_PROPERTY(graphicsPath, setGraphicsPath) KGTHEME_STRING_PROPERTY(previewPath, setPreviewPath) QMap KgTheme::customData() const { return d->m_customData; } QString KgTheme::customData(const QString& key, const QString& defaultValue) const { return d->m_customData.value(key, defaultValue); } void KgTheme::setCustomData(const QMap& customData) { d->m_customData = customData; } bool KgTheme::readFromDesktopFile(const QString& path_) { if (path_.isEmpty()) { qCDebug(GAMES_LIB) << "Refusing to load theme with no name"; return false; } //legacy support: relative paths are resolved with KStandardDirs/appdata QString path(path_); if (QFileInfo(path).isRelative()) { path = QStandardPaths::locate(QStandardPaths::AppDataLocation, path); if (path.isEmpty()) { qCDebug(GAMES_LIB) << "Could not find theme description" << path; return false; } } //default group name if (Private::s_configGroupNames.isEmpty()) { Private::s_configGroupNames << QStringLiteral("KGameTheme"); } //open file, look for a known config group KConfig config(path, KConfig::SimpleConfig); KConfigGroup group; foreach (const QString& groupName, Private::s_configGroupNames) { if (config.hasGroup(groupName)) { group = config.group(groupName); } } if (!group.isValid()) { qCDebug(GAMES_LIB) << "Could not read theme description at" << path; return false; } //check format version if (group.readEntry("VersionFormat", 1) > 1) { qCDebug(GAMES_LIB) << "Format of theme description too new at" << path; return false; } //resolve paths const QFileInfo fi(path); const QDir dir = fi.dir(); QString graphicsPath = group.readEntry("FileName", QString()); if (!graphicsPath.isEmpty() && QFileInfo(graphicsPath).isRelative()) graphicsPath = dir.absoluteFilePath(graphicsPath); QString previewPath = group.readEntry("Preview", QString()); if (!previewPath.isEmpty() && QFileInfo(previewPath).isRelative()) previewPath = dir.absoluteFilePath(previewPath); //create theme setName(group.readEntry("Name", QString())); setDescription(group.readEntry("Description", QString())); setAuthor(group.readEntry("Author", QString())); setAuthorEmail(group.readEntry("AuthorEmail", QString())); setGraphicsPath(graphicsPath); setPreviewPath(previewPath); setCustomData(group.entryMap()); //store modification date of this file in private property (KGameRenderer //wants to clear its cache also if the theme description changes) - setProperty("_k_themeDescTimestamp", fi.lastModified().toTime_t()); + setProperty("_k_themeDescTimestamp", fi.lastModified().toSecsSinceEpoch()); return true; } diff --git a/libkdegamesprivate/kgamecanvas.cpp b/libkdegamesprivate/kgamecanvas.cpp index 1a6c5ea4..184f28a2 100644 --- a/libkdegamesprivate/kgamecanvas.cpp +++ b/libkdegamesprivate/kgamecanvas.cpp @@ -1,1026 +1,1027 @@ /* Originally created for KBoard Copyright 2006 Maurizio Monge BSD License Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // KGame namespace changes: mauricio@tabuleiro.com #include "kgamecanvas.h" #include #include #include #include #include #include +#include /* TODO: - (maybe) allow an item to be destroyed while calling KGameCanvasItem::advance. - When a group is hidden/destroyed should only update items (optimize for sparse groups) */ #define DEBUG_DONT_MERGE_UPDATES 0 #define DEBUG_CANVAS_PAINTS 0 /* KGameCanvasAbstract */ KGameCanvasAbstract::KGameCanvasAbstract() { } KGameCanvasAbstract::~KGameCanvasAbstract() { //Note: this does not delete the items, be sure not to leak memory! for(int i=0;im_canvas = NULL; } KGameCanvasItem* KGameCanvasAbstract::itemAt(const QPoint &pt) const { for(int i=m_items.size()-1;i>=0;i--) { KGameCanvasItem *el = m_items[i]; if(el->m_visible && el->rect().contains(pt)) return el; } return NULL; } QList KGameCanvasAbstract::itemsAt(const QPoint &pt) const { QList retv; for(int i=m_items.size()-1;i>=0;i--) { KGameCanvasItem *el = m_items[i]; if(el->m_visible && el->rect().contains(pt)) retv.append(el); } return retv; } /* KGameCanvasWidget */ class KGameCanvasWidgetPrivate { public: QTimer m_anim_timer; - QTime m_anim_time; + QElapsedTimer m_anim_time; bool m_pending_update; QRegion m_pending_update_reg; #if DEBUG_CANVAS_PAINTS bool debug_paints; #endif //DEBUG_CANVAS_PAINTS KGameCanvasWidgetPrivate() : m_pending_update(false) #if DEBUG_CANVAS_PAINTS , debug_paints(false) #endif //DEBUG_CANVAS_PAINTS {} }; KGameCanvasWidget::KGameCanvasWidget(QWidget* parent) : QWidget(parent) , priv(new KGameCanvasWidgetPrivate()) { priv->m_anim_time.start(); connect(&priv->m_anim_timer, &QTimer::timeout, this, &KGameCanvasWidget::processAnimations); } KGameCanvasWidget::~KGameCanvasWidget() { delete priv; } void KGameCanvasWidget::ensureAnimating() { if(!priv->m_anim_timer.isActive() ) priv->m_anim_timer.start(); } void KGameCanvasWidget::ensurePendingUpdate() { if(priv->m_pending_update) return; priv->m_pending_update = true; #if DEBUG_DONT_MERGE_UPDATES updateChanges(); #else //DEBUG_DONT_MERGE_UPDATES QTimer::singleShot( 0, this, &KGameCanvasWidget::updateChanges ); #endif //DEBUG_DONT_MERGE_UPDATES } void KGameCanvasWidget::updateChanges() { for(int i=0;im_changed) el->updateChanges(); } priv->m_pending_update = false; #if DEBUG_CANVAS_PAINTS repaint(); priv->debug_paints = true; repaint( priv->m_pending_update_reg ); QApplication::syncX(); priv->debug_paints = false; usleep(100000); repaint( priv->m_pending_update_reg ); QApplication::syncX(); usleep(100000); #else //DEBUG_CANVAS_PAINTS repaint( priv->m_pending_update_reg ); #endif //DEBUG_CANVAS_PAINTS priv->m_pending_update_reg = QRegion(); } void KGameCanvasWidget::invalidate(const QRect& r, bool /*translate*/) { priv->m_pending_update_reg |= r; ensurePendingUpdate(); } void KGameCanvasWidget::invalidate(const QRegion& r, bool /*translate*/) { priv->m_pending_update_reg |= r; ensurePendingUpdate(); } void KGameCanvasWidget::paintEvent(QPaintEvent *event) { #if DEBUG_CANVAS_PAINTS if(priv->debug_paints) { QPainter p(this); p.fillRect(event->rect(), Qt::magenta); return; } #endif //DEBUG_CANVAS_PAINTS {QPainter p(this); QRect evr = event->rect(); QRegion evreg = event->region(); for(int i=0;im_visible && evr.intersects( el->rect() ) && evreg.contains( el->rect() ) ) { el->m_last_rect = el->rect(); el->paintInternal(&p, evr, evreg, QPoint(), 1.0 ); } }} } void KGameCanvasWidget::processAnimations() { if(m_animated_items.empty() ) { priv->m_anim_timer.stop(); return; } int tm = priv->m_anim_time.elapsed(); // The list MUST be copied, because it could be modified calling advance. // After all since it is implicitly shared the copy will not happen unless // is it actually modified QList ait = m_animated_items; for(int i=0;iadvance(tm); } if(m_animated_items.empty() ) priv->m_anim_timer.stop(); } void KGameCanvasWidget::setAnimationDelay(int d) { priv->m_anim_timer.setInterval(d); } int KGameCanvasWidget::mSecs() { return priv->m_anim_time.elapsed(); } KGameCanvasWidget* KGameCanvasWidget::topLevelCanvas() { return this; } QPoint KGameCanvasWidget::canvasPosition() const { return QPoint(0, 0); } /* KGameCanvasItem */ KGameCanvasItem::KGameCanvasItem(KGameCanvasAbstract* KGameCanvas) : m_visible(false) , m_animated(false) , m_opacity(255) , m_pos(0,0) , m_canvas(KGameCanvas) , m_changed(false) { if(m_canvas) m_canvas->m_items.append(this); } KGameCanvasItem::~KGameCanvasItem() { if(m_canvas) { m_canvas->m_items.removeAll(this); if(m_animated) m_canvas->m_animated_items.removeAll(this); if(m_visible) m_canvas->invalidate(m_last_rect, false); } } void KGameCanvasItem::changed() { m_changed = true; //even if m_changed was already true we cannot optimize away this call, because maybe the //item has been reparented, etc. It is a very quick call anyway. if(m_canvas) m_canvas->ensurePendingUpdate(); } void KGameCanvasItem::updateChanges() { if(!m_changed) return; if(m_canvas) { m_canvas->invalidate(m_last_rect, false); if(m_visible) m_canvas->invalidate(rect()); } m_changed = false; } QPixmap *KGameCanvasItem::transparence_pixmap_cache = NULL; QPixmap* KGameCanvasItem::getTransparenceCache(const QSize &s) { if(!transparence_pixmap_cache) transparence_pixmap_cache = new QPixmap(); if(s.width()>transparence_pixmap_cache->width() || s.height()>transparence_pixmap_cache->height()) { /* Strange that a pixmap with alpha should be created this way, i think a qt bug */ *transparence_pixmap_cache = QPixmap::fromImage( QImage( s.expandedTo(transparence_pixmap_cache->size()), QImage::Format_ARGB32 ) ); } return transparence_pixmap_cache; } void KGameCanvasItem::paintInternal(QPainter* pp, const QRect& /*prect*/, const QRegion& /*preg*/, const QPoint& /*delta*/, double cumulative_opacity) { int opacity = int(cumulative_opacity*m_opacity + 0.5); if(opacity <= 0) return; if(opacity >= 255) { paint(pp); return; } if(!layered()) { pp->setOpacity(opacity/255.0); paint(pp); pp->setOpacity(1.0); return; } QRect mr = rect(); QPixmap* cache = getTransparenceCache(mr.size()); { QPainter p(cache); /* clear */ p.setBrush(QColor(255,255,255,0)); p.setPen(Qt::NoPen); p.setCompositionMode(QPainter::CompositionMode_Source); p.drawRect(QRect(QPoint(),mr.size())); /* paint on the item */ p.translate(-mr.topLeft()); paint(&p); p.translate(mr.topLeft()); /* make the opacity */ p.setBrush( QColor(255,255,255,255-opacity) ); p.setPen( Qt::NoPen ); p.setCompositionMode( QPainter::CompositionMode_DestinationOut ); p.drawRect( QRect(QPoint(),mr.size()) ); } pp->drawPixmap(mr.topLeft(), *cache, QRect(QPoint(),mr.size()) ); } void KGameCanvasItem::putInCanvas(KGameCanvasAbstract *c) { if(m_canvas == c) return; if(m_canvas) { if(m_visible) m_canvas->invalidate(m_last_rect, false); //invalidate the previously drawn rectangle m_canvas->m_items.removeAll(this); if(m_animated) m_canvas->m_animated_items.removeAll(this); } m_canvas = c; if(m_canvas) { m_canvas->m_items.append(this); if(m_animated) { m_canvas->m_animated_items.append(this); m_canvas->ensureAnimating(); } if(m_visible) changed(); } } void KGameCanvasItem::setVisible(bool v) { if(m_visible == v) return; m_visible = v; if(m_canvas) { if(!v) m_canvas->invalidate(m_last_rect, false); else changed(); } if(!v) m_last_rect = QRect(); } void KGameCanvasItem::setAnimated(bool a) { if(m_animated == a) return; m_animated = a; if(m_canvas) { if(a) { m_canvas->m_animated_items.append(this); m_canvas->ensureAnimating(); } else m_canvas->m_animated_items.removeAll(this); } } void KGameCanvasItem::setOpacity(int o) { if (o<0) o=0; if (o>255) o = 255; m_opacity = o; if(m_canvas && m_visible) changed(); } bool KGameCanvasItem::layered() const { return true; } void KGameCanvasItem::advance(int /*msecs*/) { } void KGameCanvasItem::updateAfterRestack(int from, int to) { int inc = from>to ? -1 : 1; QRegion upd; for(int i=from; i!=to;i+=inc) { KGameCanvasItem *el = m_canvas->m_items.at(i); if(!el->m_visible) continue; QRect r = el->rect() & rect(); if(!r.isEmpty()) upd |= r; } if(!upd.isEmpty()) m_canvas->invalidate(upd); } void KGameCanvasItem::raise() { if(!m_canvas || m_canvas->m_items.last() == this) return; int old_pos = m_canvas->m_items.indexOf(this); m_canvas->m_items.removeAt(old_pos); m_canvas->m_items.append(this); if(m_visible) updateAfterRestack(old_pos, m_canvas->m_items.size()-1); } void KGameCanvasItem::lower() { if(!m_canvas || m_canvas->m_items.first() == this) return; int old_pos = m_canvas->m_items.indexOf(this); m_canvas->m_items.removeAt(old_pos); m_canvas->m_items.prepend(this); if(m_visible) updateAfterRestack(old_pos, 0); } void KGameCanvasItem::stackOver(KGameCanvasItem* ref) { if(!m_canvas) return; if(ref->m_canvas != m_canvas) { qCritical("KGameCanvasItem::stackOver: Argument must be a sibling item!\n"); return; } int i = m_canvas->m_items.indexOf( ref ); if(i < m_canvas->m_items.size()-2 && m_canvas->m_items[i+1] == this) return; int old_pos = m_canvas->m_items.indexOf(this); m_canvas->m_items.removeAt(old_pos); i = m_canvas->m_items.indexOf(ref); m_canvas->m_items.insert(i+1,this); if(m_visible) updateAfterRestack(old_pos, i+1); } void KGameCanvasItem::stackUnder(KGameCanvasItem* ref) { if(!m_canvas) return; if(ref->m_canvas != m_canvas) { qCritical("KGameCanvasItem::stackUnder: Argument must be a sibling item!\n"); return; } int i = m_canvas->m_items.indexOf( ref ); if(i >= 1 && m_canvas->m_items[i-1] == this) return; int old_pos = m_canvas->m_items.indexOf(this); m_canvas->m_items.removeAt(old_pos); i = m_canvas->m_items.indexOf(ref); m_canvas->m_items.insert(i,this); if(m_visible) updateAfterRestack(old_pos, i); } void KGameCanvasItem::moveTo(const QPoint &newpos) { if(m_pos == newpos) return; m_pos = newpos; if(m_visible && m_canvas) changed(); } QPoint KGameCanvasItem::absolutePosition() const { if (m_canvas) { return m_canvas->canvasPosition() + m_pos; } else { return m_pos; } } /* KGameCanvasGroup */ KGameCanvasGroup::KGameCanvasGroup(KGameCanvasAbstract* KGameCanvas) : KGameCanvasItem(KGameCanvas) , KGameCanvasAbstract() , m_child_rect_changed(true) { } KGameCanvasGroup::~KGameCanvasGroup() { } void KGameCanvasGroup::ensureAnimating() { setAnimated(true); } void KGameCanvasGroup::ensurePendingUpdate() { if(!m_changed || !m_child_rect_changed) { m_child_rect_changed = true; KGameCanvasItem::changed(); } } void KGameCanvasGroup::updateChanges() { if(!m_changed) return; for(int i=0;im_changed) el->updateChanges(); } m_changed = false; } void KGameCanvasGroup::changed() { if(!m_changed) { KGameCanvasItem::changed(); for(int i=0;ichanged(); } } void KGameCanvasGroup::invalidate(const QRect& r, bool translate) { if(m_canvas) m_canvas->invalidate(translate ? r.translated(m_pos) : r, translate); if(!m_changed) ensurePendingUpdate(); } void KGameCanvasGroup::invalidate(const QRegion& r, bool translate) { if(m_canvas) m_canvas->invalidate(translate ? r.translated(m_pos) : r, translate); if(!m_changed) ensurePendingUpdate(); } void KGameCanvasGroup::advance(int msecs) { // The list MUST be copied, because it could be modified calling advance. // After all since it is implicitly shared the copy will not happen unless // is it actually modified QList ait = m_animated_items; for(int i=0;iadvance(msecs); } if(m_animated_items.empty()) setAnimated(false); } void KGameCanvasGroup::paintInternal(QPainter* p, const QRect& prect, const QRegion& preg, const QPoint& delta, double cumulative_opacity) { cumulative_opacity *= (m_opacity/255.0); QPoint adelta = delta; adelta += m_pos; p->translate(m_pos); for(int i=0;irect().translated(adelta); if( el->m_visible && prect.intersects( r ) && preg.contains( r ) ) { el->m_last_rect = r; el->paintInternal(p,prect,preg,adelta,cumulative_opacity); } } p->translate(-m_pos); } void KGameCanvasGroup::paint(QPainter* /*p*/) { Q_ASSERT(!"This function should never be called"); } QRect KGameCanvasGroup::rect() const { if(!m_child_rect_changed) return m_last_child_rect.translated(m_pos); m_child_rect_changed = false; m_last_child_rect = QRect(); for(int i=0;im_visible) m_last_child_rect |= el->rect(); } return m_last_child_rect.translated(m_pos); } KGameCanvasWidget* KGameCanvasGroup::topLevelCanvas() { return m_canvas ? m_canvas->topLevelCanvas() : NULL; } QPoint KGameCanvasGroup::canvasPosition() const { return KGameCanvasItem::absolutePosition(); } /* KGameCanvasDummy */ KGameCanvasDummy::KGameCanvasDummy(KGameCanvasAbstract* KGameCanvas) : KGameCanvasItem(KGameCanvas) { } KGameCanvasDummy::~KGameCanvasDummy() { } void KGameCanvasDummy::paint(QPainter* /*p*/) { } QRect KGameCanvasDummy::rect() const { return QRect(); } /* KGameCanvasPixmap */ KGameCanvasPixmap::KGameCanvasPixmap(const QPixmap& p, KGameCanvasAbstract* KGameCanvas) : KGameCanvasItem(KGameCanvas), m_pixmap(p) { } KGameCanvasPixmap::KGameCanvasPixmap(KGameCanvasAbstract* KGameCanvas) : KGameCanvasItem(KGameCanvas) { } KGameCanvasPixmap::~KGameCanvasPixmap() { } void KGameCanvasPixmap::setPixmap(const QPixmap& p) { m_pixmap = p; if(visible() && canvas() ) changed(); } void KGameCanvasPixmap::paint(QPainter* p) { p->drawPixmap(pos(), m_pixmap); } QRect KGameCanvasPixmap::rect() const { return QRect(pos(), m_pixmap.size()); } KGameCanvasRenderedPixmap::KGameCanvasRenderedPixmap(KGameRenderer* renderer, const QString& spriteKey, KGameCanvasAbstract* canvas) : KGameCanvasPixmap(canvas) , KGameRendererClient(renderer, spriteKey) { } void KGameCanvasRenderedPixmap::receivePixmap(const QPixmap& pixmap) { KGameCanvasPixmap::setPixmap(pixmap); } /* KGameCanvasTiledPixmap */ KGameCanvasTiledPixmap::KGameCanvasTiledPixmap(const QPixmap& pixmap, const QSize &size, const QPoint &origin, bool move_orig, KGameCanvasAbstract* KGameCanvas) : KGameCanvasItem(KGameCanvas) , m_pixmap(pixmap) , m_size(size) , m_origin(origin) , m_move_orig(move_orig) { } KGameCanvasTiledPixmap::KGameCanvasTiledPixmap(KGameCanvasAbstract* KGameCanvas) : KGameCanvasItem(KGameCanvas) , m_size(0,0) , m_origin(0,0) , m_move_orig(false) { } KGameCanvasTiledPixmap::~KGameCanvasTiledPixmap() { } void KGameCanvasTiledPixmap::setPixmap(const QPixmap& pixmap) { m_pixmap = pixmap; if(visible() && canvas() ) changed(); } void KGameCanvasTiledPixmap::setSize(const QSize &size) { m_size = size; if(visible() && canvas() ) changed(); } void KGameCanvasTiledPixmap::setOrigin(const QPoint &origin) { m_origin = m_move_orig ? origin - pos() : origin; if(visible() && canvas() ) changed(); } void KGameCanvasTiledPixmap::setMoveOrigin(bool move_orig) { if(move_orig && !m_move_orig) m_origin -= pos(); if(move_orig && !m_move_orig) m_origin += pos(); m_move_orig = move_orig; } void KGameCanvasTiledPixmap::paint(QPainter* p) { if(m_move_orig) p->drawTiledPixmap( rect(), m_pixmap, m_origin); else p->drawTiledPixmap( rect(), m_pixmap, m_origin+pos() ); } QRect KGameCanvasTiledPixmap::rect() const { return QRect(pos(), m_size); } /* KGameCanvasRectangle */ KGameCanvasRectangle::KGameCanvasRectangle(const QColor& color, const QSize &size, KGameCanvasAbstract* KGameCanvas) : KGameCanvasItem(KGameCanvas) , m_color(color) , m_size(size) { } KGameCanvasRectangle::KGameCanvasRectangle(KGameCanvasAbstract* KGameCanvas) : KGameCanvasItem(KGameCanvas) , m_size(0,0) { } KGameCanvasRectangle::~KGameCanvasRectangle() { } void KGameCanvasRectangle::setColor(const QColor& color) { m_color = color; if(visible() && canvas() ) changed(); } void KGameCanvasRectangle::setSize(const QSize &size) { m_size = size; if(visible() && canvas() ) changed(); } void KGameCanvasRectangle::paint(QPainter* p) { p->fillRect( rect(), m_color ); } QRect KGameCanvasRectangle::rect() const { return QRect(pos(), m_size); } /* KGameCanvasText */ KGameCanvasText::KGameCanvasText(const QString& text, const QColor& color, const QFont& font, HPos hp, VPos vp, KGameCanvasAbstract* KGameCanvas) : KGameCanvasItem(KGameCanvas) , m_text(text) , m_color(color) , m_font(font) , m_hpos(hp) , m_vpos(vp) { calcBoundingRect(); } KGameCanvasText::KGameCanvasText(KGameCanvasAbstract* KGameCanvas) : KGameCanvasItem(KGameCanvas) //, m_text("") , m_color(Qt::black) , m_font(QApplication::font()) , m_hpos(HStart) , m_vpos(VBaseline) { } KGameCanvasText::~KGameCanvasText() { } void KGameCanvasText::calcBoundingRect() { m_bounding_rect = QFontMetrics(m_font).boundingRect(m_text); /*printf("b rect is %d %d %d %d\n", m_bounding_rect.x(), m_bounding_rect.y(), m_bounding_rect.width(), m_bounding_rect.height() );*/ } void KGameCanvasText::setText(const QString& text) { if(m_text == text) return; m_text = text; calcBoundingRect(); if(visible() && canvas() ) changed(); } void KGameCanvasText::setColor(const QColor& color) { m_color = color; } void KGameCanvasText::setFont(const QFont& font) { m_font = font; calcBoundingRect(); if(visible() && canvas() ) changed(); } void KGameCanvasText::setPositioning(HPos hp, VPos vp) { pos() += offsetToDrawPos(); m_hpos = hp; m_vpos = vp; pos() -= offsetToDrawPos(); } QPoint KGameCanvasText::offsetToDrawPos() const { QPoint retv; switch(m_hpos) { case HStart: retv.setX(0); break; case HLeft: retv.setX(-m_bounding_rect.left()); break; case HRight: retv.setX(-m_bounding_rect.right()); break; case HCenter: retv.setX(-(m_bounding_rect.left()+m_bounding_rect.right())/2); break; } switch(m_vpos) { case VBaseline: retv.setY(0); break; case VTop: retv.setY(-m_bounding_rect.top()); break; case VBottom: retv.setY(-m_bounding_rect.bottom()); break; case VCenter: retv.setY(-(m_bounding_rect.top()+m_bounding_rect.bottom())/2); break; } return retv; } void KGameCanvasText::paint(QPainter* p) { p->setPen(m_color); p->setFont(m_font); p->drawText( pos() + offsetToDrawPos(), m_text); } QRect KGameCanvasText::rect() const { return m_bounding_rect.translated( pos() + offsetToDrawPos() ); } /* KGameCanvasPicture */ KGameCanvasPicture::KGameCanvasPicture(const QPicture& p, KGameCanvasAbstract* KGameCanvas) : KGameCanvasItem(KGameCanvas), m_picture(p) { } KGameCanvasPicture::KGameCanvasPicture(KGameCanvasAbstract* KGameCanvas) : KGameCanvasItem(KGameCanvas) { } KGameCanvasPicture::~KGameCanvasPicture() { } void KGameCanvasPicture::setPicture(const QPicture& p) { m_picture = p; if(visible() && canvas() ) changed(); } void KGameCanvasPicture::paint(QPainter* p) { p->drawPicture(pos(), m_picture); } QRect KGameCanvasPicture::rect() const { return m_picture.boundingRect().translated( pos()); } KGameCanvasAdapter::KGameCanvasAdapter() : m_child_rect_valid(false) { } QRect KGameCanvasAdapter::childRect() { if (!m_child_rect_valid) { m_child_rect = QRect(); foreach (KGameCanvasItem* el, m_items) { m_child_rect |= el->rect(); } m_child_rect_valid = true; } return m_child_rect; } void KGameCanvasAdapter::render(QPainter *painter) { foreach (KGameCanvasItem* el, m_items) { if (el->m_visible) { el->m_last_rect = el->rect(); el->paintInternal(painter, childRect(), childRect(), QPoint(), 1.0); } } } void KGameCanvasAdapter::ensurePendingUpdate() { m_child_rect_valid = false; foreach (KGameCanvasItem* el, m_items) { if (el->m_changed) { el->updateChanges(); } } updateParent(m_invalidated_rect); m_invalidated_rect = QRect(); } void KGameCanvasAdapter::invalidate(const QRegion& r, bool) { invalidate(r.boundingRect()); } void KGameCanvasAdapter::invalidate(const QRect& r, bool) { m_invalidated_rect |= r; }