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"
18#include <QList>
19#include <QPointF>
20#include <QPushButton>
21#include <QTransform>
22#include <kactioncollection.h>
23#include <kis_algebra_2d.h>
24#include <klocalizedstring.h>
25#include <ktoggleaction.h>
26
27#include <QApplication>
28#include <QPainter>
29#include <QPainterPath>
30
31const int BUTTON_SIZE = 25;
32const int BUFFER_SPACE = 5;
33
35
43
46 {
47 }
48
51
52 bool m_dragging = false;
53 bool m_visible = false;
54 bool m_enabled = true;
55
56 struct DragHandle {
57 QPoint position = QPoint(0, 0);
58 QPoint dragOrigin = QPoint(0, 0);
59 };
60
61 QScopedPointer<DragHandle> m_dragHandle;
62
65 {
66 static const QVector<ActionButtonData> data = {
67 {"select-all", i18n("Select All"), &KisSelectionManager::selectAll},
68 {"select-invert", i18n("Invert Selection"), &KisSelectionManager::invert},
69 {"select-clear", i18n("Deselect"), &KisSelectionManager::deselect},
70 {"krita_tool_color_fill", i18n("Fill Selection with Color"), &KisSelectionManager::fillForegroundColor},
71 {"draw-eraser", i18n("Clear Selection"), &KisSelectionManager::clear},
72 {"duplicatelayer", i18n("Copy To New Layer"), &KisSelectionManager::copySelectionToNewLayer},
73 {"tool_crop", i18n("Crop to Selection"), &KisSelectionManager::imageResizeToSelection}};
74 return data;
75 }
76 int m_buttonCount = buttonData().size() + 1; // buttons + drag handle
77
79};
80
82 : QWidget(parent)
83 , d(new Private)
84{
85 d->m_viewManager = viewManager;
86 d->m_selectionManager = viewManager->selectionManager();
87
88 // Setup buttons...
89 for (const ActionButtonData &buttonData : Private::buttonData()) {
90 QPushButton *button = createButton(buttonData.iconName, buttonData.tooltip);
91 connect(button, &QPushButton::clicked, d->m_selectionManager, buttonData.slot);
92 d->m_buttons.append(button);
93 }
94}
95
97{
98 // buttons are children of the canvas, but we should still delete
99 // them to make sure they are not accessed after the decoration dies
100 qDeleteAll(d->m_buttons);
101 d->m_buttons.clear();
102}
103
104void KisSelectionActionsPanel::draw(QPainter &painter)
105{
106 KisSelectionSP selection = d->m_viewManager->selection();
107
108 if (!selection || !d->m_enabled || !d->m_visible) {
109 return;
110 }
111
113
114 for (int i = 0; i < d->m_buttons.size(); i++) {
115 QPushButton *button = d->m_buttons[i];
116 int buttonPosition = i * BUTTON_SIZE;
117 button->move(d->m_dragHandle->position.x() + buttonPosition, d->m_dragHandle->position.y());
118 button->show();
119 }
120}
121
123{
124 QWidget *canvasWidget = dynamic_cast<QWidget *>(d->m_viewManager->canvas());
125 if (!canvasWidget) {
126 return;
127 }
128
129 p_visible &= d->m_enabled;
130
131 const bool VISIBILITY_CHANGED = d->m_visible != p_visible;
132 if (!VISIBILITY_CHANGED) {
133 return;
134 }
135
136 if (d->m_viewManager->selection() && p_visible) { // Now visible!
137 canvasWidget->installEventFilter(this);
138
139 if (!d->m_dragHandle) {
140 d->m_dragHandle.reset(new Private::DragHandle());
141 d->m_dragHandle->position = initialDragHandlePosition();
142 }
143
144 for (QPushButton *button : d->m_buttons) {
145 button->setParent(canvasWidget);
146 }
147 } else { // Now hidden!
148 canvasWidget->removeEventFilter(this);
149
150 for (QPushButton *button : d->m_buttons) {
151 button->hide();
152 }
153
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 bool eventHandled = false;
173
174 bool focusInEvent = event->type() == QEvent::FocusIn;
175 if (focusInEvent && !eventHandled) {
176 eventHandled = true;
177 }
178
179 // Clicks...
180 bool clickEvent = event->type() == QEvent::MouseButtonPress || event->type() == QEvent::TabletPress || event->type() == QEvent::TouchBegin;
181 if (clickEvent && !eventHandled ) {
182 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
183 QRect dragHandleRect(d->m_dragHandle->position, QSize(25 * (d->m_buttonCount), 25));
184 if (dragHandleRect.contains(mouseEvent->pos())) {
185 d->m_dragging = true;
186 d->m_dragHandle->dragOrigin = mouseEvent->pos() - d->m_dragHandle->position;
187
188 eventHandled = true;
189 }
190 }
191
192 // Drags...
193 bool dragEvent = d->m_dragging && (event->type() == QEvent::MouseMove || event->type() == QEvent::TouchUpdate);
194
195 if (dragEvent && !eventHandled) {
196 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
197 QPoint newPos = mouseEvent->pos() - d->m_dragHandle->dragOrigin;
198
199 // bound actionBar to stay within canvas space
200 QWidget *canvasWidget = dynamic_cast<QWidget *>(d->m_viewManager->canvas());
201
202 if (obj == canvasWidget) {
203 d->m_dragHandle->position = updateCanvasBoundaries(newPos, canvasWidget);
204 canvasWidget->update();
205 eventHandled = true;
206 }
207 }
208
209 // Releases...
210 bool releaseEvent = d->m_dragging && (event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::TabletRelease || event->type() == QEvent::TouchEnd);
211
212 if (releaseEvent && d->m_dragging) {
213 d->m_dragging = false;
214 eventHandled = true;
215 }
216
217 return eventHandled;
218}
219
220QPoint KisSelectionActionsPanel::updateCanvasBoundaries(QPoint position, QWidget *canvasWidget) const
221{
222 QRect canvasBounds = canvasWidget->rect();
223
224 const int ACTION_BAR_WIDTH = d->m_actionBarWidth;
225 const int ACTION_BAR_HEIGHT = BUTTON_SIZE;
226
227 position.setX(qBound(canvasBounds.left() + BUFFER_SPACE,
228 position.x(),
229 canvasBounds.right() - ACTION_BAR_WIDTH - BUFFER_SPACE));
230
231 position.setY(qBound(canvasBounds.top() + BUFFER_SPACE,
232 position.y(),
233 canvasBounds.bottom() - ACTION_BAR_HEIGHT - BUFFER_SPACE));
234
235 return position;
236}
237
239{
240 KisSelectionSP selection = d->m_viewManager->selection();
241 KisCanvasWidgetBase *canvas = dynamic_cast<KisCanvasWidgetBase*>(d->m_viewManager->canvas());
242 KIS_ASSERT(selection);
243 KIS_ASSERT(canvas);
244
245 QRectF selectionBounds = selection->selectedRect();
246 int selectionBottom = selectionBounds.bottom();
247 QPointF selectionCenter = selectionBounds.center();
248 QPointF bottomCenter(selectionCenter.x(), selectionBottom);
249
250 QPointF widgetBottomCenter = canvas->coordinatesConverter()->imageToWidget(bottomCenter); // converts current selection's QPointF into canvasWidget's QPointF space
251
252 widgetBottomCenter.setX(widgetBottomCenter.x() - (d->m_actionBarWidth / 2)); // centers toolbar midpoint with the selection center
253 widgetBottomCenter.setY(widgetBottomCenter.y() + BUFFER_SPACE);
254
255 return updateCanvasBoundaries(widgetBottomCenter.toPoint(), d->m_viewManager->canvas());
256}
257
258QPushButton *KisSelectionActionsPanel::createButton(const QString &iconName, const QString &tooltip)
259{
260 QPushButton *button = new QPushButton();
261 button->setIcon(KisIconUtils::loadIcon(iconName));
262 button->setFixedSize(BUTTON_SIZE, BUTTON_SIZE);
263 button->setToolTip(tooltip);
264 return button;
265}
266
268{
269 const int CORNER_RADIUS = 4;
270 const int PEN_WIDTH = 5;
271 const QColor BACKGROUND_COLOR = Qt::darkGray;
272 const QColor OUTLINE_COLOR(60, 60, 60, 80);
273 const QColor DOT_COLOR = Qt::lightGray;
274 const int DOT_SIZE = 4;
275 const int DOT_SPACING = 5;
276 const QPoint DRAG_HANDLE_RECT_DOTS_OFFSET(10, 10);
277
278 QRectF actionBarRect(d->m_dragHandle->position, QSize(d->m_actionBarWidth, BUTTON_SIZE));
279 QPainterPath bgPath;
280 bgPath.addRoundedRect(actionBarRect, CORNER_RADIUS, CORNER_RADIUS);
281 painter.fillPath(bgPath, BACKGROUND_COLOR);
282
283 QPen pen(OUTLINE_COLOR);
284 pen.setWidth(PEN_WIDTH);
285 painter.setPen(pen);
286 painter.drawPath(bgPath);
287
288 QRectF dragHandleRect(
289 QPoint(d->m_dragHandle->position.x() + d->m_actionBarWidth - BUTTON_SIZE, d->m_dragHandle->position.y()),
290 QSize(BUTTON_SIZE, BUTTON_SIZE));
291 QPainterPath dragHandlePath;
292 dragHandlePath.addRect(dragHandleRect);
293 painter.fillPath(dragHandlePath, BACKGROUND_COLOR);
294
295 const std::list<std::pair<int, int>> DOT_OFFSETS = {{0, 0},
296 {DOT_SPACING, 0},
297 {-DOT_SPACING, 0},
298 {0, DOT_SPACING},
299 {0, -DOT_SPACING},
300 {DOT_SPACING, DOT_SPACING},
301 {DOT_SPACING, -DOT_SPACING},
302 {-DOT_SPACING, DOT_SPACING},
303 {-DOT_SPACING, -DOT_SPACING}};
304
305 QPainterPath dragHandleRectDots;
306 for (const std::pair<int, int> &offset : DOT_OFFSETS) {
307 dragHandleRectDots.addEllipse(offset.first, offset.second, DOT_SIZE, DOT_SIZE);
308 };
309
310 dragHandleRectDots.translate(dragHandleRect.topLeft() + DRAG_HANDLE_RECT_DOTS_OFFSET);
311 painter.fillPath(dragHandleRectDots, DOT_COLOR);
312}
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
KisCoordinatesConverter * coordinatesConverter() const
_Private::Traits< T >::Result imageToWidget(const T &obj) const
QPoint updateCanvasBoundaries(QPoint position, QWidget *canvasWidget) const
bool eventFilter(QObject *obj, QEvent *event) override
void drawActionBarBackground(QPainter &gc) const
QPushButton * createButton(const QString &iconName, const QString &tooltip)
KisSelectionManager * selectionManager()
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
const int BUTTON_SIZE
const int BUFFER_SPACE
QString button(const QWheelEvent &ev)
QIcon loadIcon(const QString &name)
void(KisSelectionManager::*)() TargetSlot
static const QVector< ActionButtonData > & buttonData()
QRect selectedRect() const