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