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