Commit b1bf3453 authored by Emmet O'Neill's avatar Emmet O'Neill
Browse files

Generalized popup widget.

We've taken the concept of the popup palette and generalized it
by allowing all of Krita's individual tools to return a popup
widget to be temporarily displayed directly on the canvas.

Ideally, this would work for menus, tool options, editor windows
and (of course) custom widgets like the popup palette. :)
parent 1754315d
......@@ -39,7 +39,7 @@ version=5
0={1;2;[1000020,52];1;0;0}
1={0;2;[52];1;0;0}
[Show Popup Palette]
[Show Popup Widget]
0={0;2;[];2;0;0}
[Switch Time]
......
......@@ -37,7 +37,7 @@ version=5
[Select Layer]
0={0;2;[52];1;0;0}
[Show Popup Palette]
[Show Popup Widget]
0={0;2;[1000021,1000023];2;0;0}
[Switch Time]
......
......@@ -36,7 +36,7 @@ version=5
[Select Layer]
0={0;2;[];0;0;0}
[Show Popup Palette]
[Show Popup Widget]
0={0;2;[];2;0;0}
[Switch Time]
......
......@@ -38,7 +38,7 @@ version=5
0={0;2;[52];1;0;0}
1={1;2;[1000020,52];1;0;0}
[Show Popup Palette]
[Show Popup Widget]
0={0;2;[];2;0;0}
1={0;1;[1000032];0;0;0}
......
......@@ -56,7 +56,6 @@ bool KoToolBase::isActivated() const
return d->isActivated;
}
void KoToolBase::activate(const QSet<KoShape *> &shapes)
{
Q_UNUSED(shapes);
......@@ -230,11 +229,6 @@ void KoToolBase::cut()
deleteSelection();
}
QMenu *KoToolBase::popupActionsMenu()
{
return 0;
}
KoCanvasBase * KoToolBase::canvas() const
{
Q_D(const KoToolBase);
......
......@@ -297,7 +297,13 @@ public:
* @return a menu with context-aware actions for the current selection. If
* the returned value is null, no context menu is shown.
*/
virtual QMenu* popupActionsMenu();
virtual QMenu* popupActionsMenu() {return nullptr;}
/**
* @return a widget with useful controls to be popped up on top of the canvas.
* Will not be called if `popupActionsMenu()` does not return null.
*/
virtual QWidget* popupWidget() {return nullptr;}
/// Returns the canvas the tool is working on
KoCanvasBase *canvas() const;
......
......@@ -349,6 +349,11 @@ QMenu *KoToolProxy::popupActionsMenu()
return d->activeTool ? d->activeTool->popupActionsMenu() : 0;
}
QWidget* KoToolProxy::popupWidget()
{
return d->activeTool ? d->activeTool->popupWidget() : nullptr;
}
void KoToolProxy::setActiveTool(KoToolBase *tool)
{
if (d->activeTool)
......
......@@ -98,6 +98,9 @@ public:
/// Forwarded to the current KoToolBase
QMenu* popupActionsMenu();
/// Forwarded to the current KoToolBase
QWidget* popupWidget();
/// Forwarded to the current KoToolBase
void deleteSelection();
......
......@@ -1001,6 +1001,11 @@ QRect KisCanvas2::renderingLimit() const
return m_d->renderingLimit;
}
KisPopupPalette *KisCanvas2::popupPalette()
{
return m_d->popupPalette;
}
void KisCanvas2::slotTrySwitchShapeManager()
{
KisNodeSP node = m_d->view->currentNode();
......@@ -1168,7 +1173,7 @@ void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favorite
connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas()));
connect(m_d->view->mainWindow(), SIGNAL(themeChanged()), m_d->popupPalette, SLOT(slotUpdateIcons()));
m_d->popupPalette->showPopupPalette(false);
m_d->popupPalette->setVisible(false);
}
void KisCanvas2::slotPopupPaletteRequestedZoomChange(int zoom ) {
......@@ -1203,15 +1208,6 @@ void KisCanvas2::slotSelectionChanged()
}
}
bool KisCanvas2::isPopupPaletteVisible() const
{
if (!m_d->popupPalette) {
return false;
}
return m_d->popupPalette->isVisible();
}
void KisCanvas2::setWrapAroundViewingMode(bool value)
{
KisCanvasDecorationSP infinityDecoration =
......@@ -1257,15 +1253,6 @@ bool KisCanvas2::lodPreferredInCanvas() const
return m_d->lodPreferredInImage;
}
void KisCanvas2::slotShowPopupPalette(const QPoint &p)
{
if (!m_d->popupPalette) {
return;
}
m_d->popupPalette->showPopupPalette(p);
}
KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const
{
KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration");
......
......@@ -45,6 +45,7 @@ class KisShapeController;
class KisCoordinatesConverter;
class KoViewConverter;
class KisAbstractCanvasWidget;
class KisPopupPalette;
/**
* KisCanvas2 is not an actual widget class, but rather an adapter for
......@@ -220,6 +221,8 @@ public: // KisCanvas2 methods
*/
QRect renderingLimit() const;
KisPopupPalette* popupPalette();
Q_SIGNALS:
void sigCanvasEngineChanged();
......@@ -293,11 +296,8 @@ private Q_SLOTS:
void slotReferenceImagesChanged();
void slotImageColorSpaceChanged();
public:
bool isPopupPaletteVisible() const;
void slotShowPopupPalette(const QPoint& = QPoint(0,0));
public:
// interface for KisCanvasController only
void setWrapAroundViewingMode(bool value);
bool wrapAroundViewingMode() const;
......
......@@ -14,6 +14,7 @@
#include <QApplication>
#include <QTouchEvent>
#include <QElapsedTimer>
#include <QWidget>
#include <KoToolManager.h>
......@@ -96,6 +97,11 @@ void KisInputManager::removeTrackedCanvas(KisCanvas2 *canvas)
d->canvasSwitcher.removeCanvas(canvas);
}
void KisInputManager::registerPopupWidget(QWidget *popup)
{
d->popupWidget = popup;
}
void KisInputManager::toggleTabletLogger()
{
KisTabletDebugger::instance()->toggleDebugging();
......@@ -331,6 +337,27 @@ bool KisInputManager::eventFilterImpl(QEvent * event)
{
bool retval = false;
// Try closing any open popup widget and
// consume input if possible.
if (d->popupWidget) {
QEvent::Type type = event->type();
bool wasVisible = d->popupWidget->isVisible();
if (type == QEvent::MouseButtonPress
|| type == QEvent::MouseButtonDblClick
|| type == QEvent::TabletPress
|| type == QEvent::TouchBegin
|| type == QEvent::NativeGesture) {
d->popupWidget->setVisible(false);
d->popupWidget = nullptr;
if (wasVisible) {
return true;
}
}
}
if (shouldResetWheelDelta(event)) {
d->accumulatedScrollDelta = 0;
}
......@@ -343,7 +370,7 @@ bool KisInputManager::eventFilterImpl(QEvent * event)
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (d->tryHidePopupPalette()) {
if (d->popupWidget) {
retval = true;
} else {
//Make sure the input actions know we are active.
......@@ -532,9 +559,8 @@ bool KisInputManager::eventFilterImpl(QEvent * event)
case QEvent::TabletPress: {
d->debugEvent<QTabletEvent, false>(event);
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
if (d->tryHidePopupPalette()) {
retval = true;
} else {
{
//Make sure the input actions know we are active.
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.buttonPressed(tabletEvent->button(), tabletEvent);
......@@ -543,6 +569,7 @@ bool KisInputManager::eventFilterImpl(QEvent * event)
d->touchHasBlockedPressEvents = false;
}
}
event->setAccepted(true);
retval = true;
d->blockMouseEvents();
......@@ -769,16 +796,14 @@ bool KisInputManager::eventFilterImpl(QEvent * event)
bool KisInputManager::startTouch(bool &retval)
{
Q_UNUSED(retval);
// Touch rejection: if touch is disabled on canvas, no need to block mouse press events
if (KisConfig(true).disableTouchOnCanvas()) {
d->eatOneMousePress();
}
if (d->tryHidePopupPalette()) {
retval = true;
return false;
} else {
return true;
}
return true;
}
void KisInputManager::endTouch()
......
......@@ -52,6 +52,8 @@ public:
void addTrackedCanvas(KisCanvas2 *canvas);
void removeTrackedCanvas(KisCanvas2 *canvas);
void registerPopupWidget(QWidget* popup);
void toggleTabletLogger();
/**
......
......@@ -24,6 +24,7 @@
#include "kis_extended_modifiers_mapper.h"
#include "kis_zoom_and_rotate_action.h"
#include "kis_popup_palette.h"
/**
* This hungry class EventEater encapsulates event masking logic.
......@@ -170,11 +171,11 @@ KisInputManager::Private::Private(KisInputManager *qq)
KisSignalCompressor::FIRST_ACTIVE,
KisSignalCompressor::ADDITIVE_INTERVAL)
, priorityEventFilterSeqNo(0)
, popupWidget(nullptr)
, canvasSwitcher(this, qq)
{
KisConfig cfg(true);
moveEventCompressor.setDelay(cfg.tabletEventsDelay());
testingAcceptCompressedTabletEvents = cfg.testingAcceptCompressedTabletEvents();
testingCompressBrushEvents = cfg.testingCompressBrushEvents();
......@@ -602,15 +603,6 @@ bool KisInputManager::Private::processUnhandledEvent(QEvent *event)
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());
......
......@@ -32,7 +32,6 @@ class KisInputManager::Private
{
public:
Private(KisInputManager *qq);
bool tryHidePopupPalette();
void addStrokeShortcut(KisAbstractInputAction* action, int index, const QList< Qt::Key >& modifiers, Qt::MouseButtons buttons);
void addKeyShortcut(KisAbstractInputAction* action, int index,const QList<Qt::Key> &keys);
void addTouchShortcut( KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture );
......@@ -73,6 +72,8 @@ public:
QPointF previousPos;
bool buttonPressed = false;
QWidget* popupWidget;
void blockMouseEvents();
void allowMouseEvents();
void eatOneMousePress();
......
......@@ -360,7 +360,7 @@ void KisInputProfileManager::Private::createActions()
actions.append(new KisPanAction());
actions.append(new KisRotateCanvasAction());
actions.append(new KisZoomAction());
actions.append(new KisShowPaletteAction());
actions.append(new KisPopupWidgetAction());
actions.append(new KisSelectLayerAction());
actions.append(new KisGammaExposureAction());
actions.append(new KisChangeFrameAction());
......
......@@ -14,79 +14,75 @@
#include <kis_favorite_resource_manager.h>
#include <kis_canvas2.h>
#include "kis_tool_proxy.h"
#include "kis_popup_palette.h"
#include "kis_input_manager.h"
KisShowPaletteAction::KisShowPaletteAction()
: KisAbstractInputAction("Show Popup Palette"),
m_requestedWithStylus(false)
struct SinglePressEventEater : public QObject
{
setName(i18n("Show Popup Palette"));
setDescription(i18n("The <i>Show Popup Palette</i> displays the popup palette."));
}
bool eventFilter(QObject *, QEvent *event) override {
if (hungry && event->type() == QEvent::MouseButtonPress) {
hungry = false;
return true;
}
return false;
}
private:
bool hungry = true;
};
KisShowPaletteAction::~KisShowPaletteAction()
{
//=================================================================
KisPopupWidgetAction::KisPopupWidgetAction()
: KisAbstractInputAction("Show Popup Widget"),
m_requestedWithStylus(false)
{
setName(i18n("Show Popup Widget"));
setDescription(i18n("Show the current tool's popup widget."));
}
int KisShowPaletteAction::priority() const
KisPopupWidgetAction::~KisPopupWidgetAction()
{
return 1;
}
void KisShowPaletteAction::begin(int, QEvent *event)
void KisPopupWidgetAction::begin(int, QEvent *event)
{
m_menu = inputManager()->toolProxy()->popupActionsMenu();
QMenu *popupMenu = inputManager()->toolProxy()->popupActionsMenu();
QWidget *popupWidget = inputManager()->toolProxy()->popupWidget();
if (m_menu) {
if (popupMenu) { // Handle popup menus...
m_requestedWithStylus = event && event->type() == QEvent::TabletPress;
/**
* Opening a menu changes the focus of the windows, so we should not open it
* inside the filtering loop. Just raise it using the timer.
*/
QTimer::singleShot(0, this, SLOT(slotShowMenu()));
} else {
QTimer::singleShot(0, this, [this, popupMenu](){
if (popupMenu) {
QPoint stylusOffset;
QScopedPointer<SinglePressEventEater> eventEater;
if (m_requestedWithStylus) {
eventEater.reset(new SinglePressEventEater());
popupMenu->installEventFilter(eventEater.data());
stylusOffset += QPoint(10,10);
}
popupMenu->exec(QCursor::pos() + stylusOffset);
popupMenu->clear();
}
});
} else if (popupWidget) { // Handle other popup widgets...
QPoint pos = eventPos(event);
if (pos.isNull()) {
pos = inputManager()->canvas()->canvasWidget()->mapFromGlobal(QCursor::pos());
}
inputManager()->canvas()->slotShowPopupPalette(pos);
}
}
struct SinglePressEventEater : public QObject
{
bool eventFilter(QObject *, QEvent *event) override {
if (hungry && event->type() == QEvent::MouseButtonPress) {
hungry = false;
return true;
}
return false;
}
private:
bool hungry = true;
};
void KisShowPaletteAction::slotShowMenu()
{
if (m_menu) {
QPoint stylusOffset;
QScopedPointer<SinglePressEventEater> eater;
if (m_requestedWithStylus) {
eater.reset(new SinglePressEventEater());
m_menu->installEventFilter(eater.data());
stylusOffset += QPoint(10,10);
}
m_activePopup.reset(new PopupWidgetHolder(popupWidget, inputManager()));
m_menu->exec(QCursor::pos() + stylusOffset);
m_menu.clear();
m_activePopup->popup(pos);
}
}
......@@ -4,38 +4,79 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef KIS_SHOW_PALETTE_ACTION_H
#define KIS_SHOW_PALETTE_ACTION_H
#ifndef KIS_POPUP_WIDGET_ACTION_H
#define KIS_POPUP_WIDGET_ACTION_H
#include "kis_abstract_input_action.h"
#include <QObject>
#include <QPointer>
#include <QWidget>
#include <QMainWindow>
#include "kis_global.h"
#include "kis_debug.h"
#include "kis_input_manager.h"
#include "kis_canvas2.h"
class QMenu;
class PopupWidgetHolder : public QObject
{
public:
PopupWidgetHolder(QWidget* toPopUp, KisInputManager* inputManager)
: m_toPopUp(toPopUp)
, m_inputManager(inputManager)
{
m_toPopUp->setParent(m_inputManager->canvas()->canvasWidget());
}
~PopupWidgetHolder(){
}
void popup(QPoint& p) {
m_toPopUp->setVisible(!m_toPopUp->isVisible());
m_inputManager->registerPopupWidget(m_toPopUp);
adjustPopupLayout(p);
}
private:
void adjustPopupLayout(QPoint& p) {
if (m_toPopUp->isVisible() && m_toPopUp->parentWidget()) {
const float widgetMargin = -20.0f;
const QRect fitRect = kisGrowRect(m_toPopUp->parentWidget()->rect(), widgetMargin);
const QPoint paletteCenterOffset(m_toPopUp->sizeHint().width() / 2, m_toPopUp->sizeHint().height() / 2);
QRect paletteRect = m_toPopUp->rect();
paletteRect.moveTo(p - paletteCenterOffset);
paletteRect = kisEnsureInRect(paletteRect, fitRect);
m_toPopUp->move(paletteRect.topLeft());
}
}
QWidget* m_toPopUp;
KisInputManager* m_inputManager;
};
/**
* \brief Show Palette implementation of KisAbstractInputAction.
*
* The Show Palette action shows the popup palette.
* \brief Get the current tool's popup widget and display it.
*/
class KisShowPaletteAction : public QObject, public KisAbstractInputAction
class KisPopupWidgetAction : public QObject, public KisAbstractInputAction
{
Q_OBJECT
public:
explicit KisShowPaletteAction();
~KisShowPaletteAction() override;
explicit KisPopupWidgetAction();
~KisPopupWidgetAction() override;
int priority() const override;
int priority() const override {return 1;}
void begin(int, QEvent *) override;
private Q_SLOTS:
void slotShowMenu();
private:
QPointer<QMenu> m_menu;
QScopedPointer<PopupWidgetHolder> m_activePopup;
bool m_requestedWithStylus;
};
#endif // KIS_SHOW_PALETTE_ACTION_H
#endif // KIS_POPUP_WIDGET_ACTION_H
......@@ -33,6 +33,7 @@
#include <kis_paintop_preset.h>
#include "KisMouseClickEater.h"
static const int BRUSH_HUD_MARGIN = 16;
class PopupColorTriangle : public KoTriangleColorSelector
{
......@@ -49,11 +50,6 @@ public:
event->accept();
QMouseEvent* mouseEvent = 0;
// this will tell the pop-up palette widget to close
if(event->button() == Qt::RightButton) {
emit requestCloseContainer();
}
// ignore any tablet events that are done with the right click
// Tablet move events don't return a "button", so catch that too
if(event->button() == Qt::LeftButton || event->type() == QEvent::TabletMove)
......@@ -103,8 +99,6 @@ KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConv
, m_acyclicConnector(new KisAcyclicSignalConnector(this))
, m_clicksEater(new KisMouseClickEater(Qt::RightButton, 1, this))
{
// some UI controls are defined and created based off these variables
const int borderWidth = 3;
if (KisConfig(true).readEntry<bool>("popuppalette/usevisualcolorselector", false)) {
......@@ -114,7 +108,6 @@ KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConv
}
else {
m_triangleColorSelector = new PopupColorTriangle(displayRenderer, this);
connect(m_triangleColorSelector, SIGNAL(requestCloseContainer()), this, SLOT(slotHide()));
}
m_triangleColorSelector->setDisplayRenderer(displayRenderer);
m_triangleColorSelector->setConfig(true,false);
......@@ -144,8 +137,6 @@ KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConv
QRegion maskedRegion = maskedEllipse.intersected(maskedRectange);
m_triangleColorSelector->setMask(maskedRegion);
//setAttribute(Qt::WA_TranslucentBackground, true);
connect(m_triangleColorSelector, SIGNAL(sigNewColor(KoColor)),
m_colorChangeCompressor.data(), SLOT(start()));
connect(m_colorChangeCompressor.data(), SIGNAL(timeout()),
......