Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_selection_actions_panel.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2025 Ross Rosales <ross.erosales@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
10#include "KisDocument.h"
11#include "KisViewManager.h"
12#include "kis_canvas2.h"
14#include "kis_icon_utils.h"
15#include "kis_selection.h"
20#include <QList>
21#include <QMouseEvent>
22#include <QPointF>
23#include <QPushButton>
24#include <QTabletEvent>
25#include <QTouchEvent>
26#include <QTransform>
27#include <kactioncollection.h>
28#include <kis_algebra_2d.h>
29#include <klocalizedstring.h>
30#include <ktoggleaction.h>
31
32#include <QApplication>
33#include <QPainter>
34#include <QPainterPath>
35
36static constexpr int BUTTON_SIZE = 30;
37static constexpr int BUFFER_SPACE = 5;
38
40
48
51 {
52 }
55
57 bool m_pressed = false;
58 bool m_visible = false;
59 bool m_enabled = true;
60
61 struct DragHandle {
62 QPoint position = QPoint(0, 0);
63 QPoint dragOrigin = QPoint(0, 0);
64 };
65
66 QScopedPointer<DragHandle> m_dragHandle;
67
71 {
72 static const QVector<ActionButtonData> data = {
73 {"select-all", i18n("Select All"), &KisSelectionManager::selectAll},
74 {"select-invert", i18n("Invert Selection"), &KisSelectionManager::invert},
75 {"select-clear", i18n("Deselect"), &KisSelectionManager::deselect},
76 {"krita_tool_color_fill", i18n("Fill Selection with Color"), &KisSelectionManager::fillForegroundColor},
77 {"draw-eraser", i18n("Clear Selection"), &KisSelectionManager::clear},
78 {"duplicatelayer", i18n("Copy To New Layer"), &KisSelectionManager::copySelectionToNewLayer},
79 {"tool_crop", i18n("Crop to Selection"), &KisSelectionManager::imageResizeToSelection}};
80 return data;
81 }
82 int m_buttonCount = buttonData().size() + 1; // buttons + drag handle
83
85};
86
88 : d(new Private)
89{
90 d->m_viewManager = viewManager;
91 d->m_selectionManager = viewManager->selectionManager();
92
93 // Setup buttons...
94 for (const ActionButtonData &buttonData : Private::buttonData()) {
95 KisSelectionActionsPanelButton *button = new KisSelectionActionsPanelButton(buttonData.iconName, buttonData.tooltip, BUTTON_SIZE, viewManager->canvas());
96 connect(button, &QAbstractButton::clicked, d->m_selectionManager, buttonData.slot);
97 d->m_buttons.append(button);
98 }
99
100 d->m_handleWidget = new KisSelectionActionsPanelHandle(BUTTON_SIZE, viewManager->canvas());
101}
102
106
107void KisSelectionActionsPanel::draw(QPainter &painter)
108{
109 KisSelectionSP selection = d->m_viewManager->selection();
110
111 if (!selection || !d->m_enabled || !d->m_visible) {
112 return;
113 }
114
116 Q_FOREACH(KisSelectionActionsPanelButton* button, d->m_buttons){
117 button->draw(painter);
118 }
119
120 d->m_handleWidget->draw(painter);
121}
122
124{
125 QWidget *canvasWidget = dynamic_cast<QWidget *>(d->m_viewManager->canvas());
126 if (!canvasWidget) {
127 return;
128 }
129
130 p_visible &= d->m_enabled;
131
132 const bool VISIBILITY_CHANGED = d->m_visible != p_visible;
133 if (!VISIBILITY_CHANGED) {
134 return;
135 }
136
137 if (d->m_viewManager->selection() && p_visible) { // Now visible!
138 d->m_handleWidget->installEventFilter(this);
139
140 if (!d->m_dragHandle) {
141 d->m_dragHandle.reset(new Private::DragHandle());
142 d->m_dragHandle->position = initialDragHandlePosition();
144 }
145 } else { // Now hidden!
146 d->m_handleWidget->removeEventFilter(this);
147
148 for (KisSelectionActionsPanelButton *button : d->m_buttons) {
149 button->hide();
150 }
151 d->m_handleWidget->hide();
152
153 d->m_pressed = false;
154 d->m_dragHandle.reset();
155 }
156
157 d->m_visible = p_visible;
158}
159
161{
162 bool configurationChanged = enabled != d->m_enabled;
163 d->m_enabled = enabled;
164 if (configurationChanged) {
165 // Reset visibility when configuration changes
166 setVisible(d->m_visible);
167 }
168}
169
170bool KisSelectionActionsPanel::eventFilter(QObject *obj, QEvent *event)
171{
172 switch (event->type()) {
173 case QEvent::MouseButtonPress: {
174 const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
175 return handlePress(event, mouseEventPos(mouseEvent), mouseEvent->button());
176 }
177 case QEvent::TabletPress: {
178 const QTabletEvent *tabletEvent = static_cast<QTabletEvent *>(event);
179 return handlePress(event, tabletEventPos(tabletEvent));
180 }
181 case QEvent::TouchBegin: {
182 const QTouchEvent *touchEvent = static_cast<QTouchEvent *>(event);
183 QPoint pos;
184 if (touchEventPos(touchEvent, pos)) {
185 return handlePress(event, pos);
186 }
187 break;
188 }
189
190 case QEvent::MouseMove:
191 if (d->m_pressed) {
192 const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
193 return handleMove(event, mouseEventPos(mouseEvent));
194 }
195 break;
196 case QEvent::TabletMove:
197 if (d->m_pressed) {
198 const QTabletEvent *tabletEvent = static_cast<QTabletEvent *>(event);
199 return handleMove(event, tabletEventPos(tabletEvent));
200 }
201 break;
202 case QEvent::TouchUpdate:
203 if (d->m_pressed) {
204 const QTouchEvent *touchEvent = static_cast<QTouchEvent *>(event);
205 QPoint pos;
206 if (touchEventPos(touchEvent, pos)) {
207 return handleMove(event, pos);
208 }
209 }
210 break;
211
212 case QEvent::MouseButtonRelease:
213 case QEvent::TabletRelease:
214 case QEvent::TouchEnd:
215 case QEvent::TouchCancel:
216 if (d->m_pressed) {
217 d->m_handleWidget->set_held(false);
218 d->m_pressed = false;
219 event->accept();
220 return true;
221 }
222 break;
223
224 default:
225 break;
226 }
227 return false;
228}
229
230
232{
233
234 Q_FOREACH(QWidget* btn, d->m_buttons) {
235 btn->setParent(canvas->widget());
236 btn->show();
237 }
238
239 d->m_handleWidget->setParent(canvas->widget());
240 d->m_handleWidget->show();
241}
242
243QPoint KisSelectionActionsPanel::updateCanvasBoundaries(QPoint position, QWidget *canvasWidget) const
244{
245 QRect canvasBounds = canvasWidget->rect();
246
247 const int ACTION_BAR_WIDTH = d->m_actionBarWidth;
248 const int ACTION_BAR_HEIGHT = BUTTON_SIZE;
249
250 int pos_x_min = canvasBounds.left() + BUFFER_SPACE;
251 int pos_x_max = canvasBounds.right() - ACTION_BAR_WIDTH - BUFFER_SPACE;
252
253 int pos_y_min = canvasBounds.top() + BUFFER_SPACE;
254 int pos_y_max = canvasBounds.bottom() - ACTION_BAR_HEIGHT - BUFFER_SPACE;
255
256 //Ensure that max is always bigger than min
257 //If the window is small enough max could be smaller than min
258 if (pos_x_max < pos_x_min) {
259 pos_x_max = pos_x_min;
260 }
261
262 //It is pretty implausible for it to happen vertically but better safe than sorry
263 if (pos_y_max < pos_y_min) {
264 pos_y_max = pos_y_min;
265 }
266
267 position.setX(qBound(pos_x_min, position.x(), pos_x_max));
268 position.setY(qBound(pos_y_min, position.y(), pos_y_max));
269
270 return position;
271}
272
274{
275 KisSelectionSP selection = d->m_viewManager->selection();
276 KisCanvasWidgetBase *canvas = dynamic_cast<KisCanvasWidgetBase*>(d->m_viewManager->canvas());
277 KIS_ASSERT(selection);
278 KIS_ASSERT(canvas);
279
280 QRectF selectionBounds = selection->selectedRect();
281 int selectionBottom = selectionBounds.bottom();
282 QPointF selectionCenter = selectionBounds.center();
283 QPointF bottomCenter(selectionCenter.x(), selectionBottom);
284
285 QPointF widgetBottomCenter = canvas->coordinatesConverter()->imageToWidget(bottomCenter); // converts current selection's QPointF into canvasWidget's QPointF space
286
287 widgetBottomCenter.setX(widgetBottomCenter.x() - (d->m_actionBarWidth / 2)); // centers toolbar midpoint with the selection center
288 widgetBottomCenter.setY(widgetBottomCenter.y() + BUFFER_SPACE);
289
290 return updateCanvasBoundaries(widgetBottomCenter.toPoint(), d->m_viewManager->canvas());
291}
292
294{
295 const int cornerRadius = 4;
296 QColor outlineColor = Qt::darkGray;
297 const QColor bgColor = qApp->palette().window().color();
298 QColor bgColorTrans = bgColor;
299 bgColorTrans.setAlpha(80);
300 const int outline_width = 4;
301
302 //an outer 1px wide outline for contrast against the background
303 QRectF contrastOutline(d->m_dragHandle->position - QPoint(outline_width + 1,outline_width + 1), QSize(d->m_actionBarWidth, BUTTON_SIZE) +QSize(outline_width + 1,outline_width + 1) * 2);
304 QRectF midOutline(d->m_dragHandle->position - QPoint(outline_width,outline_width), QSize(d->m_actionBarWidth, BUTTON_SIZE) +QSize(outline_width,outline_width) * 2);
305 //Add a bit of padding here for the icons
306 QRectF centerBackground(d->m_dragHandle->position - QPoint(outline_width,outline_width) / 2, QSize(d->m_actionBarWidth - BUTTON_SIZE, BUTTON_SIZE) +QSize(outline_width,outline_width));
307 QPainterPath bgPath;
308 QPainterPath outlinePath;
309 QPainterPath contrastOutlinePath;
310
311 bgPath.addRoundedRect(centerBackground, cornerRadius, cornerRadius);
312 outlinePath.addRoundedRect(midOutline, cornerRadius, cornerRadius);
313 contrastOutlinePath.addRoundedRect(contrastOutline, cornerRadius, cornerRadius);
314
315 painter.fillPath(contrastOutlinePath, bgColorTrans);
316 painter.fillPath(outlinePath, outlineColor);
317 painter.fillPath(bgPath, bgColor);
318}
319
320bool KisSelectionActionsPanel::handlePress(QEvent *event, const QPoint &pos, Qt::MouseButton button)
321{
322 if (d->m_pressed) {
323 event->accept();
324 return true;
325 }
326
327 if (button == Qt::LeftButton) {
328 d->m_pressed = true;
329 d->m_dragHandle->dragOrigin = pos - d->m_dragHandle->position;
330 d->m_handleWidget->set_held(true);
331
332 event->accept();
333 return true;
334 }
335
336 return false;
337}
338
339bool KisSelectionActionsPanel::handleMove(QEvent *event, const QPoint &pos)
340{
341 QWidget *canvasWidget = d->m_viewManager->canvas();
342 QPoint newPos = pos - d->m_dragHandle->dragOrigin;
343 d->m_dragHandle->position = updateCanvasBoundaries(newPos, canvasWidget);
345 canvasWidget->update();
346 event->accept();
347 return true;
348}
349
351{
352 d->m_handleWidget->move(d->m_dragHandle->position.x() + d->m_buttons.size() * BUTTON_SIZE, d->m_dragHandle->position.y());
353 d->m_handleWidget->show();
354
355 int i = 0;
356 Q_FOREACH(KisSelectionActionsPanelButton *button, d->m_buttons) {
357 int buttonPosition = i * BUTTON_SIZE;
358 button->move(d->m_dragHandle->position.x() + buttonPosition, d->m_dragHandle->position.y());
359 button->show();
360 i++;
361 }
362}
363
364QPoint KisSelectionActionsPanel::mouseEventPos(const QMouseEvent *mouseEvent)
365{
366#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
367 return transformHandleCoords(mouseEvent->position().toPoint());
368#else
369 return transformHandleCoords(mouseEvent->pos());
370#endif
371}
372
373QPoint KisSelectionActionsPanel::tabletEventPos(const QTabletEvent *tabletEvent)
374{
375#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
376 return transformHandleCoords(tabletEvent->position().toPoint());
377#else
378 return transformHandleCoords(tabletEvent->pos());
379#endif
380}
381
382bool KisSelectionActionsPanel::touchEventPos(const QTouchEvent *touchEvent, QPoint &outPos)
383{
384#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
385 if (touchEvent->pointCount() < 1) {
386 return false;
387 } else {
388 outPos = transformHandleCoords(touchEvent->points().first().position().toPoint());
389 return true;
390 }
391#else
392 const QList<QTouchEvent::TouchPoint> &touchPoints = touchEvent->touchPoints();
393 if (touchPoints.isEmpty()) {
394 return false;
395 } else {
396 outPos = transformHandleCoords(touchPoints.first().pos().toPoint());
397 return true;
398 }
399#endif
400}
401
403 return d->m_dragHandle->position + pos;
404}
virtual QWidget * widget()=0
KisCoordinatesConverter * coordinatesConverter() const
_Private::Traits< T >::Result imageToWidget(const T &obj) const
Custom widget for selection actions panel buttons, to prevent them being drawn over the pop-up palett...
bool handlePress(QEvent *event, const QPoint &pos, Qt::MouseButton button=Qt::LeftButton)
void movePanelWidgets()
Moves all the widgets that are a part of the panel.
QPoint updateCanvasBoundaries(QPoint position, QWidget *canvasWidget) const
constexpr QPoint transformHandleCoords(QPoint pos)
bool eventFilter(QObject *obj, QEvent *event) override
QPoint tabletEventPos(const QTabletEvent *tabletEvent)
bool handleMove(QEvent *event, const QPoint &pos)
QPoint mouseEventPos(const QMouseEvent *mouseEvent)
void canvasWidgetChanged(KisCanvasWidgetBase *canvas)
bool touchEventPos(const QTouchEvent *touchEvent, QPoint &outPos)
void drawActionBarBackground(QPainter &gc) const
QWidget * canvas() const
Return the actual widget that is displaying the current image.
KisSelectionManager * selectionManager()
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
static constexpr int BUFFER_SPACE
static constexpr int BUTTON_SIZE
QString button(const QWheelEvent &ev)
void(KisSelectionManager::*)() TargetSlot
KisSelectionActionsPanelHandle * m_handleWidget
QList< KisSelectionActionsPanelButton * > m_buttons
static const QVector< ActionButtonData > & buttonData()
QRect selectedRect() const