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 <QLabel>
37 #include <QToolButton>
38 #include <QButtonGroup>
39 #include <QVBoxLayout>
40 #include <QHBoxLayout>
41 #include <QSizePolicy>
42 
43 // KF5
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 <KoCanvasResourceManager.h>
61 #include <KoDocumentResourceManager.h>
62 #include <KoToolManager.h>
63 #include <KoSelection.h>
64 #include <KoShapeController.h>
65 #include <KoShapeManager.h>
66 #include <KoShapeStrokeCommand.h>
67 #include <KoShapeStrokeModel.h>
68 
69 class CapNJoinMenu : public QMenu
70 {
71 Q_OBJECT
72 public:
73     CapNJoinMenu(QWidget *parent = nullptr);
74     QSize sizeHint() const override;
75 
76     KoUnitDoubleSpinBox *miterLimit;
77     QButtonGroup        *capGroup;
78     QButtonGroup        *joinGroup;
79 };
80 
CapNJoinMenu(QWidget * parent)81 CapNJoinMenu::CapNJoinMenu(QWidget *parent)
82     : QMenu(parent)
83 {
84     QGridLayout *mainLayout = new QGridLayout();
85     mainLayout->setMargin(2);
86 
87      // The cap group
88     capGroup = new QButtonGroup(this);
89     capGroup->setExclusive(true);
90 
91     QToolButton *button = nullptr;
92 
93     button = new QToolButton(this);
94     button->setIcon(koIcon("stroke-cap-butt"));
95     button->setCheckable(true);
96     button->setToolTip(i18n("Butt cap"));
97     capGroup->addButton(button, Qt::FlatCap);
98     mainLayout->addWidget(button, 2, 0);
99 
100     button = new QToolButton(this);
101     button->setIcon(koIcon("stroke-cap-round"));
102     button->setCheckable(true);
103     button->setToolTip(i18n("Round cap"));
104     capGroup->addButton(button, Qt::RoundCap);
105     mainLayout->addWidget(button, 2, 1);
106 
107     button = new QToolButton(this);
108     button->setIcon(koIcon("stroke-cap-square"));
109     button->setCheckable(true);
110     button->setToolTip(i18n("Square cap"));
111     capGroup->addButton(button, Qt::SquareCap);
112     mainLayout->addWidget(button, 2, 2, Qt::AlignLeft);
113 
114     // The join group
115     joinGroup = new QButtonGroup(this);
116     joinGroup->setExclusive(true);
117 
118     button = new QToolButton(this);
119     button->setIcon(koIcon("stroke-join-miter"));
120     button->setCheckable(true);
121     button->setToolTip(i18n("Miter join"));
122     joinGroup->addButton(button, Qt::MiterJoin);
123     mainLayout->addWidget(button, 3, 0);
124 
125     button = new QToolButton(this);
126     button->setIcon(koIcon("stroke-join-round"));
127     button->setCheckable(true);
128     button->setToolTip(i18n("Round join"));
129     joinGroup->addButton(button, Qt::RoundJoin);
130     mainLayout->addWidget(button, 3, 1);
131 
132     button = new QToolButton(this);
133     button->setIcon(koIcon("stroke-join-bevel"));
134     button->setCheckable(true);
135     button->setToolTip(i18n("Bevel join"));
136     joinGroup->addButton(button, Qt::BevelJoin);
137     mainLayout->addWidget(button, 3, 2, Qt::AlignLeft);
138 
139     // Miter limit
140     // set min/max/step and value in points, then set actual unit
141     miterLimit = new KoUnitDoubleSpinBox(this);
142     miterLimit->setMinMaxStep(0.0, 1000.0, 0.5);
143     miterLimit->setDecimals(2);
144     miterLimit->setUnit(KoUnit(KoUnit::Point));
145     miterLimit->setToolTip(i18n("Miter limit"));
146     mainLayout->addWidget(miterLimit, 4, 0, 1, 3);
147 
148     mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
149     setLayout(mainLayout);
150 }
151 
sizeHint() const152 QSize CapNJoinMenu::sizeHint() const
153 {
154     return layout()->sizeHint();
155 }
156 
157 
158 class Q_DECL_HIDDEN KoStrokeConfigWidget::Private
159 {
160 public:
Private()161     Private()
162         : canvas(nullptr),
163         active(true)
164     {
165     }
166 
167     KoLineStyleSelector *lineStyle;
168     KoUnitDoubleSpinBox *lineWidth;
169     KoMarkerSelector    *startMarkerSelector;
170     KoMarkerSelector    *endMarkerSelector;
171 
172     CapNJoinMenu *capNJoinMenu;
173     QToolButton *colorButton;
174     KoColorPopupAction *colorAction;
175 
176     QWidget *spacer;
177 
178     KoCanvasBase *canvas;
179 
180     bool active;
181 };
182 
KoStrokeConfigWidget(QWidget * parent)183 KoStrokeConfigWidget::KoStrokeConfigWidget(QWidget * parent)
184     : QWidget(parent)
185     , d(new Private())
186 {
187     setObjectName("Stroke widget");
188     QVBoxLayout *mainLayout = new QVBoxLayout(this);
189     mainLayout->setMargin(0);
190 
191     QHBoxLayout *firstLineLayout = new QHBoxLayout();
192 
193     // Start marker
194     QList<KoMarker*> markers;
195 
196     d->startMarkerSelector = new KoMarkerSelector(KoMarkerData::MarkerStart, this);
197     d->startMarkerSelector->updateMarkers(markers);
198     d->startMarkerSelector->setMaximumWidth(50);
199     firstLineLayout->addWidget(d->startMarkerSelector);
200 
201     // Line style
202     d->lineStyle = new KoLineStyleSelector(this);
203     d->lineStyle->setMinimumWidth(70);
204     firstLineLayout->addWidget(d->lineStyle);
205 
206     // End marker
207     d->endMarkerSelector = new KoMarkerSelector(KoMarkerData::MarkerEnd, this);
208     d->endMarkerSelector->updateMarkers(markers);
209     d->endMarkerSelector->setMaximumWidth(50);
210     firstLineLayout->addWidget(d->endMarkerSelector);
211 
212     QHBoxLayout *secondLineLayout = new QHBoxLayout();
213 
214     // Line width
215     QLabel *l = new QLabel(this);
216     l->setText(i18n("Thickness:"));
217     l->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
218     l->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
219     secondLineLayout->addWidget(l);
220 
221     // set min/max/step and value in points, then set actual unit
222     d->lineWidth = new KoUnitDoubleSpinBox(this);
223     d->lineWidth->setMinMaxStep(0.0, 1000.0, 0.5);
224     d->lineWidth->setDecimals(2);
225     d->lineWidth->setUnit(KoUnit(KoUnit::Point));
226     d->lineWidth->setToolTip(i18n("Set line width of actual selection"));
227     secondLineLayout->addWidget(d->lineWidth);
228 
229     QToolButton *capNJoinButton = new QToolButton(this);
230     capNJoinButton->setMinimumHeight(25);
231     d->capNJoinMenu = new CapNJoinMenu(this);
232     capNJoinButton->setMenu(d->capNJoinMenu);
233     capNJoinButton->setText("...");
234     capNJoinButton->setPopupMode(QToolButton::InstantPopup);
235 
236     secondLineLayout->addWidget(capNJoinButton);
237 
238     d->colorButton = new QToolButton(this);
239     secondLineLayout->addWidget(d->colorButton);
240     d->colorAction = new KoColorPopupAction(this);
241     d->colorAction->setIcon(koIcon("format-stroke-color"));
242     d->colorAction->setToolTip(i18n("Change the color of the line/border"));
243     d->colorButton->setDefaultAction(d->colorAction);
244 
245     mainLayout->addLayout(firstLineLayout);
246     mainLayout->addLayout(secondLineLayout);
247 
248     // Spacer
249     d->spacer = new QWidget();
250     d->spacer->setObjectName("SpecialSpacer");
251     mainLayout->addWidget(d->spacer);
252 
253 
254     // set sensitive defaults
255     d->lineStyle->setLineStyle(Qt::SolidLine);
256     d->lineWidth->changeValue(1);
257     d->colorAction->setCurrentColor(Qt::black);
258 
259     // Make the signals visible on the outside of this widget.
260     connect(d->lineStyle,  SIGNAL(currentIndexChanged(int)), this, SLOT(applyChanges()));
261     connect(d->lineWidth,  SIGNAL(valueChangedPt(qreal)),    this, SLOT(applyChanges()));
262     connect(d->colorAction, SIGNAL(colorChanged(KoColor)), this, SLOT(applyChanges()));
263     connect(d->capNJoinMenu->capGroup,   SIGNAL(buttonClicked(int)),       this, SLOT(applyChanges()));
264     connect(d->capNJoinMenu->joinGroup,  SIGNAL(buttonClicked(int)),       this, SLOT(applyChanges()));
265     connect(d->capNJoinMenu->miterLimit, SIGNAL(valueChangedPt(qreal)),    this, SLOT(applyChanges()));
266     connect(d->startMarkerSelector,  SIGNAL(currentIndexChanged(int)), this, SLOT(startMarkerChanged()));
267     connect(d->endMarkerSelector,  SIGNAL(currentIndexChanged(int)), this, SLOT(endMarkerChanged()));
268 }
269 
~KoStrokeConfigWidget()270 KoStrokeConfigWidget::~KoStrokeConfigWidget()
271 {
272     delete d;
273 }
274 
275 // ----------------------------------------------------------------
276 //                         getters and setters
277 
278 
lineStyle() const279 Qt::PenStyle KoStrokeConfigWidget::lineStyle() const
280 {
281     return d->lineStyle->lineStyle();
282 }
283 
lineDashes() const284 QVector<qreal> KoStrokeConfigWidget::lineDashes() const
285 {
286     return d->lineStyle->lineDashes();
287 }
288 
lineWidth() const289 qreal KoStrokeConfigWidget::lineWidth() const
290 {
291     return d->lineWidth->value();
292 }
293 
color() const294 QColor KoStrokeConfigWidget::color() const
295 {
296     return d->colorAction->currentColor();
297 }
298 
miterLimit() const299 qreal KoStrokeConfigWidget::miterLimit() const
300 {
301     return d->capNJoinMenu->miterLimit->value();
302 }
303 
startMarker() const304 KoMarker *KoStrokeConfigWidget::startMarker() const
305 {
306     return d->startMarkerSelector->marker();
307 }
308 
endMarker() const309 KoMarker *KoStrokeConfigWidget::endMarker() const
310 {
311     return d->endMarkerSelector->marker();
312 }
313 
capStyle() const314 Qt::PenCapStyle KoStrokeConfigWidget::capStyle() const
315 {
316     return static_cast<Qt::PenCapStyle>(d->capNJoinMenu->capGroup->checkedId());
317 }
318 
joinStyle() const319 Qt::PenJoinStyle KoStrokeConfigWidget::joinStyle() const
320 {
321     return static_cast<Qt::PenJoinStyle>(d->capNJoinMenu->joinGroup->checkedId());
322 }
323 
createShapeStroke() const324 KoShapeStroke* KoStrokeConfigWidget::createShapeStroke() const
325 {
326     KoShapeStroke *stroke = new KoShapeStroke();
327 
328     stroke->setColor(color());
329     stroke->setLineWidth(lineWidth());
330     stroke->setCapStyle(capStyle());
331     stroke->setJoinStyle(joinStyle());
332     stroke->setMiterLimit(miterLimit());
333     stroke->setLineStyle(lineStyle(), lineDashes());
334 
335     return stroke;
336 }
337 
338 // ----------------------------------------------------------------
339 //                         Other public functions
340 
updateControls(KoShapeStrokeModel * stroke,KoMarker * startMarker,KoMarker * endMarker)341 void KoStrokeConfigWidget::updateControls(KoShapeStrokeModel *stroke, KoMarker *startMarker, KoMarker *endMarker)
342 {
343     blockChildSignals(true);
344 
345     const KoShapeStroke *lineStroke = dynamic_cast<const KoShapeStroke*>(stroke);
346     if (lineStroke) {
347         d->lineWidth->changeValue(lineStroke->lineWidth());
348         QAbstractButton *button = d->capNJoinMenu->capGroup->button(lineStroke->capStyle());
349         if (button) {
350             button->setChecked(true);
351         }
352         button = d->capNJoinMenu->joinGroup->button(lineStroke->joinStyle());
353         if (button) {
354             button->setChecked(true);
355         }
356         d->capNJoinMenu->miterLimit->changeValue(lineStroke->miterLimit());
357         d->capNJoinMenu->miterLimit->setEnabled(lineStroke->joinStyle() == Qt::MiterJoin);
358         d->lineStyle->setLineStyle(lineStroke->lineStyle(), lineStroke->lineDashes());
359         d->colorAction->setCurrentColor(lineStroke->color());
360     }
361     else {
362         d->lineWidth->changeValue(0.0);
363         d->capNJoinMenu->capGroup->button(Qt::FlatCap)->setChecked(true);
364         d->capNJoinMenu->joinGroup->button(Qt::MiterJoin)->setChecked(true);
365         d->capNJoinMenu->miterLimit->changeValue(0.0);
366         d->capNJoinMenu->miterLimit->setEnabled(true);
367         d->lineStyle->setLineStyle(Qt::NoPen, QVector<qreal>());
368     }
369 
370     d->startMarkerSelector->setMarker(startMarker);
371     d->endMarkerSelector->setMarker(endMarker);
372 
373     blockChildSignals(false);
374 }
375 
setUnit(const KoUnit & unit)376 void KoStrokeConfigWidget::setUnit(const KoUnit &unit)
377 {
378     blockChildSignals(true);
379 
380     KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController();
381     KoSelection *selection = canvasController->canvas()->shapeManager()->selection();
382     KoShape * shape = selection->firstSelectedShape();
383 
384     /**
385      * KoStrokeShape knows nothing about the transformations applied
386      * to the shape, which doesn't prevent the shape to apply them and
387      * display the stroke differently. So just take that into account
388      * and show the user correct values using the multiplier in KoUnit.
389      */
390     KoUnit newUnit(unit);
391     if (shape) {
392         newUnit.adjustByPixelTransform(shape->absoluteTransformation(0));
393     }
394 
395     d->lineWidth->setUnit(newUnit);
396     d->capNJoinMenu->miterLimit->setUnit(newUnit);
397 
398     blockChildSignals(false);
399 }
400 
updateMarkers(const QList<KoMarker * > & markers)401 void KoStrokeConfigWidget::updateMarkers(const QList<KoMarker*> &markers)
402 {
403     d->startMarkerSelector->updateMarkers(markers);
404     d->endMarkerSelector->updateMarkers(markers);
405 }
406 
blockChildSignals(bool block)407 void KoStrokeConfigWidget::blockChildSignals(bool block)
408 {
409     d->colorAction->blockSignals(block);
410     d->lineWidth->blockSignals(block);
411     d->capNJoinMenu->capGroup->blockSignals(block);
412     d->capNJoinMenu->joinGroup->blockSignals(block);
413     d->capNJoinMenu->miterLimit->blockSignals(block);
414     d->lineStyle->blockSignals(block);
415     d->startMarkerSelector->blockSignals(block);
416     d->endMarkerSelector->blockSignals(block);
417 }
418 
setActive(bool active)419 void KoStrokeConfigWidget::setActive(bool active)
420 {
421     d->active = active;
422 }
423 
424 //------------------------
applyChanges()425 void KoStrokeConfigWidget::applyChanges()
426 {
427     KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController();
428     KoSelection *selection = canvasController->canvas()->shapeManager()->selection();
429 
430     //FIXME d->canvas->resourceManager()->setActiveStroke( d->stroke );
431 
432     if (!selection || !selection->count()) {
433         return;
434     }
435 
436     KoShapeStroke *newStroke = new KoShapeStroke();
437     KoShapeStroke *oldStroke = dynamic_cast<KoShapeStroke*>( selection->firstSelectedShape()->stroke() );
438     if (oldStroke) {
439         newStroke->setLineBrush(oldStroke->lineBrush());
440     }
441     newStroke->setColor(color());
442     newStroke->setLineWidth(lineWidth());
443     newStroke->setCapStyle(static_cast<Qt::PenCapStyle>(d->capNJoinMenu->capGroup->checkedId()));
444     newStroke->setJoinStyle(static_cast<Qt::PenJoinStyle>(d->capNJoinMenu->joinGroup->checkedId()));
445     newStroke->setMiterLimit(miterLimit());
446     newStroke->setLineStyle(lineStyle(), lineDashes());
447 
448     if (d->active) {
449         KoShapeStrokeCommand *cmd = new KoShapeStrokeCommand(selection->selectedShapes(), newStroke);
450         canvasController->canvas()->addCommand(cmd);
451     }
452 }
453 
applyMarkerChanges(KoMarkerData::MarkerPosition position)454 void KoStrokeConfigWidget::applyMarkerChanges(KoMarkerData::MarkerPosition position)
455 {
456     KoMarker *marker = nullptr;
457     if (position == KoMarkerData::MarkerStart) {
458         marker = startMarker();
459     }
460     else if (position == KoMarkerData::MarkerEnd) {
461         marker = endMarker();
462     }
463 
464     KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController();
465     KoSelection *selection = canvasController->canvas()->shapeManager()->selection();
466 
467     if (! selection || !selection->count()) {
468         return;
469     }
470 
471     const QList<KoShape*> shapeList = selection->selectedShapes();
472     QList<KoPathShape*> pathShapeList;
473     for (auto itShape : shapeList) {
474         KoPathShape* pathShape = dynamic_cast<KoPathShape*>(itShape);
475         if (pathShape) {
476             pathShapeList << pathShape;
477         }
478     }
479 
480     if (!pathShapeList.empty()) {
481         KoPathShapeMarkerCommand* cmdMarker = new KoPathShapeMarkerCommand(pathShapeList, marker, position);
482         canvasController->canvas()->addCommand(cmdMarker);
483     }
484 }
485 
startMarkerChanged()486 void KoStrokeConfigWidget::startMarkerChanged()
487 {
488     applyMarkerChanges(KoMarkerData::MarkerStart);
489 }
490 
endMarkerChanged()491 void KoStrokeConfigWidget::endMarkerChanged()
492 {
493     applyMarkerChanges(KoMarkerData::MarkerEnd);
494 }
495 // ----------------------------------------------------------------
496 
497 
498 
selectionChanged()499 void KoStrokeConfigWidget::selectionChanged()
500 {
501     // see a comment in setUnit()
502     setUnit(d->canvas->unit());
503 
504 
505     KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController();
506     KoSelection *selection = canvasController->canvas()->shapeManager()->selection();
507     KoShape * shape = selection->firstSelectedShape();
508     if (shape && shape->stroke()) {
509         KoPathShape *pathShape = dynamic_cast<KoPathShape *>(shape);
510         if (pathShape) {
511             updateControls(shape->stroke(), pathShape->marker(KoMarkerData::MarkerStart),
512                                              pathShape->marker(KoMarkerData::MarkerEnd));
513         }
514         else {
515             updateControls(shape->stroke(), 0 ,0);
516         }
517     }
518 }
519 
setCanvas(KoCanvasBase * canvas)520 void KoStrokeConfigWidget::setCanvas( KoCanvasBase *canvas )
521 {
522     if (canvas) {
523         connect(canvas->shapeManager()->selection(), SIGNAL(selectionChanged()),
524                 this, SLOT(selectionChanged()));
525         connect(canvas->shapeManager(), SIGNAL(selectionContentChanged()),
526                 this, SLOT(selectionChanged()));
527         connect(canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
528                 this, SLOT(canvasResourceChanged(int,QVariant)));
529         setUnit(canvas->unit());
530 
531         KoDocumentResourceManager *resourceManager = canvas->shapeController()->resourceManager();
532         if (resourceManager) {
533             KoMarkerCollection *collection = resourceManager->resource(KoDocumentResourceManager::MarkerCollection).value<KoMarkerCollection*>();
534             if (collection) {
535                 updateMarkers(collection->markers());
536             }
537         }
538     }
539 
540     d->canvas = canvas;
541 }
542 
canvasResourceChanged(int key,const QVariant & value)543 void KoStrokeConfigWidget::canvasResourceChanged(int key, const QVariant &value)
544 {
545     switch (key) {
546     case KoCanvasResourceManager::Unit:
547         setUnit(value.value<KoUnit>());
548         break;
549     }
550 }
551 #include "KoStrokeConfigWidget.moc"
552