diff --git a/krita/data/input/kritadefault.profile b/krita/data/input/kritadefault.profile index 9f09feca8f..da28ca5dd0 100644 --- a/krita/data/input/kritadefault.profile +++ b/krita/data/input/kritadefault.profile @@ -1,70 +1,73 @@ [Alternate Invocation] 0={1;2;[1000023,1000020];1;0;0} 1={0;2;[1000021,1000020];1;0;0} 2={5;2;[1000021];2;0;0} 3={3;2;[1000021,1000023];2;0;0} 4={2;2;[1000023,1000021];1;0;0} 5={4;2;[1000021];1;0;0} [Change Primary Setting] 0={0;2;[1000020];1;0;0} [Exposure or Gamma] 0={0;2;[59];1;0;0} [General] name=Krita Default version=3 [Pan Canvas] 0={0;4;[];0;0;2} 1={0;2;[20];1;0;0} 2={0;2;[];4;0;0} 3={1;1;[];0;0;0} 4={2;1;[];0;0;0} 5={3;1;[];0;0;0} 6={4;1;[];0;0;0} 7={0;3;[];0;5;0} [Rotate Canvas] 0={0;2;[1000020,20];1;0;0} 1={1;2;[1000020,1000023,20];1;0;0} 2={0;2;[1000020];4;0;0} 3={2;1;[34];0;0;0} 4={4;1;[35];0;0;0} 5={3;1;[36];0;0;0} 6={0;4;[];0;0;3} [Select Layer] 0={1;2;[1000020,52];1;0;0} 1={0;2;[52];1;0;0} [Show Popup Palette] 0={0;2;[];2;0;0} [Switch Time] 0={0;1;[1000014];0;0;0} 1={1;1;[1000012];0;0;0} [Tool Invocation] 0={3;2;[56];1;0;0} 1={1;1;[1000005];0;0;0} 2={0;2;[];1;0;0} 3={1;1;[1000004];0;0;0} 4={2;1;[1000000];0;0;0} [Zoom Canvas] 0={2;1;[2b];0;0;0} 1={4;1;[31];0;0;0} 10={5;1;[32];0;0;0} 11={0;4;[];0;0;1} 12={8;2;[1000021,1000023];4;0;0} 13={0;4;[];0;0;4} 2={3;1;[2d];0;0;0} 3={2;1;[3d];0;0;0} 4={3;3;[];0;2;0} 5={2;3;[];0;1;0} 6={7;2;[1000021];4;0;0} 7={7;2;[1000021,20];1;0;0} 8={6;1;[33];0;0;0} 9={8;2;[1000021,1000023,20];1;0;0} + +[Zoom and Rotate Canvas] +0={0;4;[];0;0;5} diff --git a/libs/ui/input/kis_input_manager_p.cpp b/libs/ui/input/kis_input_manager_p.cpp index 085e089ff1..d14f94d011 100644 --- a/libs/ui/input/kis_input_manager_p.cpp +++ b/libs/ui/input/kis_input_manager_p.cpp @@ -1,673 +1,674 @@ /* * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_input_manager_p.h" #include #include #include #include #include "kis_input_manager.h" #include "kis_config.h" #include "kis_abstract_input_action.h" #include "kis_tool_invocation_action.h" #include "kis_stroke_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_native_gesture_shortcut.h" #include "kis_input_profile_manager.h" #include "kis_extended_modifiers_mapper.h" #include "kis_zoom_and_rotate_action.h" /** * This hungry class EventEater encapsulates event masking logic. * * Its basic role is to kill synthetic mouseMove events sent by Xorg or Qt after * tablet events. Those events are sent in order to allow widgets that haven't * implemented tablet specific functionality to seamlessly behave as if one were * using a mouse. These synthetic events are *supposed* to be optional, or at * least come with a flag saying "This is a fake event!!" but neither of those * methods is trustworthy. (This is correct as of Qt 5.4 + Xorg.) * * Qt 5.4 provides no reliable way to see if a user's tablet is being hovered * over the pad, since it converts all tablethover events into mousemove, with * no option to turn this off. Moreover, sometimes the MouseButtonPress event * from the tapping their tablet happens BEFORE the TabletPress event. This * means we have to resort to a somewhat complicated logic. What makes this * truly a joke is that we are not guaranteed to observe TabletProximityEnter * events when we're using a tablet, either, you may only see an Enter event. * * Once we see tablet events heading our way, we can say pretty confidently that * every mouse event is fake. There are two painful cases to consider - a * mousePress event could arrive before the tabletPress event, or it could * arrive much later, e.g. after tabletRelease. The first was only seen on Linux * with Qt's XInput2 code, the solution was to hold onto mousePress events * temporarily and wait for tabletPress later, this is contained in git history * but is now removed. The second case is currently handled by the * eatOneMousePress function, which waits as long as necessary to detect and * block a single mouse press event. */ static bool isMouseEventType(QEvent::Type t) { return (t == QEvent::MouseMove || t == QEvent::MouseButtonPress || t == QEvent::MouseButtonRelease || t == QEvent::MouseButtonDblClick); } KisInputManager::Private::EventEater::EventEater() { KisConfig cfg(true); activateSecondaryButtonsWorkaround = cfg.useRightMiddleTabletButtonWorkaround(); } bool KisInputManager::Private::EventEater::eventFilter(QObject* target, QEvent* event ) { Q_UNUSED(target) auto debugEvent = [&](int i) { if (KisTabletDebugger::instance()->debugEnabled()) { QString pre = QString("[BLOCKED %1:]").arg(i); QMouseEvent *ev = static_cast(event); dbgTablet << KisTabletDebugger::instance()->eventToString(*ev, pre); } }; auto debugTabletEvent = [&](int i) { if (KisTabletDebugger::instance()->debugEnabled()) { QString pre = QString("[BLOCKED %1:]").arg(i); QTabletEvent *ev = static_cast(event); dbgTablet << KisTabletDebugger::instance()->eventToString(*ev, pre); } }; if (peckish && event->type() == QEvent::MouseButtonPress // Drop one mouse press following tabletPress or touchBegin && (static_cast(event)->button() == Qt::LeftButton)) { peckish = false; debugEvent(1); return true; } if (activateSecondaryButtonsWorkaround) { if (event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease) { QTabletEvent *te = static_cast(event); if (te->button() != Qt::LeftButton) { debugTabletEvent(3); return true; } } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonDblClick) { QMouseEvent *me = static_cast(event); if (me->button() != Qt::LeftButton) { return false; } } } if (isMouseEventType(event->type()) && (hungry // On Mac, we need mouse events when the tablet is in proximity, but not pressed down // since tablet move events are not generated until after tablet press. #ifndef Q_OS_MAC || (eatSyntheticEvents && static_cast(event)->source() != Qt::MouseEventNotSynthesized) #endif )) { // Drop mouse events if enabled or event was synthetic & synthetic events are disabled debugEvent(2); return true; } return false; // All clear - let this one through! } void KisInputManager::Private::EventEater::activate() { if (!hungry && (KisTabletDebugger::instance()->debugEnabled())) { dbgTablet << "Start blocking mouse events"; } hungry = true; } void KisInputManager::Private::EventEater::deactivate() { if (hungry && (KisTabletDebugger::instance()->debugEnabled())) { dbgTablet << "Stop blocking mouse events"; } hungry = false; } void KisInputManager::Private::EventEater::eatOneMousePress() { // Enable on other platforms if getting full-pressure splotches peckish = true; } bool KisInputManager::Private::ignoringQtCursorEvents() { return eventEater.hungry; } void KisInputManager::Private::setMaskSyntheticEvents(bool value) { eventEater.eatSyntheticEvents = value; } KisInputManager::Private::Private(KisInputManager *qq) : q(qq) , moveEventCompressor(10 /* ms */, KisSignalCompressor::FIRST_ACTIVE) , priorityEventFilterSeqNo(0) , canvasSwitcher(this, qq) { KisConfig cfg(true); moveEventCompressor.setDelay(cfg.tabletEventsDelay()); testingAcceptCompressedTabletEvents = cfg.testingAcceptCompressedTabletEvents(); testingCompressBrushEvents = cfg.testingCompressBrushEvents(); if (cfg.trackTabletEventLatency()) { tabletLatencyTracker = new TabletLatencyTracker(); } matcher.setInputActionGroupsMaskCallback( [this] () { return this->canvas ? this->canvas->inputActionGroupsMask() : AllActionGroup; }); } static const int InputWidgetsThreshold = 2000; static const int OtherWidgetsThreshold = 400; KisInputManager::Private::CanvasSwitcher::CanvasSwitcher(Private *_d, QObject *p) : QObject(p), d(_d), eatOneMouseStroke(false), focusSwitchThreshold(InputWidgetsThreshold) { } void KisInputManager::Private::CanvasSwitcher::setupFocusThreshold(QObject* object) { QWidget *widget = qobject_cast(object); KIS_SAFE_ASSERT_RECOVER_RETURN(widget); thresholdConnections.clear(); thresholdConnections.addConnection(&focusSwitchThreshold, SIGNAL(timeout()), widget, SLOT(setFocus())); } void KisInputManager::Private::CanvasSwitcher::addCanvas(KisCanvas2 *canvas) { if (!canvas) return; QObject *canvasWidget = canvas->canvasWidget(); if (!canvasResolver.contains(canvasWidget)) { canvasResolver.insert(canvasWidget, canvas); d->q->setupAsEventFilter(canvasWidget); canvasWidget->installEventFilter(this); setupFocusThreshold(canvasWidget); focusSwitchThreshold.setEnabled(false); d->canvas = canvas; d->toolProxy = qobject_cast(canvas->toolProxy()); } else { KIS_ASSERT_RECOVER_RETURN(d->canvas == canvas); } } void KisInputManager::Private::CanvasSwitcher::removeCanvas(KisCanvas2 *canvas) { QObject *widget = canvas->canvasWidget(); canvasResolver.remove(widget); if (d->eventsReceiver == widget) { d->q->setupAsEventFilter(0); } widget->removeEventFilter(this); } bool isInputWidget(QWidget *w) { if (!w) return false; QList types; types << QLatin1String("QAbstractSlider"); types << QLatin1String("QAbstractSpinBox"); types << QLatin1String("QLineEdit"); types << QLatin1String("QTextEdit"); types << QLatin1String("QPlainTextEdit"); types << QLatin1String("QComboBox"); types << QLatin1String("QKeySequenceEdit"); Q_FOREACH (const QLatin1String &type, types) { if (w->inherits(type.data())) { return true; } } return false; } bool KisInputManager::Private::CanvasSwitcher::eventFilter(QObject* object, QEvent* event ) { if (canvasResolver.contains(object)) { switch (event->type()) { case QEvent::FocusIn: { QFocusEvent *fevent = static_cast(event); KisCanvas2 *canvas = canvasResolver.value(object); // only relevant canvases from the same main window should be // registered in the switcher KIS_SAFE_ASSERT_RECOVER_BREAK(canvas); if (canvas != d->canvas) { eatOneMouseStroke = 2 * (fevent->reason() == Qt::MouseFocusReason); } d->canvas = canvas; d->toolProxy = qobject_cast(canvas->toolProxy()); d->q->setupAsEventFilter(object); object->removeEventFilter(this); object->installEventFilter(this); setupFocusThreshold(object); focusSwitchThreshold.setEnabled(false); const QPoint globalPos = QCursor::pos(); const QPoint localPos = d->canvas->canvasWidget()->mapFromGlobal(globalPos); QWidget *canvasWindow = d->canvas->canvasWidget()->window(); const QPoint windowsPos = canvasWindow ? canvasWindow->mapFromGlobal(globalPos) : localPos; QEnterEvent event(localPos, windowsPos, globalPos); d->q->eventFilter(object, &event); break; } case QEvent::FocusOut: { focusSwitchThreshold.setEnabled(true); break; } case QEvent::Enter: { break; } case QEvent::Leave: { focusSwitchThreshold.stop(); break; } case QEvent::Wheel: { QWidget *widget = static_cast(object); widget->setFocus(); break; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::TabletPress: case QEvent::TabletRelease: focusSwitchThreshold.forceDone(); if (eatOneMouseStroke) { eatOneMouseStroke--; return true; } break; case QEvent::MouseButtonDblClick: focusSwitchThreshold.forceDone(); if (eatOneMouseStroke) { return true; } break; case QEvent::MouseMove: case QEvent::TabletMove: { QWidget *widget = static_cast(object); if (!widget->hasFocus()) { const int delay = isInputWidget(QApplication::focusWidget()) ? InputWidgetsThreshold : OtherWidgetsThreshold; focusSwitchThreshold.setDelayThreshold(delay); focusSwitchThreshold.start(); } } break; default: break; } } return QObject::eventFilter(object, event); } KisInputManager::Private::ProximityNotifier::ProximityNotifier(KisInputManager::Private *_d, QObject *p) : QObject(p), d(_d) {} bool KisInputManager::Private::ProximityNotifier::eventFilter(QObject* object, QEvent* event ) { /** * All Qt builds in range 5.7.0...5.11.X on X11 had a problem that made all * the tablet events be accepted by default. It meant that no mouse * events were synthesized, and, therefore, no Enter/Leave were generated. * * The fix for this bug has been added only in Qt 5.12.0: * https://codereview.qt-project.org/#/c/239918/ * * To avoid this problem we should explicitly ignore all the tablet events. */ #if defined Q_OS_LINUX && \ QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) && \ QT_VERSION < QT_VERSION_CHECK(5, 12, 0) if (event->type() == QEvent::TabletMove || event->type() == QEvent::TabletPress || event->type() == QEvent::TabletRelease) { event->ignore(); } #endif switch (event->type()) { case QEvent::TabletEnterProximity: d->debugEvent(event); // Tablet proximity events are unreliable AND fake mouse events do not // necessarily come after tablet events, so this is insufficient. // d->eventEater.eatOneMousePress(); // Qt sends fake mouse events instead of hover events, so not very useful. // Don't block mouse events on tablet since tablet move events are not generated until // after tablet press. #ifndef Q_OS_MACOS d->blockMouseEvents(); #endif break; case QEvent::TabletLeaveProximity: d->debugEvent(event); d->allowMouseEvents(); break; default: break; } return QObject::eventFilter(object, event); } void KisInputManager::Private::addStrokeShortcut(KisAbstractInputAction* action, int index, const QList &modifiers, Qt::MouseButtons buttons) { KisStrokeShortcut *strokeShortcut = new KisStrokeShortcut(action, index); QList buttonList; if(buttons & Qt::LeftButton) { buttonList << Qt::LeftButton; } if(buttons & Qt::RightButton) { buttonList << Qt::RightButton; } if(buttons & Qt::MidButton) { buttonList << Qt::MidButton; } if(buttons & Qt::XButton1) { buttonList << Qt::XButton1; } if(buttons & Qt::XButton2) { buttonList << Qt::XButton2; } if (buttonList.size() > 0) { strokeShortcut->setButtons(QSet::fromList(modifiers), QSet::fromList(buttonList)); matcher.addShortcut(strokeShortcut); } else { delete strokeShortcut; } } void KisInputManager::Private::addKeyShortcut(KisAbstractInputAction* action, int index, const QList &keys) { if (keys.size() == 0) return; KisSingleActionShortcut *keyShortcut = new KisSingleActionShortcut(action, index); //Note: Ordering is important here, Shift + V is different from V + Shift, //which is the reason we use the last key here since most users will enter //shortcuts as "Shift + V". Ideally this should not happen, but this is //the way the shortcut matcher is currently implemented. QList allKeys = keys; Qt::Key key = allKeys.takeLast(); QSet modifiers = QSet::fromList(allKeys); keyShortcut->setKey(modifiers, key); matcher.addShortcut(keyShortcut); } void KisInputManager::Private::addWheelShortcut(KisAbstractInputAction* action, int index, const QList &modifiers, KisShortcutConfiguration::MouseWheelMovement wheelAction) { QScopedPointer keyShortcut( new KisSingleActionShortcut(action, index)); KisSingleActionShortcut::WheelAction a; switch(wheelAction) { case KisShortcutConfiguration::WheelUp: a = KisSingleActionShortcut::WheelUp; break; case KisShortcutConfiguration::WheelDown: a = KisSingleActionShortcut::WheelDown; break; case KisShortcutConfiguration::WheelLeft: a = KisSingleActionShortcut::WheelLeft; break; case KisShortcutConfiguration::WheelRight: a = KisSingleActionShortcut::WheelRight; break; case KisShortcutConfiguration::WheelTrackpad: a = KisSingleActionShortcut::WheelTrackpad; break; default: return; } keyShortcut->setWheel(QSet::fromList(modifiers), a); matcher.addShortcut(keyShortcut.take()); } void KisInputManager::Private::addTouchShortcut(KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture) { - KisTouchShortcut *shortcut = new KisTouchShortcut(action, index); + KisTouchShortcut *shortcut = new KisTouchShortcut(action, index, gesture); + dbgKrita << "TouchAction:" << action->name(); switch(gesture) { case KisShortcutConfiguration::RotateGesture: case KisShortcutConfiguration::PinchGesture: - shortcut = new KisTouchShortcut(new KisZoomAndRotateAction, index); + case KisShortcutConfiguration::ZoomAndRotateGesture: shortcut->setMinimumTouchPoints(2); shortcut->setMaximumTouchPoints(2); break; case KisShortcutConfiguration::PanGesture: shortcut->setMinimumTouchPoints(3); shortcut->setMaximumTouchPoints(10); break; default: break; } matcher.addShortcut(shortcut); } bool KisInputManager::Private::addNativeGestureShortcut(KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture) { // Qt5 only implements QNativeGestureEvent for macOS Qt::NativeGestureType type; switch (gesture) { #ifdef Q_OS_MACOS case KisShortcutConfiguration::PinchGesture: type = Qt::ZoomNativeGesture; break; case KisShortcutConfiguration::PanGesture: type = Qt::PanNativeGesture; break; case KisShortcutConfiguration::RotateGesture: type = Qt::RotateNativeGesture; break; case KisShortcutConfiguration::SmartZoomGesture: type = Qt::SmartZoomNativeGesture; break; #endif default: return false; } KisNativeGestureShortcut *shortcut = new KisNativeGestureShortcut(action, index, type); matcher.addShortcut(shortcut); return true; } void KisInputManager::Private::setupActions() { QList actions = KisInputProfileManager::instance()->actions(); Q_FOREACH (KisAbstractInputAction *action, actions) { KisToolInvocationAction *toolAction = dynamic_cast(action); if(toolAction) { defaultInputAction = toolAction; } } connect(KisInputProfileManager::instance(), SIGNAL(currentProfileChanged()), q, SLOT(profileChanged())); if(KisInputProfileManager::instance()->currentProfile()) { q->profileChanged(); } } bool KisInputManager::Private::processUnhandledEvent(QEvent *event) { bool retval = false; if (forwardAllEventsToTool || event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { defaultInputAction->processUnhandledEvent(event); retval = true; } return retval && !forwardAllEventsToTool; } bool KisInputManager::Private::tryHidePopupPalette() { if (canvas->isPopupPaletteVisible()) { canvas->slotShowPopupPalette(); return true; } return false; } #ifdef HAVE_X11 inline QPointF dividePoints(const QPointF &pt1, const QPointF &pt2) { return QPointF(pt1.x() / pt2.x(), pt1.y() / pt2.y()); } inline QPointF multiplyPoints(const QPointF &pt1, const QPointF &pt2) { return QPointF(pt1.x() * pt2.x(), pt1.y() * pt2.y()); } #endif void KisInputManager::Private::blockMouseEvents() { eventEater.activate(); } void KisInputManager::Private::allowMouseEvents() { eventEater.deactivate(); } void KisInputManager::Private::eatOneMousePress() { eventEater.eatOneMousePress(); } void KisInputManager::Private::resetCompressor() { compressedMoveEvent.reset(); moveEventCompressor.stop(); } bool KisInputManager::Private::handleCompressedTabletEvent(QEvent *event) { bool retval = false; /** * When Krita (as an application) has no input focus, we cannot * handle key events. But at the same time, when the user hovers * Krita canvas, we should still show him the correct cursor. * * So here we just add a simple workaround to resync shortcut * matcher's state at least against the basic modifiers, like * Shift, Control and Alt. */ QWidget *recievingWidget = dynamic_cast(eventsReceiver); if (recievingWidget && !recievingWidget->hasFocus()) { QVector guessedKeys; KisExtendedModifiersMapper mapper; Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers(); Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) { QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers); guessedKeys << KisExtendedModifiersMapper::workaroundShiftAltMetaHell(&kevent); } matcher.recoveryModifiersWithoutFocus(guessedKeys); } if (!matcher.pointerMoved(event) && toolProxy) { toolProxy->forwardHoverEvent(event); } retval = true; event->setAccepted(true); return retval; } qint64 KisInputManager::Private::TabletLatencyTracker::currentTimestamp() const { // on OS X, we need to compute the timestamp that compares correctly against the native event timestamp, // which seems to be the msecs since system startup. On Linux with WinTab, we produce the timestamp that // we compare against ourselves in QWindowSystemInterface. QElapsedTimer elapsed; elapsed.start(); return elapsed.msecsSinceReference(); } void KisInputManager::Private::TabletLatencyTracker::print(const QString &message) { dbgTablet << qUtf8Printable(message); } diff --git a/libs/ui/input/kis_input_profile_manager.cpp b/libs/ui/input/kis_input_profile_manager.cpp index 8018784557..7b863bf4bd 100644 --- a/libs/ui/input/kis_input_profile_manager.cpp +++ b/libs/ui/input/kis_input_profile_manager.cpp @@ -1,377 +1,379 @@ /* * This file is part of the KDE project * Copyright (C) 2013 Arjen Hiemstra * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_input_profile_manager.h" #include "kis_input_profile.h" #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_alternate_invocation_action.h" #include "kis_change_primary_setting_action.h" #include "kis_pan_action.h" #include "kis_rotate_canvas_action.h" #include "kis_show_palette_action.h" #include "kis_tool_invocation_action.h" #include "kis_zoom_action.h" #include "kis_shortcut_configuration.h" #include "kis_select_layer_action.h" #include "kis_gamma_exposure_action.h" #include "kis_change_frame_action.h" +#include "kis_zoom_and_rotate_action.h" #define PROFILE_VERSION 3 class Q_DECL_HIDDEN KisInputProfileManager::Private { public: Private() : currentProfile(0) { } void createActions(); QString profileFileName(const QString &profileName); KisInputProfile *currentProfile; QMap profiles; QList actions; }; Q_GLOBAL_STATIC(KisInputProfileManager, inputProfileManager) KisInputProfileManager *KisInputProfileManager::instance() { return inputProfileManager; } QList< KisInputProfile * > KisInputProfileManager::profiles() const { return d->profiles.values(); } QStringList KisInputProfileManager::profileNames() const { return d->profiles.keys(); } KisInputProfile *KisInputProfileManager::profile(const QString &name) const { if (d->profiles.contains(name)) { return d->profiles.value(name); } return 0; } KisInputProfile *KisInputProfileManager::currentProfile() const { return d->currentProfile; } void KisInputProfileManager::setCurrentProfile(KisInputProfile *profile) { if (profile && profile != d->currentProfile) { d->currentProfile = profile; emit currentProfileChanged(); } } KisInputProfile *KisInputProfileManager::addProfile(const QString &name) { if (d->profiles.contains(name)) { return d->profiles.value(name); } KisInputProfile *profile = new KisInputProfile(this); profile->setName(name); d->profiles.insert(name, profile); emit profilesChanged(); return profile; } void KisInputProfileManager::removeProfile(const QString &name) { if (d->profiles.contains(name)) { QString currentProfileName = d->currentProfile->name(); delete d->profiles.value(name); d->profiles.remove(name); //Delete the settings file for the removed profile, if it exists QDir userDir(KoResourcePaths::saveLocation("data", "input/")); if (userDir.exists(d->profileFileName(name))) { userDir.remove(d->profileFileName(name)); } if (currentProfileName == name) { d->currentProfile = d->profiles.begin().value(); emit currentProfileChanged(); } emit profilesChanged(); } } bool KisInputProfileManager::renameProfile(const QString &oldName, const QString &newName) { if (!d->profiles.contains(oldName)) { return false; } KisInputProfile *profile = d->profiles.value(oldName); d->profiles.remove(oldName); profile->setName(newName); d->profiles.insert(newName, profile); emit profilesChanged(); return true; } void KisInputProfileManager::duplicateProfile(const QString &name, const QString &newName) { if (!d->profiles.contains(name) || d->profiles.contains(newName)) { return; } KisInputProfile *newProfile = new KisInputProfile(this); newProfile->setName(newName); d->profiles.insert(newName, newProfile); KisInputProfile *profile = d->profiles.value(name); QList shortcuts = profile->allShortcuts(); Q_FOREACH(KisShortcutConfiguration * shortcut, shortcuts) { newProfile->addShortcut(new KisShortcutConfiguration(*shortcut)); } emit profilesChanged(); } QList< KisAbstractInputAction * > KisInputProfileManager::actions() { return d->actions; } struct ProfileEntry { QString name; QString fullpath; int version; }; void KisInputProfileManager::loadProfiles() { //Remove any profiles that already exist d->currentProfile = 0; qDeleteAll(d->profiles); d->profiles.clear(); //Look up all profiles (this includes those installed to $prefix as well as the user's local data dir) QStringList profiles = KoResourcePaths::findAllResources("data", "input/*.profile", KoResourcePaths::Recursive); dbgKrita << "profiles" << profiles; QMap > profileEntries; // Get only valid entries... Q_FOREACH(const QString & p, profiles) { ProfileEntry entry; entry.fullpath = p; KConfig config(p, KConfig::SimpleConfig); if (!config.hasGroup("General") || !config.group("General").hasKey("name") || !config.group("General").hasKey("version")) { //Skip if we don't have the proper settings. continue; } // Only entries of exactly the right version can be considered entry.version = config.group("General").readEntry("version", 0); if (entry.version != PROFILE_VERSION) { continue; } entry.name = config.group("General").readEntry("name"); if (!profileEntries.contains(entry.name)) { profileEntries[entry.name] = QList(); } if (p.contains(".kde") || p.contains(".krita")) { // It's the user define one, drop the others profileEntries[entry.name].clear(); profileEntries[entry.name].append(entry); break; } else { profileEntries[entry.name].append(entry); } } QStringList profilePaths; Q_FOREACH(const QString & profileName, profileEntries.keys()) { if (profileEntries[profileName].isEmpty()) { continue; } // we have one or more entries for this profile name. We'll take the first, // because that's the most local one. ProfileEntry entry = profileEntries[profileName].first(); QString path(QFileInfo(entry.fullpath).dir().absolutePath()); if (!profilePaths.contains(path)) { profilePaths.append(path); } KConfig config(entry.fullpath, KConfig::SimpleConfig); KisInputProfile *newProfile = addProfile(entry.name); Q_FOREACH(KisAbstractInputAction * action, d->actions) { if (!config.hasGroup(action->id())) { continue; } KConfigGroup grp = config.group(action->id()); //Read the settings for the action and create the appropriate shortcuts. Q_FOREACH(const QString & entry, grp.entryMap()) { KisShortcutConfiguration *shortcut = new KisShortcutConfiguration; shortcut->setAction(action); if (shortcut->unserialize(entry)) { newProfile->addShortcut(shortcut); } else { delete shortcut; } } } } // QString profilePathsStr(profilePaths.join("' AND '")); // qDebug() << "input profiles were read from '" << qUtf8Printable(profilePathsStr) << "'."; KisConfig cfg(true); QString currentProfile = cfg.currentInputProfile(); if (d->profiles.size() > 0) { if (currentProfile.isEmpty() || !d->profiles.contains(currentProfile)) { d->currentProfile = d->profiles.begin().value(); } else { d->currentProfile = d->profiles.value(currentProfile); } } if (d->currentProfile) { emit currentProfileChanged(); } } void KisInputProfileManager::saveProfiles() { QString storagePath = KoResourcePaths::saveLocation("data", "input/", true); Q_FOREACH(KisInputProfile * p, d->profiles) { QString fileName = d->profileFileName(p->name()); KConfig config(storagePath + fileName, KConfig::SimpleConfig); config.group("General").writeEntry("name", p->name()); config.group("General").writeEntry("version", PROFILE_VERSION); Q_FOREACH(KisAbstractInputAction * action, d->actions) { KConfigGroup grp = config.group(action->id()); grp.deleteGroup(); //Clear the group of any existing shortcuts. int index = 0; QList shortcuts = p->shortcutsForAction(action); Q_FOREACH(KisShortcutConfiguration * shortcut, shortcuts) { grp.writeEntry(QString("%1").arg(index++), shortcut->serialize()); } } config.sync(); } KisConfig config(false); config.setCurrentInputProfile(d->currentProfile->name()); //Force a reload of the current profile in input manager and whatever else uses the profile. emit currentProfileChanged(); } void KisInputProfileManager::resetAll() { QString kdeHome = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QStringList profiles = KoResourcePaths::findAllResources("data", "input/*", KoResourcePaths::Recursive); Q_FOREACH (const QString &profile, profiles) { if(profile.contains(kdeHome)) { //This is a local file, remove it. QFile::remove(profile); } } //Load the profiles again, this should now only load those shipped with Krita. loadProfiles(); emit profilesChanged(); } KisInputProfileManager::KisInputProfileManager(QObject *parent) : QObject(parent), d(new Private()) { d->createActions(); } KisInputProfileManager::~KisInputProfileManager() { qDeleteAll(d->profiles); qDeleteAll(d->actions); delete d; } void KisInputProfileManager::Private::createActions() { //TODO: Make this plugin based //Note that the ordering here determines how things show up in the UI actions.append(new KisToolInvocationAction()); actions.append(new KisAlternateInvocationAction()); actions.append(new KisChangePrimarySettingAction()); actions.append(new KisPanAction()); actions.append(new KisRotateCanvasAction()); actions.append(new KisZoomAction()); actions.append(new KisShowPaletteAction()); actions.append(new KisSelectLayerAction()); actions.append(new KisGammaExposureAction()); actions.append(new KisChangeFrameAction()); + actions.append(new KisZoomAndRotateAction()); } QString KisInputProfileManager::Private::profileFileName(const QString &profileName) { return profileName.toLower().remove(QRegExp("[^a-z0-9]")).append(".profile"); } diff --git a/libs/ui/input/kis_shortcut_configuration.h b/libs/ui/input/kis_shortcut_configuration.h index 9dd1121cc3..ec128a7c29 100644 --- a/libs/ui/input/kis_shortcut_configuration.h +++ b/libs/ui/input/kis_shortcut_configuration.h @@ -1,300 +1,301 @@ /* * This file is part of the KDE project * Copyright (C) 2013 Arjen Hiemstra * * 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 KISSHORTCUTCONFIGURATION_H #define KISSHORTCUTCONFIGURATION_H #include #include class QString; class KisAbstractInputAction; /** * \brief A class encapsulating all settings for a single shortcut. * * This class encapsulates mouse buttons, keyboard keys and other settings * related to a single shortcut for a single action. * * \note Each action can have several modes that activate it with usually * different behaviour for each mode. Different shortcuts can activate * different modes. */ class KisShortcutConfiguration { public: /** * The type of shortcut, i.e. what kind of input does it expect. */ enum ShortcutType { UnknownType, ///< Unknown, empty shortcut. KeyCombinationType, ///< A list of keys that should be pressed. MouseButtonType, ///< A mouse button, possibly with key modifiers. MouseWheelType, ///< Mouse wheel movement, possibly with key modifiers. GestureType, ///< A touch gesture. }; /** * The type of mouse wheel movement. */ enum MouseWheelMovement { NoMovement, ///< No movement. WheelUp, ///< Upwards movement, away from the user. WheelDown, ///< Downwards movement, toward the user. WheelLeft, ///< Left movement. WheelRight, ///< Right movement. WheelTrackpad, ///< A pan movement on a trackpad. }; /** * The type of gesture. */ enum GestureAction { NoGesture, ///< No gesture. PinchGesture, ///< Pinch gesture, fingers moving towards or away from each other. PanGesture, ///< Pan gesture, fingers staying together but moving across the screen. RotateGesture, /// keys() const; /** * Set the list of keys that will trigger this shortcut. * * \param newKeys The list of keys to use. * * \note Not applicable when type is GestureType. */ void setKeys(const QList &newKeys); /** * \return The mouse buttons that will trigger this shortcut. * * \note Only applicable when type is MouseButtonType. */ Qt::MouseButtons buttons() const; /** * Set the mouse buttons that will trigger this shortcut. * * \param newButtons The mouse buttons to use. * * \note Only applicable when type is MouseButtonType. */ void setButtons(Qt::MouseButtons newButtons); /** * \return The mouse wheel movement that will trigger this shortcut. * * \note Only applicable when type is MouseWheelType. */ MouseWheelMovement wheel() const; /** * Set the mouse wheel movement that will trigger this shortcut. * * \param type The wheel movement to use. * * \note Only applicable when type is MouseWheelType. */ void setWheel(MouseWheelMovement type); /** * \return The gesture that will trigger this shortcut. * * \note Only applicable when type is GestureType. */ GestureAction gesture() const; /** * Set the gesture that will trigger this shortcut. * * \param type The gesture to use. * * \note Only applicable when type is GestureType. */ void setGesture(GestureAction type); /** * Convert a set of mouse buttons into a user-readable * string. * * This will convert the given set of buttons into a * string that can be shown to a user. For example, the * combination Qt::LeftButton + Qt::RightButton will produce * the string "Left + Right Button". * * \param buttons The buttons to convert. * * \return A string representing the buttons that can be shown * to a user. * * \note An empty set will produce the string "None". */ static QString buttonsToText(Qt::MouseButtons buttons); /** * Convert a list of keys to a user-readable string. * * This will convert the given list of keys into a string * that can be shown to a user. For example, the list * [Qt::Key_Shift, Qt::Key_Space] will produce the string * "Shift + Space". * * \param keys The keys to convert. * * \return A string representing the keys that can be shown * to a user. * * \note An empty list will produce the string "None". */ static QString keysToText(const QList &keys); /** * Convert the given mouse wheel movement to a string. * * This will convert the given mouse wheel movement into a * string that can be shown to a user. For example, WheelUp * will produce the string "Mouse Wheel Up". * * \param wheel The mouse wheel movement to convert. * * \return A string representing the mouse wheel movement * that can be shown to a user. * * \note NoMovement will produce the string "None". */ static QString wheelToText(MouseWheelMovement wheel); /** * Convert a shortcut build of a set of keys and a set of mouse * buttons into a user-readable string. * * This will convert the given mouse buttons-based shortcut into a * string that can be shown to a user. For example, the combination * of Qt::Key_Control and Qt::LeftButton + Qt::RightButton will * produce the string "Ctrl + Left + Right Button". * * \param keys The keys to convert. * \param buttons The mouse buttons to convert. * * \return A string representing the shortcut that can be shown * to a user. * * \note An empty set of buttons will appear as the string "None". */ static QString buttonsInputToText(const QList &keys, Qt::MouseButtons buttons); /** * Convert a shortcut build of a set of keys and a set of mouse * wheel buttons into a user-readable string. * * This will convert the given mouse wheel-based shortcut into a * string that can be shown to a user. For example, the combination * of Qt::Key_Control and WheelUp will produce the string * "Ctrl + Mouse Wheel Up". * * \param keys The keys to convert. * \param wheel The mouse wheel buttons to convert. * * \return A string representing the shortcut that can be shown * to a user. * * \note An empty set of wheel buttons will appear as * the string "None". */ static QString wheelInputToText(const QList &keys, MouseWheelMovement wheel); private: class Private; Private *const d; }; Q_DECLARE_METATYPE(KisShortcutConfiguration *); #endif // KISSHORTCUTCONFIGURATION_H diff --git a/libs/ui/input/kis_touch_shortcut.cpp b/libs/ui/input/kis_touch_shortcut.cpp index d61477b4f5..48c06f503f 100644 --- a/libs/ui/input/kis_touch_shortcut.cpp +++ b/libs/ui/input/kis_touch_shortcut.cpp @@ -1,63 +1,70 @@ /* * This file is part of the KDE project * Copyright (C) 2012 Arjen Hiemstra * * 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_touch_shortcut.h" +#include "kis_abstract_input_action.h" #include class KisTouchShortcut::Private { public: - Private() : minTouchPoints(0), maxTouchPoints(0) { } + Private(GestureAction type) + : minTouchPoints(0) + , maxTouchPoints(0) + , type(type) + { } int minTouchPoints; int maxTouchPoints; + GestureAction type; }; -KisTouchShortcut::KisTouchShortcut( KisAbstractInputAction* action, int index ) - : KisAbstractShortcut(action, index), d( new Private ) +KisTouchShortcut::KisTouchShortcut(KisAbstractInputAction* action, int index, GestureAction type) + : KisAbstractShortcut(action, index) + , d(new Private(type)) { } KisTouchShortcut::~KisTouchShortcut() { delete d; } int KisTouchShortcut::priority() const { return d->maxTouchPoints; } void KisTouchShortcut::setMinimumTouchPoints(int min) { d->minTouchPoints = min; } void KisTouchShortcut::setMaximumTouchPoints(int max) { d->maxTouchPoints = max; } bool KisTouchShortcut::match( QTouchEvent* event ) { return event->touchPoints().count() >= d->minTouchPoints && event->touchPoints().count() <= d->maxTouchPoints; } diff --git a/libs/ui/input/kis_touch_shortcut.h b/libs/ui/input/kis_touch_shortcut.h index b0ca049d5a..3637899f6b 100644 --- a/libs/ui/input/kis_touch_shortcut.h +++ b/libs/ui/input/kis_touch_shortcut.h @@ -1,50 +1,53 @@ /* * This file is part of the KDE project * Copyright (C) 2012 Arjen Hiemstra * * 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 KISTOUCHSHORTCUT_H #define KISTOUCHSHORTCUT_H #include "kis_abstract_shortcut.h" +#include "kis_shortcut_configuration.h" class QTouchEvent; /** * @brief The KisTouchShortcut class only handles touch gestures * it _does not_ handle tool invocation i.e painting (which is being * handled in KisShortcutMatcher). */ class KisTouchShortcut : public KisAbstractShortcut { + using GestureAction = KisShortcutConfiguration::GestureAction; + public: - KisTouchShortcut( KisAbstractInputAction* action, int index ); + KisTouchShortcut(KisAbstractInputAction* action, int index, GestureAction type); ~KisTouchShortcut() override; int priority() const override; void setMinimumTouchPoints( int min ); void setMaximumTouchPoints( int max ); bool match( QTouchEvent* event ); private: class Private; Private * const d; }; #endif // KISTOUCHSHORTCUT_H