diff --git a/krita/main.cc b/krita/main.cc index c7c03a657f..6ddeaf3fc7 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,467 +1,488 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #if QT_VERSION >= 0x050900 #include #endif #include #include #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "data/splash/splash_screen_x2.xpm" #include "data/splash/splash_holidays_x2.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include #include "input/KisQtWidgetsTweaker.h" +#include +#include #if defined Q_OS_WIN #include #include #include #include #elif defined HAVE_X11 #include "config_use_qt_xcb.h" #ifndef USE_QT_XCB #include #endif #endif #if defined HAVE_KCRASH #include #elif defined USE_DRMINGW namespace { void tryInitDrMingw() { wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit()); } typedef enum ORIENTATION_PREFERENCE { ORIENTATION_PREFERENCE_NONE = 0x0, ORIENTATION_PREFERENCE_LANDSCAPE = 0x1, ORIENTATION_PREFERENCE_PORTRAIT = 0x2, ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED = 0x4, ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED = 0x8 } ORIENTATION_PREFERENCE; typedef BOOL WINAPI (*pSetDisplayAutoRotationPreferences_t)( ORIENTATION_PREFERENCE orientation ); void resetRotation() { QLibrary user32Lib("user32"); if (!user32Lib.load()) { qWarning() << "Failed to load user32.dll! This really should not happen."; return; } pSetDisplayAutoRotationPreferences_t pSetDisplayAutoRotationPreferences = reinterpret_cast(user32Lib.resolve("SetDisplayAutoRotationPreferences")); if (!pSetDisplayAutoRotationPreferences) { dbgKrita << "Failed to load function SetDisplayAutoRotationPreferences"; return; } bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE); dbgKrita << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result; } } // namespace #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif // Workaround a bug in QNetworkManager qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita4" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); #if QT_VERSION >= 0x050900 QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true); #endif const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); bool singleApplication = true; bool enableOpenGLDebug = false; bool openGLDebugSynchronous = false; + bool logUsage = true; { - QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); + singleApplication = kritarc.value("EnableSingleApplication", true).toBool(); -#if QT_VERSION >= 0x050600 if (kritarc.value("EnableHiDPI", true).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } -#endif + if (!qgetenv("KRITA_OPENGL_DEBUG").isEmpty()) { enableOpenGLDebug = true; } else { enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool(); } if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) { openGLDebugSynchronous = true; } KisOpenGL::setDefaultFormat(enableOpenGLDebug, openGLDebugSynchronous); + logUsage = kritarc.value("LogUsage", true).toBool(); + #ifdef Q_OS_WIN QString preferredOpenGLRenderer = kritarc.value("OpenGLRenderer", "auto").toString(); // Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP // might get weird crashes atm. qputenv("QT_ANGLE_PLATFORM", "d3d11"); // Probe QPA auto OpenGL detection char *fakeArgv[2] = { argv[0], nullptr }; // Prevents QCoreApplication from modifying the real argc/argv KisOpenGL::probeWindowsQpaOpenGL(1, fakeArgv, preferredOpenGLRenderer); // HACK: https://bugs.kde.org/show_bug.cgi?id=390651 resetRotation(); #endif } + if (logUsage) { + KisUsageLogger::initialize(); + } + QString root; QString language; { // Create a temporary application to get the root QCoreApplication app(argc, argv); Q_UNUSED(app); root = KoResourcePaths::getApplicationRoot(); QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); language = languageoverride.value(qAppName(), "").toString(); } #ifdef Q_OS_LINUX { QByteArray originalXdgDataDirs = qgetenv("XDG_DATA_DIRS"); if (originalXdgDataDirs.isEmpty()) { // We don't want to completely override the default originalXdgDataDirs = "/usr/local/share/:/usr/share/"; } qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share") + ":" + originalXdgDataDirs); } #else qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share")); #endif dbgKrita << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); // Now that the paths are set, set the language. First check the override from the language // selection dialog. dbgKrita << "Override language:" << language; bool rightToLeft = false; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too qputenv("LANG", language.split(":").first().toLocal8Bit()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); const QStringList rtlLanguages = QStringList() << "ar" << "dv" << "he" << "ha" << "ku" << "fa" << "ps" << "ur" << "yi"; if (rtlLanguages.contains(language.split(':').first())) { rightToLeft = true; } } else { dbgKrita << "Qt UI languages:" << QLocale::system().uiLanguages() << qgetenv("LANG"); // And if there isn't one, check the one set by the system. QLocale locale = QLocale::system(); if (locale.name() != QStringLiteral("en")) { QStringList uiLanguages = locale.uiLanguages(); for (QString &uiLanguage : uiLanguages) { // This list of language codes that can have a specifier should // be extended whenever we have translations that need it; right // now, only en, pt, zh are in this situation. if (uiLanguage.startsWith("en") || uiLanguage.startsWith("pt")) { uiLanguage.replace(QChar('-'), QChar('_')); } else if (uiLanguage.startsWith("zh-Hant") || uiLanguage.startsWith("zh-TW")) { uiLanguage = "zh_TW"; } else if (uiLanguage.startsWith("zh-Hans") || uiLanguage.startsWith("zh-CN")) { uiLanguage = "zh_CN"; } } for (int i = 0; i < uiLanguages.size(); i++) { QString uiLanguage = uiLanguages[i]; // Strip the country code int idx = uiLanguage.indexOf(QChar('-')); if (idx != -1) { uiLanguage = uiLanguage.left(idx); uiLanguages.replace(i, uiLanguage); } } dbgKrita << "Converted ui languages:" << uiLanguages; qputenv("LANG", uiLanguages.first().toLocal8Bit()); #ifdef Q_OS_MAC // See https://bugs.kde.org/show_bug.cgi?id=396370 KLocalizedString::setLanguages(QStringList() << uiLanguages.first()); #else KLocalizedString::setLanguages(QStringList() << uiLanguages); #endif } } // first create the application so we can create a pixmap KisApplication app(key, argc, argv); if (!language.isEmpty()) { if (rightToLeft) { app.setLayoutDirection(Qt::RightToLeft); } else { app.setLayoutDirection(Qt::LeftToRight); } } KLocalizedString::setApplicationDomain("krita"); dbgKrita << "Available translations" << KLocalizedString::availableApplicationTranslations(); dbgKrita << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); dbgKrita << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance const bool batchRun = args.exportAs(); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } #if defined HAVE_X11 #ifndef USE_QT_XCB app.installNativeEventFilter(KisXi2EventFilter::instance()); #endif #endif app.installEventFilter(KisQtWidgetsTweaker::instance()); if (!args.noSplash()) { // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } app.setSplashScreen(splash); } #if defined Q_OS_WIN KisConfig cfg(false); bool supportedWindowsVersion = true; #if QT_VERSION >= 0x050900 QOperatingSystemVersion osVersion = QOperatingSystemVersion::current(); if (osVersion.type() == QOperatingSystemVersion::Windows) { if (osVersion.majorVersion() >= QOperatingSystemVersion::Windows7.majorVersion()) { supportedWindowsVersion = true; } else { supportedWindowsVersion = false; if (cfg.readEntry("WarnedAboutUnsupportedWindows", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running an unsupported version of Windows: %1.\n" "This is not recommended. Do not report any bugs.\n" "Please update to a supported version of Windows: Windows 7, 8, 8.1 or 10.", osVersion.name())); cfg.writeEntry("WarnedAboutUnsupportedWindows", true); } } } #endif { if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(false); } if (!cfg.useWin8PointerInput()) { bool hasWinTab = KisTabletSupportWin::init(); if (!hasWinTab && supportedWindowsVersion) { if (KisTabletSupportWin8::isPenDeviceAvailable()) { // Use WinInk automatically cfg.setUseWin8PointerInput(true); } else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) { if (KisTabletSupportWin8::isAvailable()) { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } else { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } cfg.writeEntry("WarnedAboutMissingWinTab", true); } } } if (cfg.useWin8PointerInput()) { KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8(); if (penFilter->init()) { // penFilter.registerPointerDeviceNotifications(); app.installNativeEventFilter(penFilter); dbgKrita << "Using Win8 Pointer Input for tablet support"; } else { dbgKrita << "No Win8 Pointer Input available"; delete penFilter; } } } #endif if (!app.start(args)) { return 1; } #if QT_VERSION >= 0x050700 app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); #endif // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); + // Hardware information + KisUsageLogger::write("\nHardware Information\n"); + KisUsageLogger::write(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString())); + KisUsageLogger::write(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM())); + KisUsageLogger::write(QString(" Number of Cores: %1").arg(QThread::idealThreadCount())); + KisUsageLogger::write(QString(" Swap Location: %1\n").arg(KisImageConfig(true).swapDir())); + int state = app.exec(); { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } + if (logUsage) { + KisUsageLogger::close(); + } + return state; } diff --git a/libs/global/CMakeLists.txt b/libs/global/CMakeLists.txt index 74e53b8456..873eb80db6 100644 --- a/libs/global/CMakeLists.txt +++ b/libs/global/CMakeLists.txt @@ -1,53 +1,55 @@ add_subdirectory( tests ) include(CheckFunctionExists) check_function_exists(backtrace HAVE_BACKTRACE) configure_file(config-debug.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-debug.h) option(HAVE_MEMORY_LEAK_TRACKER "Enable memory leak tracker (always disabled in release build)" OFF) option(HAVE_BACKTRACE_SUPPORT "Enable recording of backtrace in memory leak tracker" OFF) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-memory-leak-tracker.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-memory-leak-tracker.h) ### WRONG PLACE??? set(kritaglobal_LIB_SRCS kis_assert.cpp kis_debug.cpp kis_algebra_2d.cpp kis_memory_leak_tracker.cpp kis_shared.cpp kis_dom_utils.cpp kis_painting_tweaks.cpp KisHandlePainterHelper.cpp KisHandleStyle.cpp kis_relaxed_timer.cpp kis_signal_compressor.cpp kis_signal_compressor_with_param.cpp kis_thread_safe_signal_compressor.cpp kis_acyclic_signal_connector.cpp kis_latency_tracker.cpp KisQPainterStateSaver.cpp KisSharedThreadPoolAdapter.cpp KisSharedRunnable.cpp KisRollingMeanAccumulatorWrapper.cpp kis_config_notifier.cpp KisDeleteLaterWrapper.cpp + KisUsageLogger.cpp ) add_library(kritaglobal SHARED ${kritaglobal_LIB_SRCS} ) generate_export_header(kritaglobal BASE_NAME kritaglobal) target_link_libraries(kritaglobal PUBLIC + kritaversion Qt5::Concurrent Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Xml KF5::I18n ) set_target_properties(kritaglobal PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaglobal ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/global/KisUsageLogger.cpp b/libs/global/KisUsageLogger.cpp new file mode 100644 index 0000000000..c1df832161 --- /dev/null +++ b/libs/global/KisUsageLogger.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2019 Boudewijn Rempt + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "KisUsageLogger.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +Q_GLOBAL_STATIC(KisUsageLogger, s_instance) + +const QString KisUsageLogger::s_sectionHeader("================================================================================\n"); + +struct KisUsageLogger::Private { + bool active {false}; + QFile logFile; +}; + +KisUsageLogger::KisUsageLogger() + : d(new Private) +{ + d->logFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/krita.log"); + + rotateLog(); + d->logFile.open(QFile::Append); + writeHeader(); +} + +KisUsageLogger::~KisUsageLogger() +{ + if (d->active) { + close(); + } +} + +void KisUsageLogger::initialize() +{ + s_instance->d->active = true; +} + +void KisUsageLogger::close() +{ + log("Closing."); + s_instance->d->active = false; + s_instance->d->logFile.flush(); + s_instance->d->logFile.close(); +} + +void KisUsageLogger::log(const QString &message) +{ + if (!s_instance->d->active) return; + if (!s_instance->d->logFile.isOpen()) return; + + s_instance->d->logFile.write(QDateTime::currentDateTime().toString(Qt::RFC2822Date).toUtf8()); + s_instance->d->logFile.write(": "); + write(message); +} + +void KisUsageLogger::write(const QString &message) +{ + if (!s_instance->d->active) return; + if (!s_instance->d->logFile.isOpen()) return; + + s_instance->d->logFile.write(message.toUtf8()); + s_instance->d->logFile.write("\n"); + + s_instance->d->logFile.flush(); +} + +void KisUsageLogger::writeHeader() +{ + Q_ASSERT(d->logFile.isOpen()); + + QString sessionHeader = QString("SESSION: %1\n\n").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)); + QString disclaimer = i18n("WARNING: This file contains information about your system and the\n" + "images you have been working with.\n" + "\n" + "If you have problems with Krita, the Krita developers might ask\n" + "you to share this file with them. The information in this file is\n" + "not shared automatically with the Krita developers in any way. You\n" + "can disable logging to this file in Krita's Configure Krita Dialog.\n" + "\n" + "Please review the contents of this file before sharing this file with\n" + "anyone.\n\n"); + + QString systemInfo; + + // NOTE: This is intentionally not translated! + + // Krita version info + systemInfo.append("Krita\n"); + systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); + systemInfo.append("\n\n"); + + systemInfo.append("Qt\n"); + systemInfo.append("\n Version (compiled): ").append(QT_VERSION_STR); + systemInfo.append("\n Version (loaded): ").append(qVersion()); + systemInfo.append("\n\n"); + + // OS information + systemInfo.append("OS Information\n"); + systemInfo.append("\n Build ABI: ").append(QSysInfo::buildAbi()); + systemInfo.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); + systemInfo.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); + systemInfo.append("\n Kernel Type: ").append(QSysInfo::kernelType()); + systemInfo.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); + systemInfo.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); + systemInfo.append("\n Product Type: ").append(QSysInfo::productType()); + systemInfo.append("\n Product Version: ").append(QSysInfo::productVersion()); + systemInfo.append("\n\n"); + + d->logFile.write(s_sectionHeader.toUtf8()); + d->logFile.write(sessionHeader.toUtf8()); + d->logFile.write(disclaimer.toUtf8()); + d->logFile.write(systemInfo.toUtf8()); + + +} + +void KisUsageLogger::rotateLog() +{ + d->logFile.open(QFile::ReadOnly); + QString log = QString::fromUtf8(d->logFile.readAll()); + int sectionCount = log.count(s_sectionHeader); + int nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); + while(sectionCount >= s_maxLogs) { + log = log.remove(0, log.indexOf(s_sectionHeader, nextSectionIndex)); + nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); + sectionCount = log.count(s_sectionHeader); + } + d->logFile.close(); + d->logFile.open(QFile::WriteOnly); + d->logFile.write(log.toUtf8()); + d->logFile.close(); +} + diff --git a/libs/global/KisUsageLogger.h b/libs/global/KisUsageLogger.h new file mode 100644 index 0000000000..5f128259f6 --- /dev/null +++ b/libs/global/KisUsageLogger.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Boudewijn Rempt + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef KISUSAGELOGGER_H +#define KISUSAGELOGGER_H + +#include +#include + +#include "kritaglobal_export.h" + +/** + * @brief The KisUsageLogger class logs messages to a logfile + */ +class KRITAGLOBAL_EXPORT KisUsageLogger +{ + +public: + + KisUsageLogger(); + ~KisUsageLogger(); + + static void initialize(); + static void close(); + + /// Logs with date/time + static void log(const QString &message); + + /// Writes without date/time + static void write(const QString &message); + +private: + + void writeHeader(); + void rotateLog(); + + Q_DISABLE_COPY(KisUsageLogger) + + struct Private; + const QScopedPointer d; + + static const QString s_sectionHeader; + static const int s_maxLogs {10}; + +}; + +#endif // KISUSAGELOGGER_H diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index ab95fd2a29..d24933e79b 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1867 +1,1904 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" #include "KisCloneDocumentStroke.h" // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *q) : docInfo(new KoDocumentInfo(q)) // deleted by QObject , importExportManager(new KisImportExportManager(q)) // deleted manually , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , globalAssistantsColor(KisConfig(true).defaultAssistantsColor()) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *q) : docInfo(new KoDocumentInfo(*rhs.docInfo, q)) , unit(rhs.unit) , importExportManager(new KisImportExportManager(q)) , mimeType(rhs.mimeType) , outputMimeType(rhs.outputMimeType) , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) , guidesConfig(rhs.guidesConfig) , m_bAutoDetectedMime(rhs.m_bAutoDetectedMime) , m_url(rhs.m_url) , m_file(rhs.m_file) , modified(rhs.modified) , readwrite(rhs.readwrite) , firstMod(rhs.firstMod) , lastMod(rhs.lastMod) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document! , globalAssistantsColor(rhs.globalAssistantsColor) , paletteList(rhs.paletteList) , gridConfig(rhs.gridConfig) , savingLock(&savingMutex) , batchMode(rhs.batchMode) { // TODO: clone assistants } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; QColor globalAssistantsColor; KisSharedPtr referenceImagesLayer; QList paletteList; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class StrippedSafeSavingLocker; }; class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(rhs.objectName()); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(true), false); if (rhs.d->preActivatedNode) { // since we clone uuid's, we can use them for lacating new // nodes. Otherwise we would need to use findSymmetricClone() d->preActivatedNode = KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid()); } } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, KisImportExportFilter::CreationError, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); return false; } KisConfig cfg(true); if (cfg.backupFile() && filePathInfo.exists()) { - KBackup::backupFile(job.filePath); + KBackup::numberedBackupFile(job.filePath); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } + KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8") + .arg(url.toLocalFile()) + .arg(QString::fromLatin1(mimeType)) + .arg(d->image->width()) + .arg(d->image->height()) + .arg(d->image->nlayers()) + .arg(d->image->animationInterface()->totalLength()) + .arg(d->image->animationInterface()->framerate()) + .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")); + return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } -bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) +bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; - return exportDocumentImpl(ExportFileJob(url.toLocalFile(), + KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, %7 framerate. Export configuration: %8") + .arg(_url.toLocalFile()) + .arg(QString::fromLatin1(mimeType)) + .arg(d->image->width()) + .arg(d->image->height()) + .arg(d->image->nlayers()) + .arg(d->image->animationInterface()->totalLength()) + .arg(d->image->animationInterface()->framerate()) + .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration") + .arg(url().toLocalFile())); + + + return exportDocumentImpl(ExportFileJob(_url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { if (status == KisImportExportFilter::UserCancelled) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage))); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { const QString existingAutoSaveBaseName = localFilePath(); const bool wasRecovered = isRecovered(); setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { /** * If undo stack is already clean/empty, it doesn't emit any * signals, so we might forget update document modified state * (which was set, e.g. while recovering an autosave file) */ if (d->undoStack->isClean()) { setModified(false); } else { d->undoStack->setClean(); } } setRecovered(false); removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning QApplication::processEvents(); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportFilter::ConversionStatus status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status == KisImportExportFilter::OK; } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus,QString)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus,QString))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { d->savingMutex.unlock(); return; } KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); + KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3") + .arg(job.filePath) + .arg(QString::fromLatin1(job.mimeType)) + .arg(status != KisImportExportFilter::OK ? exportErrorToUserMessage(status, errorMessage) : "OK")); + emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg(true); d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } KisImportExportFilter::ConversionStatus initializationStatus; d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage()); return false; } typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, ""); return; } KisImportExportFilter::ConversionStatus status = d->childSavingFuture.result(); const QString errorMessage = this->errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QRegularExpression autosavePattern("^\\..+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #endif } else { retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } if (ret) { // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

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

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window && window->viewManager()) { KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } KisImportExportFilter::ConversionStatus status; status = d->importExportManager->importDocument(localFilePath(), typeName); if (status != KisImportExportFilter::OK) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); undoStack()->clear(); return true; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered) { //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { //qDebug() << "\tremoving autosavefile" << asf; QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME //qDebug() << "Autsavefile in $home" << asf; if (QFile::exists(asf)) { //qDebug() << "\tremoving autsavefile 2" << asf; QFile::remove(asf); } QRegularExpression autosavePattern("^\\..+-autosave.kra$"); if (wasRecovered && !autosaveBaseName.isEmpty() && autosavePattern.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() && QFile::exists(autosaveBaseName)) { QFile::remove(autosaveBaseName); } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg(true); d->undoStack->setUndoLimit(cfg.undoStackLimit()); d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } QList &KisDocument::paletteList() { return d->paletteList; } void KisDocument::setPaletteList(const QList &paletteList) { d->paletteList = paletteList; } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) return false; if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) return false; d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisImageSP image; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); documentInfo()->setAboutInfo("abstract", description); KisLayerSP layer; if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) { KoColor strippedAlpha = bgColor; strippedAlpha.setOpacity(OPACITY_OPAQUE_U8); if (bgStyle == KisConfig::RASTER_LAYER) { layer = new KisPaintLayer(image.data(), "Background", OPACITY_OPAQUE_U8, cs);; layer->paintDevice()->setDefaultPixel(strippedAlpha); } else if (bgStyle == KisConfig::FILL_LAYER) { KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration(); filter_config->setProperty("color", strippedAlpha.toQColor()); layer = new KisGeneratorLayer(image.data(), "Background Fill", filter_config, image->globalSelection()); } layer->setOpacity(bgColor.opacityU8()); if (numberOfLayers > 1) { //Lock bg layer if others are present. layer->setUserLocked(true); } } else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer). image->setDefaultProjectionColor(bgColor); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); } Q_CHECK_PTR(layer); image->addNode(layer.data(), image->rootLayer().data()); layer->setDirty(QRect(0, 0, width, height)); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } KisConfig cfg(false); cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); + KisUsageLogger::log(i18n("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8" + , name + , width, height + , imageResolution * 72.0 + , image->colorSpace()->colorModelId().name(), image->colorSpace()->colorDepthId().name() + , image->colorSpace()->profile()->name() + , numberOfLayers)); + QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { if (isSaving()) { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } } KoShapeControllerBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList &value) { d->assistants = value; } KisSharedPtr KisDocument::referenceImagesLayer() const { return d->referenceImagesLayer.data(); } void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage) { if (d->referenceImagesLayer) { d->referenceImagesLayer->disconnect(this); } if (updateImage) { if (layer) { d->image->addNode(layer); } else { d->image->removeNode(d->referenceImagesLayer); } } d->referenceImagesLayer = layer; if (d->referenceImagesLayer) { connect(d->referenceImagesLayer, SIGNAL(sigUpdateCanvas(QRectF)), this, SIGNAL(sigReferenceImagesChanged())); } } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) { if (d->image) { // Disconnect existing sig/slot connections d->image->setUndoStore(new KisDumbUndoStore()); d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->image->setUndoStore(new KisDocumentUndoStore(this)); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); if (forceInitialUpdate) { d->image->initialRefreshGraph(); } } void KisDocument::hackPreliminarySetImage(KisImageSP image) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); } void KisDocument::setImageModified() { setModified(true); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage; } void KisDocument::setAssistantsGlobalColor(QColor color) { d->globalAssistantsColor = color; } QColor KisDocument::assistantsGlobalColor() { return d->globalAssistantsColor; } QRectF KisDocument::documentBounds() const { QRectF bounds = d->image->bounds(); if (d->referenceImagesLayer) { bounds |= d->referenceImagesLayer->boundingImageRect(); } return bounds; } diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp index 7e8f616ad4..e5ac96de10 100644 --- a/libs/ui/KisImportExportManager.cpp +++ b/libs/ui/KisImportExportManager.cpp @@ -1,681 +1,699 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisImportExportManager.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_config.h" #include "KisImportExportFilter.h" #include "KisDocument.h" #include #include #include "kis_painter.h" #include "kis_guides_config.h" #include "kis_grid_config.h" #include "kis_popup_button.h" #include #include "kis_async_action_feedback.h" #include "KisReferenceImagesLayer.h" // static cache for import and export mimetypes QStringList KisImportExportManager::m_importMimeTypes; QStringList KisImportExportManager::m_exportMimeTypes; class Q_DECL_HIDDEN KisImportExportManager::Private { public: KoUpdaterPtr updater; QString cachedExportFilterMimeType; QSharedPointer cachedExportFilter; }; struct KisImportExportManager::ConversionResult { ConversionResult() { } ConversionResult(const QFuture &futureStatus) : m_isAsync(true), m_futureStatus(futureStatus) { } ConversionResult(KisImportExportFilter::ConversionStatus status) : m_isAsync(false), m_status(status) { } bool isAsync() const { return m_isAsync; } QFuture futureStatus() const { // if the result is not async, then it means some failure happened, // just return a cancelled future KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || m_status != KisImportExportFilter::OK); return m_futureStatus; } KisImportExportFilter::ConversionStatus status() const { return m_status; } void setStatus(KisImportExportFilter::ConversionStatus value) { m_status = value; } private: bool m_isAsync = false; QFuture m_futureStatus; KisImportExportFilter::ConversionStatus m_status = KisImportExportFilter::UsageError; }; KisImportExportManager::KisImportExportManager(KisDocument* document) : m_document(document) , d(new Private) { } KisImportExportManager::~KisImportExportManager() { delete d; } KisImportExportFilter::ConversionStatus KisImportExportManager::importDocument(const QString& location, const QString& mimeType) { ConversionResult result = convert(Import, location, location, mimeType, false, 0, false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError); return result.status(); } KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError); return result.status(); } QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, KisImportExportFilter::ConversionStatus &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync() || result.status() != KisImportExportFilter::OK, QFuture()); status = result.status(); return result.futureStatus(); } // The static method to figure out to which parts of the // graph this mimetype has a connection to. QStringList KisImportExportManager::supportedMimeTypes(Direction direction) { // Find the right mimetype by the extension QSet mimeTypes; // mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster"; if (direction == KisImportExportManager::Import) { if (m_importMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_importMimeTypes = mimeTypes.toList(); } return m_importMimeTypes; } else if (direction == KisImportExportManager::Export) { if (m_exportMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_exportMimeTypes = mimeTypes.toList(); } return m_exportMimeTypes; } return QStringList(); } KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction) { int weight = -1; KisImportExportFilter *filter = 0; QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import"; if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) { KLibFactory *factory = qobject_cast(loader->instance()); if (!factory) { warnUI << loader->errorString(); continue; } QObject* obj = factory->create(0); if (!obj || !obj->inherits("KisImportExportFilter")) { delete obj; continue; } KisImportExportFilter *f = qobject_cast(obj); if (!f) { delete obj; continue; } int w = json.value("X-KDE-Weight").toInt(); if (w > weight) { delete filter; filter = f; f->setObjectName(loader->fileName()); weight = w; } } } qDeleteAll(list); if (filter) { filter->setMimeType(mimetype); } return filter; } bool KisImportExportManager::batchMode(void) const { return m_document->fileBatchMode(); } void KisImportExportManager::setUpdater(KoUpdaterPtr updater) { d->updater = updater; } QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent) { KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio"); if (!defaultDir.isEmpty()) { dialog.setDefaultDir(defaultDir); } QStringList mimeTypes; mimeTypes << "audio/mpeg"; mimeTypes << "audio/ogg"; mimeTypes << "audio/vorbis"; mimeTypes << "audio/vnd.wave"; mimeTypes << "audio/flac"; dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@titile:window", "Open Audio")); return dialog.filename(); } KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync) { // export configuration is supported for export only KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration)); - QString typeName = mimeType; if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true); } QSharedPointer filter; /** * Fetching a filter from the registry is a really expensive operation, * because it blocks all the threads. Cache the filter if possible. */ if (direction == KisImportExportManager::Export && d->cachedExportFilter && d->cachedExportFilterMimeType == typeName) { filter = d->cachedExportFilter; } else { filter = toQShared(filterForMimeType(typeName, direction)); if (direction == Export) { d->cachedExportFilter = filter; d->cachedExportFilterMimeType = typeName; } } if (!filter) { return KisImportExportFilter::FilterCreationError; } filter->setFilename(location); filter->setRealFilename(realLocation); filter->setBatchMode(batchMode()); filter->setMimeType(typeName); if (!d->updater.isNull()) { // WARNING: The updater is not guaranteed to be persistent! If you ever want // to add progress reporting to "Save also as .kra", make sure you create // a separate KoProgressUpdater for that! // WARNING2: the failsafe completion of the updater happens in the destructor // the filter. filter->setUpdater(d->updater); } QByteArray from, to; if (direction == Export) { from = m_document->nativeFormatMimeType(); to = typeName.toLatin1(); } else { from = typeName.toLatin1(); to = m_document->nativeFormatMimeType(); } KIS_ASSERT_RECOVER_RETURN_VALUE( direction == Import || direction == Export, KisImportExportFilter::BadConversionGraph); - - ConversionResult result = KisImportExportFilter::OK; if (direction == Import) { + + KisUsageLogger::log(QString("Importing %1 to %2. Location: %3. Real location: %4. Batchmode: %5") + .arg(QString::fromLatin1(from)) + .arg(QString::fromLatin1(to)) + .arg(location) + .arg(realLocation) + .arg(batchMode())); + + + // async importing is not yet supported! KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync); if (0 && !batchMode()) { KisAsyncActionFeedback f(i18n("Opening document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter)); } else { result = doImport(location, filter); } } else /* if (direction == Export) */ { if (!exportConfiguration) { exportConfiguration = filter->lastSavedConfiguration(from, to); } if (exportConfiguration) { fillStaticExportConfigurationProperties(exportConfiguration); } bool alsoAsKra = false; bool askUser = askUserAboutExportConfiguration(filter, exportConfiguration, from, to, batchMode(), showWarnings, &alsoAsKra); if (!batchMode() && !askUser) { return KisImportExportFilter::UserCancelled; } + KisUsageLogger::log(QString("Converting from %1 to %2. Location: %3. Real location: %4. Batchmode: %5. Configuration: %6") + .arg(QString::fromLatin1(from)) + .arg(QString::fromLatin1(to)) + .arg(location) + .arg(realLocation) + .arg(batchMode()) + .arg(exportConfiguration ? exportConfiguration->toXML() : "none")); + + + if (isAsync) { result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); // we should explicitly report that the exporting has been initiated result.setStatus(KisImportExportFilter::OK); } else if (!batchMode()) { KisAsyncActionFeedback f(i18n("Saving document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); } else { result = doExport(location, filter, exportConfiguration, alsoAsKra); } if (exportConfiguration && !batchMode() && showWarnings) { KisConfig(false).setExportConfiguration(typeName, exportConfiguration); } } return result; } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image) { KisPaintDeviceSP dev = image->projection(); const KoColorSpace* cs = dev->colorSpace(); const bool isThereAlpha = KisPainter::checkDeviceHasTransparency(image->projection()); exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha); exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id()); exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id()); const bool sRGB = (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) && !cs->profile()->name().contains(QLatin1String("g10"))); exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB); } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration) { return fillStaticExportConfigurationProperties(exportConfiguration, m_document->image()); } bool KisImportExportManager::askUserAboutExportConfiguration( QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, const bool batchMode, const bool showWarnings, bool *alsoAsKra) { // prevents the animation renderer from running this code const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to); QStringList warnings; QStringList errors; { KisPreExportChecker checker; checker.check(m_document->image(), filter->exportChecks()); warnings = checker.warnings(); errors = checker.errors(); } KisConfigWidget *wdg = 0; if (QThread::currentThread() == qApp->thread()) { wdg = filter->createConfigurationWidget(0, from, to); } // Extra checks that cannot be done by the checker, because the checker only has access to the image. if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains assistants. The assistants will not be saved.")); } if (m_document->referenceImagesLayer() && m_document->referenceImagesLayer()->shapeCount() > 0 && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains reference images. The reference images will not be saved.")); } if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains guides. The guides will not be saved.")); } if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains a custom grid configuration. The configuration will not be saved.")); } if (!batchMode && !errors.isEmpty()) { QString error = "

" + i18n("Error: cannot save this image as a %1.", mimeUserDescription) + " Reasons:

" + "

    "; Q_FOREACH(const QString &w, errors) { error += "\n
  • " + w + "
  • "; } error += "
"; QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error); return false; } if (!batchMode && (wdg || !warnings.isEmpty())) { KoDialog dlg; dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); dlg.setWindowTitle(mimeUserDescription); QWidget *page = new QWidget(&dlg); QVBoxLayout *layout = new QVBoxLayout(page); if (showWarnings && !warnings.isEmpty()) { QHBoxLayout *hLayout = new QHBoxLayout(); QLabel *labelWarning = new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hLayout->addWidget(labelWarning); KisPopupButton *bn = new KisPopupButton(0); bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as %1 will lose information from your image. ", mimeUserDescription)); hLayout->addWidget(bn); layout->addLayout(hLayout); QTextBrowser *browser = new QTextBrowser(); browser->setMinimumWidth(bn->width()); bn->setPopupWidget(browser); QString warning = "

