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
KisColorButton
QPushButton
KisSliderSpinBox
QWidget
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;
}