1 /* This file is part of the KDE project
2 * Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr)
3 * Copyright (C) 2002 Tomislav Lukman <tomislav.lukman@ck.t-com.hr>
4 * Copyright (C) 2002-2003 Rob Buis <buis@kde.org>
5 * Copyright (C) 2005-2006 Tim Beaulen <tbscope@gmail.com>
6 * Copyright (C) 2005-2007 Thomas Zander <zander@kde.org>
7 * Copyright (C) 2005-2006, 2011 Inge Wallin <inge@lysator.liu.se>
8 * Copyright (C) 2005-2008 Jan Hambrecht <jaham@gmx.net>
9 * Copyright (C) 2006 C. Boemann <cbo@boemann.dk>
10 * Copyright (C) 2006 Peter Simonsson <psn@linux.se>
11 * Copyright (C) 2006 Laurent Montel <montel@kde.org>
12 * Copyright (C) 2007,2011 Thorsten Zachmann <t.zachmann@zagge.de>
13 * Copyright (C) 2011 Jean-Nicolas Artaud <jeannicolasartaud@gmail.com>
14 *
15 * This library is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU Library General Public
17 * License as published by the Free Software Foundation; either
18 * version 2 of the License, or (at your option) any later version.
19 *
20 * This library is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * Library General Public License for more details.
24 *
25 * You should have received a copy of the GNU Library General Public License
26 * along with this library; see the file COPYING.LIB. If not, write to
27 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
28 * Boston, MA 02110-1301, USA.
29 */
30
31 // Own
32 #include "KoStrokeConfigWidget.h"
33
34 // Qt
35 #include <QMenu>
36 #include <QToolButton>
37 #include <QButtonGroup>
38 #include <QVBoxLayout>
39 #include <QHBoxLayout>
40 #include <QSizePolicy>
41 #include <KisSignalMapper.h>
42
43 // KDE
44 #include <klocalizedstring.h>
45
46 // Calligra
47 #include <KoIcon.h>
48 #include <KoUnit.h>
49 #include <KoLineStyleSelector.h>
50 #include <KoUnitDoubleSpinBox.h>
51 #include <KoMarkerSelector.h>
52 #include <KoColorPopupAction.h>
53 #include <KoMarker.h>
54 #include <KoShapeStroke.h>
55 #include <KoPathShape.h>
56 #include <KoMarkerCollection.h>
57 #include <KoPathShapeMarkerCommand.h>
58 #include <KoCanvasBase.h>
59 #include <KoCanvasController.h>
60 #include <KoCanvasResourceProvider.h>
61 #include <KoDocumentResourceManager.h>
62 #include <KoSelection.h>
63 #include <KoShapeController.h>
64 #include <KoShapeStrokeCommand.h>
65 #include <KoShapeStrokeModel.h>
66 #include <KoSelectedShapesProxy.h>
67 #include "ui_KoStrokeConfigWidget.h"
68 #include <KoFlakeUtils.h>
69 #include <KoFillConfigWidget.h>
70 #include "kis_canvas_resource_provider.h"
71 #include "kis_acyclic_signal_connector.h"
72 #include <kis_signal_compressor.h>
73
74 // Krita
75 #include "kis_double_parse_unit_spin_box.h"
76
77 class CapNJoinMenu : public QMenu
78 {
79 public:
80 CapNJoinMenu(QWidget *parent = 0);
81 QSize sizeHint() const override;
82
83 KisDoubleParseUnitSpinBox *miterLimit {0};
84 QButtonGroup *capGroup {0};
85 QButtonGroup *joinGroup {0};
86 };
87
CapNJoinMenu(QWidget * parent)88 CapNJoinMenu::CapNJoinMenu(QWidget *parent)
89 : QMenu(parent)
90 {
91 QGridLayout *mainLayout = new QGridLayout();
92 mainLayout->setMargin(2);
93
94 // The cap group
95 capGroup = new QButtonGroup(this);
96 capGroup->setExclusive(true);
97
98 QToolButton *button = 0;
99
100 button = new QToolButton(this);
101 button->setIcon(koIcon("stroke-cap-butt"));
102 button->setCheckable(true);
103 button->setToolTip(i18n("Butt cap"));
104 capGroup->addButton(button, Qt::FlatCap);
105 mainLayout->addWidget(button, 2, 0);
106
107 button = new QToolButton(this);
108 button->setIcon(koIcon("stroke-cap-round"));
109 button->setCheckable(true);
110 button->setToolTip(i18n("Round cap"));
111 capGroup->addButton(button, Qt::RoundCap);
112 mainLayout->addWidget(button, 2, 1);
113
114 button = new QToolButton(this);
115 button->setIcon(koIcon("stroke-cap-square"));
116 button->setCheckable(true);
117 button->setToolTip(i18n("Square cap"));
118 capGroup->addButton(button, Qt::SquareCap);
119 mainLayout->addWidget(button, 2, 2, Qt::AlignLeft);
120
121 // The join group
122 joinGroup = new QButtonGroup(this);
123 joinGroup->setExclusive(true);
124
125 button = new QToolButton(this);
126 button->setIcon(koIcon("stroke-join-miter"));
127 button->setCheckable(true);
128 button->setToolTip(i18n("Miter join"));
129 joinGroup->addButton(button, Qt::MiterJoin);
130 mainLayout->addWidget(button, 3, 0);
131
132 button = new QToolButton(this);
133 button->setIcon(koIcon("stroke-join-round"));
134 button->setCheckable(true);
135 button->setToolTip(i18n("Round join"));
136 joinGroup->addButton(button, Qt::RoundJoin);
137 mainLayout->addWidget(button, 3, 1);
138
139 button = new QToolButton(this);
140 button->setIcon(koIcon("stroke-join-bevel"));
141 button->setCheckable(true);
142 button->setToolTip(i18n("Bevel join"));
143 joinGroup->addButton(button, Qt::BevelJoin);
144 mainLayout->addWidget(button, 3, 2, Qt::AlignLeft);
145
146 // Miter limit
147 // set min/max/step and value in points, then set actual unit
148 miterLimit = new KisDoubleParseUnitSpinBox(this);
149 miterLimit->setMinMaxStep(0.0, 1000.0, 0.5);
150 miterLimit->setDecimals(2);
151 miterLimit->setUnit(KoUnit(KoUnit::Point));
152 miterLimit->setToolTip(i18n("Miter limit"));
153 mainLayout->addWidget(miterLimit, 4, 0, 1, 3);
154
155 mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
156 setLayout(mainLayout);
157 }
158
sizeHint() const159 QSize CapNJoinMenu::sizeHint() const
160 {
161 return layout()->sizeHint();
162 }
163
164
165 class Q_DECL_HIDDEN KoStrokeConfigWidget::Private
166 {
167 public:
Private()168 Private()
169 : selectionChangedCompressor(200, KisSignalCompressor::FIRST_ACTIVE)
170 {
171 }
172
173 KoLineStyleSelector *lineStyle {0};
174 KisDoubleParseUnitSpinBox *lineWidth {0};
175 KoMarkerSelector *startMarkerSelector {0};
176 KoMarkerSelector *midMarkerSelector {0};
177 KoMarkerSelector *endMarkerSelector {0};
178
179 CapNJoinMenu *capNJoinMenu {0};
180
181 QWidget*spacer {0};
182
183 KoCanvasBase *canvas {0};
184
185 bool active {true};
186 bool allowLocalUnitManagement {false};
187
188 KoFillConfigWidget *fillConfigWidget {0};
189 bool noSelectionTrackingMode {false};
190
191 KisAcyclicSignalConnector shapeChangedAcyclicConnector;
192 KisAcyclicSignalConnector resourceManagerAcyclicConnector;
193 KisSignalCompressor selectionChangedCompressor;
194
195 std::vector<KisAcyclicSignalConnector::Blocker> deactivationLocks;
196
197 QScopedPointer<Ui_KoStrokeConfigWidget> ui;
198 };
199
200
KoStrokeConfigWidget(KoCanvasBase * canvas,QWidget * parent)201 KoStrokeConfigWidget::KoStrokeConfigWidget(KoCanvasBase *canvas, QWidget * parent)
202 : QWidget(parent)
203 , d(new Private())
204 {
205 // configure GUI
206 d->ui.reset(new Ui_KoStrokeConfigWidget());
207 d->ui->setupUi(this);
208
209 setObjectName("Stroke widget");
210
211 { // connect the canvas
212 d->shapeChangedAcyclicConnector.connectBackwardVoid(
213 canvas->selectedShapesProxy(), SIGNAL(selectionChanged()),
214 &d->selectionChangedCompressor, SLOT(start()));
215
216 d->shapeChangedAcyclicConnector.connectBackwardVoid(
217 canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()),
218 &d->selectionChangedCompressor, SLOT(start()));
219
220 connect(&d->selectionChangedCompressor, SIGNAL(timeout()), this, SLOT(selectionChanged()));
221
222 d->resourceManagerAcyclicConnector.connectBackwardResourcePair(
223 canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
224 this, SLOT(canvasResourceChanged(int,QVariant)));
225
226 d->canvas = canvas;
227 }
228
229 {
230
231 d->fillConfigWidget = new KoFillConfigWidget(canvas, KoFlake::StrokeFill, true, this);
232 d->fillConfigWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
233 d->ui->fillConfigWidgetLayout->addWidget(d->fillConfigWidget);
234 connect(d->fillConfigWidget, SIGNAL(sigFillChanged()), SIGNAL(sigStrokeChanged()));
235 }
236
237 d->ui->thicknessLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
238 d->ui->thicknessLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
239
240 // set min/max/step and value in points, then set actual unit
241 d->ui->lineWidth->setMinMaxStep(0.5, 1000.0, 0.5); // if someone wants 0, just set to "none" on UI
242 d->ui->lineWidth->setDecimals(2);
243 d->ui->lineWidth->setUnit(KoUnit(KoUnit::Point));
244 d->ui->lineWidth->setToolTip(i18n("Set line width of actual selection"));
245
246 d->ui->capNJoinButton->setMinimumHeight(25);
247 d->capNJoinMenu = new CapNJoinMenu(this);
248 d->ui->capNJoinButton->setMenu(d->capNJoinMenu);
249 d->ui->capNJoinButton->setText("...");
250 d->ui->capNJoinButton->setPopupMode(QToolButton::InstantPopup);
251
252
253 {
254 // Line style
255 d->ui->strokeStyleLabel->setText(i18n("Line Style:"));
256 d->ui->strokeStyleLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
257
258 d->ui->lineStyle->setToolTip(i18nc("@info:tooltip", "Line style"));
259 d->ui->lineStyle->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
260 d->ui->lineStyle->setLineStyle(Qt::SolidLine, QVector<qreal>());
261 }
262
263
264 {
265 QList<KoMarker*> emptyMarkers;
266
267
268 d->startMarkerSelector = new KoMarkerSelector(KoFlake::StartMarker, this);
269 d->startMarkerSelector->setToolTip(i18nc("@info:tooltip", "Start marker"));
270 d->startMarkerSelector->updateMarkers(emptyMarkers);
271 d->startMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred );
272 d->ui->markerLayout->addWidget(d->startMarkerSelector);
273
274
275 d->midMarkerSelector = new KoMarkerSelector(KoFlake::MidMarker, this);
276 d->midMarkerSelector->setToolTip(i18nc("@info:tooltip", "Node marker"));
277 d->midMarkerSelector->updateMarkers(emptyMarkers);
278 d->midMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred );
279 d->ui->markerLayout->addWidget(d->midMarkerSelector);
280
281
282 d->endMarkerSelector = new KoMarkerSelector(KoFlake::EndMarker, this);
283 d->endMarkerSelector->setToolTip(i18nc("@info:tooltip", "End marker"));
284 d->endMarkerSelector->updateMarkers(emptyMarkers);
285 d->endMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred );
286
287 d->ui->markerLayout->addWidget(d->endMarkerSelector);
288 }
289
290 // Spacer
291 d->spacer = new QWidget();
292 d->spacer->setObjectName("SpecialSpacer");
293
294 d->ui->markerLayout->addWidget(d->spacer);
295
296 connect(d->ui->lineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(applyDashStyleChanges()));
297 connect(d->ui->lineWidth, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyLineWidthChanges()));
298
299 connect(d->capNJoinMenu->capGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyJoinCapChanges()));
300 connect(d->capNJoinMenu->joinGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyJoinCapChanges()));
301 connect(d->capNJoinMenu->miterLimit, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyJoinCapChanges()));
302
303 { // Map the marker signals correctly
304 KisSignalMapper *mapper = new KisSignalMapper(this);
305 connect(mapper, SIGNAL(mapped(int)), SLOT(applyMarkerChanges(int)));
306
307 connect(d->startMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map()));
308 connect(d->midMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map()));
309 connect(d->endMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map()));
310
311 mapper->setMapping(d->startMarkerSelector, KoFlake::StartMarker);
312 mapper->setMapping(d->midMarkerSelector, KoFlake::MidMarker);
313 mapper->setMapping(d->endMarkerSelector, KoFlake::EndMarker);
314 }
315
316 KoDocumentResourceManager *resourceManager = canvas->shapeController()->resourceManager();
317 if (resourceManager) {
318 KoMarkerCollection *collection = resourceManager->resource(KoDocumentResourceManager::MarkerCollection).value<KoMarkerCollection*>();
319 if (collection) {
320 updateMarkers(collection->markers());
321 }
322 }
323
324 d->selectionChangedCompressor.start();
325
326 // initialize deactivation locks
327 d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector));
328 d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector));
329 }
330
~KoStrokeConfigWidget()331 KoStrokeConfigWidget::~KoStrokeConfigWidget()
332 {
333 delete d;
334 }
335
setNoSelectionTrackingMode(bool value)336 void KoStrokeConfigWidget::setNoSelectionTrackingMode(bool value)
337 {
338 d->fillConfigWidget->setNoSelectionTrackingMode(value);
339 d->noSelectionTrackingMode = value;
340 if (!d->noSelectionTrackingMode) {
341 d->selectionChangedCompressor.start();
342 }
343 }
344
345 // ----------------------------------------------------------------
346 // getters and setters
347
348
lineStyle() const349 Qt::PenStyle KoStrokeConfigWidget::lineStyle() const
350 {
351 return d->ui->lineStyle->lineStyle();
352 }
353
lineDashes() const354 QVector<qreal> KoStrokeConfigWidget::lineDashes() const
355 {
356 return d->ui->lineStyle->lineDashes();
357 }
358
lineWidth() const359 qreal KoStrokeConfigWidget::lineWidth() const
360 {
361 return d->ui->lineWidth->value();
362 }
363
miterLimit() const364 qreal KoStrokeConfigWidget::miterLimit() const
365 {
366 return d->capNJoinMenu->miterLimit->value();
367 }
368
startMarker() const369 KoMarker *KoStrokeConfigWidget::startMarker() const
370 {
371 return d->startMarkerSelector->marker();
372 }
373
endMarker() const374 KoMarker *KoStrokeConfigWidget::endMarker() const
375 {
376 return d->endMarkerSelector->marker();
377 }
378
capStyle() const379 Qt::PenCapStyle KoStrokeConfigWidget::capStyle() const
380 {
381 return static_cast<Qt::PenCapStyle>(d->capNJoinMenu->capGroup->checkedId());
382 }
383
joinStyle() const384 Qt::PenJoinStyle KoStrokeConfigWidget::joinStyle() const
385 {
386 return static_cast<Qt::PenJoinStyle>(d->capNJoinMenu->joinGroup->checkedId());
387 }
388
createShapeStroke()389 KoShapeStrokeSP KoStrokeConfigWidget::createShapeStroke()
390 {
391 KoShapeStrokeSP stroke(d->fillConfigWidget->createShapeStroke());
392
393 stroke->setLineWidth(lineWidth());
394 stroke->setCapStyle(capStyle());
395 stroke->setJoinStyle(joinStyle());
396 stroke->setMiterLimit(miterLimit());
397 stroke->setLineStyle(lineStyle(), lineDashes());
398
399 return stroke;
400 }
401
402 // ----------------------------------------------------------------
403 // Other public functions
404
updateStyleControlsAvailability(bool enabled)405 void KoStrokeConfigWidget::updateStyleControlsAvailability(bool enabled)
406 {
407 d->ui->lineWidth->setEnabled(enabled);
408 d->capNJoinMenu->setEnabled(enabled);
409 d->ui->lineStyle->setEnabled(enabled);
410
411 d->startMarkerSelector->setEnabled(enabled);
412 d->midMarkerSelector->setEnabled(enabled);
413 d->endMarkerSelector->setEnabled(enabled);
414 }
415
setUnit(const KoUnit & unit,KoShape * representativeShape)416 void KoStrokeConfigWidget::setUnit(const KoUnit &unit, KoShape *representativeShape)
417 {
418 if (!d->allowLocalUnitManagement) {
419 return; //the unit management is completely transferred to the unitManagers.
420 }
421
422 blockChildSignals(true);
423
424 /**
425 * KoStrokeShape knows nothing about the transformations applied
426 * to the shape, which doesn't prevent the shape to apply them and
427 * display the stroke differently. So just take that into account
428 * and show the user correct values using the multiplier in KoUnit.
429 */
430 KoUnit newUnit(unit);
431 if (representativeShape) {
432 newUnit.adjustByPixelTransform(representativeShape->absoluteTransformation());
433 }
434
435 d->ui->lineWidth->setUnit(newUnit);
436 d->capNJoinMenu->miterLimit->setUnit(newUnit);
437
438 d->ui->lineWidth->setLineStep(1.0);
439 d->capNJoinMenu->miterLimit->setLineStep(1.0);
440
441 blockChildSignals(false);
442 }
443
setUnitManagers(KisSpinBoxUnitManager * managerLineWidth,KisSpinBoxUnitManager * managerMitterLimit)444 void KoStrokeConfigWidget::setUnitManagers(KisSpinBoxUnitManager* managerLineWidth,
445 KisSpinBoxUnitManager *managerMitterLimit)
446 {
447 blockChildSignals(true);
448 d->allowLocalUnitManagement = false;
449 d->ui->lineWidth->setUnitManager(managerLineWidth);
450 d->capNJoinMenu->miterLimit->setUnitManager(managerMitterLimit);
451 blockChildSignals(false);
452 }
453
updateMarkers(const QList<KoMarker * > & markers)454 void KoStrokeConfigWidget::updateMarkers(const QList<KoMarker*> &markers)
455 {
456 d->startMarkerSelector->updateMarkers(markers);
457 d->midMarkerSelector->updateMarkers(markers);
458 d->endMarkerSelector->updateMarkers(markers);
459 }
460
activate()461 void KoStrokeConfigWidget::activate()
462 {
463 KIS_SAFE_ASSERT_RECOVER_NOOP(!d->deactivationLocks.empty());
464 d->deactivationLocks.clear();
465 d->fillConfigWidget->activate();
466
467 if (!d->noSelectionTrackingMode) {
468 d->selectionChangedCompressor.start();
469 } else {
470 loadCurrentStrokeFillFromResourceServer();
471 }
472 }
473
deactivate()474 void KoStrokeConfigWidget::deactivate()
475 {
476 KIS_SAFE_ASSERT_RECOVER_NOOP(d->deactivationLocks.empty());
477
478 d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector));
479 d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector));
480 d->fillConfigWidget->deactivate();
481 }
482
blockChildSignals(bool block)483 void KoStrokeConfigWidget::blockChildSignals(bool block)
484 {
485 d->ui->lineWidth->blockSignals(block);
486 d->capNJoinMenu->capGroup->blockSignals(block);
487 d->capNJoinMenu->joinGroup->blockSignals(block);
488 d->capNJoinMenu->miterLimit->blockSignals(block);
489 d->ui->lineStyle->blockSignals(block);
490 d->startMarkerSelector->blockSignals(block);
491 d->midMarkerSelector->blockSignals(block);
492 d->endMarkerSelector->blockSignals(block);
493 }
494
setActive(bool active)495 void KoStrokeConfigWidget::setActive(bool active)
496 {
497 d->active = active;
498 }
499
500 //------------------------
501
502 template <typename ModifyFunction>
applyChangeToStrokes(KoCanvasBase * canvas,ModifyFunction modifyFunction)503 auto applyChangeToStrokes(KoCanvasBase *canvas, ModifyFunction modifyFunction)
504 -> decltype(modifyFunction(KoShapeStrokeSP()), void())
505 {
506 KoSelection *selection = canvas->selectedShapesProxy()->selection();
507
508 if (!selection) return;
509
510 QList<KoShape*> shapes = selection->selectedEditableShapes();
511
512 KUndo2Command *command = KoFlake::modifyShapesStrokes(shapes, modifyFunction);
513
514 if (command) {
515 canvas->addCommand(command);
516 }
517 }
518
applyDashStyleChanges()519 void KoStrokeConfigWidget::applyDashStyleChanges()
520 {
521 applyChangeToStrokes(
522 d->canvas,
523 [this] (KoShapeStrokeSP stroke) {
524 stroke->setLineStyle(lineStyle(), lineDashes());
525 });
526
527 emit sigStrokeChanged();
528 }
529
applyLineWidthChanges()530 void KoStrokeConfigWidget::applyLineWidthChanges()
531 {
532 applyChangeToStrokes(
533 d->canvas,
534 [this] (KoShapeStrokeSP stroke) {
535 stroke->setLineWidth(lineWidth());
536 });
537
538 emit sigStrokeChanged();
539 }
540
applyJoinCapChanges()541 void KoStrokeConfigWidget::applyJoinCapChanges()
542 {
543 applyChangeToStrokes(
544 d->canvas,
545 [this] (KoShapeStrokeSP stroke) {
546
547 stroke->setCapStyle(static_cast<Qt::PenCapStyle>(d->capNJoinMenu->capGroup->checkedId()));
548 stroke->setJoinStyle(static_cast<Qt::PenJoinStyle>(d->capNJoinMenu->joinGroup->checkedId()));
549 stroke->setMiterLimit(miterLimit());
550 });
551
552 emit sigStrokeChanged();
553 }
554
applyMarkerChanges(int rawPosition)555 void KoStrokeConfigWidget::applyMarkerChanges(int rawPosition)
556 {
557 KoSelection *selection = d->canvas->selectedShapesProxy()->selection();
558 if (!selection) {
559 emit sigStrokeChanged();
560 return;
561 }
562
563 QList<KoShape*> shapes = selection->selectedEditableShapes();
564 QList<KoPathShape*> pathShapes;
565 Q_FOREACH (KoShape *shape, shapes) {
566 KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
567 if (pathShape) {
568 pathShapes << pathShape;
569 }
570 }
571
572 if (pathShapes.isEmpty()) {
573 emit sigStrokeChanged();
574 return;
575 }
576
577
578 KoFlake::MarkerPosition position = KoFlake::MarkerPosition(rawPosition);
579 QScopedPointer<KoMarker> marker;
580
581 switch (position) {
582 case KoFlake::StartMarker:
583 if (d->startMarkerSelector->marker()) {
584 marker.reset(new KoMarker(*d->startMarkerSelector->marker()));
585 }
586 break;
587 case KoFlake::MidMarker:
588 if (d->midMarkerSelector->marker()) {
589 marker.reset(new KoMarker(*d->midMarkerSelector->marker()));
590 }
591 break;
592 case KoFlake::EndMarker:
593 if (d->endMarkerSelector->marker()) {
594 marker.reset(new KoMarker(*d->endMarkerSelector->marker()));
595 }
596 break;
597 }
598
599 KUndo2Command* command = new KoPathShapeMarkerCommand(pathShapes, marker.take(), position);
600 d->canvas->addCommand(command);
601
602 emit sigStrokeChanged();
603 }
604
605 // ----------------------------------------------------------------
606
607 struct CheckShapeStrokeStyleBasePolicy
608 {
609 typedef KoShapeStrokeSP PointerType;
getPropertyCheckShapeStrokeStyleBasePolicy610 static PointerType getProperty(KoShape *shape) {
611 return qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
612 }
613 };
614
615 struct CheckShapeStrokeDashesPolicy : public CheckShapeStrokeStyleBasePolicy
616 {
compareToCheckShapeStrokeDashesPolicy617 static bool compareTo(PointerType p1, PointerType p2) {
618 return p1->lineStyle() == p2->lineStyle() &&
619 p1->lineDashes() == p2->lineDashes() &&
620 p1->dashOffset() == p2->dashOffset();
621 }
622 };
623
624 struct CheckShapeStrokeCapJoinPolicy : public CheckShapeStrokeStyleBasePolicy
625 {
compareToCheckShapeStrokeCapJoinPolicy626 static bool compareTo(PointerType p1, PointerType p2) {
627 return p1->capStyle() == p2->capStyle() &&
628 p1->joinStyle() == p2->joinStyle() &&
629 p1->miterLimit() == p2->miterLimit();
630 }
631 };
632
633 struct CheckShapeStrokeWidthPolicy : public CheckShapeStrokeStyleBasePolicy
634 {
compareToCheckShapeStrokeWidthPolicy635 static bool compareTo(PointerType p1, PointerType p2) {
636 return p1->lineWidth() == p2->lineWidth();
637 }
638 };
639
640 struct CheckShapeMarkerPolicy
641 {
CheckShapeMarkerPolicyCheckShapeMarkerPolicy642 CheckShapeMarkerPolicy(KoFlake::MarkerPosition position)
643 : m_position(position)
644 {
645 }
646
647 typedef KoMarker* PointerType;
getPropertyCheckShapeMarkerPolicy648 PointerType getProperty(KoShape *shape) const {
649 KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
650 return pathShape ? pathShape->marker(m_position) : 0;
651 }
compareToCheckShapeMarkerPolicy652 bool compareTo(PointerType p1, PointerType p2) const {
653 if ((!p1 || !p2) && p1 != p2) return false;
654 if (!p1 && p1 == p2) return true;
655
656 return p1 == p2 || *p1 == *p2;
657 }
658
659 KoFlake::MarkerPosition m_position;
660 };
661
selectionChanged()662 void KoStrokeConfigWidget::selectionChanged()
663 {
664 if (d->noSelectionTrackingMode) return;
665
666 KoSelection *selection = d->canvas->selectedShapesProxy()->selection();
667 if (!selection) return;
668
669 // we need to linearize update order, and force the child widget to update
670 // before we start doing it
671
672 QList<KoShape*> shapes = selection->selectedEditableShapes();
673
674 d->fillConfigWidget->forceUpdateOnSelectionChanged(); // calls shapeChanged() logic
675
676 KoShape *shape = !shapes.isEmpty() ? shapes.first() : 0;
677
678 const KoShapeStrokeSP stroke = shape ? qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke()) : KoShapeStrokeSP();
679
680 // setUnit uses blockChildSignals() so take care not to use it inside the block
681 setUnit(d->canvas->unit(), shape);
682
683 blockChildSignals(true);
684
685 // line width
686 if (stroke && KoFlake::compareShapePropertiesEqual<CheckShapeStrokeWidthPolicy>(shapes)) {
687 d->ui->lineWidth->changeValue(stroke->lineWidth());
688 } else {
689 d->ui->lineWidth->changeValue(0);
690 }
691
692
693 // caps & joins
694 if (stroke && KoFlake::compareShapePropertiesEqual<CheckShapeStrokeCapJoinPolicy>(shapes)) {
695 Qt::PenCapStyle capStyle = stroke->capStyle() >= 0 ? stroke->capStyle() : Qt::FlatCap;
696 Qt::PenJoinStyle joinStyle = stroke->joinStyle() >= 0 ? stroke->joinStyle() : Qt::MiterJoin;
697
698 {
699 QAbstractButton *button = d->capNJoinMenu->capGroup->button(capStyle);
700 KIS_SAFE_ASSERT_RECOVER_RETURN(button);
701 button->setChecked(true);
702 }
703
704 {
705 QAbstractButton *button = d->capNJoinMenu->joinGroup->button(joinStyle);
706 KIS_SAFE_ASSERT_RECOVER_RETURN(button);
707 button->setChecked(true);
708 }
709
710 d->capNJoinMenu->miterLimit->changeValue(stroke->miterLimit());
711 d->capNJoinMenu->miterLimit->setEnabled(joinStyle == Qt::MiterJoin);
712 } else {
713 d->capNJoinMenu->capGroup->button(Qt::FlatCap)->setChecked(true);
714 d->capNJoinMenu->joinGroup->button(Qt::MiterJoin)->setChecked(true);
715 d->capNJoinMenu->miterLimit->changeValue(0.0);
716 d->capNJoinMenu->miterLimit->setEnabled(true);
717 }
718
719
720 // dashes style
721 if (stroke && KoFlake::compareShapePropertiesEqual<CheckShapeStrokeDashesPolicy>(shapes)) {
722 d->ui->lineStyle->setLineStyle(stroke->lineStyle(), stroke->lineDashes());
723 } else {
724 d->ui->lineStyle->setLineStyle(Qt::SolidLine, QVector<qreal>());
725 }
726
727 // markers
728 KoPathShape *pathShape = dynamic_cast<KoPathShape *>(shape);
729 if (pathShape) {
730 if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::StartMarker))) {
731 d->startMarkerSelector->setMarker(pathShape->marker(KoFlake::StartMarker));
732 }
733 if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::MidMarker))) {
734 d->midMarkerSelector->setMarker(pathShape->marker(KoFlake::MidMarker));
735 }
736 if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::EndMarker))) {
737 d->endMarkerSelector->setMarker(pathShape->marker(KoFlake::EndMarker));
738 }
739 }
740
741 const bool lineOptionsVisible = (d->fillConfigWidget->selectedFillIndex() != 0);
742
743 // This switch statement is to help the tab widget "pages" to be closer to the correct size
744 // if we don't do this the internal widgets get rendered, then the tab page has to get resized to
745 // fill up the space, then the internal widgets have to resize yet again...causing flicker
746 switch(d->fillConfigWidget->selectedFillIndex()) {
747 case 0: // no fill
748 this->setMinimumHeight(130);
749 break;
750 case 1: // solid fill
751 this->setMinimumHeight(200);
752 break;
753 case 2: // gradient fill
754 this->setMinimumHeight(350);
755 case 3: // pattern fill
756 break;
757 }
758
759
760 d->ui->thicknessLineBreak->setVisible(lineOptionsVisible);
761 d->ui->lineWidth->setVisible(lineOptionsVisible);
762 d->ui->capNJoinButton->setVisible(lineOptionsVisible);
763 d->ui->lineStyle->setVisible(lineOptionsVisible);
764 d->startMarkerSelector->setVisible(lineOptionsVisible);
765 d->midMarkerSelector->setVisible(lineOptionsVisible);
766 d->endMarkerSelector->setVisible(lineOptionsVisible);
767 d->ui->thicknessLabel->setVisible(lineOptionsVisible);
768 d->ui->strokeStyleLabel->setVisible(lineOptionsVisible);
769
770
771
772 blockChildSignals(false);
773
774 updateStyleControlsAvailability(!shapes.isEmpty());
775
776 }
777
canvasResourceChanged(int key,const QVariant & value)778 void KoStrokeConfigWidget::canvasResourceChanged(int key, const QVariant &value)
779 {
780 switch (key) {
781 case KoCanvasResourceProvider::Unit:
782 // we request the whole selection to reload because the
783 // unit of the stroke width depends on the selected shape
784 d->selectionChangedCompressor.start();
785 break;
786 case KisCanvasResourceProvider::Size:
787 if (d->noSelectionTrackingMode) {
788 d->ui->lineWidth->changeValue(d->canvas->unit().fromUserValue(value.toReal()));
789 }
790 break;
791 }
792 }
793
loadCurrentStrokeFillFromResourceServer()794 void KoStrokeConfigWidget::loadCurrentStrokeFillFromResourceServer()
795 {
796 if (d->canvas) {
797 const QVariant value = d->canvas->resourceManager()->resource(KisCanvasResourceProvider::Size);
798 canvasResourceChanged(KisCanvasResourceProvider::Size, value);
799
800 updateStyleControlsAvailability(true);
801
802 emit sigStrokeChanged();
803 }
804 }
805