" + i18n("You will lose information when saving this image as a %1.", mimeUserDescription); if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); } if (wdg) { QGroupBox *box = new QGroupBox(i18n("Options")); QVBoxLayout *boxLayout = new QVBoxLayout(box); wdg->setConfiguration(exportConfiguration); boxLayout->addWidget(wdg); layout->addWidget(box); } QCheckBox *chkAlsoAsKra = 0; if (showWarnings && !warnings.isEmpty()) { chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file.")); chkAlsoAsKra->setChecked(KisConfig(true).readEntry("AlsoSaveAsKra", false)); layout->addWidget(chkAlsoAsKra); } dlg.setMainWidget(page); dlg.resize(dlg.minimumSize()); if (showWarnings || wdg) { if (!dlg.exec()) { return false; } } *alsoAsKra = false; if (chkAlsoAsKra) { KisConfig(false).writeEntry("AlsoSaveAsKra", chkAlsoAsKra->isChecked()); *alsoAsKra = chkAlsoAsKra->isChecked(); } if (wdg) { *exportConfiguration = *wdg->configuration(); } } return true; } KisImportExportFilter::ConversionStatus KisImportExportManager::doImport(const QString &location, QSharedPointer filter) { QFile file(location); if (!file.exists()) { return KisImportExportFilter::FileNotFound; } if (filter->supportsIO() && !file.open(QFile::ReadOnly)) { return KisImportExportFilter::FileNotFound; } KisImportExportFilter::ConversionStatus status = filter->convert(m_document, &file, KisPropertiesConfigurationSP()); if (file.isOpen()) { file.close(); } return status; } KisImportExportFilter::ConversionStatus KisImportExportManager::doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra) { KisImportExportFilter::ConversionStatus status = doExportImpl(location, filter, exportConfiguration); if (alsoAsKra && status == KisImportExportFilter::OK) { QString kraLocation = location + ".kra"; QByteArray mime = m_document->nativeFormatMimeType(); QSharedPointer filter( filterForMimeType(QString::fromLatin1(mime), Export)); KIS_SAFE_ASSERT_RECOVER_NOOP(filter); if (filter) { filter->setFilename(kraLocation); KisPropertiesConfigurationSP kraExportConfiguration = filter->lastSavedConfiguration(mime, mime); status = doExportImpl(kraLocation, filter, kraExportConfiguration); } else { status = KisImportExportFilter::FilterCreationError; } } return status; } // Temporary workaround until QTBUG-57299 is fixed. #ifndef Q_OS_WIN #define USE_QSAVEFILE #endif KisImportExportFilter::ConversionStatus KisImportExportManager::doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration) { #ifdef USE_QSAVEFILE QSaveFile file(location); file.setDirectWriteFallback(true); if (filter->supportsIO() && !file.open(QFile::WriteOnly)) { #else QFileInfo fi(location); QTemporaryFile file(fi.absolutePath() + ".XXXXXX.kra"); if (filter->supportsIO() && !file.open()) { #endif QString error = file.errorString(); if (error.isEmpty()) { error = i18n("Could not open %1 for writing.", location); } m_document->setErrorMessage(error); #ifdef USE_QSAVEFILE file.cancelWriting(); #endif return KisImportExportFilter::CreationError; } KisImportExportFilter::ConversionStatus status = filter->convert(m_document, &file, exportConfiguration); if (filter->supportsIO()) { if (status != KisImportExportFilter::OK) { #ifdef USE_QSAVEFILE file.cancelWriting(); #endif } else { #ifdef USE_QSAVEFILE if (!file.commit()) { qWarning() << "Could not commit QSaveFile"; QString error = file.errorString(); if (error.isEmpty()) { error = i18n("Could not write to %1.", location); } if (m_document->errorMessage().isEmpty()) { m_document->setErrorMessage(error); } status = KisImportExportFilter::CreationError; } #else file.flush(); file.close(); QFile target(location); if (target.exists()) { // There should already be a .kra~ backup target.remove(); } if (!file.copy(location)) { file.setAutoRemove(false); m_document->setErrorMessage(i18n("Could not copy %1 to its final location %2", file.fileName(), location)); return KisImportExportFilter::CreationError; } #endif } } return status; } #include diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index ffadadb009..88b6629395 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1459 +1,1461 @@ /* * preferencesdlg.cc - part of KImageShop * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_preferences.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 "KoID.h" #include #include #include #include #include #include #include "kis_action_registry.h" #include #include #include "kis_clipboard.h" #include "widgets/kis_cmb_idlist.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "KoColorConversionTransformation.h" #include "kis_cursor.h" #include "kis_config.h" #include "kis_canvas_resource_provider.h" #include "kis_preference_set_registry.h" #include "kis_color_manager.h" #include "KisProofingConfiguration.h" #include "kis_image_config.h" #include "slider_and_spin_box_sync.h" // for the performance update #include #include #include "input/config/kis_input_configuration_page.h" #include "input/wintab/drawpile_tablettester/tablettester.h" #ifdef Q_OS_WIN # include #endif GeneralTab::GeneralTab(QWidget *_parent, const char *_name) : WdgGeneralSettings(_parent, _name) { KisConfig cfg(true); // // Cursor Tab // m_cmbCursorShape->addItem(i18n("No Cursor")); m_cmbCursorShape->addItem(i18n("Tool Icon")); m_cmbCursorShape->addItem(i18n("Arrow")); m_cmbCursorShape->addItem(i18n("Small Circle")); m_cmbCursorShape->addItem(i18n("Crosshair")); m_cmbCursorShape->addItem(i18n("Triangle Righthanded")); m_cmbCursorShape->addItem(i18n("Triangle Lefthanded")); m_cmbCursorShape->addItem(i18n("Black Pixel")); m_cmbCursorShape->addItem(i18n("White Pixel")); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle()); m_cmbOutlineShape->addItem(i18n("No Outline")); m_cmbOutlineShape->addItem(i18n("Circle Outline")); m_cmbOutlineShape->addItem(i18n("Preview Outline")); m_cmbOutlineShape->addItem(i18n("Tilt Outline")); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle()); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting()); m_changeBrushOutline->setChecked(!cfg.forceAlwaysFullSizedOutline()); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor()); cursorColorBtutton->setColor(cursorColor); // // Window Tab // m_cmbMDIType->setCurrentIndex(cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView)); m_backgroundimage->setText(cfg.getMDIBackgroundImage()); connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage())); connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage())); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor()); m_mdiColor->setColor(mdiColor); m_chkRubberBand->setChecked(cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); m_chkHiDPI->setChecked(kritarc.value("EnableHiDPI", true).toBool()); - + chkUsageLogging->setChecked(kritarc.value("LogUsage", true).toBool()); m_chkSingleApplication->setChecked(kritarc.value("EnableSingleApplication", true).toBool()); // // Tools tab // m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker()); cmbFlowMode->setCurrentIndex((int)!cfg.readEntry("useCreamyAlphaDarken", true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt()); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas()); chkEnableTranformToolAfterPaste->setChecked(cfg.activateTransformToolAfterPaste()); m_groupBoxKineticScrollingSettings->setChecked(cfg.kineticScrollingEnabled()); m_cmbKineticScrollingGesture->addItem(i18n("On Touch Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Click Drag")); m_cmbKineticScrollingGesture->addItem(i18n("On Middle-Click Drag")); //m_cmbKineticScrollingGesture->addItem(i18n("On Right Click Drag")); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture()); m_kineticScrollingSensitivitySlider->setRange(0, 100); m_kineticScrollingSensitivitySlider->setValue(cfg.kineticScrollingSensitivity()); m_chkKineticScrollingHideScrollbars->setChecked(cfg.kineticScrollingHiddenScrollbars()); // // Miscellaneous // cmbStartupSession->addItem(i18n("Open default window")); cmbStartupSession->addItem(i18n("Load previous session")); cmbStartupSession->addItem(i18n("Show session manager")); cmbStartupSession->setCurrentIndex(cfg.sessionOnStartup()); chkSaveSessionOnQuit->setChecked(cfg.saveSessionOnQuit(false)); int autosaveInterval = cfg.autoSaveInterval(); //convert to minutes m_autosaveSpinBox->setValue(autosaveInterval / 60); m_autosaveCheckBox->setChecked(autosaveInterval > 0); m_chkCompressKra->setChecked(cfg.compressKra()); chkZip64->setChecked(cfg.useZip64()); m_backupFileCheckBox->setChecked(cfg.backupFile()); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); m_undoStackSize->setValue(cfg.undoStackLimit()); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets()); chkShowRootLayer->setChecked(cfg.showRootLayer()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); bool dontUseNative = true; #ifdef Q_OS_UNIX if (qgetenv("XDG_CURRENT_DESKTOP") == "KDE") { dontUseNative = false; } #endif #ifdef Q_OS_WIN dontUseNative = false; #endif m_chkNativeFileDialog->setChecked(!group.readEntry("DontUseNativeFileDialog", dontUseNative)); intMaxBrushSize->setValue(cfg.readEntry("maximumBrushSize", 1000)); } void GeneralTab::setDefault() { KisConfig cfg(true); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle(true)); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle(true)); chkShowRootLayer->setChecked(cfg.showRootLayer(true)); m_autosaveCheckBox->setChecked(cfg.autoSaveInterval(true) > 0); //convert to minutes m_autosaveSpinBox->setValue(cfg.autoSaveInterval(true) / 60); m_undoStackSize->setValue(cfg.undoStackLimit(true)); m_backupFileCheckBox->setChecked(cfg.backupFile(true)); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting(true)); m_changeBrushOutline->setChecked(!cfg.forceAlwaysFullSizedOutline(true)); m_chkNativeFileDialog->setChecked(false); intMaxBrushSize->setValue(1000); m_cmbMDIType->setCurrentIndex((int)QMdiArea::TabbedView); m_chkRubberBand->setChecked(cfg.useOpenGL(true)); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets(true)); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor(true)); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage(true)); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages(true)); m_chkCompressKra->setChecked(cfg.compressKra(true)); chkZip64->setChecked(cfg.useZip64(true)); m_chkHiDPI->setChecked(false); m_chkSingleApplication->setChecked(true); m_chkHiDPI->setChecked(true); + chkUsageLogging->setChecked(true); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true)); cmbFlowMode->setCurrentIndex(0); m_groupBoxKineticScrollingSettings->setChecked(cfg.kineticScrollingEnabled(true)); m_cmbKineticScrollingGesture->setCurrentIndex(cfg.kineticScrollingGesture(true)); m_kineticScrollingSensitivitySlider->setValue(cfg.kineticScrollingSensitivity(true)); m_chkKineticScrollingHideScrollbars->setChecked(cfg.kineticScrollingHiddenScrollbars(true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true)); chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas(true)); chkEnableTranformToolAfterPaste->setChecked(cfg.activateTransformToolAfterPaste(true)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true)); KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8()); cursorColor.fromQColor(cfg.getCursorMainColor(true)); cursorColorBtutton->setColor(cursorColor); } CursorStyle GeneralTab::cursorStyle() { return (CursorStyle)m_cmbCursorShape->currentIndex(); } OutlineStyle GeneralTab::outlineStyle() { return (OutlineStyle)m_cmbOutlineShape->currentIndex(); } KisConfig::SessionOnStartup GeneralTab::sessionOnStartup() const { return (KisConfig::SessionOnStartup)cmbStartupSession->currentIndex(); } bool GeneralTab::saveSessionOnQuit() const { return chkSaveSessionOnQuit->isChecked(); } bool GeneralTab::showRootLayer() { return chkShowRootLayer->isChecked(); } int GeneralTab::autoSaveInterval() { //convert to seconds return m_autosaveCheckBox->isChecked() ? m_autosaveSpinBox->value() * 60 : 0; } int GeneralTab::undoStackSize() { return m_undoStackSize->value(); } bool GeneralTab::showOutlineWhilePainting() { return m_showOutlinePainting->isChecked(); } int GeneralTab::mdiMode() { return m_cmbMDIType->currentIndex(); } int GeneralTab::favoritePresets() { return m_favoritePresetsSpinBox->value(); } bool GeneralTab::showCanvasMessages() { return m_chkCanvasMessages->isChecked(); } bool GeneralTab::compressKra() { return m_chkCompressKra->isChecked(); } bool GeneralTab::useZip64() { return chkZip64->isChecked(); } bool GeneralTab::toolOptionsInDocker() { return m_radioToolOptionsInDocker->isChecked(); } bool GeneralTab::kineticScrollingEnabled() { return m_groupBoxKineticScrollingSettings->isChecked(); } int GeneralTab::kineticScrollingGesture() { return m_cmbKineticScrollingGesture->currentIndex(); } int GeneralTab::kineticScrollingSensitivity() { return m_kineticScrollingSensitivitySlider->value(); } bool GeneralTab::kineticScrollingHiddenScrollbars() { return m_chkKineticScrollingHideScrollbars->isChecked(); } bool GeneralTab::switchSelectionCtrlAlt() { return m_chkSwitchSelectionCtrlAlt->isChecked(); } bool GeneralTab::convertToImageColorspaceOnImport() { return m_chkConvertOnImport->isChecked(); } void GeneralTab::getBackgroundImage() { KoFileDialog dialog(this, KoFileDialog::OpenFile, "BackgroundImages"); dialog.setCaption(i18n("Select a Background Image")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setImageFilters(); QString fn = dialog.filename(); // dialog box was canceled or somehow no file was selected if (fn.isEmpty()) { return; } QImage image(fn); if (image.isNull()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn)); } else { m_backgroundimage->setText(fn); } } void GeneralTab::clearBackgroundImage() { // clearing the background image text will implicitly make the background color be used m_backgroundimage->setText(""); } #include "kactioncollection.h" #include "KisActionsSnapshot.h" ShortcutSettingsTab::ShortcutSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgShortcutSettings(this); l->addWidget(m_page, 0, 0); m_snapshot.reset(new KisActionsSnapshot); KActionCollection *collection = KisPart::instance()->currentMainwindow()->actionCollection(); Q_FOREACH (QAction *action, collection->actions()) { m_snapshot->addAction(action->objectName(), action); } QMap sortedCollections = m_snapshot->actionCollections(); for (auto it = sortedCollections.constBegin(); it != sortedCollections.constEnd(); ++it) { m_page->addCollection(it.value(), it.key()); } } ShortcutSettingsTab::~ShortcutSettingsTab() { } void ShortcutSettingsTab::setDefault() { m_page->allDefault(); } void ShortcutSettingsTab::saveChanges() { m_page->save(); KisActionRegistry::instance()->settingsPageSaved(); } void ShortcutSettingsTab::cancelChanges() { m_page->undo(); } ColorSettingsTab::ColorSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); // XXX: Make sure only profiles that fit the specified color model // are shown in the profile combos QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgColorSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg(true); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile()); connect(m_page->chkUseSystemMonitorProfile, SIGNAL(toggled(bool)), this, SLOT(toggleAllowMonitorProfileSelection(bool))); m_page->cmbWorkingColorSpace->setIDList(KoColorSpaceRegistry::instance()->listKeys()); m_page->cmbWorkingColorSpace->setCurrent(cfg.workingColorSpace()); m_page->bnAddColorProfile->setIcon(KisIconUtils::loadIcon("document-open")); m_page->bnAddColorProfile->setToolTip( i18n("Open Color Profile") ); connect(m_page->bnAddColorProfile, SIGNAL(clicked()), SLOT(installProfile())); QFormLayout *monitorProfileGrid = new QFormLayout(m_page->monitorprofileholder); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { QLabel *lbl = new QLabel(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileLabels << lbl; SqueezedComboBox *cmb = new SqueezedComboBox(); cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); monitorProfileGrid->addRow(lbl, cmb); m_monitorProfileWidgets << cmb; } refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation()); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization()); m_page->chkForcePaletteColor->setChecked(cfg.forcePaletteColors()); KisImageConfig cfgImage(true); KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); m_page->sldAdaptationState->setMaximum(20); m_page->sldAdaptationState->setMinimum(0); m_page->sldAdaptationState->setValue((int)proofingConfig->adaptationState*20); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_pasteBehaviourGroup.addButton(m_page->radioPasteWeb, PASTE_ASSUME_WEB); m_pasteBehaviourGroup.addButton(m_page->radioPasteMonitor, PASTE_ASSUME_MONITOR); m_pasteBehaviourGroup.addButton(m_page->radioPasteAsk, PASTE_ASK); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour()); Q_ASSERT(button); if (button) { button->setChecked(true); } m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent()); toggleAllowMonitorProfileSelection(cfg.useSystemMonitorProfile()); } void ColorSettingsTab::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { if (!QFile::copy(profileName, saveLocation + QFileInfo(profileName).fileName())) { qWarning() << "Could not install profile!" << saveLocation + QFileInfo(profileName).fileName(); continue; } iccEngine->addProfile(saveLocation + QFileInfo(profileName).fileName()); } KisConfig cfg(true); refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } void ColorSettingsTab::toggleAllowMonitorProfileSelection(bool useSystemProfile) { KisConfig cfg(true); if (useSystemProfile) { QStringList devices = KisColorManager::instance()->devices(); if (devices.size() == QApplication::desktop()->screenCount()) { for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); QString monitorForScreen = cfg.monitorForScreen(i, devices[i]); Q_FOREACH (const QString &device, devices) { m_monitorProfileLabels[i]->setText(i18nc("The display/screen we got from Qt", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->addSqueezedItem(KisColorManager::instance()->deviceName(device), device); if (devices[i] == monitorForScreen) { m_monitorProfileWidgets[i]->setCurrentIndex(i); } } } } } else { refillMonitorProfiles(KoID("RGBA")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } } void ColorSettingsTab::setDefault() { m_page->cmbWorkingColorSpace->setCurrent("RGBA"); refillMonitorProfiles(KoID("RGBA")); KisConfig cfg(true); KisImageConfig cfgImage(true); KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,proofingConfig->proofingDepth,proofingConfig->proofingProfile); if (proofingSpace) { m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); } m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_page->sldAdaptationState->setValue(0); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation(true)); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization(true)); m_page->chkForcePaletteColor->setChecked(cfg.forcePaletteColors(true)); m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent(true)); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile(true)); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour(true)); Q_ASSERT(button); if (button) { button->setChecked(true); } } void ColorSettingsTab::refillMonitorProfiles(const KoID & colorSpaceId) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); } QMap profileList; Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId.id())) { profileList[profile->name()] = profile; } Q_FOREACH (const KoColorProfile *profile, profileList.values()) { //qDebug() << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile(); if (profile->isSuitableForDisplay()) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->addSqueezedItem(profile->name()); } } } for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileLabels[i]->setText(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->setCurrent(KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId.id())); } } //--------------------------------------------------------------------------------------------------- void TabletSettingsTab::setDefault() { KisCubicCurve curve; curve.fromString(DEFAULT_CURVE_STRING); m_page->pressureCurve->setCurve(curve); #ifdef Q_OS_WIN if (KisTabletSupportWin8::isAvailable()) { KisConfig cfg(true); m_page->radioWintab->setChecked(!cfg.useWin8PointerInput(true)); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput(true)); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); } #endif } TabletSettingsTab::TabletSettingsTab(QWidget* parent, const char* name): QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgTabletSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg(true); KisCubicCurve curve; curve.fromString( cfg.pressureTabletCurve() ); m_page->pressureCurve->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_page->pressureCurve->setCurve(curve); #ifdef Q_OS_WIN if (KisTabletSupportWin8::isAvailable()) { m_page->radioWintab->setChecked(!cfg.useWin8PointerInput()); m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput()); } else { m_page->radioWintab->setChecked(true); m_page->radioWin8PointerInput->setChecked(false); m_page->grpTabletApi->setVisible(false); } #else m_page->grpTabletApi->setVisible(false); #endif connect(m_page->btnTabletTest, SIGNAL(clicked()), SLOT(slotTabletTest())); } void TabletSettingsTab::slotTabletTest() { TabletTestDialog tabletTestDialog(this); tabletTestDialog.exec(); } //--------------------------------------------------------------------------------------------------- #include "kis_acyclic_signal_connector.h" int getTotalRAM() { return KisImageConfig(true).totalRAM(); } int PerformanceTab::realTilesRAM() { return intMemoryLimit->value() - intPoolLimit->value(); } PerformanceTab::PerformanceTab(QWidget *parent, const char *name) : WdgPerformanceSettings(parent, name) { KisImageConfig cfg(true); const double totalRAM = cfg.totalRAM(); lblTotalMemory->setText(KFormat().formatByteSize(totalRAM * 1024 * 1024, 0, KFormat::IECBinaryDialect, KFormat::UnitMegaByte)); sliderMemoryLimit->setSuffix(i18n(" %")); sliderMemoryLimit->setRange(1, 100, 2); sliderMemoryLimit->setSingleStep(0.01); sliderPoolLimit->setSuffix(i18n(" %")); sliderPoolLimit->setRange(0, 20, 2); sliderMemoryLimit->setSingleStep(0.01); sliderUndoLimit->setSuffix(i18n(" %")); sliderUndoLimit->setRange(0, 50, 2); sliderMemoryLimit->setSingleStep(0.01); intMemoryLimit->setMinimumWidth(80); intPoolLimit->setMinimumWidth(80); intUndoLimit->setMinimumWidth(80); SliderAndSpinBoxSync *sync1 = new SliderAndSpinBoxSync(sliderMemoryLimit, intMemoryLimit, getTotalRAM); sync1->slotParentValueChanged(); m_syncs << sync1; SliderAndSpinBoxSync *sync2 = new SliderAndSpinBoxSync(sliderPoolLimit, intPoolLimit, std::bind(&KisIntParseSpinBox::value, intMemoryLimit)); connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync2, SLOT(slotParentValueChanged())); sync2->slotParentValueChanged(); m_syncs << sync2; SliderAndSpinBoxSync *sync3 = new SliderAndSpinBoxSync(sliderUndoLimit, intUndoLimit, std::bind(&PerformanceTab::realTilesRAM, this)); connect(intPoolLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged())); sync3->slotParentValueChanged(); m_syncs << sync3; sliderSwapSize->setSuffix(i18n(" GiB")); sliderSwapSize->setRange(1, 64); intSwapSize->setRange(1, 64); KisAcyclicSignalConnector *swapSizeConnector = new KisAcyclicSignalConnector(this); swapSizeConnector->connectForwardInt(sliderSwapSize, SIGNAL(valueChanged(int)), intSwapSize, SLOT(setValue(int))); swapSizeConnector->connectBackwardInt(intSwapSize, SIGNAL(valueChanged(int)), sliderSwapSize, SLOT(setValue(int))); lblSwapFileLocation->setText(cfg.swapDir()); connect(bnSwapFile, SIGNAL(clicked()), SLOT(selectSwapDir())); sliderThreadsLimit->setRange(1, QThread::idealThreadCount()); sliderFrameClonesLimit->setRange(1, QThread::idealThreadCount()); sliderFpsLimit->setRange(20, 100); sliderFpsLimit->setSuffix(i18n(" fps")); connect(sliderThreadsLimit, SIGNAL(valueChanged(int)), SLOT(slotThreadsLimitChanged(int))); connect(sliderFrameClonesLimit, SIGNAL(valueChanged(int)), SLOT(slotFrameClonesLimitChanged(int))); intCachedFramesSizeLimit->setRange(1, 10000); intCachedFramesSizeLimit->setSuffix(i18n(" px")); intCachedFramesSizeLimit->setSingleStep(1); intCachedFramesSizeLimit->setPageStep(1000); intRegionOfInterestMargin->setRange(1, 100); intRegionOfInterestMargin->setSuffix(i18n(" %")); intRegionOfInterestMargin->setSingleStep(1); intRegionOfInterestMargin->setPageStep(10); connect(chkCachedFramesSizeLimit, SIGNAL(toggled(bool)), intCachedFramesSizeLimit, SLOT(setEnabled(bool))); connect(chkUseRegionOfInterest, SIGNAL(toggled(bool)), intRegionOfInterestMargin, SLOT(setEnabled(bool))); load(false); } PerformanceTab::~PerformanceTab() { qDeleteAll(m_syncs); } void PerformanceTab::load(bool requestDefault) { KisImageConfig cfg(true); sliderMemoryLimit->setValue(cfg.memoryHardLimitPercent(requestDefault)); sliderPoolLimit->setValue(cfg.memoryPoolLimitPercent(requestDefault)); sliderUndoLimit->setValue(cfg.memorySoftLimitPercent(requestDefault)); chkPerformanceLogging->setChecked(cfg.enablePerfLog(requestDefault)); chkProgressReporting->setChecked(cfg.enableProgressReporting(requestDefault)); sliderSwapSize->setValue(cfg.maxSwapSize(requestDefault) / 1024); lblSwapFileLocation->setText(cfg.swapDir(requestDefault)); m_lastUsedThreadsLimit = cfg.maxNumberOfThreads(requestDefault); m_lastUsedClonesLimit = cfg.frameRenderingClones(requestDefault); sliderThreadsLimit->setValue(m_lastUsedThreadsLimit); sliderFrameClonesLimit->setValue(m_lastUsedClonesLimit); sliderFpsLimit->setValue(cfg.fpsLimit(requestDefault)); { KisConfig cfg2(true); chkOpenGLFramerateLogging->setChecked(cfg2.enableOpenGLFramerateLogging(requestDefault)); chkBrushSpeedLogging->setChecked(cfg2.enableBrushSpeedLogging(requestDefault)); chkDisableVectorOptimizations->setChecked(cfg2.enableAmdVectorizationWorkaround(requestDefault)); chkBackgroundCacheGeneration->setChecked(cfg2.calculateAnimationCacheInBackground(requestDefault)); } if (cfg.useOnDiskAnimationCacheSwapping(requestDefault)) { optOnDisk->setChecked(true); } else { optInMemory->setChecked(true); } chkCachedFramesSizeLimit->setChecked(cfg.useAnimationCacheFrameSizeLimit(requestDefault)); intCachedFramesSizeLimit->setValue(cfg.animationCacheFrameSizeLimit(requestDefault)); intCachedFramesSizeLimit->setEnabled(chkCachedFramesSizeLimit->isChecked()); chkUseRegionOfInterest->setChecked(cfg.useAnimationCacheRegionOfInterest(requestDefault)); intRegionOfInterestMargin->setValue(cfg.animationCacheRegionOfInterestMargin(requestDefault) * 100.0); intRegionOfInterestMargin->setEnabled(chkUseRegionOfInterest->isChecked()); } void PerformanceTab::save() { KisImageConfig cfg(false); cfg.setMemoryHardLimitPercent(sliderMemoryLimit->value()); cfg.setMemorySoftLimitPercent(sliderUndoLimit->value()); cfg.setMemoryPoolLimitPercent(sliderPoolLimit->value()); cfg.setEnablePerfLog(chkPerformanceLogging->isChecked()); cfg.setEnableProgressReporting(chkProgressReporting->isChecked()); cfg.setMaxSwapSize(sliderSwapSize->value() * 1024); cfg.setSwapDir(lblSwapFileLocation->text()); cfg.setMaxNumberOfThreads(sliderThreadsLimit->value()); cfg.setFrameRenderingClones(sliderFrameClonesLimit->value()); cfg.setFpsLimit(sliderFpsLimit->value()); { KisConfig cfg2(true); cfg2.setEnableOpenGLFramerateLogging(chkOpenGLFramerateLogging->isChecked()); cfg2.setEnableBrushSpeedLogging(chkBrushSpeedLogging->isChecked()); cfg2.setEnableAmdVectorizationWorkaround(chkDisableVectorOptimizations->isChecked()); cfg2.setCalculateAnimationCacheInBackground(chkBackgroundCacheGeneration->isChecked()); } cfg.setUseOnDiskAnimationCacheSwapping(optOnDisk->isChecked()); cfg.setUseAnimationCacheFrameSizeLimit(chkCachedFramesSizeLimit->isChecked()); cfg.setAnimationCacheFrameSizeLimit(intCachedFramesSizeLimit->value()); cfg.setUseAnimationCacheRegionOfInterest(chkUseRegionOfInterest->isChecked()); cfg.setAnimationCacheRegionOfInterestMargin(intRegionOfInterestMargin->value() / 100.0); } void PerformanceTab::selectSwapDir() { KisImageConfig cfg(true); QString swapDir = cfg.swapDir(); swapDir = QFileDialog::getExistingDirectory(0, i18nc("@title:window", "Select a swap directory"), swapDir); if (swapDir.isEmpty()) { return; } lblSwapFileLocation->setText(swapDir); } void PerformanceTab::slotThreadsLimitChanged(int value) { KisSignalsBlocker b(sliderFrameClonesLimit); sliderFrameClonesLimit->setValue(qMin(m_lastUsedClonesLimit, value)); m_lastUsedThreadsLimit = value; } void PerformanceTab::slotFrameClonesLimitChanged(int value) { KisSignalsBlocker b(sliderThreadsLimit); sliderThreadsLimit->setValue(qMax(m_lastUsedThreadsLimit, value)); m_lastUsedClonesLimit = value; } //--------------------------------------------------------------------------------------------------- #include "KoColor.h" DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name) : WdgDisplaySettings(parent, name) { KisConfig cfg(true); const QString rendererOpenGLText = i18nc("canvas renderer", "OpenGL"); #ifdef Q_OS_WIN const QString rendererAngleText = i18nc("canvas renderer", "Direct3D 11 via ANGLE"); cmbRenderer->clear(); QString qtPreferredRendererText; if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererAngle) { qtPreferredRendererText = rendererAngleText; } else { qtPreferredRendererText = rendererOpenGLText; } cmbRenderer->addItem(i18nc("canvas renderer", "Auto (%1)", qtPreferredRendererText), KisOpenGL::RendererAuto); cmbRenderer->setCurrentIndex(0); if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererDesktopGL) { cmbRenderer->addItem(rendererOpenGLText, KisOpenGL::RendererDesktopGL); if (KisOpenGL::getNextUserOpenGLRendererConfig() == KisOpenGL::RendererDesktopGL) { cmbRenderer->setCurrentIndex(cmbRenderer->count() - 1); } } if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererAngle) { cmbRenderer->addItem(rendererAngleText, KisOpenGL::RendererAngle); if (KisOpenGL::getNextUserOpenGLRendererConfig() == KisOpenGL::RendererAngle) { cmbRenderer->setCurrentIndex(cmbRenderer->count() - 1); } } #else lblRenderer->setEnabled(false); cmbRenderer->setEnabled(false); cmbRenderer->clear(); cmbRenderer->addItem(rendererOpenGLText); cmbRenderer->setCurrentIndex(0); #endif #ifdef Q_OS_WIN if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererAngle))) { #else if (!KisOpenGL::hasOpenGL()) { #endif grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL()); chkUseTextureBuffer->setEnabled(cfg.useOpenGL()); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer()); chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings()); chkDisableVsync->setEnabled(cfg.useOpenGL()); chkDisableVsync->setChecked(cfg.disableVSync()); cmbFilterMode->setEnabled(cfg.useOpenGL()); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode()); // Don't show the high quality filtering mode if it's not available if (!KisOpenGL::supportsLoD()) { cmbFilterMode->removeItem(3); } } const QStringList openglWarnings = KisOpenGL::getOpenGLWarnings(); if (openglWarnings.isEmpty()) { lblOpenGLWarnings->setVisible(false); } else { QString text(" "); text.append(i18n("Warning(s):")); text.append("
    "); Q_FOREACH (const QString &warning, openglWarnings) { text.append("
  • "); text.append(warning.toHtmlEscaped()); text.append("
  • "); } text.append("
