File indexing completed on 2024-12-22 04:13:18

0001 /* This file is part of the KDE project
0002  * Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr)
0003  * SPDX-FileCopyrightText: 2002 Tomislav Lukman <tomislav.lukman@ck.t-com.hr>
0004  * SPDX-FileCopyrightText: 2002-2003 Rob Buis <buis@kde.org>
0005  * SPDX-FileCopyrightText: 2005-2006 Tim Beaulen <tbscope@gmail.com>
0006  * SPDX-FileCopyrightText: 2005-2007 Thomas Zander <zander@kde.org>
0007  * SPDX-FileCopyrightText: 2005-2006, 2011 Inge Wallin <inge@lysator.liu.se>
0008  * SPDX-FileCopyrightText: 2005-2008 Jan Hambrecht <jaham@gmx.net>
0009  * SPDX-FileCopyrightText: 2006 C. Boemann <cbo@boemann.dk>
0010  * SPDX-FileCopyrightText: 2006 Peter Simonsson <psn@linux.se>
0011  * SPDX-FileCopyrightText: 2006 Laurent Montel <montel@kde.org>
0012  * SPDX-FileCopyrightText: 2007, 2011 Thorsten Zachmann <t.zachmann@zagge.de>
0013  * SPDX-FileCopyrightText: 2011 Jean-Nicolas Artaud <jeannicolasartaud@gmail.com>
0014  *
0015  * SPDX-License-Identifier: LGPL-2.0-or-later
0016  */
0017 
0018 // Own
0019 #include "KoStrokeConfigWidget.h"
0020 
0021 // Qt
0022 #include <QMenu>
0023 #include <QToolButton>
0024 #include <QButtonGroup>
0025 #include <QVBoxLayout>
0026 #include <QHBoxLayout>
0027 #include <QSizePolicy>
0028 #include <KisSignalMapper.h>
0029 
0030 // KDE
0031 #include <klocalizedstring.h>
0032 
0033 // Calligra
0034 #include <KoIcon.h>
0035 #include <KoUnit.h>
0036 #include <KoLineStyleSelector.h>
0037 #include <KoUnitDoubleSpinBox.h>
0038 #include <KoMarkerSelector.h>
0039 #include <KoColorPopupAction.h>
0040 #include <KoMarker.h>
0041 #include <KoShapeStroke.h>
0042 #include <KoPathShape.h>
0043 #include <KoMarkerCollection.h>
0044 #include <KoPathShapeMarkerCommand.h>
0045 #include <KoCanvasBase.h>
0046 #include <KoCanvasController.h>
0047 #include <KoCanvasResourceProvider.h>
0048 #include <KoDocumentResourceManager.h>
0049 #include <KoSelection.h>
0050 #include <KoShapeController.h>
0051 #include <KoShapeStrokeCommand.h>
0052 #include <KoShapeStrokeModel.h>
0053 #include <KoSelectedShapesProxy.h>
0054 #include "ui_KoStrokeConfigWidget.h"
0055 #include <KoFlakeUtils.h>
0056 #include <KoFillConfigWidget.h>
0057 #include "kis_canvas_resource_provider.h"
0058 #include "kis_acyclic_signal_connector.h"
0059 #include <kis_signal_compressor.h>
0060 
0061 // Krita
0062 #include "kis_double_parse_unit_spin_box.h"
0063 
0064 class CapNJoinMenu : public QMenu
0065 {
0066 public:
0067     CapNJoinMenu(QWidget *parent = 0);
0068     QSize sizeHint() const override;
0069 
0070     KisDoubleParseUnitSpinBox *miterLimit {0};
0071     QButtonGroup *capGroup {0};
0072     QButtonGroup *joinGroup {0};
0073 };
0074 
0075 CapNJoinMenu::CapNJoinMenu(QWidget *parent)
0076     : QMenu(parent)
0077 {
0078     QGridLayout *mainLayout = new QGridLayout(this);
0079     mainLayout->setMargin(2);
0080 
0081     // The cap group
0082     capGroup = new QButtonGroup(this);
0083     capGroup->setExclusive(true);
0084 
0085     QToolButton *button = 0;
0086 
0087     button = new QToolButton(this);
0088     button->setIcon(koIcon("stroke-cap-butt"));
0089     button->setCheckable(true);
0090     button->setToolTip(i18n("Butt cap"));
0091     capGroup->addButton(button, Qt::FlatCap);
0092     mainLayout->addWidget(button, 2, 0);
0093 
0094     button = new QToolButton(this);
0095     button->setIcon(koIcon("stroke-cap-round"));
0096     button->setCheckable(true);
0097     button->setToolTip(i18n("Round cap"));
0098     capGroup->addButton(button, Qt::RoundCap);
0099     mainLayout->addWidget(button, 2, 1);
0100 
0101     button = new QToolButton(this);
0102     button->setIcon(koIcon("stroke-cap-square"));
0103     button->setCheckable(true);
0104     button->setToolTip(i18n("Square cap"));
0105     capGroup->addButton(button, Qt::SquareCap);
0106     mainLayout->addWidget(button, 2, 2, Qt::AlignLeft);
0107 
0108     // The join group
0109     joinGroup = new QButtonGroup(this);
0110     joinGroup->setExclusive(true);
0111 
0112     button = new QToolButton(this);
0113     button->setIcon(koIcon("stroke-join-miter"));
0114     button->setCheckable(true);
0115     button->setToolTip(i18n("Miter join"));
0116     joinGroup->addButton(button, Qt::MiterJoin);
0117     mainLayout->addWidget(button, 3, 0);
0118 
0119     button = new QToolButton(this);
0120     button->setIcon(koIcon("stroke-join-round"));
0121     button->setCheckable(true);
0122     button->setToolTip(i18n("Round join"));
0123     joinGroup->addButton(button, Qt::RoundJoin);
0124     mainLayout->addWidget(button, 3, 1);
0125 
0126     button = new QToolButton(this);
0127     button->setIcon(koIcon("stroke-join-bevel"));
0128     button->setCheckable(true);
0129     button->setToolTip(i18n("Bevel join"));
0130     joinGroup->addButton(button, Qt::BevelJoin);
0131     mainLayout->addWidget(button, 3, 2, Qt::AlignLeft);
0132 
0133     // Miter limit
0134     // set min/max/step and value in points, then set actual unit
0135     miterLimit = new KisDoubleParseUnitSpinBox(this);
0136     miterLimit->setMinMaxStep(0.0, 1000.0, 0.5);
0137     miterLimit->setDecimals(2);
0138     miterLimit->setUnit(KoUnit(KoUnit::Point));
0139     miterLimit->setToolTip(i18n("Miter limit"));
0140     mainLayout->addWidget(miterLimit, 4, 0, 1, 3);
0141 
0142     mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
0143 }
0144 
0145 QSize CapNJoinMenu::sizeHint() const
0146 {
0147     return layout()->sizeHint();
0148 }
0149 
0150 
0151 class Q_DECL_HIDDEN KoStrokeConfigWidget::Private
0152 {
0153 public:
0154     Private()
0155         : selectionChangedCompressor(200, KisSignalCompressor::FIRST_ACTIVE)
0156     {
0157     }
0158 
0159     KoLineStyleSelector *lineStyle {0};
0160     KisDoubleParseUnitSpinBox *lineWidth {0};
0161     KoMarkerSelector *startMarkerSelector {0};
0162     KoMarkerSelector *midMarkerSelector {0};
0163     KoMarkerSelector *endMarkerSelector {0};
0164 
0165     CapNJoinMenu *capNJoinMenu {0};
0166 
0167     QWidget*spacer {0};
0168 
0169     KoCanvasBase *canvas {0};
0170 
0171     bool active {true};
0172     bool allowLocalUnitManagement {false};
0173 
0174     KoFillConfigWidget *fillConfigWidget {0};
0175     bool noSelectionTrackingMode {false};
0176 
0177     KisAcyclicSignalConnector shapeChangedAcyclicConnector;
0178     KisAcyclicSignalConnector resourceManagerAcyclicConnector;
0179     KisSignalCompressor selectionChangedCompressor;
0180 
0181     std::vector<KisAcyclicSignalConnector::Blocker> deactivationLocks;
0182 
0183     QScopedPointer<Ui_KoStrokeConfigWidget> ui;
0184 };
0185 
0186 
0187 KoStrokeConfigWidget::KoStrokeConfigWidget(KoCanvasBase *canvas, QWidget * parent)
0188     : QWidget(parent)
0189     , d(new Private())
0190 {
0191     // configure GUI
0192     d->ui.reset(new Ui_KoStrokeConfigWidget());
0193     d->ui->setupUi(this);
0194 
0195     setObjectName("Stroke widget");
0196 
0197     { // connect the canvas
0198         d->shapeChangedAcyclicConnector.connectBackwardVoid(
0199                     canvas->selectedShapesProxy(), SIGNAL(selectionChanged()),
0200                     &d->selectionChangedCompressor, SLOT(start()));
0201 
0202         d->shapeChangedAcyclicConnector.connectBackwardVoid(
0203                     canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()),
0204                     &d->selectionChangedCompressor, SLOT(start()));
0205 
0206         connect(&d->selectionChangedCompressor, SIGNAL(timeout()), this, SLOT(selectionChanged()));
0207 
0208         d->resourceManagerAcyclicConnector.connectBackwardResourcePair(
0209                     canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
0210                     this, SLOT(canvasResourceChanged(int,QVariant)));
0211 
0212         d->canvas = canvas;
0213     }
0214 
0215     {
0216 
0217         d->fillConfigWidget = new KoFillConfigWidget(canvas, KoFlake::StrokeFill, true, this);
0218         d->fillConfigWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
0219         d->ui->fillConfigWidgetLayout->addWidget(d->fillConfigWidget);
0220         connect(d->fillConfigWidget, SIGNAL(sigFillChanged()), SIGNAL(sigStrokeChanged()));
0221     }
0222 
0223     d->ui->thicknessLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
0224     d->ui->thicknessLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0225 
0226     // set min/max/step and value in points, then set actual unit
0227     d->ui->lineWidth->setMinMaxStep(0.5, 1000.0, 0.5); // if someone wants 0, just set to "none" on UI
0228     d->ui->lineWidth->setDecimals(2);
0229     d->ui->lineWidth->setUnit(KoUnit(KoUnit::Point));
0230     d->ui->lineWidth->setToolTip(i18n("Set line width of actual selection"));
0231 
0232     d->ui->capNJoinButton->setMinimumHeight(25);
0233     d->capNJoinMenu = new CapNJoinMenu(this);
0234     d->ui->capNJoinButton->setMenu(d->capNJoinMenu);
0235     d->ui->capNJoinButton->setText("...");
0236     d->ui->capNJoinButton->setPopupMode(QToolButton::InstantPopup);
0237 
0238 
0239     {
0240         // Line style
0241         d->ui->strokeStyleLabel->setText(i18n("Line Style:"));
0242         d->ui->strokeStyleLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
0243 
0244         d->ui->lineStyle->setToolTip(i18nc("@info:tooltip", "Line style"));
0245         d->ui->lineStyle->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
0246         d->ui->lineStyle->setLineStyle(Qt::SolidLine,  QVector<qreal>());
0247     }
0248 
0249 
0250     {
0251         QList<KoMarker*> emptyMarkers;
0252 
0253 
0254         d->startMarkerSelector = new KoMarkerSelector(KoFlake::StartMarker, this);
0255         d->startMarkerSelector->setToolTip(i18nc("@info:tooltip", "Start marker"));
0256         d->startMarkerSelector->updateMarkers(emptyMarkers);
0257         d->startMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred );
0258         d->ui->markerLayout->addWidget(d->startMarkerSelector);
0259 
0260 
0261         d->midMarkerSelector = new KoMarkerSelector(KoFlake::MidMarker, this);
0262         d->midMarkerSelector->setToolTip(i18nc("@info:tooltip", "Node marker"));
0263         d->midMarkerSelector->updateMarkers(emptyMarkers);
0264         d->midMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred );
0265         d->ui->markerLayout->addWidget(d->midMarkerSelector);
0266 
0267 
0268         d->endMarkerSelector = new KoMarkerSelector(KoFlake::EndMarker, this);
0269         d->endMarkerSelector->setToolTip(i18nc("@info:tooltip", "End marker"));
0270         d->endMarkerSelector->updateMarkers(emptyMarkers);
0271         d->endMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred );
0272 
0273         d->ui->markerLayout->addWidget(d->endMarkerSelector);
0274     }
0275 
0276     // Spacer
0277     d->spacer = new QWidget();
0278     d->spacer->setObjectName("SpecialSpacer");
0279 
0280     d->ui->markerLayout->addWidget(d->spacer);
0281 
0282     connect(d->ui->lineStyle,  SIGNAL(currentIndexChanged(int)), this, SLOT(applyDashStyleChanges()));
0283     connect(d->ui->lineWidth,  SIGNAL(valueChangedPt(qreal)),    this, SLOT(applyLineWidthChanges()));
0284 
0285     connect(d->capNJoinMenu->capGroup,   SIGNAL(buttonClicked(int)),       this, SLOT(applyJoinCapChanges()));
0286     connect(d->capNJoinMenu->joinGroup,  SIGNAL(buttonClicked(int)),       this, SLOT(applyJoinCapChanges()));
0287     connect(d->capNJoinMenu->miterLimit, SIGNAL(valueChangedPt(qreal)),    this, SLOT(applyJoinCapChanges()));
0288 
0289     { // Map the marker signals correctly
0290         KisSignalMapper *mapper = new KisSignalMapper(this);
0291         connect(mapper, SIGNAL(mapped(int)), SLOT(applyMarkerChanges(int)));
0292 
0293         connect(d->startMarkerSelector,  SIGNAL(currentIndexChanged(int)), mapper, SLOT(map()));
0294         connect(d->midMarkerSelector,  SIGNAL(currentIndexChanged(int)), mapper, SLOT(map()));
0295         connect(d->endMarkerSelector,  SIGNAL(currentIndexChanged(int)), mapper, SLOT(map()));
0296 
0297         mapper->setMapping(d->startMarkerSelector, KoFlake::StartMarker);
0298         mapper->setMapping(d->midMarkerSelector, KoFlake::MidMarker);
0299         mapper->setMapping(d->endMarkerSelector, KoFlake::EndMarker);
0300     }
0301 
0302     KoDocumentResourceManager *resourceManager = canvas->shapeController()->resourceManager();
0303     if (resourceManager) {
0304         KoMarkerCollection *collection = resourceManager->resource(KoDocumentResourceManager::MarkerCollection).value<KoMarkerCollection*>();
0305         if (collection) {
0306             updateMarkers(collection->markers());
0307         }
0308     }
0309 
0310     d->selectionChangedCompressor.start();
0311 
0312     // initialize deactivation locks
0313     d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector));
0314     d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector));
0315 }
0316 
0317 KoStrokeConfigWidget::~KoStrokeConfigWidget()
0318 {
0319     delete d;
0320 }
0321 
0322 void KoStrokeConfigWidget::setNoSelectionTrackingMode(bool value)
0323 {
0324     d->fillConfigWidget->setNoSelectionTrackingMode(value);
0325     d->noSelectionTrackingMode = value;
0326     if (!d->noSelectionTrackingMode) {
0327         d->selectionChangedCompressor.start();
0328     }
0329 }
0330 
0331 // ----------------------------------------------------------------
0332 //                         getters and setters
0333 
0334 
0335 Qt::PenStyle KoStrokeConfigWidget::lineStyle() const
0336 {
0337     return d->ui->lineStyle->lineStyle();
0338 }
0339 
0340 QVector<qreal> KoStrokeConfigWidget::lineDashes() const
0341 {
0342     return d->ui->lineStyle->lineDashes();
0343 }
0344 
0345 qreal KoStrokeConfigWidget::lineWidth() const
0346 {
0347     return d->ui->lineWidth->value();
0348 }
0349 
0350 qreal KoStrokeConfigWidget::miterLimit() const
0351 {
0352     return d->capNJoinMenu->miterLimit->value();
0353 }
0354 
0355 KoMarker *KoStrokeConfigWidget::startMarker() const
0356 {
0357     return d->startMarkerSelector->marker();
0358 }
0359 
0360 KoMarker *KoStrokeConfigWidget::endMarker() const
0361 {
0362     return d->endMarkerSelector->marker();
0363 }
0364 
0365 Qt::PenCapStyle KoStrokeConfigWidget::capStyle() const
0366 {
0367     return static_cast<Qt::PenCapStyle>(d->capNJoinMenu->capGroup->checkedId());
0368 }
0369 
0370 Qt::PenJoinStyle KoStrokeConfigWidget::joinStyle() const
0371 {
0372     return static_cast<Qt::PenJoinStyle>(d->capNJoinMenu->joinGroup->checkedId());
0373 }
0374 
0375 KoShapeStrokeSP KoStrokeConfigWidget::createShapeStroke()
0376 {
0377     KoShapeStrokeSP stroke(d->fillConfigWidget->createShapeStroke());
0378 
0379     stroke->setLineWidth(lineWidth());
0380     stroke->setCapStyle(capStyle());
0381     stroke->setJoinStyle(joinStyle());
0382     stroke->setMiterLimit(miterLimit());
0383     stroke->setLineStyle(lineStyle(), lineDashes());
0384 
0385     return stroke;
0386 }
0387 
0388 // ----------------------------------------------------------------
0389 //                         Other public functions
0390 
0391 void KoStrokeConfigWidget::updateStyleControlsAvailability(bool enabled)
0392 {
0393     d->ui->lineWidth->setEnabled(enabled);
0394     d->capNJoinMenu->setEnabled(enabled);
0395     d->ui->lineStyle->setEnabled(enabled);
0396 
0397     d->startMarkerSelector->setEnabled(enabled);
0398     d->midMarkerSelector->setEnabled(enabled);
0399     d->endMarkerSelector->setEnabled(enabled);
0400 }
0401 
0402 void KoStrokeConfigWidget::setUnit(const KoUnit &unit, KoShape *representativeShape)
0403 {
0404     if (!d->allowLocalUnitManagement) {
0405         return; //the unit management is completely transferred to the unitManagers.
0406     }
0407 
0408     blockChildSignals(true);
0409 
0410     /**
0411      * KoStrokeShape knows nothing about the transformations applied
0412      * to the shape, which doesn't prevent the shape to apply them and
0413      * display the stroke differently. So just take that into account
0414      * and show the user correct values using the multiplier in KoUnit.
0415      */
0416     KoUnit newUnit(unit);
0417     if (representativeShape) {
0418         newUnit.adjustByPixelTransform(representativeShape->absoluteTransformation());
0419     }
0420 
0421     d->ui->lineWidth->setUnit(newUnit);
0422     d->capNJoinMenu->miterLimit->setUnit(newUnit);
0423 
0424     d->ui->lineWidth->setLineStep(1.0);
0425     d->capNJoinMenu->miterLimit->setLineStep(1.0);
0426 
0427     blockChildSignals(false);
0428 }
0429 
0430 void KoStrokeConfigWidget::setUnitManagers(KisSpinBoxUnitManager* managerLineWidth,
0431                                            KisSpinBoxUnitManager *managerMitterLimit)
0432 {
0433     blockChildSignals(true);
0434     d->allowLocalUnitManagement = false;
0435     d->ui->lineWidth->setUnitManager(managerLineWidth);
0436     d->capNJoinMenu->miterLimit->setUnitManager(managerMitterLimit);
0437     blockChildSignals(false);
0438 }
0439 
0440 void KoStrokeConfigWidget::updateMarkers(const QList<KoMarker*> &markers)
0441 {
0442     d->startMarkerSelector->updateMarkers(markers);
0443     d->midMarkerSelector->updateMarkers(markers);
0444     d->endMarkerSelector->updateMarkers(markers);
0445 }
0446 
0447 void KoStrokeConfigWidget::activate()
0448 {
0449     KIS_SAFE_ASSERT_RECOVER_NOOP(!d->deactivationLocks.empty());
0450     d->deactivationLocks.clear();
0451     d->fillConfigWidget->activate();
0452 
0453     if (!d->noSelectionTrackingMode) {
0454         d->selectionChangedCompressor.start();
0455     } else {
0456         loadCurrentStrokeFillFromResourceServer();
0457     }
0458 }
0459 
0460 void KoStrokeConfigWidget::deactivate()
0461 {
0462     KIS_SAFE_ASSERT_RECOVER_NOOP(d->deactivationLocks.empty());
0463 
0464     d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector));
0465     d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector));
0466     d->fillConfigWidget->deactivate();
0467 }
0468 
0469 void KoStrokeConfigWidget::blockChildSignals(bool block)
0470 {
0471     d->ui->lineWidth->blockSignals(block);
0472     d->capNJoinMenu->capGroup->blockSignals(block);
0473     d->capNJoinMenu->joinGroup->blockSignals(block);
0474     d->capNJoinMenu->miterLimit->blockSignals(block);
0475     d->ui->lineStyle->blockSignals(block);
0476     d->startMarkerSelector->blockSignals(block);
0477     d->midMarkerSelector->blockSignals(block);
0478     d->endMarkerSelector->blockSignals(block);
0479 }
0480 
0481 void KoStrokeConfigWidget::setActive(bool active)
0482 {
0483     d->active = active;
0484 }
0485 
0486 //------------------------
0487 
0488 template <typename ModifyFunction>
0489 auto applyChangeToStrokes(KoCanvasBase *canvas, ModifyFunction modifyFunction)
0490 -> decltype(modifyFunction(KoShapeStrokeSP()), void())
0491 {
0492     KoSelection *selection = canvas->selectedShapesProxy()->selection();
0493 
0494     if (!selection) return;
0495 
0496     QList<KoShape*> shapes = selection->selectedEditableShapes();
0497 
0498     KUndo2Command *command = KoFlake::modifyShapesStrokes(shapes, modifyFunction);
0499 
0500     if (command) {
0501         canvas->addCommand(command);
0502     }
0503 }
0504 
0505 void KoStrokeConfigWidget::applyDashStyleChanges()
0506 {
0507     applyChangeToStrokes(
0508                 d->canvas,
0509                 [this] (KoShapeStrokeSP stroke) {
0510         stroke->setLineStyle(lineStyle(), lineDashes());
0511     });
0512 
0513     emit sigStrokeChanged();
0514 }
0515 
0516 void KoStrokeConfigWidget::applyLineWidthChanges()
0517 {
0518     applyChangeToStrokes(
0519                 d->canvas,
0520                 [this] (KoShapeStrokeSP stroke) {
0521         stroke->setLineWidth(lineWidth());
0522     });
0523 
0524     emit sigStrokeChanged();
0525 }
0526 
0527 void KoStrokeConfigWidget::applyJoinCapChanges()
0528 {
0529     applyChangeToStrokes(
0530                 d->canvas,
0531                 [this] (KoShapeStrokeSP stroke) {
0532 
0533         stroke->setCapStyle(static_cast<Qt::PenCapStyle>(d->capNJoinMenu->capGroup->checkedId()));
0534         stroke->setJoinStyle(static_cast<Qt::PenJoinStyle>(d->capNJoinMenu->joinGroup->checkedId()));
0535         stroke->setMiterLimit(miterLimit());
0536     });
0537 
0538     emit sigStrokeChanged();
0539 }
0540 
0541 void KoStrokeConfigWidget::applyMarkerChanges(int rawPosition)
0542 {
0543     KoSelection *selection = d->canvas->selectedShapesProxy()->selection();
0544     if (!selection) {
0545         emit sigStrokeChanged();
0546         return;
0547     }
0548 
0549     QList<KoShape*> shapes = selection->selectedEditableShapes();
0550     QList<KoPathShape*> pathShapes;
0551     Q_FOREACH (KoShape *shape, shapes) {
0552         KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
0553         if (pathShape) {
0554             pathShapes << pathShape;
0555         }
0556     }
0557 
0558     if (pathShapes.isEmpty()) {
0559         emit sigStrokeChanged();
0560         return;
0561     }
0562 
0563 
0564     KoFlake::MarkerPosition position = KoFlake::MarkerPosition(rawPosition);
0565     QScopedPointer<KoMarker> marker;
0566 
0567     switch (position) {
0568     case KoFlake::StartMarker:
0569         if (d->startMarkerSelector->marker()) {
0570             marker.reset(new KoMarker(*d->startMarkerSelector->marker()));
0571         }
0572         break;
0573     case KoFlake::MidMarker:
0574         if (d->midMarkerSelector->marker()) {
0575             marker.reset(new KoMarker(*d->midMarkerSelector->marker()));
0576         }
0577         break;
0578     case KoFlake::EndMarker:
0579         if (d->endMarkerSelector->marker()) {
0580             marker.reset(new KoMarker(*d->endMarkerSelector->marker()));
0581         }
0582         break;
0583     }
0584 
0585     KUndo2Command* command = new KoPathShapeMarkerCommand(pathShapes, marker.take(), position);
0586     d->canvas->addCommand(command);
0587 
0588     emit sigStrokeChanged();
0589 }
0590 
0591 // ----------------------------------------------------------------
0592 
0593 struct CheckShapeStrokeStyleBasePolicy
0594 {
0595     typedef KoShapeStrokeSP PointerType;
0596     static PointerType getProperty(KoShape *shape) {
0597         return qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
0598     }
0599 };
0600 
0601 struct CheckShapeStrokeDashesPolicy : public CheckShapeStrokeStyleBasePolicy
0602 {
0603     static bool compareTo(PointerType p1, PointerType p2) {
0604         return p1->lineStyle() == p2->lineStyle() &&
0605                 p1->lineDashes() == p2->lineDashes() &&
0606                 p1->dashOffset() == p2->dashOffset();
0607     }
0608 };
0609 
0610 struct CheckShapeStrokeCapJoinPolicy : public CheckShapeStrokeStyleBasePolicy
0611 {
0612     static bool compareTo(PointerType p1, PointerType p2) {
0613         return p1->capStyle() == p2->capStyle() &&
0614                 p1->joinStyle() == p2->joinStyle() &&
0615                 p1->miterLimit() == p2->miterLimit();
0616     }
0617 };
0618 
0619 struct CheckShapeStrokeWidthPolicy : public CheckShapeStrokeStyleBasePolicy
0620 {
0621     static bool compareTo(PointerType p1, PointerType p2) {
0622         return p1->lineWidth() == p2->lineWidth();
0623     }
0624 };
0625 
0626 struct CheckShapeMarkerPolicy
0627 {
0628     CheckShapeMarkerPolicy(KoFlake::MarkerPosition position)
0629         : m_position(position)
0630     {
0631     }
0632 
0633     typedef KoMarker* PointerType;
0634     PointerType getProperty(KoShape *shape) const {
0635         KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
0636         return pathShape ? pathShape->marker(m_position) : 0;
0637     }
0638     bool compareTo(PointerType p1, PointerType p2) const {
0639         if ((!p1 || !p2) && p1 != p2) return false;
0640         if (!p1 && p1 == p2) return true;
0641 
0642         return p1 == p2 || *p1 == *p2;
0643     }
0644 
0645     KoFlake::MarkerPosition m_position;
0646 };
0647 
0648 void KoStrokeConfigWidget::selectionChanged()
0649 {
0650     if (d->noSelectionTrackingMode) return;
0651 
0652     KoSelection *selection = d->canvas->selectedShapesProxy()->selection();
0653     if (!selection) return;
0654 
0655     // we need to linearize update order, and force the child widget to update
0656     // before we start doing it
0657 
0658     QList<KoShape*> shapes = selection->selectedEditableShapes();
0659 
0660     KoShape *shape = !shapes.isEmpty() ? shapes.first() : 0;
0661 
0662     const KoShapeStrokeSP stroke = shape ? qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke()) : KoShapeStrokeSP();
0663 
0664     // setUnit uses blockChildSignals() so take care not to use it inside the block
0665     setUnit(d->canvas->unit(), shape);
0666 
0667     blockChildSignals(true);
0668 
0669     // line width
0670     if (stroke && KoFlake::compareShapePropertiesEqual<CheckShapeStrokeWidthPolicy>(shapes)) {
0671         d->ui->lineWidth->changeValue(stroke->lineWidth());
0672     } else {
0673         d->ui->lineWidth->changeValue(0);
0674     }
0675 
0676 
0677     // caps & joins
0678     if (stroke && KoFlake::compareShapePropertiesEqual<CheckShapeStrokeCapJoinPolicy>(shapes)) {
0679         Qt::PenCapStyle capStyle = stroke->capStyle() >= 0 ? stroke->capStyle() : Qt::FlatCap;
0680         Qt::PenJoinStyle joinStyle = stroke->joinStyle() >= 0 ? stroke->joinStyle() : Qt::MiterJoin;
0681 
0682         {
0683             QAbstractButton *button = d->capNJoinMenu->capGroup->button(capStyle);
0684             KIS_SAFE_ASSERT_RECOVER_RETURN(button);
0685             button->setChecked(true);
0686         }
0687 
0688         {
0689             QAbstractButton *button = d->capNJoinMenu->joinGroup->button(joinStyle);
0690             KIS_SAFE_ASSERT_RECOVER_RETURN(button);
0691             button->setChecked(true);
0692         }
0693 
0694         d->capNJoinMenu->miterLimit->changeValue(stroke->miterLimit());
0695         d->capNJoinMenu->miterLimit->setEnabled(joinStyle == Qt::MiterJoin);
0696     } else {
0697         d->capNJoinMenu->capGroup->button(Qt::FlatCap)->setChecked(true);
0698         d->capNJoinMenu->joinGroup->button(Qt::MiterJoin)->setChecked(true);
0699         d->capNJoinMenu->miterLimit->changeValue(0.0);
0700         d->capNJoinMenu->miterLimit->setEnabled(true);
0701     }
0702 
0703 
0704     // dashes style
0705     if (stroke && KoFlake::compareShapePropertiesEqual<CheckShapeStrokeDashesPolicy>(shapes)) {
0706         d->ui->lineStyle->setLineStyle(stroke->lineStyle(), stroke->lineDashes());
0707     } else {
0708         d->ui->lineStyle->setLineStyle(Qt::SolidLine, QVector<qreal>());
0709     }
0710 
0711     // markers
0712     KoPathShape *pathShape = dynamic_cast<KoPathShape *>(shape);
0713     if (pathShape) {
0714         if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::StartMarker))) {
0715             d->startMarkerSelector->setMarker(pathShape->marker(KoFlake::StartMarker));
0716         }
0717         if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::MidMarker))) {
0718             d->midMarkerSelector->setMarker(pathShape->marker(KoFlake::MidMarker));
0719         }
0720         if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::EndMarker))) {
0721             d->endMarkerSelector->setMarker(pathShape->marker(KoFlake::EndMarker));
0722         }
0723     }
0724 
0725     const bool lineOptionsVisible = (d->fillConfigWidget->selectedFillIndex() != 0);
0726 
0727     // This switch statement is to help the tab widget "pages" to be closer to the correct size
0728     // if we don't do this the internal widgets get rendered, then the tab page has to get resized to
0729     // fill up the space, then the internal widgets have to resize yet again...causing flicker
0730     switch(d->fillConfigWidget->selectedFillIndex()) {
0731     case 0: // no fill
0732         this->setMinimumHeight(130);
0733         break;
0734     case 1: // solid fill
0735         this->setMinimumHeight(200);
0736         break;
0737     case 2: // gradient fill
0738         this->setMinimumHeight(350);
0739     case 3: // pattern fill
0740         break;
0741     }
0742 
0743 
0744     d->ui->thicknessLineBreak->setVisible(lineOptionsVisible);
0745     d->ui->lineWidth->setVisible(lineOptionsVisible);
0746     d->ui->capNJoinButton->setVisible(lineOptionsVisible);
0747     d->ui->lineStyle->setVisible(lineOptionsVisible);
0748     d->startMarkerSelector->setVisible(lineOptionsVisible);
0749     d->midMarkerSelector->setVisible(lineOptionsVisible);
0750     d->endMarkerSelector->setVisible(lineOptionsVisible);
0751     d->ui->thicknessLabel->setVisible(lineOptionsVisible);
0752     d->ui->strokeStyleLabel->setVisible(lineOptionsVisible);
0753 
0754 
0755 
0756     blockChildSignals(false);
0757 
0758     updateStyleControlsAvailability(!shapes.isEmpty());
0759 
0760 }
0761 
0762 void KoStrokeConfigWidget::canvasResourceChanged(int key, const QVariant &value)
0763 {
0764     switch (key) {
0765     case KoCanvasResource::Unit:
0766         // we request the whole selection to reload because the
0767         // unit of the stroke width depends on the selected shape
0768         d->selectionChangedCompressor.start();
0769         break;
0770     case KoCanvasResource::Size:
0771         if (d->noSelectionTrackingMode) {
0772             d->ui->lineWidth->changeValue(d->canvas->unit().fromUserValue(value.toReal()));
0773         }
0774         break;
0775     }
0776 }
0777 
0778 void KoStrokeConfigWidget::loadCurrentStrokeFillFromResourceServer()
0779 {
0780     if (d->canvas) {
0781         const QVariant value = d->canvas->resourceManager()->resource(KoCanvasResource::Size);
0782         canvasResourceChanged(KoCanvasResource::Size, value);
0783 
0784         updateStyleControlsAvailability(true);
0785 
0786         emit sigStrokeChanged();
0787     }
0788 }