Krita Source Code Documentation
Loading...
Searching...
No Matches
DefaultToolGeometryWidget.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2007 Martin Pfeiffer <hubipete@gmx.net>
3 * SPDX-FileCopyrightText: 2007 Jan Hambrecht <jaham@gmx.net>
4 * SPDX-FileCopyrightText: 2008 Thorsten Zachmann <zachmann@kde.org>
5 * SPDX-FileCopyrightText: 2010 Thomas Zander <zander@kde.org>
6 *
7 * SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9
11#include "DefaultTool.h"
12
13#include <KoInteractionTool.h>
14#include <KoCanvasBase.h>
17#include <KoSelection.h>
18#include <KoUnit.h>
26#include "SelectionDecorator.h"
27#include <KoShapeGroup.h>
28
30
31#include <QAction>
32#include <QSize>
33#include <QRadioButton>
34#include <QCheckBox>
35#include <QDoubleSpinBox>
36#include <QList>
37#include <kis_algebra_2d.h>
38
40#include "kis_debug.h"
43#include "kis_signals_blocker.h"
44#include "kis_icon.h"
45
46
48 : QWidget(parent)
49 , m_tool(tool)
50 , m_sizeAspectLocker(new KisAspectRatioLocker())
51 , m_savedUniformScaling(false)
52{
53 setupUi(this);
54
56
57 // Connect and initialize automated aspect locker
58 m_sizeAspectLocker->connectSpinBoxes(widthSpinBox, heightSpinBox, aspectButton);
59 aspectButton->setKeepAspectRatio(false);
60
61
62 // TODO: use valueChanged() instead!
63 connect(positionXSpinBox, SIGNAL(valueChangedPt(qreal)), this, SLOT(slotRepositionShapes()));
64 connect(positionYSpinBox, SIGNAL(valueChangedPt(qreal)), this, SLOT(slotRepositionShapes()));
65
66 KoSelectedShapesProxy *selectedShapesProxy = m_tool->canvas()->selectedShapesProxy();
67
68 connect(selectedShapesProxy, SIGNAL(selectionChanged()), this, SLOT(slotUpdateCheckboxes()));
69 connect(selectedShapesProxy, SIGNAL(selectionChanged()), this, SLOT(slotUpdatePositionBoxes()));
70 connect(selectedShapesProxy, SIGNAL(selectionChanged()), this, SLOT(slotUpdateOpacitySlider()));
71
72 connect(selectedShapesProxy, SIGNAL(selectionContentChanged()), this, SLOT(slotUpdatePositionBoxes()));
73 connect(selectedShapesProxy, SIGNAL(selectionContentChanged()), this, SLOT(slotUpdateOpacitySlider()));
74
75 connect(chkGlobalCoordinates, SIGNAL(toggled(bool)), SLOT(slotUpdateSizeBoxes()));
76 connect(chkGlobalCoordinates, SIGNAL(toggled(bool)), SLOT(slotUpdateAspectButton()));
77
78
82 KisAcyclicSignalConnector *acyclicConnector = new KisAcyclicSignalConnector(this);
83 acyclicConnector->connectForwardVoid(m_sizeAspectLocker.data(), SIGNAL(aspectButtonChanged()), this, SLOT(slotAspectButtonToggled()));
84 acyclicConnector->connectBackwardVoid(selectedShapesProxy, SIGNAL(selectionChanged()), this, SLOT(slotUpdateAspectButton()));
85 acyclicConnector->connectBackwardVoid(selectedShapesProxy, SIGNAL(selectionContentChanged()), this, SLOT(slotUpdateAspectButton()));
86
87 KisAcyclicSignalConnector *sizeConnector = acyclicConnector->createCoordinatedConnector();
88 sizeConnector->connectForwardVoid(m_sizeAspectLocker.data(), SIGNAL(sliderValueChanged()), this, SLOT(slotResizeShapes()));
89 sizeConnector->connectBackwardVoid(selectedShapesProxy, SIGNAL(selectionChanged()), this, SLOT(slotUpdateSizeBoxes()));
90
91 KisAcyclicSignalConnector *contentSizeConnector = acyclicConnector->createCoordinatedConnector();
92 contentSizeConnector->connectBackwardVoid(selectedShapesProxy, SIGNAL(selectionContentChanged()), this, SLOT(slotUpdateSizeBoxesNoAspectChange()));
93
94
95 // Connect and initialize anchor point resource
97 connect(resourceManager,
98 SIGNAL(canvasResourceChanged(int,QVariant)),
99 SLOT(resourceChanged(int,QVariant)));
101 positionSelector->setValue(KoFlake::AnchorPosition(resourceManager->resource(DefaultTool::HotPosition).toInt()));
102
103 // Connect anchor point selector
104 connect(positionSelector, SIGNAL(valueChanged(KoFlake::AnchorPosition)), SLOT(slotAnchorPointChanged()));
105
106 cmbPaintOrder->setIconSize(QSize(22, 22));
107 cmbPaintOrder->addItem(KisIconUtils::loadIcon("paint-order-fill-stroke-marker"), i18n("Fill, Stroke, Markers"));
108 cmbPaintOrder->addItem(KisIconUtils::loadIcon("paint-order-fill-marker-stroke"), i18n("Fill, Markers, Stroke"));
109 cmbPaintOrder->addItem(KisIconUtils::loadIcon("paint-order-stroke-fill-marker"), i18n("Stroke, Fill, Markers"));
110 cmbPaintOrder->addItem(KisIconUtils::loadIcon("paint-order-stroke-marker-fill"), i18n("Stroke, Markers, Fill"));
111 cmbPaintOrder->addItem(KisIconUtils::loadIcon("paint-order-marker-fill-stroke"), i18n("Markers, Fill, Stroke"));
112 cmbPaintOrder->addItem(KisIconUtils::loadIcon("paint-order-marker-stroke-fill"), i18n("Markers, Stroke, Fill"));
113 connect(cmbPaintOrder, SIGNAL(currentIndexChanged(int)), SLOT(slotPaintOrderChanged()));
114
115
116 dblOpacity->setRange(0.0, 1.0, 2);
117 dblOpacity->setSingleStep(0.01);
118 dblOpacity->setFastSliderStep(0.1);
119 dblOpacity->setTextTemplates(i18nc("{n} is the number value, % is the percent sign", "Opacity: {n}"),
120 i18nc("{n} is the number value, % is the percent sign", "Opacity [*varies*]: {n}"));
121
122 dblOpacity->setValueGetter(
123 [](KoShape *s) { return 1.0 - s->transparency(); }
124 );
125
126 connect(dblOpacity, SIGNAL(valueChanged(qreal)), SLOT(slotOpacitySliderChanged(qreal)));
127
128 // cold init
130}
131
135
136namespace {
137
138void tryAnchorPosition(KoFlake::AnchorPosition anchor,
139 const QRectF &rect,
140 QPointF *position)
141{
142 bool valid = false;
143 QPointF anchoredPosition = KoFlake::anchorToPoint(anchor, rect, &valid);
144
145 if (valid) {
146 *position = anchoredPosition;
147 }
148}
149
150QRectF calculateSelectionBounds(KoSelection *selection,
152 bool useGlobalSize,
153 QList<KoShape*> *outShapes = 0)
154{
155 QList<KoShape*> shapes = selection->selectedEditableShapes();
156
157 KoShape *shape = shapes.size() == 1 ? shapes.first() : selection;
158
159 QRectF resultRect = shape->outlineRect();
160
161 QPointF resultPoint = resultRect.topLeft();
162 tryAnchorPosition(anchor, resultRect, &resultPoint);
163
164 if (useGlobalSize) {
165 resultRect = shape->absoluteTransformation().mapRect(resultRect);
166 } else {
174 resultRect = matrix.scaleTransform().mapRect(resultRect);
175 }
176
177 resultPoint = shape->absoluteTransformation().map(resultPoint);
178
179 if (outShapes) {
180 *outShapes = shapes;
181 }
182
183 return QRectF(resultPoint, resultRect.size());
184}
185
186}
187
189{
190 if (!isVisible()) return;
191
192 QVariant newValue(positionSelector->value());
193 m_tool->canvas()->resourceManager()->setResource(DefaultTool::HotPosition, newValue);
195}
196
198{
199 if (!isVisible()) return;
200
202 QList<KoShape*> shapes = selection->selectedEditableShapes();
203
204 KoShapeGroup *onlyGroupShape = 0;
205
206 if (shapes.size() == 1) {
207 onlyGroupShape = dynamic_cast<KoShapeGroup*>(shapes.first());
208 }
209
210 const bool uniformScalingAvailable = shapes.size() <= 1 && !onlyGroupShape;
211
212 if (uniformScalingAvailable && !chkUniformScaling->isEnabled()) {
213 chkUniformScaling->setChecked(m_savedUniformScaling);
214 chkUniformScaling->setEnabled(uniformScalingAvailable);
215 } else if (!uniformScalingAvailable && chkUniformScaling->isEnabled()) {
216 m_savedUniformScaling = chkUniformScaling->isChecked();
217 chkUniformScaling->setChecked(true);
218 chkUniformScaling->setEnabled(uniformScalingAvailable);
219 }
220
221 // TODO: not implemented yet!
222 chkAnchorLock->setEnabled(false);
223}
224
226{
228 QList<KoShape*> shapes = selection->selectedEditableShapes();
229
230 KUndo2Command *cmd =
231 new KoShapeKeepAspectRatioCommand(shapes, aspectButton->keepAspectRatio());
232
233 m_tool->canvas()->addCommand(cmd);
234}
235
237{
238 if (!isVisible()) return;
239
241 QList<KoShape*> shapes = selection->selectedEditableShapes();
242
243 bool hasKeepAspectRatio = false;
244 bool hasNotKeepAspectRatio = false;
245
246 Q_FOREACH (KoShape *shape, shapes) {
247 if (shape->keepAspectRatio()) {
248 hasKeepAspectRatio = true;
249 } else {
250 hasNotKeepAspectRatio = true;
251 }
252
253 if (hasKeepAspectRatio && hasNotKeepAspectRatio) break;
254 }
255
256 Q_UNUSED(hasNotKeepAspectRatio); // TODO: use for tristated mode of the checkbox
257
258 const bool useGlobalSize = chkGlobalCoordinates->isChecked();
259 const KoFlake::AnchorPosition anchor = positionSelector->value();
260 const QRectF bounds = calculateSelectionBounds(selection, anchor, useGlobalSize);
261 const bool hasNullDimensions = bounds.isEmpty();
262
263 aspectButton->setKeepAspectRatio(hasKeepAspectRatio && !hasNullDimensions);
264 aspectButton->setEnabled(!hasNullDimensions);
265}
266
267//namespace {
268//qreal calculateCommonShapeTransparency(const QList<KoShape*> &shapes)
269//{
270// qreal commonTransparency = -1.0;
271
272// Q_FOREACH (KoShape *shape, shapes) {
273// if (commonTransparency < 0) {
274// commonTransparency = shape->transparency();
275// } else if (!qFuzzyCompare(commonTransparency, shape->transparency())) {
276// commonTransparency = -1.0;
277// break;
278// }
279// }
280
281// return commonTransparency;
282//}
283//}
284
286{
288 QList<KoShape*> shapes = selection->selectedEditableShapes();
289 if (shapes.isEmpty()) return;
290
291 KUndo2Command *cmd =
292 new KoShapeTransparencyCommand(shapes, 1.0 - newOpacity);
293
294 m_tool->canvas()->addCommand(cmd);
295}
296
298{
299 if (!isVisible()) return;
300
302 QList<KoShape*> shapes = selection->selectedEditableShapes();
303
304 dblOpacity->setSelection(shapes);
305}
306
308{
310 QList<KoShape*> shapes = selection->selectedEditableShapes();
311 if (shapes.isEmpty()) return;
312
315
316 switch(cmbPaintOrder->currentIndex()) {
317 case 1:
318 first = KoShape::Fill;
319 second = KoShape::Markers;
320 break;
321 case 2:
322 first = KoShape::Stroke;
323 second = KoShape::Fill;
324 break;
325 case 3:
326 first = KoShape::Stroke;
327 second = KoShape::Markers;
328 break;
329 case 4:
330 first = KoShape::Markers;
331 second = KoShape::Fill;
332 break;
333 case 5:
334 first = KoShape::Markers;
335 second = KoShape::Stroke;
336 break;
337 default:
338 first = KoShape::Fill;
339 second = KoShape::Stroke;
340 }
341
342 KUndo2Command *cmd =
343 new KoShapePaintOrderCommand(shapes, first, second);
344
345 m_tool->canvas()->addCommand(cmd);
346}
347
349 if (!isVisible()) return;
350
352 QList<KoShape*> shapes = selection->selectedEditableShapes();
353
354 if (!shapes.isEmpty()) {
355 KoShape *shape = shapes.first();
356 QVector<KoShape::PaintOrder> paintOrder = shape->paintOrder();
357 int index = 0;
358 if (paintOrder.first() == KoShape::Fill) {
359 index = paintOrder.at(1) == KoShape::Stroke? 0: 1;
360 } else if (paintOrder.first() == KoShape::Stroke) {
361 index = paintOrder.at(1) == KoShape::Fill? 2: 3;
362 } else {
363 index = paintOrder.at(1) == KoShape::Fill? 4: 5;
364 }
365 cmbPaintOrder->setCurrentIndex(index);
366 }
367}
368
370{
371 if (!isVisible()) return;
372
373 const bool useGlobalSize = chkGlobalCoordinates->isChecked();
374 const KoFlake::AnchorPosition anchor = positionSelector->value();
375
377 const QRectF bounds = calculateSelectionBounds(selection, anchor, useGlobalSize);
378
379 const bool hasSizeConfiguration = !bounds.isNull();
380
381 widthSpinBox->setEnabled(hasSizeConfiguration && bounds.width() > 0);
382 heightSpinBox->setEnabled(hasSizeConfiguration && bounds.height() > 0);
383
384 if (hasSizeConfiguration) {
385 KisSignalsBlocker b(widthSpinBox, heightSpinBox);
386 widthSpinBox->changeValue(bounds.width());
387 heightSpinBox->changeValue(bounds.height());
388
389 if (updateAspect) {
390 m_sizeAspectLocker->updateAspect();
391 }
392 }
393}
394
399
401{
402 if (!isVisible()) return;
403
404 const bool useGlobalSize = chkGlobalCoordinates->isChecked();
405 const KoFlake::AnchorPosition anchor = positionSelector->value();
406
408 QRectF bounds = calculateSelectionBounds(selection, anchor, useGlobalSize);
409
410 const bool hasSizeConfiguration = !bounds.isNull();
411
412 positionXSpinBox->setEnabled(hasSizeConfiguration);
413 positionYSpinBox->setEnabled(hasSizeConfiguration);
414
415 if (hasSizeConfiguration) {
416 KisSignalsBlocker b(positionXSpinBox, positionYSpinBox);
417 positionXSpinBox->changeValue(bounds.x());
418 positionYSpinBox->changeValue(bounds.y());
419 }
420}
421
423{
424 static const qreal eps = 1e-6;
425
426 const bool useGlobalSize = chkGlobalCoordinates->isChecked();
427 const KoFlake::AnchorPosition anchor = positionSelector->value();
428
429 QList<KoShape*> shapes;
431 QRectF bounds = calculateSelectionBounds(selection, anchor, useGlobalSize, &shapes);
432
433 if (bounds.isNull()) return;
434
435 const QPointF oldPosition = bounds.topLeft();
436 const QPointF newPosition(positionXSpinBox->value(), positionYSpinBox->value());
437 const QPointF diff = newPosition - oldPosition;
438
439 if (diff.manhattanLength() < eps) return;
440
441 QList<QPointF> oldPositions;
442 QList<QPointF> newPositions;
443
444 Q_FOREACH (KoShape *shape, shapes) {
445 const QPointF oldShapePosition = shape->absolutePosition(anchor);
446
447 oldPositions << shape->absolutePosition(anchor);
448 newPositions << oldShapePosition + diff;
449 }
450
451 KUndo2Command *cmd = new KoShapeMoveCommand(shapes, oldPositions, newPositions, anchor);
452 m_tool->canvas()->addCommand(cmd);
453}
454
456{
457 static const qreal eps = 1e-4;
458
459 const bool useGlobalSize = chkGlobalCoordinates->isChecked();
460 const KoFlake::AnchorPosition anchor = positionSelector->value();
461
462 QList<KoShape*> shapes;
464 QRectF bounds = calculateSelectionBounds(selection, anchor, useGlobalSize, &shapes);
465
466 if (bounds.isNull()) return;
467
468 const QSizeF oldSize(bounds.size());
469
470 QSizeF newSize(widthSpinBox->value(), heightSpinBox->value());
471 newSize = KisAlgebra2D::ensureSizeNotSmaller(newSize, QSizeF(eps, eps));
472
473 const qreal scaleX = oldSize.width() > 0 ? newSize.width() / oldSize.width() : 1.0;
474 const qreal scaleY = oldSize.height() > 0 ? newSize.height() / oldSize.height() : 1.0;
475
476 if (qAbs(scaleX - 1.0) < eps && qAbs(scaleY - 1.0) < eps) return;
477
478 const bool usePostScaling =
479 shapes.size() > 1 || chkUniformScaling->isChecked();
480
481 KUndo2Command *cmd = new KoShapeResizeCommand(shapes,
482 scaleX, scaleY,
483 bounds.topLeft(),
484 useGlobalSize,
485 usePostScaling,
486 selection->transformation());
487 m_tool->canvas()->addCommand(cmd);
488}
489
491{
492 positionXSpinBox->setUnit(unit);
493 positionYSpinBox->setUnit(unit);
494 widthSpinBox->setUnit(unit);
495 heightSpinBox->setUnit(unit);
496
497 positionXSpinBox->setDecimals(2);
498 positionYSpinBox->setDecimals(2);
499 widthSpinBox->setDecimals(2);
500 heightSpinBox->setDecimals(2);
501
502 // here we need to ensure that even for pixels unit, we can use decimals
503 positionXSpinBox->preventDecimalsChangeFromUnitManager(true);
504 positionYSpinBox->preventDecimalsChangeFromUnitManager(true);
505 widthSpinBox->preventDecimalsChangeFromUnitManager(true);
506 heightSpinBox->preventDecimalsChangeFromUnitManager(true);
507
508 positionXSpinBox->setLineStep(1.0);
509 positionYSpinBox->setLineStep(1.0);
510 widthSpinBox->setLineStep(1.0);
511 heightSpinBox->setLineStep(1.0);
512
515}
516
518{
519 return chkUniformScaling->isChecked();
520}
521
523{
524 QWidget::showEvent(event);
525
533}
534
535void DefaultToolGeometryWidget::resourceChanged(int key, const QVariant &res)
536{
537 if (key == KoCanvasResource::Unit) {
538 setUnit(res.value<KoUnit>());
539 } else if (key == DefaultTool::HotPosition) {
540 positionSelector->setValue(KoFlake::AnchorPosition(res.toInt()));
541 }
542}
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
void setUnit(const KoUnit &unit)
Sets the unit used by the unit aware child widgets.
QScopedPointer< KisAspectRatioLocker > m_sizeAspectLocker
void slotUpdateSizeBoxes(bool updateAspect=true)
void showEvent(QShowEvent *event) override
DefaultToolGeometryWidget(KoInteractionTool *tool, QWidget *parent=0)
void resourceChanged(int key, const QVariant &res)
void slotOpacitySliderChanged(qreal newOpacity)
KisAcyclicSignalConnector * createCoordinatedConnector()
create a coordinated connector that can be used for extending the number of self-locking connection.
void connectBackwardVoid(QObject *sender, const char *signal, QObject *receiver, const char *method)
void connectForwardVoid(QObject *sender, const char *signal, QObject *receiver, const char *method)
virtual void addCommand(KUndo2Command *command)=0
QPointer< KoCanvasResourceProvider > resourceManager
virtual KoSelectedShapesProxy * selectedShapesProxy() const =0
selectedShapesProxy() is a special interface for keeping a persistent connections to selectionChanged...
void setResource(int key, const QVariant &value)
The KoSelectedShapesProxy class is a special interface of KoCanvasBase to have a stable connection to...
virtual KoSelection * selection()=0
const QList< KoShape * > selectedEditableShapes() const
The undo / redo command for shape moving.
The undo / redo command for setting the shape transparency.
virtual QRectF outlineRect() const
Definition KoShape.cpp:637
virtual QVector< PaintOrder > paintOrder() const
paintOrder
Definition KoShape.cpp:773
QPointF absolutePosition(KoFlake::AnchorPosition anchor=KoFlake::Center) const
Definition KoShape.cpp:653
QTransform absoluteTransformation() const
Definition KoShape.cpp:382
QTransform transformation() const
Returns the shapes local transformation matrix.
Definition KoShape.cpp:424
bool keepAspectRatio() const
Definition KoShape.cpp:1052
@ Stroke
Definition KoShape.h:147
@ Markers
Definition KoShape.h:148
qreal transparency(bool recursive=false) const
Definition KoShape.cpp:730
KoCanvasBase * canvas() const
Returns the canvas the tool is working on.
@ Point
Postscript point, 1/72th of an Inco.
Definition KoUnit.h:76
#define bounds(x, a, b)
const qreal eps
Size ensureSizeNotSmaller(const Size &size, const Size &bounds)
QIcon loadIcon(const QString &name)
@ Unit
The unit of this canvas.
AnchorPosition
Definition KoFlake.h:85
@ Center
Definition KoFlake.h:90
KRITAFLAKE_EXPORT QPointF anchorToPoint(AnchorPosition anchor, const QRectF rect, bool *valid=0)
Definition KoFlake.cpp:329