"); lblOpenGLWarnings->setText(text); lblOpenGLWarnings->setVisible(true); } if (qApp->applicationName() == "kritasketch" || qApp->applicationName() == "kritagemini") { grpOpenGL->setVisible(false); grpOpenGL->setMaximumHeight(0); } KisImageConfig imageCfg(false); KoColor c; c.fromQColor(imageCfg.selectionOverlayMaskColor()); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2); sldSelectionOverlayOpacity->setSingleStep(0.05); sldSelectionOverlayOpacity->setValue(imageCfg.selectionOverlayMaskColor().alphaF()); intCheckSize->setValue(cfg.checkSize()); chkMoving->setChecked(cfg.scrollCheckers()); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1()); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2()); colorChecks2->setColor(ck2); KoColor cb(KoColorSpaceRegistry::instance()->rgb8()); cb.fromQColor(cfg.canvasBorderColor()); canvasBorder->setColor(cb); hideScrollbars->setChecked(cfg.hideScrollbars()); chkCurveAntialiasing->setChecked(cfg.antialiasCurves()); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline()); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor()); chkHidePopups->setChecked(cfg.hidePopups()); connect(grpOpenGL, SIGNAL(toggled(bool)), SLOT(slotUseOpenGLToggled(bool))); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor()); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold() * 100); } void DisplaySettingsTab::setDefault() { KisConfig cfg(true); cmbRenderer->setCurrentIndex(0); #ifdef Q_OS_WIN if (!(KisOpenGL::getSupportedOpenGLRenderers() & (KisOpenGL::RendererDesktopGL | KisOpenGL::RendererAngle))) { #else if (!KisOpenGL::hasOpenGL()) { #endif grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL(true)); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true)); chkUseTextureBuffer->setEnabled(true); chkDisableVsync->setEnabled(true); chkDisableVsync->setChecked(cfg.disableVSync(true)); cmbFilterMode->setEnabled(true); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode(true)); } chkMoving->setChecked(cfg.scrollCheckers(true)); intCheckSize->setValue(cfg.checkSize(true)); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1(true)); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2(true)); colorChecks2->setColor(ck2); KoColor cvb(KoColorSpaceRegistry::instance()->rgb8()); cvb.fromQColor(cfg.canvasBorderColor(true)); canvasBorder->setColor(cvb); hideScrollbars->setChecked(cfg.hideScrollbars(true)); chkCurveAntialiasing->setChecked(cfg.antialiasCurves(true)); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline(true)); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor(true)); chkHidePopups->setChecked(cfg.hidePopups(true)); KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8()); gridColor.fromQColor(cfg.getPixelGridColor(true)); pixelGridColorButton->setColor(gridColor); pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold(true) * 100); } void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked) { chkUseTextureBuffer->setEnabled(isChecked); chkDisableVsync->setEnabled(isChecked); cmbFilterMode->setEnabled(isChecked); } //--------------------------------------------------------------------------------------------------- FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent) { KisConfig cfg(true); chkDockers->setChecked(cfg.hideDockersFullscreen()); chkMenu->setChecked(cfg.hideMenuFullscreen()); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen()); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen()); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen()); chkToolbar->setChecked(cfg.hideToolbarFullscreen()); } void FullscreenSettingsTab::setDefault() { KisConfig cfg(true); chkDockers->setChecked(cfg.hideDockersFullscreen(true)); chkMenu->setChecked(cfg.hideMenuFullscreen(true)); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen(true)); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen(true)); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen(true)); chkToolbar->setChecked(cfg.hideToolbarFullscreen(true)); } //--------------------------------------------------------------------------------------------------- KisDlgPreferences::KisDlgPreferences(QWidget* parent, const char* name) : KPageDialog(parent) { Q_UNUSED(name); setWindowTitle(i18n("Configure Krita")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); setFaceType(KPageDialog::Tree); // General KoVBox *vbox = new KoVBox(); KPageWidgetItem *page = new KPageWidgetItem(vbox, i18n("General")); page->setObjectName("general"); page->setHeader(i18n("General")); page->setIcon(KisIconUtils::loadIcon("go-home")); m_pages << page; addPage(page); m_general = new GeneralTab(vbox); // Shortcuts vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Keyboard Shortcuts")); page->setObjectName("shortcuts"); page->setHeader(i18n("Shortcuts")); page->setIcon(KisIconUtils::loadIcon("document-export")); m_pages << page; addPage(page); m_shortcutSettings = new ShortcutSettingsTab(vbox); connect(this, SIGNAL(accepted()), m_shortcutSettings, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_shortcutSettings, SLOT(cancelChanges())); // Canvas input settings m_inputConfiguration = new KisInputConfigurationPage(); page = addPage(m_inputConfiguration, i18n("Canvas Input Settings")); page->setHeader(i18n("Canvas Input")); page->setObjectName("canvasinput"); page->setIcon(KisIconUtils::loadIcon("configure")); m_pages << page; // Display vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Display")); page->setObjectName("display"); page->setHeader(i18n("Display")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display")); m_pages << page; addPage(page); m_displaySettings = new DisplaySettingsTab(vbox); // Color vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Color Management")); page->setObjectName("colormanagement"); page->setHeader(i18n("Color")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-color")); m_pages << page; addPage(page); m_colorSettings = new ColorSettingsTab(vbox); // Performance vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Performance")); page->setObjectName("performance"); page->setHeader(i18n("Performance")); page->setIcon(KisIconUtils::loadIcon("applications-system")); m_pages << page; addPage(page); m_performanceSettings = new PerformanceTab(vbox); // Tablet vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Tablet settings")); page->setObjectName("tablet"); page->setHeader(i18n("Tablet")); page->setIcon(KisIconUtils::loadIcon("document-edit")); m_pages << page; addPage(page); m_tabletSettings = new TabletSettingsTab(vbox); // full-screen mode vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Canvas-only settings")); page->setObjectName("canvasonly"); page->setHeader(i18n("Canvas-only")); page->setIcon(KisIconUtils::loadIcon("folder-pictures")); m_pages << page; addPage(page); m_fullscreenSettings = new FullscreenSettingsTab(vbox); // Author profiles m_authorPage = new KoConfigAuthorPage(); page = addPage(m_authorPage, i18nc("@title:tab Author page", "Author" )); page->setObjectName("author"); page->setHeader(i18n("Author")); page->setIcon(KisIconUtils::loadIcon("im-user")); m_pages << page; QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults); restoreDefaultsButton->setText(i18nc("@action:button", "Restore Defaults")); connect(this, SIGNAL(accepted()), m_inputConfiguration, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_inputConfiguration, SLOT(revertChanges())); KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); Q_FOREACH (KisAbstractPreferenceSetFactory *preferenceSetFactory, preferenceSetRegistry->values()) { KisPreferenceSet* preferenceSet = preferenceSetFactory->createPreferenceSet(); vbox = new KoVBox(); page = new KPageWidgetItem(vbox, preferenceSet->name()); page->setHeader(preferenceSet->header()); page->setIcon(preferenceSet->icon()); addPage(page); preferenceSet->setParent(vbox); preferenceSet->loadPreferences(); connect(restoreDefaultsButton, SIGNAL(clicked(bool)), preferenceSet, SLOT(loadDefaultPreferences()), Qt::UniqueConnection); connect(this, SIGNAL(accepted()), preferenceSet, SLOT(savePreferences()), Qt::UniqueConnection); } connect(restoreDefaultsButton, SIGNAL(clicked(bool)), this, SLOT(slotDefault())); KisConfig cfg(true); QString currentPageName = cfg.readEntry("KisDlgPreferences/CurrentPage"); Q_FOREACH(KPageWidgetItem *page, m_pages) { if (page->objectName() == currentPageName) { setCurrentPage(page); break; } } } KisDlgPreferences::~KisDlgPreferences() { KisConfig cfg(true); cfg.writeEntry("KisDlgPreferences/CurrentPage", currentPage()->objectName()); } void KisDlgPreferences::showEvent(QShowEvent *event){ KPageDialog::showEvent(event); button(QDialogButtonBox::Cancel)->setAutoDefault(false); button(QDialogButtonBox::Ok)->setAutoDefault(false); button(QDialogButtonBox::RestoreDefaults)->setAutoDefault(false); button(QDialogButtonBox::Cancel)->setDefault(false); button(QDialogButtonBox::Ok)->setDefault(false); button(QDialogButtonBox::RestoreDefaults)->setDefault(false); } void KisDlgPreferences::slotDefault() { if (currentPage()->objectName() == "general") { m_general->setDefault(); } else if (currentPage()->objectName() == "shortcuts") { m_shortcutSettings->setDefault(); } else if (currentPage()->objectName() == "display") { m_displaySettings->setDefault(); } else if (currentPage()->objectName() == "colormanagement") { m_colorSettings->setDefault(); } else if (currentPage()->objectName() == "performance") { m_performanceSettings->load(true); } else if (currentPage()->objectName() == "tablet") { m_tabletSettings->setDefault(); } else if (currentPage()->objectName() == "canvasonly") { m_fullscreenSettings->setDefault(); } else if (currentPage()->objectName() == "canvasinput") { m_inputConfiguration->setDefaults(); } } bool KisDlgPreferences::editPreferences() { KisDlgPreferences* dialog; dialog = new KisDlgPreferences(); bool baccept = (dialog->exec() == Accepted); if (baccept) { // General settings KisConfig cfg(false); cfg.setNewCursorStyle(dialog->m_general->cursorStyle()); cfg.setNewOutlineStyle(dialog->m_general->outlineStyle()); cfg.setShowRootLayer(dialog->m_general->showRootLayer()); cfg.setShowOutlineWhilePainting(dialog->m_general->showOutlineWhilePainting()); cfg.setForceAlwaysFullSizedOutline(!dialog->m_general->m_changeBrushOutline->isChecked()); cfg.setSessionOnStartup(dialog->m_general->sessionOnStartup()); cfg.setSaveSessionOnQuit(dialog->m_general->saveSessionOnQuit()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); group.writeEntry("DontUseNativeFileDialog", !dialog->m_general->m_chkNativeFileDialog->isChecked()); cfg.writeEntry("maximumBrushSize", dialog->m_general->intMaxBrushSize->value()); cfg.writeEntry("mdi_viewmode", dialog->m_general->mdiMode()); cfg.setMDIBackgroundColor(dialog->m_general->m_mdiColor->color().toQColor()); cfg.setMDIBackgroundImage(dialog->m_general->m_backgroundimage->text()); cfg.setAutoSaveInterval(dialog->m_general->autoSaveInterval()); cfg.setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked()); cfg.setShowCanvasMessages(dialog->m_general->showCanvasMessages()); cfg.setCompressKra(dialog->m_general->compressKra()); cfg.setUseZip64(dialog->m_general->useZip64()); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("EnableHiDPI", dialog->m_general->m_chkHiDPI->isChecked()); kritarc.setValue("EnableSingleApplication", dialog->m_general->m_chkSingleApplication->isChecked()); + kritarc.setValue("LogUsage", dialog->m_general->chkUsageLogging->isChecked()); cfg.setToolOptionsInDocker(dialog->m_general->toolOptionsInDocker()); cfg.writeEntry("useCreamyAlphaDarken", (bool)!dialog->m_general->cmbFlowMode->currentIndex()); cfg.setKineticScrollingEnabled(dialog->m_general->kineticScrollingEnabled()); cfg.setKineticScrollingGesture(dialog->m_general->kineticScrollingGesture()); cfg.setKineticScrollingSensitivity(dialog->m_general->kineticScrollingSensitivity()); cfg.setKineticScrollingHideScrollbars(dialog->m_general->kineticScrollingHiddenScrollbars()); cfg.setSwitchSelectionCtrlAlt(dialog->m_general->switchSelectionCtrlAlt()); cfg.setDisableTouchOnCanvas(!dialog->m_general->chkEnableTouch->isChecked()); cfg.setActivateTransformToolAfterPaste(dialog->m_general->chkEnableTranformToolAfterPaste->isChecked()); cfg.setConvertToImageColorspaceOnImport(dialog->m_general->convertToImageColorspaceOnImport()); cfg.setUndoStackLimit(dialog->m_general->undoStackSize()); cfg.setFavoritePresets(dialog->m_general->favoritePresets()); // Color settings cfg.setUseSystemMonitorProfile(dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()) { int currentIndex = dialog->m_colorSettings->m_monitorProfileWidgets[i]->currentIndex(); QString monitorid = dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemData(currentIndex).toString(); cfg.setMonitorForScreen(i, monitorid); } else { cfg.setMonitorProfile(i, dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemHighlighted(), dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); } } cfg.setWorkingColorSpace(dialog->m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id()); KisImageConfig cfgImage(false); cfgImage.setDefaultProofingConfig(dialog->m_colorSettings->m_page->proofingSpaceSelector->currentColorSpace(), dialog->m_colorSettings->m_page->cmbProofingIntent->currentIndex(), dialog->m_colorSettings->m_page->ckbProofBlackPoint->isChecked(), dialog->m_colorSettings->m_page->gamutAlarm->color(), (double)dialog->m_colorSettings->m_page->sldAdaptationState->value()/20); cfg.setUseBlackPointCompensation(dialog->m_colorSettings->m_page->chkBlackpoint->isChecked()); cfg.setAllowLCMSOptimization(dialog->m_colorSettings->m_page->chkAllowLCMSOptimization->isChecked()); cfg.setForcePaletteColors(dialog->m_colorSettings->m_page->chkForcePaletteColor->isChecked()); cfg.setPasteBehaviour(dialog->m_colorSettings->m_pasteBehaviourGroup.checkedId()); cfg.setRenderIntent(dialog->m_colorSettings->m_page->cmbMonitorIntent->currentIndex()); // Tablet settings cfg.setPressureTabletCurve( dialog->m_tabletSettings->m_page->pressureCurve->curve().toString() ); #ifdef Q_OS_WIN if (KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(dialog->m_tabletSettings->m_page->radioWin8PointerInput->isChecked()); } #endif dialog->m_performanceSettings->save(); #ifdef Q_OS_WIN { KisOpenGL::OpenGLRenderer renderer = static_cast( dialog->m_displaySettings->cmbRenderer->itemData( dialog->m_displaySettings->cmbRenderer->currentIndex()).toInt()); KisOpenGL::setNextUserOpenGLRendererConfig(renderer); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("OpenGLRenderer", KisOpenGL::convertOpenGLRendererToConfig(renderer)); } #endif if (!cfg.useOpenGL() && dialog->m_displaySettings->grpOpenGL->isChecked()) cfg.setCanvasState("TRY_OPENGL"); cfg.setUseOpenGL(dialog->m_displaySettings->grpOpenGL->isChecked()); cfg.setUseOpenGLTextureBuffer(dialog->m_displaySettings->chkUseTextureBuffer->isChecked()); cfg.setOpenGLFilteringMode(dialog->m_displaySettings->cmbFilterMode->currentIndex()); cfg.setDisableVSync(dialog->m_displaySettings->chkDisableVsync->isChecked()); cfg.setCheckSize(dialog->m_displaySettings->intCheckSize->value()); cfg.setScrollingCheckers(dialog->m_displaySettings->chkMoving->isChecked()); cfg.setCheckersColor1(dialog->m_displaySettings->colorChecks1->color().toQColor()); cfg.setCheckersColor2(dialog->m_displaySettings->colorChecks2->color().toQColor()); cfg.setCanvasBorderColor(dialog->m_displaySettings->canvasBorder->color().toQColor()); cfg.setHideScrollbars(dialog->m_displaySettings->hideScrollbars->isChecked()); KoColor c = dialog->m_displaySettings->btnSelectionOverlayColor->color(); c.setOpacity(dialog->m_displaySettings->sldSelectionOverlayOpacity->value()); cfgImage.setSelectionOverlayMaskColor(c.toQColor()); cfg.setAntialiasCurves(dialog->m_displaySettings->chkCurveAntialiasing->isChecked()); cfg.setAntialiasSelectionOutline(dialog->m_displaySettings->chkSelectionOutlineAntialiasing->isChecked()); cfg.setShowSingleChannelAsColor(dialog->m_displaySettings->chkChannelsAsColor->isChecked()); cfg.setHidePopups(dialog->m_displaySettings->chkHidePopups->isChecked()); cfg.setHideDockersFullscreen(dialog->m_fullscreenSettings->chkDockers->checkState()); cfg.setHideMenuFullscreen(dialog->m_fullscreenSettings->chkMenu->checkState()); cfg.setHideScrollbarsFullscreen(dialog->m_fullscreenSettings->chkScrollbars->checkState()); cfg.setHideStatusbarFullscreen(dialog->m_fullscreenSettings->chkStatusbar->checkState()); cfg.setHideTitlebarFullscreen(dialog->m_fullscreenSettings->chkTitlebar->checkState()); cfg.setHideToolbarFullscreen(dialog->m_fullscreenSettings->chkToolbar->checkState()); cfg.setCursorMainColor(dialog->m_general->cursorColorBtutton->color().toQColor()); cfg.setPixelGridColor(dialog->m_displaySettings->pixelGridColorButton->color().toQColor()); cfg.setPixelGridDrawingThreshold(dialog->m_displaySettings->pixelGridDrawingThresholdBox->value() / 100); dialog->m_authorPage->apply(); } delete dialog; return baccept; } diff --git a/libs/ui/forms/wdggeneralsettings.ui b/libs/ui/forms/wdggeneralsettings.ui index 129061ff29..00abf30b18 100644 --- a/libs/ui/forms/wdggeneralsettings.ui +++ b/libs/ui/forms/wdggeneralsettings.ui @@ -1,819 +1,836 @@ WdgGeneralSettings 0 0 759 468 0 0 552 295 0 Cursor 10 10 10 10 10 10 0 0 Cursor Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Outline Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter While painting... 3 9 3 3 0 0 200 0 Show outline Use effective outline size Cursor Color: 48 25 Qt::Vertical 20 40 Window 0 0 Multiple Document Mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 1 Subwindows Tabs Background Image (overrides color): 200 0 QFrame::StyledPanel QFrame::Sunken ... 0 0 Clear Window Background: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Qt::Vertical 0 18 General: 0 0 Don't show contents when moving sub-windows Show on-canvas popup messages Enable Hi-DPI support Allow only one instance of Krita Qt::Vertical 20 40 Tools Tool Options Location (needs restart) In Doc&ker I&n Toolbar true Brush Flow Mode (needs restart): Creamy (Krita 4.2+) Hard (Krita 4.1 and earlier versions) Switch Control/Alt Selection Modifiers Enable Touch Painting Activate transform tool after pasting Kinetic Scrolling (needs restart) true true Sensitivity: Hide Scrollbars false Qt::Vertical 250 71 Miscellaneous 0 0 When Krita starts Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + + + + 0 + 0 + + + Save session when Krita closes Autosave: 0 0 Enabled true 0 0 75 0 min Every 1 1440 5 15 Compress .kra files more (slows loading/saving) Create backup file On importing images as layers, convert to the image colorspace 0 0 Undo stack size: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 75 0 0 1000 5 30 0 0 Number of Palette Presets Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Show root layer - + Warning: if you enable this setting and the file dialogs do weird stuff, do not report a bug. Enable native file dialogs (warning: may not work correctly on some systems) - + Maximum brush size: - + 0 0 The maximum diameter of a brush in pixels. px 100 10000 1000 (Needs restart) - + Qt::Vertical 504 13 0 0 75 0 10 30 <html><head/><body><p>Only use this option for <span style=" font-weight:600;">very</span> large files: larger than 4 GiB on disk.</p></body></html> Use Zip64 (for very large files: cannot be opened in versions of Krita older than 4.2.0) + + + + Enable Logging for bug reports + + + true + + + KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisColorButton QPushButton
kis_color_button.h
KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
m_autosaveCheckBox toggled(bool) m_autosaveSpinBox setEnabled(bool) 20 20 20 20
diff --git a/libs/ui/input/wintab/kis_tablet_support_win.cpp b/libs/ui/input/wintab/kis_tablet_support_win.cpp index f977bdfa5e..83bd63981c 100644 --- a/libs/ui/input/wintab/kis_tablet_support_win.cpp +++ b/libs/ui/input/wintab/kis_tablet_support_win.cpp @@ -1,990 +1,1001 @@ /* * Copyright (c) 2013 Digia Plc and/or its subsidiary(-ies). * Copyright (c) 2013 Boudewijn Rempt * Copyright (c) 2013 Dmitry Kazakov * Copyright (c) 2015 Michael Abrahams * Copyright (c) 2015 The Qt Company Ltd. * * 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_tablet_support_win_p.h" #include "kis_tablet_support_win.h" #include +#include #include #include #include +#include // #include // #include // #include #include #include #include #include #include #define Q_PI M_PI #include #include // For "inline tool switches" #include #include #include "kis_screen_size_choice_dialog.h" // NOTE: we stub out qwindowcontext.cpp::347 to disable Qt's own tablet support. // Note: The definition of the PACKET structure in pktdef.h depends on this define. #define PACKETDATA (PK_X | PK_Y | PK_BUTTONS | PK_TIME | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION | PK_CURSOR | PK_Z) #include "pktdef.h" QT_BEGIN_NAMESPACE enum { PacketMode = 0, TabletPacketQSize = 128, DeviceIdMask = 0xFF6, // device type mask && device color mask CursorTypeBitMask = 0x0F06 // bitmask to find the specific cursor type (see Wacom FAQ) }; /* * * Krita extensions begin here * * */ QWindowsTabletSupport *QTAB = 0; static QPointer targetWindow = 0; //< Window receiving last tablet event static QPointer qt_tablet_target = 0; //< Widget receiving last tablet event static bool dialogOpen = false; //< KisTabletSupportWin is not a Q_OBJECT and can't accept dialog signals HWND createDummyWindow(const QString &className, const wchar_t *windowName, WNDPROC wndProc) { if (!wndProc) wndProc = DefWindowProc; WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = 0; wc.lpfnWndProc = wndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = (HINSTANCE)GetModuleHandle(0); wc.hCursor = 0; wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW); wc.hIcon = 0; wc.hIconSm = 0; wc.lpszMenuName = 0; wc.lpszClassName = (wchar_t*)className.utf16(); ATOM atom = RegisterClassEx(&wc); if (!atom) qErrnoWarning("Registering tablet fake window class failed."); return CreateWindowEx(0, (wchar_t*)className.utf16(), windowName, WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_MESSAGE, NULL, (HINSTANCE)GetModuleHandle(0), NULL); } void printContext(const LOGCONTEXT &lc) { - dbgTablet << "# Getting current context data:"; - dbgTablet << ppVar(lc.lcName); - dbgTablet << ppVar(lc.lcDevice); - dbgTablet << ppVar(lc.lcInOrgX); - dbgTablet << ppVar(lc.lcInOrgY); - dbgTablet << ppVar(lc.lcInExtX); - dbgTablet << ppVar(lc.lcInExtY); - dbgTablet << ppVar(lc.lcOutOrgX); - dbgTablet << ppVar(lc.lcOutOrgY); - dbgTablet << ppVar(lc.lcOutExtX); - dbgTablet << ppVar(lc.lcOutExtY); - dbgTablet << ppVar(lc.lcSysOrgX); - dbgTablet << ppVar(lc.lcSysOrgY); - dbgTablet << ppVar(lc.lcSysExtX); - dbgTablet << ppVar(lc.lcSysExtY); - - dbgTablet << "Qt Desktop Geometry" << QApplication::desktop()->geometry(); + QByteArray ba; + QBuffer buf(&ba); + buf.open(QBuffer::WriteOnly); + + buf << "\nTablet context data:\n "; + buf << ppVar(lc.lcName) << "\n "; + buf << ppVar(lc.lcDevice) << "\n "; + buf << ppVar(lc.lcInOrgX) << "\n "; + buf << ppVar(lc.lcInOrgY) << "\n "; + buf << ppVar(lc.lcInExtX) << "\n "; + buf << ppVar(lc.lcInExtY) << "\n "; + buf << ppVar(lc.lcOutOrgX) << "\n "; + buf << ppVar(lc.lcOutOrgY) << "\n "; + buf << ppVar(lc.lcOutExtX) << "\n "; + buf << ppVar(lc.lcOutExtY) << "\n "; + buf << ppVar(lc.lcSysOrgX) << "\n "; + buf << ppVar(lc.lcSysOrgY) << "\n "; + buf << ppVar(lc.lcSysExtX) << "\n "; + buf << ppVar(lc.lcSysExtY) << "\n "; + + buf << "Qt Desktop Geometry" << QApplication::desktop()->geometry() << "\n "; + + buf.close(); + + dbgTablet << buf; + KisUsageLogger::write(QString::fromUtf8(ba)); } static QRect mapToNative(const QRect &qRect, int m_factor) { return QRect(qRect.x() * m_factor, qRect.y() * m_factor, qRect.width() * m_factor, qRect.height() * m_factor); } static inline QEvent::Type mouseEventType(QEvent::Type t) { return (t == QEvent::TabletMove ? QEvent::MouseMove : t == QEvent::TabletPress ? QEvent::MouseButtonPress : t == QEvent::TabletRelease ? QEvent::MouseButtonRelease : QEvent::None); } static inline bool isMouseEventType(QEvent::Type t) { return (t == QEvent::MouseMove || t == QEvent::MouseButtonPress || t == QEvent::MouseButtonRelease); } static QPoint mousePosition() { POINT p; GetCursorPos(&p); return QPoint(p.x, p.y); } QWindowsWinTab32DLL QWindowsTabletSupport::m_winTab32DLL; bool KisTabletSupportWin::init() { if (!QWindowsTabletSupport::m_winTab32DLL.init()) { qWarning() << "Failed to initialize Wintab"; return false; } QTAB = QWindowsTabletSupport::create(); // Refresh tablet context after tablet rotated, screen added, etc. QObject::connect(qApp->primaryScreen(), &QScreen::geometryChanged, [=](const QRect & geometry){ delete QTAB; QTAB = QWindowsTabletSupport::create(); }); return true; } // Derived from qwidgetwindow. // // The work done by processTabletEvent from qguiapplicationprivate is divided // between here and translateTabletPacketEvent. static void handleTabletEvent(QWidget *windowWidget, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButton button, Qt::MouseButtons buttons, qreal pressure,int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uniqueId, Qt::KeyboardModifiers modifiers, QEvent::Type type, LONG time) { // Lock in target window if (type == QEvent::TabletPress) { targetWindow = windowWidget; dbgInput << "Locking target window" << targetWindow; } else if ((type == QEvent::TabletRelease || buttons == Qt::NoButton) && (targetWindow != 0)) { dbgInput << "Releasing target window" << targetWindow; targetWindow = 0; } if (!windowWidget) // Should never happen return; // We do this instead of constructing the event e beforehand const QPoint localPos = local.toPoint(); const QPoint globalPos = global.toPoint(); if (type == QEvent::TabletPress) { QWidget *widget = windowWidget->childAt(localPos); if (!widget) widget = windowWidget; qt_tablet_target = widget; } QWidget *finalDestination = qt_tablet_target; if (!finalDestination) { finalDestination = windowWidget->childAt(localPos); } if ((type == QEvent::TabletRelease || buttons == Qt::NoButton) && (qt_tablet_target != 0)) { dbgInput << "releasing tablet target" << qt_tablet_target; qt_tablet_target = 0; } if (finalDestination) { // The event was specified relative to windowWidget, so we remap it QPointF delta = global - globalPos; QPointF mapped = finalDestination->mapFromGlobal(global.toPoint()) + delta; QTabletEvent ev(type, mapped, global, device, pointerType, pressure, xTilt, yTilt, tangentialPressure, rotation, z, modifiers, uniqueId, button, buttons); ev.setTimestamp(time); QGuiApplication::sendEvent(finalDestination, &ev); if (ev.isAccepted()) { // dbgTablet << "Tablet event" << type << "accepted" << "by target widget" << finalDestination; } else { // Turn off eventEater send a synthetic mouse event. // dbgTablet << "Tablet event" << type << "rejected; sending mouse event to" << finalDestination; qt_tablet_target = 0; // We shouldn't ever get a widget accepting a tablet event from this // call, so we won't worry about any interactions with our own // widget-locking code. // QWindow *target = platformScreen->topLevelAt(globalPos); // if (!target) return; // QPointF windowLocal = global - QPointF(target->mapFromGlobal(QPoint())) + delta; // QWindowSystemInterface::handleTabletEvent(target, ev.timestamp(), windowLocal, // global, device, pointerType, // buttons, pressure, xTilt, yTilt, // tangentialPressure, rotation, z, // uniqueId, modifiers); } } else { qt_tablet_target = 0; targetWindow = 0; } } /** * This is a default implementation of a class for converting the * WinTab value of the buttons pressed to the Qt buttons. This class * may be substituted from the UI. */ struct DefaultButtonsConverter { void convert(DWORD btnOld, DWORD btnNew, Qt::MouseButton *button, Qt::MouseButtons *buttons, const QWindowsTabletDeviceData &tdd) { int pressedButtonValue = btnNew ^ btnOld; *button = buttonValueToEnum(pressedButtonValue, tdd); *buttons = Qt::NoButton; for (int i = 0; i < 3; i++) { int btn = 0x1 << i; if (btn & btnNew) { Qt::MouseButton convertedButton = buttonValueToEnum(btn, tdd); *buttons |= convertedButton; /** * If a button that is present in hardware input is * mapped to a Qt::NoButton, it means that it is going * to be eaten by the driver, for example by its * "Pan/Scroll" feature. Therefore we shouldn't handle * any of the events associated to it. So just return * Qt::NoButton here. */ if (convertedButton == Qt::NoButton) { /** * Sometimes the driver-handled shortcuts are just * keyboard modifiers, so ideally we should handle * them as well. The problem is that we cannot * know if the shortcut was a pan/zoom action or a * shortcut. So here we use a "hackish" approach. * We just check if any modifier has been pressed * and, if so, pass the button to Krita. Of * course, if the driver uses some really complex * shortcuts like "Shift + stylus btn" to generate * some recorded shortcut, it will not work. But I * guess it will be ok for the most of the * usecases. * * WARNING: this hack will *not* work if you bind * any non-modifier key to the stylus * button, e.g. Space. */ const Qt::KeyboardModifiers keyboardModifiers = QApplication::queryKeyboardModifiers(); if (KisTabletDebugger::instance()->shouldEatDriverShortcuts() || keyboardModifiers == Qt::NoModifier) { *button = Qt::NoButton; *buttons = Qt::NoButton; break; } } } } } private: Qt::MouseButton buttonValueToEnum(DWORD button, const QWindowsTabletDeviceData &tdd) { const int leftButtonValue = 0x1; const int middleButtonValue = 0x2; const int rightButtonValue = 0x4; const int doubleClickButtonValue = 0x7; button = tdd.buttonsMap.value(button); return button == leftButtonValue ? Qt::LeftButton : button == rightButtonValue ? Qt::RightButton : button == doubleClickButtonValue ? Qt::MiddleButton : button == middleButtonValue ? Qt::MiddleButton : button ? Qt::LeftButton /* fallback item */ : Qt::NoButton; } }; static DefaultButtonsConverter *globalButtonsConverter = new DefaultButtonsConverter(); /* * * Krita extensions end here * * */ // This is the WndProc for a single additional hidden window used to collect tablet events. extern "C" LRESULT QT_WIN_CALLBACK qWindowsTabletSupportWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WT_PROXIMITY: if (QTAB->translateTabletProximityEvent(wParam, lParam)) return 0; break; case WT_PACKET: if (QTAB->translateTabletPacketEvent()) return 0; break; } return DefWindowProc(hwnd, message, wParam, lParam); } // Scale tablet coordinates to screen coordinates. static inline int sign(int x) { return x >= 0 ? 1 : -1; } inline QPointF QWindowsTabletDeviceData::scaleCoordinates(int coordX, int coordY, const QRect &targetArea) const { const int targetX = targetArea.x(); const int targetY = targetArea.y(); const int targetWidth = targetArea.width(); const int targetHeight = targetArea.height(); const qreal x = sign(targetWidth) == sign(maxX) ? ((coordX - minX) * qAbs(targetWidth) / qAbs(qreal(maxX - minX))) + targetX : ((qAbs(maxX) - (coordX - minX)) * qAbs(targetWidth) / qAbs(qreal(maxX - minX))) + targetX; const qreal y = sign(targetHeight) == sign(maxY) ? ((coordY - minY) * qAbs(targetHeight) / qAbs(qreal(maxY - minY))) + targetY : ((qAbs(maxY) - (coordY - minY)) * qAbs(targetHeight) / qAbs(qreal(maxY - minY))) + targetY; return QPointF(x, y); } /*! \class QWindowsWinTab32DLL \brief Functions from wintabl32.dll shipped with WACOM tablets used by QWindowsTabletSupport. \internal \ingroup qt-lighthouse-win */ bool QWindowsWinTab32DLL::init() { if (wTInfo) return true; QLibrary library(QStringLiteral("wintab32")); if (!library.load()) { qWarning() << QString("Could not load wintab32 dll: %1").arg(library.errorString()); return false; } wTOpen = (PtrWTOpen) library.resolve("WTOpenW"); wTClose = (PtrWTClose) library.resolve("WTClose"); wTInfo = (PtrWTInfo) library.resolve("WTInfoW"); wTEnable = (PtrWTEnable) library.resolve("WTEnable"); wTOverlap = (PtrWTOverlap) library.resolve("WTOverlap"); wTPacketsGet = (PtrWTPacketsGet) library.resolve("WTPacketsGet"); wTGet = (PtrWTGet) library.resolve("WTGetW"); wTQueueSizeGet = (PtrWTQueueSizeGet) library.resolve("WTQueueSizeGet"); wTQueueSizeSet = (PtrWTQueueSizeSet) library.resolve("WTQueueSizeSet"); if (wTOpen && wTClose && wTInfo && wTEnable && wTOverlap && wTPacketsGet && wTQueueSizeGet && wTQueueSizeSet) { return true; } qWarning() << "Could not resolve the following symbols:\n" << "\t wTOpen" << wTOpen << "\n" << "\t wtClose" << wTClose << "\n" << "\t wtInfo" << wTInfo << "\n" << "\t wTEnable" << wTEnable << "\n" << "\t wTOverlap" << wTOverlap << "\n" << "\t wTPacketsGet" << wTPacketsGet << "\n" << "\t wTQueueSizeGet" << wTQueueSizeGet << "\n" << "\t wTQueueSizeSet" << wTQueueSizeSet << "\n"; return false; } /*! \class QWindowsTabletSupport \brief Tablet support for Windows. Support for WACOM tablets. \sa http://www.wacomeng.com/windows/docs/Wintab_v140.htm \internal \since 5.2 \ingroup qt-lighthouse-win */ QWindowsTabletSupport::QWindowsTabletSupport(HWND window, HCTX context) : m_window(window) , m_context(context) , m_absoluteRange(20) , m_tiltSupport(false) , m_currentDevice(-1) { AXIS orientation[3]; // Some tablets don't support tilt, check if it is possible, if (QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES, DVC_ORIENTATION, &orientation)) m_tiltSupport = orientation[0].axResolution && orientation[1].axResolution; } QWindowsTabletSupport::~QWindowsTabletSupport() { QWindowsTabletSupport::m_winTab32DLL.wTClose(m_context); DestroyWindow(m_window); } QWindowsTabletSupport *QWindowsTabletSupport::create() { const HWND window = createDummyWindow(QStringLiteral("TabletDummyWindow"), L"TabletDummyWindow", qWindowsTabletSupportWndProc); LOGCONTEXT lcMine; // build our context from the default context QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEFSYSCTX, 0, &lcMine); // Go for the raw coordinates, the tablet event will return good stuff. The // defaults for lcOut rectangle are the desktop dimensions in pixels, which // means Wintab will do lossy rounding. Instead we specify this trivial // scaling for the output context, then do the scaling ourselves later to // obtain higher resolution coordinates. lcMine.lcOptions |= CXO_MESSAGES | CXO_CSRMESSAGES; lcMine.lcPktData = lcMine.lcMoveMask = PACKETDATA; lcMine.lcPktMode = PacketMode; lcMine.lcOutOrgX = 0; lcMine.lcOutExtX = lcMine.lcInExtX; lcMine.lcOutOrgY = 0; lcMine.lcOutExtY = -lcMine.lcInExtY; const HCTX context = QWindowsTabletSupport::m_winTab32DLL.wTOpen(window, &lcMine, true); if (!context) { dbgTablet << __FUNCTION__ << "Unable to open tablet."; DestroyWindow(window); return 0; } // Set the size of the Packet Queue to the correct size const int currentQueueSize = QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeGet(context); if (currentQueueSize != TabletPacketQSize) { if (!QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeSet(context, TabletPacketQSize)) { if (!QWindowsTabletSupport::m_winTab32DLL.wTQueueSizeSet(context, currentQueueSize)) { qWarning() << "Unable to set queue size on tablet. The tablet will not work."; QWindowsTabletSupport::m_winTab32DLL.wTClose(context); DestroyWindow(window); return 0; } // cannot restore old size } // cannot set } // mismatch dbgTablet << "Opened tablet context " << context << " on window " << window << "changed packet queue size " << currentQueueSize << "->" << TabletPacketQSize; return new QWindowsTabletSupport(window, context); } unsigned QWindowsTabletSupport::options() const { UINT result = 0; m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_CTXOPTIONS, &result); return result; } QString QWindowsTabletSupport::description() const { const unsigned size = m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_WINTABID, 0); if (!size) return QString(); QVarLengthArray winTabId(size + 1); m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_WINTABID, winTabId.data()); WORD implementationVersion = 0; m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_IMPLVERSION, &implementationVersion); WORD specificationVersion = 0; m_winTab32DLL.wTInfo(WTI_INTERFACE, IFC_SPECVERSION, &specificationVersion); const unsigned opts = options(); QString result = QString::fromLatin1("%1 specification: v%2.%3 implementation: v%4.%5 options: 0x%6") .arg(QString::fromWCharArray(winTabId.data())) .arg(specificationVersion >> 8).arg(specificationVersion & 0xFF) .arg(implementationVersion >> 8).arg(implementationVersion & 0xFF) .arg(opts, 0, 16); if (opts & CXO_MESSAGES) result += QStringLiteral(" CXO_MESSAGES"); if (opts & CXO_CSRMESSAGES) result += QStringLiteral(" CXO_CSRMESSAGES"); if (m_tiltSupport) result += QStringLiteral(" tilt"); return result; } void QWindowsTabletSupport::notifyActivate() { // Cooperate with other tablet applications, but when we get focus, I want to use the tablet. const bool result = QWindowsTabletSupport::m_winTab32DLL.wTEnable(m_context, true) && QWindowsTabletSupport::m_winTab32DLL.wTOverlap(m_context, true); dbgTablet << __FUNCTION__ << result; } static inline int indexOfDevice(const QVector &devices, qint64 uniqueId) { for (int i = 0; i < devices.size(); ++i) if (devices.at(i).uniqueId == uniqueId) return i; return -1; } static inline QTabletEvent::TabletDevice deviceType(const UINT cursorType) { if (((cursorType & 0x0006) == 0x0002) && ((cursorType & CursorTypeBitMask) != 0x0902)) return QTabletEvent::Stylus; if (cursorType == 0x4020) // Surface Pro 2 tablet device return QTabletEvent::Stylus; switch (cursorType & CursorTypeBitMask) { case 0x0802: return QTabletEvent::Stylus; case 0x0902: return QTabletEvent::Airbrush; case 0x0004: return QTabletEvent::FourDMouse; case 0x0006: return QTabletEvent::Puck; case 0x0804: return QTabletEvent::RotationStylus; default: break; } return QTabletEvent::NoDevice; }; static inline QTabletEvent::PointerType pointerType(unsigned pkCursor) { switch (pkCursor % 3) { // %3 for dual track case 0: return QTabletEvent::Cursor; case 1: return QTabletEvent::Pen; case 2: return QTabletEvent::Eraser; default: break; } return QTabletEvent::UnknownPointer; } QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t) { d << "TabletDevice id:" << t.uniqueId << " pressure: " << t.minPressure << ".." << t.maxPressure << " tan pressure: " << t.minTanPressure << ".." << t.maxTanPressure << " area:" << t.minX << t.minY << t.minZ << ".." << t.maxX << t.maxY << t.maxZ << " device " << t.currentDevice << " pointer " << t.currentPointerType; return d; } QWindowsTabletDeviceData QWindowsTabletSupport::tabletInit(const quint64 uniqueId, const UINT cursorType) const { QWindowsTabletDeviceData result; result.uniqueId = uniqueId; /* browse WinTab's many info items to discover pressure handling. */ AXIS axis; LOGCONTEXT lc; /* get the current context for its device variable. */ QWindowsTabletSupport::m_winTab32DLL.wTGet(m_context, &lc); if (KisTabletDebugger::instance()->initializationDebugEnabled()) { printContext(lc); } /* get the size of the pressure axis. */ QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES + lc.lcDevice, DVC_NPRESSURE, &axis); result.minPressure = int(axis.axMin); result.maxPressure = int(axis.axMax); QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES + lc.lcDevice, DVC_TPRESSURE, &axis); result.minTanPressure = int(axis.axMin); result.maxTanPressure = int(axis.axMax); result.minX = int(lc.lcOutOrgX); result.maxX = int(lc.lcOutExtX) + int(lc.lcOutOrgX); result.minY = int(lc.lcOutOrgY); result.maxY = -int(lc.lcOutExtY) + int(lc.lcOutOrgY); // These are set to 0 when we opened the tablet context in QWindowsTabletSupport::create KIS_SAFE_ASSERT_RECOVER_NOOP(lc.lcOutOrgX == 0); KIS_SAFE_ASSERT_RECOVER_NOOP(lc.lcOutOrgY == 0); result.maxZ = int(lc.lcOutExtZ) - int(lc.lcOutOrgZ); result.currentDevice = deviceType(cursorType); // Define a rectangle representing the whole screen as seen by Wintab. QRect qtDesktopRect = QApplication::desktop()->geometry(); QRect wintabDesktopRect(lc.lcSysOrgX, lc.lcSysOrgY, lc.lcSysExtX, lc.lcSysExtY); qDebug() << ppVar(qtDesktopRect); qDebug() << ppVar(wintabDesktopRect); // Show screen choice dialog if (!dialogOpen) { KisScreenSizeChoiceDialog dlg(0, wintabDesktopRect, qtDesktopRect); KisExtendedModifiersMapper mapper; KisExtendedModifiersMapper::ExtendedModifiers modifiers = mapper.queryExtendedModifiers(); if (modifiers.contains(Qt::Key_Shift) || (!dlg.canUseDefaultSettings() && qtDesktopRect != wintabDesktopRect)) { dialogOpen = true; dlg.exec(); } result.virtualDesktopArea = dlg.screenRect(); dialogOpen = false; } else { // This branch should've been explicitly prevented. KIS_SAFE_ASSERT_RECOVER_NOOP(!dialogOpen); warnTablet << "Trying to init a WinTab device while screen resolution dialog is active, this should not happen!"; warnTablet << "Tablet coordinates could be wrong as a result."; result.virtualDesktopArea = qtDesktopRect; } return result; }; bool QWindowsTabletSupport::translateTabletProximityEvent(WPARAM /* wParam */, LPARAM lParam) { if (dialogOpen) { // tabletInit(...) may show the screen resolution dialog and is blocking. // During this period, don't process any tablet events at all. dbgTablet << "WinTab screen resolution dialog is active, ignoring WinTab proximity event"; return false; } auto sendProximityEvent = [&](QEvent::Type type) { QPointF emptyPos; qreal zero = 0.0; QTabletEvent e(type, emptyPos, emptyPos, 0, m_devices.at(m_currentDevice).currentPointerType, zero, 0, 0, zero, zero, 0, Qt::NoModifier, m_devices.at(m_currentDevice).uniqueId, Qt::NoButton, (Qt::MouseButtons)0); qApp->sendEvent(qApp, &e); }; if (!LOWORD(lParam)) { // dbgTablet << "leave proximity for device #" << m_currentDevice; sendProximityEvent(QEvent::TabletLeaveProximity); return true; } PACKET proximityBuffer[1]; // we are only interested in the first packet in this case const int totalPacks = QWindowsTabletSupport::m_winTab32DLL.wTPacketsGet(m_context, 1, proximityBuffer); if (!totalPacks) return false; UINT pkCursor = proximityBuffer[0].pkCursor; // initializing and updating the cursor should be done in response to // WT_CSRCHANGE. We do it in WT_PROXIMITY because some wintab never send // the event WT_CSRCHANGE even if asked with CXO_CSRMESSAGES tabletUpdateCursor(pkCursor); // dbgTablet << "enter proximity for device #" << m_currentDevice << m_devices.at(m_currentDevice); sendProximityEvent(QEvent::TabletEnterProximity); return true; } bool QWindowsTabletSupport::translateTabletPacketEvent() { static PACKET localPacketBuf[TabletPacketQSize]; // our own tablet packet queue. const int packetCount = QWindowsTabletSupport::m_winTab32DLL.wTPacketsGet(m_context, TabletPacketQSize, &localPacketBuf); if (!packetCount || m_currentDevice < 0 || dialogOpen) return false; // In contrast to Qt, these will not be "const" during our loop. // This is because the Surface Pro 3 may cause us to switch devices. QWindowsTabletDeviceData tabletData = m_devices.at(m_currentDevice); int currentDevice = tabletData.currentDevice; int currentPointerType = tabletData.currentPointerType; // static Qt::MouseButtons buttons = Qt::NoButton, btnOld, btnChange; static DWORD btnNew, btnOld, btnChange; // The tablet can be used in 2 different modes, depending on its settings: // 1) Absolute (pen) mode: // The coordinates are scaled to the virtual desktop (by default). The user // can also choose to scale to the monitor or a region of the screen. // When entering proximity, the tablet driver snaps the mouse pointer to the // tablet position scaled to that area and keeps it in sync. // 2) Relative (mouse) mode: // The pen follows the mouse. The constant 'absoluteRange' specifies the // manhattanLength difference for detecting if a tablet input device is in this mode, // in which case we snap the position to the mouse position. // It seems there is no way to find out the mode programmatically, the LOGCONTEXT orgX/Y/Ext // area is always the virtual desktop. static qreal dpr = 1.0; auto activeWindow = qApp->activeWindow(); if (activeWindow) { dpr = activeWindow->devicePixelRatio(); } const Qt::KeyboardModifiers keyboardModifiers = QApplication::queryKeyboardModifiers(); for (int i = 0; i < packetCount; ++i) { const PACKET &packet = localPacketBuf[i]; btnOld = btnNew; btnNew = localPacketBuf[i].pkButtons; btnChange = btnOld ^ btnNew; bool buttonPressed = btnChange && btnNew > btnOld; bool buttonReleased = btnChange && btnNew < btnOld; bool anyButtonsStillPressed = btnNew; Qt::MouseButton button = Qt::NoButton; Qt::MouseButtons buttons; globalButtonsConverter->convert(btnOld, btnNew, &button, &buttons, tabletData); QEvent::Type type = QEvent::TabletMove; if (buttonPressed && button != Qt::NoButton) { type = QEvent::TabletPress; } else if (buttonReleased && button != Qt::NoButton) { type = QEvent::TabletRelease; } const int z = currentDevice == QTabletEvent::FourDMouse ? int(packet.pkZ) : 0; // NOTE: we shouldn't postpone the tablet events like Qt does, because we // don't support mouse mode (which was the reason for introducing this // postponing). See bug 363284. QPointF globalPosF = tabletData.scaleCoordinates(packet.pkX, packet.pkY, tabletData.virtualDesktopArea); globalPosF /= dpr; // Convert from "native" to "device independent pixels." QPoint globalPos = globalPosF.toPoint(); // Find top-level window QWidget *w = targetWindow; // If we had a target already, use it. if (!w) { w = qApp->activePopupWidget(); if (!w) w = qApp->activeModalWidget(); if (!w) w = qApp->topLevelAt(globalPos); if (!w) continue; w = w->window(); } const QPoint localPos = w->mapFromGlobal(globalPos); const QPointF delta = globalPosF - globalPos; const QPointF localPosF = globalPosF + QPointF(localPos - globalPos) + delta; const qreal pressureNew = packet.pkButtons && (currentPointerType == QTabletEvent::Pen || currentPointerType == QTabletEvent::Eraser) ? m_devices.at(m_currentDevice).scalePressure(packet.pkNormalPressure) : qreal(0); const qreal tangentialPressure = currentDevice == QTabletEvent::Airbrush ? m_devices.at(m_currentDevice).scaleTangentialPressure(packet.pkTangentPressure) : qreal(0); int tiltX = 0; int tiltY = 0; qreal rotation = 0; if (m_tiltSupport) { // Convert from azimuth and altitude to x tilt and y tilt. What // follows is the optimized version. Here are the equations used: // X = sin(azimuth) * cos(altitude) // Y = cos(azimuth) * cos(altitude) // Z = sin(altitude) // X Tilt = arctan(X / Z) // Y Tilt = arctan(Y / Z) const double radAzim = (packet.pkOrientation.orAzimuth / 10.0) * (M_PI / 180); const double tanAlt = std::tan((std::abs(packet.pkOrientation.orAltitude / 10.0)) * (M_PI / 180)); const double degX = std::atan(std::sin(radAzim) / tanAlt); const double degY = std::atan(std::cos(radAzim) / tanAlt); tiltX = int(degX * (180 / M_PI)); tiltY = int(-degY * (180 / M_PI)); rotation = 360.0 - (packet.pkOrientation.orTwist / 10.0); if (rotation > 180.0) rotation -= 360.0; } // This is adds *a lot* of noise to the output log if (false) { dbgTablet << "Packet #" << (i+1) << '/' << packetCount << "button:" << packet.pkButtons << globalPosF << z << "to:" << w << localPos << "(packet" << packet.pkX << packet.pkY << ") dev:" << currentDevice << "pointer:" << currentPointerType << "P:" << pressureNew << "tilt:" << tiltX << ',' << tiltY << "tanP:" << tangentialPressure << "rotation:" << rotation; } // Reusable helper function. Better than compiler macros! auto sendTabletEvent = [&](QTabletEvent::Type t){ handleTabletEvent(w, localPosF, globalPosF, currentDevice, currentPointerType, button, buttons, pressureNew, tiltX, tiltY, tangentialPressure, rotation, z, m_devices.at(m_currentDevice).uniqueId, keyboardModifiers, t, packet.pkTime); }; /** * Workaround to deal with "inline" tool switches. * These are caused by the eraser trigger button on the Surface Pro 3. * We shoot out a tabletUpdateCursor request and a switchInputDevice request. */ if (isSurfacePro3 && (packet.pkCursor != currentPkCursor)) { dbgTablet << "Got an inline tool switch."; // Send tablet release event. sendTabletEvent(QTabletEvent::TabletRelease); // Read the new cursor info. UINT pkCursor = packet.pkCursor; tabletUpdateCursor(pkCursor); // Update the local loop variables. tabletData = m_devices.at(m_currentDevice); currentDevice = deviceType(tabletData.currentDevice); currentPointerType = pointerType(pkCursor); sendTabletEvent(QTabletEvent::TabletPress); } sendTabletEvent(type); } // Loop over packets return true; } void QWindowsTabletSupport::tabletUpdateCursor(const int pkCursor) { UINT physicalCursorId; QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + pkCursor, CSR_PHYSID, &physicalCursorId); UINT cursorType; QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_CURSORS + pkCursor, CSR_TYPE, &cursorType); const qint64 uniqueId = (qint64(cursorType & DeviceIdMask) << 32L) | qint64(physicalCursorId); m_currentDevice = indexOfDevice(m_devices, uniqueId); if (m_currentDevice < 0) { m_currentDevice = m_devices.size(); m_devices.push_back(tabletInit(uniqueId, cursorType)); // Note: ideally we might check this button map for changes every // update. However there seems to be an issue with Wintab altering the // button map when the user right-clicks in Krita while another // application has focus. This forces Krita to load button settings only // once, when the tablet is first detected. // // See https://bugs.kde.org/show_bug.cgi?id=359561 BYTE logicalButtons[32]; memset(logicalButtons, 0, 32); m_winTab32DLL.wTInfo(WTI_CURSORS + pkCursor, CSR_SYSBTNMAP, &logicalButtons); m_devices[m_currentDevice].buttonsMap[0x1] = logicalButtons[0]; m_devices[m_currentDevice].buttonsMap[0x2] = logicalButtons[1]; m_devices[m_currentDevice].buttonsMap[0x4] = logicalButtons[2]; } m_devices[m_currentDevice].currentPointerType = pointerType(pkCursor); currentPkCursor = pkCursor; // Check tablet name to enable Surface Pro 3 workaround. #ifdef UNICODE if (!isSurfacePro3) { /** * Some really "nice" tablet drivers don't know that they are * supposed to return their name length when the buffer is * null and they try to write into it effectively causing a * suicide. So we cannot rely on it :( * * We workaround it by just allocating a big array and hoping * for the best. * * Failing tablets: * - Adesso Cybertablet M14 * - Peritab-302 * - Trust Tablet TB7300 * - VisTablet Realm Pro * - Aiptek 14000u (latest driver: v5.03, 2013-10-21) * - Genius G-Pen F350 * - Genius G-Pen 560 (supported on win7 only) */ // we cannot use the correct api :( // UINT nameLength = QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES, DVC_NAME, 0); // 1024 chars should be enough for everyone! (c) UINT nameLength = 1024; TCHAR* dvcName = new TCHAR[nameLength + 1]; memset(dvcName, 0, sizeof(TCHAR) * nameLength); UINT writtenBytes = QWindowsTabletSupport::m_winTab32DLL.wTInfo(WTI_DEVICES, DVC_NAME, dvcName); if (writtenBytes > sizeof(TCHAR) * nameLength) { qWarning() << "WINTAB WARNING: tablet name is too long!" << writtenBytes; // avoid crash when trying to read it dvcName[nameLength - 1] = (TCHAR)0; } QString qDvcName = QString::fromWCharArray((const wchar_t*)dvcName); dbgInput << "DVC_NAME =" << qDvcName; // Name changed between older and newer Surface Pro 3 drivers if (qDvcName == QString::fromLatin1("N-trig DuoSense device") || qDvcName == QString::fromLatin1("Microsoft device")) { dbgInput << "This looks like a Surface Pro 3. Enabling eraser workaround."; isSurfacePro3 = true; } delete[] dvcName; } #endif } QT_END_NAMESPACE diff --git a/libs/ui/kis_statusbar.cc b/libs/ui/kis_statusbar.cc index c30348ba0f..991beb8245 100644 --- a/libs/ui/kis_statusbar.cc +++ b/libs/ui/kis_statusbar.cc @@ -1,384 +1,394 @@ /* This file is part of KimageShop^WKrayon^WKrita * * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_statusbar.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include + #include #include #include #include #include #include #include "kis_memory_statistics_server.h" #include "KisView.h" +#include "KisDocument.h" #include "KisViewManager.h" #include "canvas/kis_canvas2.h" #include "kis_progress_widget.h" #include "kis_zoom_manager.h" #include "KisMainWindow.h" #include "kis_config.h" enum { IMAGE_SIZE_ID, POINTER_POSITION_ID }; KisStatusBar::KisStatusBar(KisViewManager *viewManager) : m_viewManager(viewManager) , m_imageView(0) , m_statusBar(0) { } void KisStatusBar::setup() { m_selectionStatus = new QToolButton(); m_selectionStatus->setObjectName("selection status"); m_selectionStatus->setIconSize(QSize(16,16)); m_selectionStatus->setAutoRaise(true); m_selectionStatus->setEnabled(false); updateSelectionIcon(); m_statusBar = m_viewManager->mainWindow()->statusBar(); connect(m_selectionStatus, SIGNAL(clicked()), m_viewManager->selectionManager(), SLOT(slotToggleSelectionDecoration())); connect(m_viewManager->selectionManager(), SIGNAL(displaySelectionChanged()), SLOT(updateSelectionToolTip())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), this, SLOT(updateSelectionIcon())); addStatusBarItem(m_selectionStatus); m_selectionStatus->setVisible(false); m_statusBarStatusLabel = new KSqueezedTextLabel(); m_statusBarStatusLabel->setObjectName("statsBarStatusLabel"); m_statusBarStatusLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); m_statusBarStatusLabel->setContentsMargins(5, 5, 5, 5); connect(KoToolManager::instance(), SIGNAL(changedStatusText(QString)), m_statusBarStatusLabel, SLOT(setText(QString))); addStatusBarItem(m_statusBarStatusLabel, 2); m_statusBarStatusLabel->setVisible(false); m_statusBarProfileLabel = new KSqueezedTextLabel(); m_statusBarProfileLabel->setObjectName("statsBarProfileLabel"); m_statusBarProfileLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); m_statusBarProfileLabel->setContentsMargins(5, 5, 5, 5); addStatusBarItem(m_statusBarProfileLabel, 3); m_statusBarProfileLabel->setVisible(false); m_progress = new KisProgressWidget(); m_progress->setObjectName("ProgressBar"); addStatusBarItem(m_progress); m_progress->setVisible(false); connect(m_progress, SIGNAL(sigCancellationRequested()), this, SIGNAL(sigCancellationRequested())); m_progressUpdater.reset(new KisProgressUpdater(m_progress, m_progress->progressProxy())); m_progressUpdater->setAutoNestNames(true); m_memoryReportBox = new QPushButton(); m_memoryReportBox->setObjectName("memoryReportBox"); m_memoryReportBox->setFlat(true); m_memoryReportBox->setContentsMargins(5, 5, 5, 5); m_memoryReportBox->setMinimumWidth(120); addStatusBarItem(m_memoryReportBox); m_memoryReportBox->setVisible(false); connect(m_memoryReportBox, SIGNAL(clicked()), SLOT(showMemoryInfoToolTip())); m_pointerPositionLabel = new QLabel(QString()); m_pointerPositionLabel->setObjectName("pointerPositionLabel"); m_pointerPositionLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_pointerPositionLabel->setMinimumWidth(100); m_pointerPositionLabel->setContentsMargins(5,5, 5, 5); addStatusBarItem(m_pointerPositionLabel); m_pointerPositionLabel->setVisible(false); connect(KisMemoryStatisticsServer::instance(), SIGNAL(sigUpdateMemoryStatistics()), SLOT(imageSizeChanged())); } KisStatusBar::~KisStatusBar() { } void KisStatusBar::setView(QPointer imageView) { if (m_imageView == imageView) { return; } if (m_imageView) { m_imageView->disconnect(this); removeStatusBarItem(m_imageView->zoomManager()->zoomActionWidget()); m_imageView = 0; } if (imageView) { m_imageView = imageView; connect(m_imageView, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SLOT(updateStatusBarProfileLabel())); connect(m_imageView, SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SLOT(updateStatusBarProfileLabel())); connect(m_imageView, SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(imageSizeChanged())); updateStatusBarProfileLabel(); addStatusBarItem(m_imageView->zoomManager()->zoomActionWidget()); } imageSizeChanged(); } void KisStatusBar::addStatusBarItem(QWidget *widget, int stretch, bool permanent) { StatusBarItem sbItem(widget); if (permanent) { m_statusBar->addPermanentWidget(widget, stretch); } else { m_statusBar->addWidget(widget, stretch); } widget->setVisible(true); m_statusBarItems.append(sbItem); } void KisStatusBar::removeStatusBarItem(QWidget *widget) { int i = 0; Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) { if (sbItem.widget() == widget) { break; } i++; } if (i < m_statusBarItems.count()) { m_statusBar->removeWidget(m_statusBarItems[i].widget()); m_statusBarItems.remove(i); } } void KisStatusBar::hideAllStatusBarItems() { Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) { sbItem.hide(); } } void KisStatusBar::showAllStatusBarItems() { Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) { sbItem.show(); } } void KisStatusBar::documentMousePositionChanged(const QPointF &pos) { if (!m_imageView) return; QPoint pixelPos = m_imageView->image()->documentToImagePixelFloored(pos); pixelPos.setX(qBound(0, pixelPos.x(), m_viewManager->image()->width() - 1)); pixelPos.setY(qBound(0, pixelPos.y(), m_viewManager->image()->height() - 1)); m_pointerPositionLabel->setText(QString("%1, %2").arg(pixelPos.x()).arg(pixelPos.y())); } void KisStatusBar::imageSizeChanged() { updateMemoryStatus(); QString sizeText; KisImageWSP image = m_imageView ? m_imageView->image() : 0; if (image) { qint32 w = image->width(); qint32 h = image->height(); sizeText = QString("%1 x %2 (%3)").arg(w).arg(h).arg(m_shortMemoryTag); } else { sizeText = m_shortMemoryTag; } m_memoryReportBox->setIcon(m_memoryStatusIcon); m_memoryReportBox->setText(sizeText); m_memoryReportBox->setToolTip(m_longMemoryTag); } void KisStatusBar::updateSelectionIcon() { QIcon icon; if (!m_viewManager->selectionManager()->displaySelection()) { icon = KisIconUtils::loadIcon("selection-mode_invisible"); } else if (m_viewManager->selectionManager()->showSelectionAsMask()) { icon = KisIconUtils::loadIcon("selection-mode_mask"); } else /* if (!m_view->selectionManager()->showSelectionAsMask()) */ { icon = KisIconUtils::loadIcon("selection-mode_ants"); } m_selectionStatus->setIcon(icon); } void KisStatusBar::updateMemoryStatus() { KisMemoryStatisticsServer::Statistics stats = KisMemoryStatisticsServer::instance() ->fetchMemoryStatistics(m_imageView ? m_imageView->image() : 0); const KFormat format; const QString imageStatsMsg = i18nc("tooltip on statusbar memory reporting button (image stats)", "Image size:\t %1\n" " - layers:\t\t %2\n" " - projections:\t %3\n" " - instant preview:\t %4\n", format.formatByteSize(stats.imageSize), format.formatByteSize(stats.layersSize), format.formatByteSize(stats.projectionsSize), format.formatByteSize(stats.lodSize)); const QString memoryStatsMsg = i18nc("tooltip on statusbar memory reporting button (total stats)", "Memory used:\t %1 / %2\n" " image data:\t %3 / %4\n" " pool:\t\t %5 / %6\n" " undo data:\t %7\n" "\n" "Swap used:\t %8", format.formatByteSize(stats.totalMemorySize), format.formatByteSize(stats.totalMemoryLimit), format.formatByteSize(stats.realMemorySize), format.formatByteSize(stats.tilesHardLimit), format.formatByteSize(stats.poolSize), format.formatByteSize(stats.tilesPoolLimit), format.formatByteSize(stats.historicalMemorySize), format.formatByteSize(stats.swapSize)); QString longStats = imageStatsMsg + "\n" + memoryStatsMsg; QString shortStats = format.formatByteSize(stats.imageSize); QIcon icon; const qint64 warnLevel = stats.tilesHardLimit - stats.tilesHardLimit / 8; if (stats.imageSize > warnLevel || stats.realMemorySize > warnLevel) { + if (!m_memoryWarningLogged) { + m_memoryWarningLogged = true; + KisUsageLogger::log(QString("WARNING: %1 is running out of memory:%2\n").arg(m_imageView->document()->url().toLocalFile()).arg(longStats)); + } + icon = KisIconUtils::loadIcon("dialog-warning"); QString suffix = i18nc("tooltip on statusbar memory reporting button", "\n\nWARNING:\tOut of memory! Swapping has been started.\n" "\t\tPlease configure more RAM for Krita in Settings dialog"); longStats += suffix; + + } m_shortMemoryTag = shortStats; m_longMemoryTag = longStats; m_memoryStatusIcon = icon; emit memoryStatusUpdated(); } void KisStatusBar::showMemoryInfoToolTip() { QToolTip::showText(QCursor::pos(), m_memoryReportBox->toolTip(), m_memoryReportBox); } void KisStatusBar::updateSelectionToolTip() { updateSelectionIcon(); KisSelectionSP selection = m_viewManager->selection(); if (selection) { m_selectionStatus->setEnabled(true); QRect r = selection->selectedExactRect(); QString displayMode = !m_viewManager->selectionManager()->displaySelection() ? i18n("Hidden") : (m_viewManager->selectionManager()->showSelectionAsMask() ? i18n("Mask") : i18n("Ants")); m_selectionStatus->setToolTip( i18n("Selection: x = %1 y = %2 width = %3 height = %4\n" "Display Mode: %5", r.x(), r.y(), r.width(), r.height(), displayMode)); } else { m_selectionStatus->setEnabled(false); m_selectionStatus->setToolTip(i18n("No Selection")); } } void KisStatusBar::setSelection(KisImageWSP image) { Q_UNUSED(image); updateSelectionToolTip(); } void KisStatusBar::setProfile(KisImageWSP image) { if (m_statusBarProfileLabel == 0) { return; } if (!image) return; if (image->profile() == 0) { m_statusBarProfileLabel->setText(i18n("No profile")); } else { m_statusBarProfileLabel->setText(image->colorSpace()->name() + " " + image->profile()->name()); } } void KisStatusBar::setHelp(const QString &t) { Q_UNUSED(t); } void KisStatusBar::updateStatusBarProfileLabel() { if (!m_imageView) return; setProfile(m_imageView->image()); } KoProgressUpdater *KisStatusBar::progressUpdater() { return m_progressUpdater.data(); } diff --git a/libs/ui/kis_statusbar.h b/libs/ui/kis_statusbar.h index b890f64906..14467e9180 100644 --- a/libs/ui/kis_statusbar.h +++ b/libs/ui/kis_statusbar.h @@ -1,138 +1,140 @@ /* This file is part of KimageShop^WKrayon^WKrita * * Copyright (c) 2003-200^ Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_STATUSBAR_H #define KIS_STATUSBAR_H #include #include #include #include #include #include "KisView.h" class QLabel; class QToolButton; class QPushButton; class KSqueezedTextLabel; class KisViewManager; class KisProgressWidget; class KoProgressUpdater; #include "kritaui_export.h" class KRITAUI_EXPORT KisStatusBar : public QObject { class StatusBarItem { public: StatusBarItem() : m_widget(0) {} StatusBarItem(QWidget * widget) : m_widget(widget) {} bool operator==(const StatusBarItem& rhs) { return m_widget == rhs.m_widget; } bool operator!=(const StatusBarItem& rhs) { return m_widget != rhs.m_widget; } QWidget * widget() const { return m_widget; } void show() const { m_widget->show(); } void hide() const { m_widget->hide(); } private: QPointer m_widget; }; Q_OBJECT public: explicit KisStatusBar(KisViewManager *view); ~KisStatusBar() override; void setup(); void setView(QPointer imageView); void hideAllStatusBarItems(); void showAllStatusBarItems(); KoProgressUpdater *progressUpdater(); public Q_SLOTS: void documentMousePositionChanged(const QPointF &p); void imageSizeChanged(); void setSelection(KisImageWSP image); void setProfile(KisImageWSP image); void setHelp(const QString &t); void updateStatusBarProfileLabel(); void updateSelectionToolTip(); private Q_SLOTS: void updateSelectionIcon(); void showMemoryInfoToolTip(); Q_SIGNALS: void sigCancellationRequested(); /// tell the listener that the memory usage has changed /// and it needs to update its stats void memoryStatusUpdated(); private: void removeStatusBarItem(QWidget *widget); void addStatusBarItem(QWidget *widget, int stretch = 0, bool permanent = false); void updateMemoryStatus(); private: QPointer m_viewManager; QPointer m_imageView; QPointer m_statusBar; KisProgressWidget *m_progress; QScopedPointer m_progressUpdater; QToolButton *m_selectionStatus; QPushButton *m_memoryReportBox; QLabel *m_pointerPositionLabel; KSqueezedTextLabel *m_statusBarStatusLabel; KSqueezedTextLabel *m_statusBarProfileLabel; QString m_shortMemoryTag; QString m_longMemoryTag; QIcon m_memoryStatusIcon; QVector m_statusBarItems; + + bool m_memoryWarningLogged {false}; }; #endif diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp index 5728a165ae..c57df03100 100644 --- a/libs/ui/opengl/kis_opengl.cpp +++ b/libs/ui/opengl/kis_opengl.cpp @@ -1,305 +1,310 @@ /* * Copyright (c) 2007 Adrian Page * * 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 "opengl/kis_opengl.h" #include "opengl/kis_opengl_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include + #include #ifndef GL_RENDERER # define GL_RENDERER 0x1F01 #endif using namespace KisOpenGLPrivate; namespace { bool defaultFormatIsSet = false; bool isDebugEnabled = false; bool isDebugSynchronous = false; boost::optional openGLCheckResult; bool NeedsFenceWorkaround = false; bool NeedsPixmapCacheWorkaround = false; QString debugText("OpenGL Info\n **OpenGL not initialized**"); QVector openglWarningStrings; void openglOnMessageLogged(const QOpenGLDebugMessage& debugMessage) { qDebug() << "OpenGL:" << debugMessage; } } KisOpenGLPrivate::OpenGLCheckResult::OpenGLCheckResult(QOpenGLContext &context) { if (!context.isValid()) { return; } QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used m_rendererString = QString(reinterpret_cast(funcs->glGetString(GL_RENDERER))); m_driverVersionString = QString(reinterpret_cast(funcs->glGetString(GL_VERSION))); m_glMajorVersion = context.format().majorVersion(); m_glMinorVersion = context.format().minorVersion(); m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); m_isOpenGLES = context.isOpenGLES(); } void KisOpenGLPrivate::appendOpenGLWarningString(KLocalizedString warning) { openglWarningStrings << warning; } bool KisOpenGLPrivate::isDefaultFormatSet() { return defaultFormatIsSet; } void KisOpenGL::initialize() { if (openGLCheckResult) return; KIS_SAFE_ASSERT_RECOVER(defaultFormatIsSet) { qWarning() << "Default OpenGL format was not set before calling KisOpenGL::initialize. This might be a BUG!"; setDefaultFormat(); } // we need a QSurface active to get our GL functions from the context QWindow surface; surface.setSurfaceType( QSurface::OpenGLSurface ); surface.create(); QOpenGLContext context; if (!context.create()) { qDebug() << "OpenGL context cannot be created"; + KisUsageLogger::log("OpenGL context cannot be created"); return; } if (!context.isValid()) { qDebug() << "OpenGL context is not valid"; + KisUsageLogger::log("OpenGL context is not valid"); return; } if (!context.makeCurrent(&surface)) { qDebug() << "OpenGL context cannot be made current"; + KisUsageLogger::log("OpenGL context cannot be made current"); return; } QOpenGLFunctions *funcs = context.functions(); openGLCheckResult = OpenGLCheckResult(context); debugText.clear(); QDebug debugOut(&debugText); - debugOut << "OpenGL Info"; + debugOut << "OpenGL Info\n"; debugOut << "\n Vendor: " << reinterpret_cast(funcs->glGetString(GL_VENDOR)); debugOut << "\n Renderer: " << openGLCheckResult->rendererString(); debugOut << "\n Version: " << openGLCheckResult->driverVersionString(); debugOut << "\n Shading language: " << reinterpret_cast(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION)); debugOut << "\n Requested format: " << QSurfaceFormat::defaultFormat(); debugOut << "\n Current format: " << context.format(); debugOut.nospace(); debugOut << "\n Version: " << openGLCheckResult->glMajorVersion() << "." << openGLCheckResult->glMinorVersion(); debugOut.resetFormat(); debugOut << "\n Supports deprecated functions" << openGLCheckResult->supportsDeprecatedFunctions(); debugOut << "\n is OpenGL ES:" << openGLCheckResult->isOpenGLES(); appendPlatformOpenGLDebugText(debugOut); dbgOpenGL.noquote() << debugText; - + KisUsageLogger::write(debugText); } void KisOpenGL::initializeContext(QOpenGLContext *ctx) { KisConfig cfg(true); initialize(); dbgUI << "OpenGL: Opening new context"; if (isDebugEnabled) { // Passing ctx for ownership management only, not specifying context. // QOpenGLDebugLogger only function on the current active context. // FIXME: Do we need to make sure ctx is the active context? QOpenGLDebugLogger* openglLogger = new QOpenGLDebugLogger(ctx); if (openglLogger->initialize()) { qDebug() << "QOpenGLDebugLogger is initialized. Check whether you get a message below."; QObject::connect(openglLogger, &QOpenGLDebugLogger::messageLogged, &openglOnMessageLogged); openglLogger->startLogging(isDebugSynchronous ? QOpenGLDebugLogger::SynchronousLogging : QOpenGLDebugLogger::AsynchronousLogging); openglLogger->logMessage(QOpenGLDebugMessage::createApplicationMessage(QStringLiteral("QOpenGLDebugLogger is logging."))); } else { qDebug() << "QOpenGLDebugLogger cannot be initialized."; delete openglLogger; } } // Double check we were given the version we requested QSurfaceFormat format = ctx->format(); QOpenGLFunctions *f = ctx->functions(); f->initializeOpenGLFunctions(); QFile log(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/krita-opengl.txt"); log.open(QFile::WriteOnly); QString vendor((const char*)f->glGetString(GL_VENDOR)); log.write(vendor.toLatin1()); log.write(", "); log.write(openGLCheckResult->rendererString().toLatin1()); log.write(", "); QString version((const char*)f->glGetString(GL_VERSION)); log.write(version.toLatin1()); log.close(); // Check if we have a bugged driver that needs fence workaround bool isOnX11 = false; #ifdef HAVE_X11 isOnX11 = true; #endif if ((isOnX11 && openGLCheckResult->rendererString().startsWith("AMD")) || cfg.forceOpenGLFenceWorkaround()) { NeedsFenceWorkaround = true; } /** * NVidia + Qt's openGL don't play well together and one cannot * draw a pixmap on a widget more than once in one rendering cycle. * * It can be workarounded by drawing strictly via QPixmapCache and * only when the pixmap size in bigger than doubled size of the * display framebuffer. That is for 8-bit HD display, you should have * a cache bigger than 16 MiB. Don't ask me why. (DK) * * See bug: https://bugs.kde.org/show_bug.cgi?id=361709 * * TODO: check if this workaround is still needed after merging * Qt5+openGL3 branch. */ if (vendor.toUpper().contains("NVIDIA")) { NeedsPixmapCacheWorkaround = true; const QRect screenSize = QApplication::desktop()->screenGeometry(); const int minCacheSize = 20 * 1024; const int cacheSize = 2048 + 2 * 4 * screenSize.width() * screenSize.height() / 1024; //KiB QPixmapCache::setCacheLimit(qMax(minCacheSize, cacheSize)); } } const QString &KisOpenGL::getDebugText() { initialize(); return debugText; } QStringList KisOpenGL::getOpenGLWarnings() { QStringList strings; Q_FOREACH (const KLocalizedString &item, openglWarningStrings) { strings << item.toString(); } return strings; } // XXX Temporary function to allow LoD on OpenGL3 without triggering // all of the other 3.2 functionality, can be removed once we move to Qt5.7 bool KisOpenGL::supportsLoD() { initialize(); return openGLCheckResult->supportsLoD(); } bool KisOpenGL::hasOpenGL3() { initialize(); return openGLCheckResult->hasOpenGL3(); } bool KisOpenGL::hasOpenGLES() { initialize(); return openGLCheckResult->isOpenGLES(); } bool KisOpenGL::supportsFenceSync() { initialize(); return openGLCheckResult->supportsFenceSync(); } bool KisOpenGL::needsFenceWorkaround() { initialize(); return NeedsFenceWorkaround; } bool KisOpenGL::needsPixmapCacheWorkaround() { initialize(); return NeedsPixmapCacheWorkaround; } void KisOpenGL::setDefaultFormat(bool enableDebug, bool debugSynchronous) { if (defaultFormatIsSet) { return; } defaultFormatIsSet = true; QSurfaceFormat format; #ifdef Q_OS_OSX format.setVersion(3, 2); format.setProfile(QSurfaceFormat::CoreProfile); #else // XXX This can be removed once we move to Qt5.7 format.setVersion(3, 0); format.setProfile(QSurfaceFormat::CompatibilityProfile); format.setOptions(QSurfaceFormat::DeprecatedFunctions); #endif format.setDepthBufferSize(24); format.setStencilBufferSize(8); format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); format.setSwapInterval(0); // Disable vertical refresh syncing isDebugEnabled = enableDebug; if (enableDebug) { format.setOption(QSurfaceFormat::DebugContext, true); isDebugSynchronous = debugSynchronous; qDebug() << "QOpenGLDebugLogger will be enabled, synchronous:" << debugSynchronous; } QSurfaceFormat::setDefaultFormat(format); } bool KisOpenGL::hasOpenGL() { return openGLCheckResult->isSupportedVersion(); } diff --git a/plugins/extensions/buginfo/dlg_buginfo.cpp b/plugins/extensions/buginfo/dlg_buginfo.cpp index b19dc932a1..362c9e1253 100644 --- a/plugins/extensions/buginfo/dlg_buginfo.cpp +++ b/plugins/extensions/buginfo/dlg_buginfo.cpp @@ -1,103 +1,118 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dlg_buginfo.h" #include #include #include #include #include #include #include #include #include +#include +#include +#include +#include #include "kis_document_aware_spin_box_unit_manager.h" DlgBugInfo::DlgBugInfo(QWidget *parent) : KoDialog(parent) { setCaption(i18n("Please paste this information in your bug report")); setButtons(User1 | Ok); setButtonText(User1, i18n("Copy to clipboard")); setDefaultButton(Ok); m_page = new WdgBugInfo(this); Q_CHECK_PTR(m_page); setMainWidget(m_page); QString info; - // NOTE: This is intentionally not translated! - - // Krita version info - info.append("Krita"); - info.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); - info.append("\n\n"); - - info.append("Qt"); - info.append("\n Version (compiled): ").append(QT_VERSION_STR); - info.append("\n Version (loaded): ").append(qVersion()); - info.append("\n\n"); - - // OS information - info.append("OS Information"); - info.append("\n Build ABI: ").append(QSysInfo::buildAbi()); - info.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); - info.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); - info.append("\n Kernel Type: ").append(QSysInfo::kernelType()); - info.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); - info.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); - info.append("\n Product Type: ").append(QSysInfo::productType()); - info.append("\n Product Version: ").append(QSysInfo::productVersion()); - info.append("\n\n"); - - // OpenGL information - info.append("\n").append(KisOpenGL::getDebugText()); - info.append("\n\n"); - // Hardware information - info.append("Hardware Information"); - info.append(QString("\n Memory: %1").arg(KisImageConfig(true).totalRAM() / 1024)).append(" Gb"); - info.append(QString("\n Cores: %1").arg(QThread::idealThreadCount())); - info.append("\n Swap: ").append(KisImageConfig(true).swapDir()); - + const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); + + if (!kritarc.value("LogUsage", true).toBool() || !QFileInfo(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log").exists()) { + + // NOTE: This is intentionally not translated! + + // Krita version info + info.append("Krita"); + info.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); + info.append("\n\n"); + + info.append("Qt"); + info.append("\n Version (compiled): ").append(QT_VERSION_STR); + info.append("\n Version (loaded): ").append(qVersion()); + info.append("\n\n"); + + // OS information + info.append("OS Information"); + info.append("\n Build ABI: ").append(QSysInfo::buildAbi()); + info.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); + info.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); + info.append("\n Kernel Type: ").append(QSysInfo::kernelType()); + info.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); + info.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); + info.append("\n Product Type: ").append(QSysInfo::productType()); + info.append("\n Product Version: ").append(QSysInfo::productVersion()); + info.append("\n\n"); + + // OpenGL information + info.append("\n").append(KisOpenGL::getDebugText()); + info.append("\n\n"); + // Hardware information + info.append("Hardware Information"); + info.append(QString("\n Memory: %1").arg(KisImageConfig(true).totalRAM() / 1024)).append(" Gb"); + info.append(QString("\n Cores: %1").arg(QThread::idealThreadCount())); + info.append("\n Swap: ").append(KisImageConfig(true).swapDir()); + } + else { + QFile f(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log"); + f.open(QFile::ReadOnly); + info = QString::fromUtf8(f.readAll()); + f.close(); + } // calculate a default height for the widget int wheight = m_page->sizeHint().height(); m_page->txtBugInfo->setText(info); QFontMetrics fm = m_page->txtBugInfo->fontMetrics(); int target_height = fm.height() * info.split('\n').size() + wheight; QDesktopWidget dw; QRect screen_rect = dw.availableGeometry(dw.primaryScreen()); resize(m_page->size().width(), target_height > screen_rect.height() ? screen_rect.height() : target_height); connect(this, &KoDialog::user1Clicked, this, [this](){ QGuiApplication::clipboard()->setText(m_page->txtBugInfo->toPlainText()); m_page->txtBugInfo->selectAll(); // feedback }); } DlgBugInfo::~DlgBugInfo() { delete m_page; } diff --git a/plugins/tools/basictools/kis_tool_brush.cc b/plugins/tools/basictools/kis_tool_brush.cc index e1117114e3..a8b313fcab 100644 --- a/plugins/tools/basictools/kis_tool_brush.cc +++ b/plugins/tools/basictools/kis_tool_brush.cc @@ -1,477 +1,485 @@ /* * kis_tool_brush.cc - part of Krita * * Copyright (c) 2003-2004 Boudewijn Rempt * Copyright (c) 2015 Moritz Molch * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_brush.h" #include #include #include #include #include #include #include #include #include #include #include "kis_cursor.h" #include "kis_config.h" #include "kis_slider_spin_box.h" #include "kundo2magicstring.h" +#include + #define MAXIMUM_SMOOTHNESS_DISTANCE 1000.0 // 0..1000.0 == weight in gui #define MAXIMUM_MAGNETISM 1000 void KisToolBrush::addSmoothingAction(int enumId, const QString &id) { /** * KisToolBrush is the base of several tools, but the actions * should be unique, so let's be careful with them */ QAction *a = action(id); connect(a, SIGNAL(triggered()), &m_signalMapper, SLOT(map())); m_signalMapper.setMapping(a, enumId); } KisToolBrush::KisToolBrush(KoCanvasBase * canvas) : KisToolFreehand(canvas, KisCursor::load("tool_freehand_cursor.png", 5, 5), kundo2_i18n("Freehand Brush Stroke")) { setObjectName("tool_brush"); createOptionWidget(); connect(this, SIGNAL(smoothingTypeChanged()), this, SLOT(resetCursorStyle())); addSmoothingAction(KisSmoothingOptions::NO_SMOOTHING, "set_no_brush_smoothing"); addSmoothingAction(KisSmoothingOptions::SIMPLE_SMOOTHING, "set_simple_brush_smoothing"); addSmoothingAction(KisSmoothingOptions::WEIGHTED_SMOOTHING, "set_weighted_brush_smoothing"); addSmoothingAction(KisSmoothingOptions::STABILIZER, "set_stabilizer_brush_smoothing"); } KisToolBrush::~KisToolBrush() { } void KisToolBrush::activate(ToolActivation activation, const QSet &shapes) { KisToolFreehand::activate(activation, shapes); connect(&m_signalMapper, SIGNAL(mapped(int)), SLOT(slotSetSmoothingType(int)), Qt::UniqueConnection); QAction *toggleaction = action("toggle_assistant"); connect(toggleaction, SIGNAL(triggered(bool)), m_chkAssistant, SLOT(toggle()), Qt::UniqueConnection); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolBrush::deactivate() { disconnect(&m_signalMapper, 0, this, 0); QAction *toggleaction = action("toggle_assistant"); disconnect(toggleaction, 0, m_chkAssistant, 0); KisToolFreehand::deactivate(); } int KisToolBrush::smoothingType() const { return smoothingOptions()->smoothingType(); } bool KisToolBrush::smoothPressure() const { return smoothingOptions()->smoothPressure(); } int KisToolBrush::smoothnessQuality() const { return smoothingOptions()->smoothnessDistance(); } qreal KisToolBrush::smoothnessFactor() const { return smoothingOptions()->tailAggressiveness(); } void KisToolBrush::slotSetSmoothingType(int index) { /** * The slot can also be called from smoothing-type-switching * action that would mean the combo box will not be synchronized */ if (m_cmbSmoothingType->currentIndex() != index) { m_cmbSmoothingType->setCurrentIndex(index); } + if (smoothingOptions()->smoothingType() == index) return; + switch (index) { case 0: + KisUsageLogger::log("Disabled smoothing."); smoothingOptions()->setSmoothingType(KisSmoothingOptions::NO_SMOOTHING); showControl(m_sliderSmoothnessDistance, false); showControl(m_sliderTailAggressiveness, false); showControl(m_chkSmoothPressure, false); showControl(m_chkUseScalableDistance, false); showControl(m_sliderDelayDistance, false); showControl(m_chkFinishStabilizedCurve, false); showControl(m_chkStabilizeSensors, false); break; case 1: + KisUsageLogger::log("Enabled simple smoothing."); smoothingOptions()->setSmoothingType(KisSmoothingOptions::SIMPLE_SMOOTHING); showControl(m_sliderSmoothnessDistance, false); showControl(m_sliderTailAggressiveness, false); showControl(m_chkSmoothPressure, false); showControl(m_chkUseScalableDistance, false); showControl(m_sliderDelayDistance, false); showControl(m_chkFinishStabilizedCurve, false); showControl(m_chkStabilizeSensors, false); break; case 2: + KisUsageLogger::log("Enabled weighted smoothing."); smoothingOptions()->setSmoothingType(KisSmoothingOptions::WEIGHTED_SMOOTHING); showControl(m_sliderSmoothnessDistance, true); showControl(m_sliderTailAggressiveness, true); showControl(m_chkSmoothPressure, true); showControl(m_chkUseScalableDistance, true); showControl(m_sliderDelayDistance, false); showControl(m_chkFinishStabilizedCurve, false); showControl(m_chkStabilizeSensors, false); break; case 3: default: + KisUsageLogger::log("Enabled stabilizer."); smoothingOptions()->setSmoothingType(KisSmoothingOptions::STABILIZER); showControl(m_sliderSmoothnessDistance, true); showControl(m_sliderTailAggressiveness, false); showControl(m_chkSmoothPressure, false); showControl(m_chkUseScalableDistance, true); showControl(m_sliderDelayDistance, true); showControl(m_chkFinishStabilizedCurve, true); showControl(m_chkStabilizeSensors, true); } emit smoothingTypeChanged(); } void KisToolBrush::slotSetSmoothnessDistance(qreal distance) { smoothingOptions()->setSmoothnessDistance(distance); emit smoothnessQualityChanged(); } void KisToolBrush::slotSetTailAgressiveness(qreal argh_rhhrr) { smoothingOptions()->setTailAggressiveness(argh_rhhrr); emit smoothnessFactorChanged(); } // used with weighted smoothing void KisToolBrush::setSmoothPressure(bool value) { smoothingOptions()->setSmoothPressure(value); } void KisToolBrush::slotSetMagnetism(int magnetism) { m_magnetism = expf(magnetism / (double)MAXIMUM_MAGNETISM) / expf(1.0); } bool KisToolBrush::useScalableDistance() const { return smoothingOptions()->useScalableDistance(); } // used with weighted smoothing void KisToolBrush::setUseScalableDistance(bool value) { smoothingOptions()->setUseScalableDistance(value); emit useScalableDistanceChanged(); } void KisToolBrush::resetCursorStyle() { KisConfig cfg(true); CursorStyle cursorStyle = cfg.newCursorStyle(); // When the stabilizer is in use, we avoid using the brush outline cursor, // because it would hide the real position of the cursor to the user, // yielding unexpected results. if (smoothingOptions()->smoothingType() == KisSmoothingOptions::STABILIZER && smoothingOptions()->useDelayDistance() && cursorStyle == CURSOR_STYLE_NO_CURSOR) { useCursor(KisCursor::roundCursor()); } else { KisToolFreehand::resetCursorStyle(); } overrideCursorIfNotEditable(); } // stabilizer brush settings bool KisToolBrush::useDelayDistance() const { return smoothingOptions()->useDelayDistance(); } qreal KisToolBrush::delayDistance() const { return smoothingOptions()->delayDistance(); } void KisToolBrush::setUseDelayDistance(bool value) { smoothingOptions()->setUseDelayDistance(value); m_sliderDelayDistance->setEnabled(value); enableControl(m_chkFinishStabilizedCurve, !value); emit useDelayDistanceChanged(); } void KisToolBrush::setDelayDistance(qreal value) { smoothingOptions()->setDelayDistance(value); emit delayDistanceChanged(); } void KisToolBrush::setFinishStabilizedCurve(bool value) { smoothingOptions()->setFinishStabilizedCurve(value); emit finishStabilizedCurveChanged(); } bool KisToolBrush::finishStabilizedCurve() const { return smoothingOptions()->finishStabilizedCurve(); } void KisToolBrush::setStabilizeSensors(bool value) { smoothingOptions()->setStabilizeSensors(value); emit stabilizeSensorsChanged(); } bool KisToolBrush::stabilizeSensors() const { return smoothingOptions()->stabilizeSensors(); } void KisToolBrush::updateSettingsViews() { m_cmbSmoothingType->setCurrentIndex(smoothingOptions()->smoothingType()); m_sliderSmoothnessDistance->setValue(smoothingOptions()->smoothnessDistance()); m_chkDelayDistance->setChecked(smoothingOptions()->useDelayDistance()); m_sliderDelayDistance->setValue(smoothingOptions()->delayDistance()); m_sliderTailAggressiveness->setValue(smoothingOptions()->tailAggressiveness()); m_chkSmoothPressure->setChecked(smoothingOptions()->smoothPressure()); m_chkUseScalableDistance->setChecked(smoothingOptions()->useScalableDistance()); m_cmbSmoothingType->setCurrentIndex((int)smoothingOptions()->smoothingType()); m_chkStabilizeSensors->setChecked(smoothingOptions()->stabilizeSensors()); emit smoothnessQualityChanged(); emit smoothnessFactorChanged(); emit smoothPressureChanged(); emit smoothingTypeChanged(); emit useScalableDistanceChanged(); emit useDelayDistanceChanged(); emit delayDistanceChanged(); emit finishStabilizedCurveChanged(); emit stabilizeSensorsChanged(); KisTool::updateSettingsViews(); } QWidget * KisToolBrush::createOptionWidget() { QWidget *optionsWidget = KisToolFreehand::createOptionWidget(); optionsWidget->setObjectName(toolId() + "option widget"); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); optionsWidget->layout()->addWidget(specialSpacer); // Line smoothing configuration m_cmbSmoothingType = new QComboBox(optionsWidget); m_cmbSmoothingType->addItems(QStringList() << i18n("None") << i18n("Basic") << i18n("Weighted") << i18n("Stabilizer")); connect(m_cmbSmoothingType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetSmoothingType(int))); addOptionWidgetOption(m_cmbSmoothingType, new QLabel(i18n("Brush Smoothing:"))); m_sliderSmoothnessDistance = new KisDoubleSliderSpinBox(optionsWidget); m_sliderSmoothnessDistance->setRange(3.0, MAXIMUM_SMOOTHNESS_DISTANCE, 1); m_sliderSmoothnessDistance->setExponentRatio(3.0); // help pick smaller values m_sliderSmoothnessDistance->setEnabled(true); connect(m_sliderSmoothnessDistance, SIGNAL(valueChanged(qreal)), SLOT(slotSetSmoothnessDistance(qreal))); m_sliderSmoothnessDistance->setValue(smoothingOptions()->smoothnessDistance()); addOptionWidgetOption(m_sliderSmoothnessDistance, new QLabel(i18n("Distance:"))); // Finish stabilizer curve m_chkFinishStabilizedCurve = new QCheckBox(optionsWidget); m_chkFinishStabilizedCurve->setMinimumHeight(qMax(m_sliderSmoothnessDistance->sizeHint().height()-3, m_chkFinishStabilizedCurve->sizeHint().height())); connect(m_chkFinishStabilizedCurve, SIGNAL(toggled(bool)), this, SLOT(setFinishStabilizedCurve(bool))); m_chkFinishStabilizedCurve->setChecked(smoothingOptions()->finishStabilizedCurve()); // Delay Distance for Stabilizer QWidget* delayWidget = new QWidget(optionsWidget); QHBoxLayout* delayLayout = new QHBoxLayout(delayWidget); delayLayout->setContentsMargins(0,0,0,0); delayLayout->setSpacing(1); QLabel* delayLabel = new QLabel(i18n("Delay:"), optionsWidget); delayLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); delayLayout->addWidget(delayLabel); m_chkDelayDistance = new QCheckBox(optionsWidget); m_chkDelayDistance->setLayoutDirection(Qt::RightToLeft); delayWidget->setToolTip(i18n("Delay the brush stroke to make the line smoother")); connect(m_chkDelayDistance, SIGNAL(toggled(bool)), this, SLOT(setUseDelayDistance(bool))); delayLayout->addWidget(m_chkDelayDistance); m_sliderDelayDistance = new KisDoubleSliderSpinBox(optionsWidget); m_sliderDelayDistance->setToolTip(i18n("Radius where the brush is blocked")); m_sliderDelayDistance->setRange(0, 500); m_sliderDelayDistance->setExponentRatio(3.0); // help pick smaller values m_sliderDelayDistance->setSuffix(i18n(" px")); connect(m_sliderDelayDistance, SIGNAL(valueChanged(qreal)), SLOT(setDelayDistance(qreal))); addOptionWidgetOption(m_sliderDelayDistance, delayWidget); addOptionWidgetOption(m_chkFinishStabilizedCurve, new QLabel(i18n("Finish line:"))); m_sliderDelayDistance->setValue(smoothingOptions()->delayDistance()); m_chkDelayDistance->setChecked(smoothingOptions()->useDelayDistance()); // if the state is not flipped, then the previous line doesn't generate any signals setUseDelayDistance(m_chkDelayDistance->isChecked()); // Stabilize sensors m_chkStabilizeSensors = new QCheckBox(optionsWidget); m_chkStabilizeSensors->setMinimumHeight(qMax(m_sliderSmoothnessDistance->sizeHint().height()-3, m_chkStabilizeSensors->sizeHint().height())); connect(m_chkStabilizeSensors, SIGNAL(toggled(bool)), this, SLOT(setStabilizeSensors(bool))); m_chkStabilizeSensors->setChecked(smoothingOptions()->stabilizeSensors()); addOptionWidgetOption(m_chkStabilizeSensors, new QLabel(i18n("Stabilize Sensors:"))); m_sliderTailAggressiveness = new KisDoubleSliderSpinBox(optionsWidget); m_sliderTailAggressiveness->setRange(0.0, 1.0, 2); m_sliderTailAggressiveness->setEnabled(true); connect(m_sliderTailAggressiveness, SIGNAL(valueChanged(qreal)), SLOT(slotSetTailAgressiveness(qreal))); m_sliderTailAggressiveness->setValue(smoothingOptions()->tailAggressiveness()); addOptionWidgetOption(m_sliderTailAggressiveness, new QLabel(i18n("Stroke Ending:"))); m_chkSmoothPressure = new QCheckBox(optionsWidget); m_chkSmoothPressure->setMinimumHeight(qMax(m_sliderSmoothnessDistance->sizeHint().height()-3, m_chkSmoothPressure->sizeHint().height())); m_chkSmoothPressure->setChecked(smoothingOptions()->smoothPressure()); connect(m_chkSmoothPressure, SIGNAL(toggled(bool)), this, SLOT(setSmoothPressure(bool))); addOptionWidgetOption(m_chkSmoothPressure, new QLabel(QString("%1:").arg(i18n("Smooth Pressure")))); m_chkUseScalableDistance = new QCheckBox(optionsWidget); m_chkUseScalableDistance->setChecked(smoothingOptions()->useScalableDistance()); m_chkUseScalableDistance->setMinimumHeight(qMax(m_sliderSmoothnessDistance->sizeHint().height()-3, m_chkUseScalableDistance->sizeHint().height())); m_chkUseScalableDistance->setToolTip(i18nc("@info:tooltip", "Scalable distance takes zoom level " "into account and makes the distance " "be visually constant whatever zoom " "level is chosen")); connect(m_chkUseScalableDistance, SIGNAL(toggled(bool)), this, SLOT(setUseScalableDistance(bool))); addOptionWidgetOption(m_chkUseScalableDistance, new QLabel(QString("%1:").arg(i18n("Scalable Distance")))); // add a line spacer so we know that the next set of options are for different settings QFrame* line = new QFrame(optionsWidget); line->setObjectName(QString::fromUtf8("line")); line->setFrameShape(QFrame::HLine); addOptionWidgetOption(line); // Drawing assistant configuration QWidget* assistantWidget = new QWidget(optionsWidget); QGridLayout* assistantLayout = new QGridLayout(assistantWidget); assistantLayout->setContentsMargins(10,0,0,0); assistantLayout->setSpacing(5); m_chkAssistant = new QCheckBox(optionsWidget); m_chkAssistant->setText(i18n("Snap to Assistants")); assistantWidget->setToolTip(i18n("You need to add Assistants before this tool will work.")); connect(m_chkAssistant, SIGNAL(toggled(bool)), this, SLOT(setAssistant(bool))); addOptionWidgetOption(assistantWidget, m_chkAssistant); m_sliderMagnetism = new KisSliderSpinBox(optionsWidget); m_sliderMagnetism->setToolTip(i18n("Assistant Magnetism")); m_sliderMagnetism->setRange(0, MAXIMUM_MAGNETISM); m_sliderMagnetism->setValue(m_magnetism * MAXIMUM_MAGNETISM); connect(m_sliderMagnetism, SIGNAL(valueChanged(int)), SLOT(slotSetMagnetism(int))); QLabel* magnetismLabel = new QLabel(i18n("Magnetism:")); addOptionWidgetOption(m_sliderMagnetism, magnetismLabel); QLabel* snapSingleLabel = new QLabel(i18n("Snap Single:")); m_chkOnlyOneAssistant = new QCheckBox(optionsWidget); m_chkOnlyOneAssistant->setToolTip(i18nc("@info:tooltip","Make it only snap to a single assistant, prevents snapping mess while using the infinite assistants.")); m_chkOnlyOneAssistant->setCheckState(Qt::Checked);//turn on by default. connect(m_chkOnlyOneAssistant, SIGNAL(toggled(bool)), this, SLOT(setOnlyOneAssistantSnap(bool))); addOptionWidgetOption(m_chkOnlyOneAssistant, snapSingleLabel); // set the assistant snapping options to hidden by default and toggle their visibility based based off snapping checkbox m_sliderMagnetism->setVisible(false); m_chkOnlyOneAssistant->setVisible(false); snapSingleLabel->setVisible(false); magnetismLabel->setVisible(false); connect(m_chkAssistant, SIGNAL(toggled(bool)), m_sliderMagnetism, SLOT(setVisible(bool))); connect(m_chkAssistant, SIGNAL(toggled(bool)), m_chkOnlyOneAssistant, SLOT(setVisible(bool))); connect(m_chkAssistant, SIGNAL(toggled(bool)), snapSingleLabel, SLOT(setVisible(bool))); connect(m_chkAssistant, SIGNAL(toggled(bool)), magnetismLabel, SLOT(setVisible(bool))); KisConfig cfg(true); slotSetSmoothingType(cfg.lineSmoothingType()); return optionsWidget; } QList KisToolBrushFactory::createActionsImpl() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QList actions = KisToolPaintFactoryBase::createActionsImpl(); actions << actionRegistry->makeQAction("set_no_brush_smoothing"); actions << actionRegistry->makeQAction("set_simple_brush_smoothing"); actions << actionRegistry->makeQAction("set_weighted_brush_smoothing"); actions << actionRegistry->makeQAction("set_stabilizer_brush_smoothing"); actions << actionRegistry->makeQAction("toggle_assistant"); return actions; }