1 /* This file is part of the KDE project
2 * Copyright (c) 2009-2011 Jan Hambrecht <jaham@gmx.net>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #include "KarbonFilterEffectsTool.h"
21
22 #include "KoFilterEffect.h"
23 #include "KoFilterEffectStack.h"
24 #include "KoFilterEffectFactoryBase.h"
25 #include "KoFilterEffectRegistry.h"
26 #include "KoFilterEffectConfigWidgetBase.h"
27 #include "KoCanvasBase.h"
28 #include "KoDocumentResourceManager.h"
29 #include "KoSelectedShapesProxy.h"
30 #include "KoViewConverter.h"
31 #include "KoSelection.h"
32 #include "FilterEffectEditWidget.h"
33 #include "FilterEffectResource.h"
34 #include "FilterResourceServerProvider.h"
35 #include "FilterStackSetCommand.h"
36 #include "FilterRegionChangeCommand.h"
37 #include "FilterRegionEditStrategy.h"
38 #include "KoResourceServerAdapter.h"
39 #include "KoResourceSelector.h"
40 #include <KoPointerEvent.h>
41
42 #include <KoIcon.h>
43
44 #include <kcombobox.h>
45 #include <klocalizedstring.h>
46 #include <QDialog>
47 #include <QSpinBox>
48
49 #include <QWidget>
50 #include <QGridLayout>
51 #include <QToolButton>
52 #include <QStackedWidget>
53 #include <QLabel>
54 #include <QDialogButtonBox>
55 #include <QPushButton>
56 #include <QVBoxLayout>
57
58 #include "kis_double_parse_spin_box.h"
59
60 class KarbonFilterEffectsTool::Private
61 {
62 public:
Private()63 Private()
64 : filterSelector(0)
65 , configSelector(0)
66 , configStack(0)
67 , posX(0)
68 , posY(0)
69 , posW(0)
70 , posH(0)
71 , clearButton(0)
72 , currentEffect(0)
73 , currentPanel(0)
74 , currentShape(0)
75 {
76 }
77
fillConfigSelector(KoShape * shape,KarbonFilterEffectsTool * tool)78 void fillConfigSelector(KoShape *shape, KarbonFilterEffectsTool *tool)
79 {
80 if (!configSelector) {
81 return;
82 }
83
84 configSelector->clear();
85 clearButton->setEnabled(false);
86
87 if (!shape || !shape->filterEffectStack()) {
88 addWidgetForEffect(0, tool);
89 return;
90 }
91
92 configSelector->blockSignals(true);
93
94 int index = 0;
95 Q_FOREACH (KoFilterEffect *effect, shape->filterEffectStack()->filterEffects()) {
96 configSelector->addItem(QString("%1 - ").arg(index) + effect->name());
97 index++;
98 }
99
100 configSelector->blockSignals(false);
101
102 KoFilterEffect *effect = index > 0 ? shape->filterEffectStack()->filterEffects().first() : 0;
103
104 addWidgetForEffect(effect, tool);
105 clearButton->setEnabled(shape->filterEffectStack() != 0);
106 }
107
addWidgetForEffect(KoFilterEffect * filterEffect,KarbonFilterEffectsTool * tool)108 void addWidgetForEffect(KoFilterEffect *filterEffect, KarbonFilterEffectsTool *tool)
109 {
110 // remove current widget if new effect is zero or effect type has changed
111 if (!filterEffect || (currentEffect && filterEffect->id() != currentEffect->id())) {
112 while (configStack->count()) {
113 configStack->removeWidget(configStack->widget(0));
114 }
115 }
116
117 if (!filterEffect) {
118 currentEffect = 0;
119 currentPanel = 0;
120 } else if (!currentEffect || currentEffect->id() != filterEffect->id()) {
121 // when a effect is set and is differs from the previous one
122 // get the config widget and insert it into the option widget
123 currentEffect = filterEffect;
124
125 KoFilterEffectRegistry *registry = KoFilterEffectRegistry::instance();
126 KoFilterEffectFactoryBase *factory = registry->value(currentEffect->id());
127 if (!factory) {
128 return;
129 }
130
131 currentPanel = factory->createConfigWidget();
132 if (!currentPanel) {
133 return;
134 }
135
136 currentPanel->layout()->setContentsMargins(0, 0, 0, 0);
137 configStack->insertWidget(0, currentPanel);
138 configStack->layout()->setContentsMargins(0, 0, 0, 0);
139 connect(currentPanel, SIGNAL(filterChanged()), tool, SLOT(filterChanged()));
140 }
141
142 if (currentPanel) {
143 currentPanel->editFilterEffect(filterEffect);
144 }
145
146 updateFilterRegion();
147 }
148
updateFilterRegion()149 void updateFilterRegion()
150 {
151 QRectF region = currentEffect ? currentEffect->filterRect() : QRectF(0, 0, 0, 0);
152
153 posX->blockSignals(true);
154 posX->setValue(100.0 * region.x());
155 posX->blockSignals(false);
156 posX->setEnabled(currentEffect != 0);
157 posY->blockSignals(true);
158 posY->setValue(100.0 * region.y());
159 posY->blockSignals(false);
160 posY->setEnabled(currentEffect != 0);
161 posW->blockSignals(true);
162 posW->setValue(100.0 * region.width());
163 posW->blockSignals(false);
164 posW->setEnabled(currentEffect != 0);
165 posH->blockSignals(true);
166 posH->setValue(100.0 * region.height());
167 posH->blockSignals(false);
168 posH->setEnabled(currentEffect != 0);
169 }
170
editModeFromMousePosition(const QPointF & mousePosition,KarbonFilterEffectsTool * tool)171 EditMode editModeFromMousePosition(const QPointF &mousePosition, KarbonFilterEffectsTool *tool)
172 {
173 if (currentShape && currentShape->filterEffectStack() && currentEffect) {
174 // get the size rect of the shape
175 QRectF sizeRect(QPointF(), currentShape->size());
176 // get the filter rectangle in shape coordinates
177 QRectF filterRect = currentEffect->filterRectForBoundingRect(sizeRect);
178 // get the transformation from document to shape coordinates
179 QTransform transform = currentShape->absoluteTransformation().inverted();
180 // adjust filter rectangle by grab sensitivity
181 const int grabDistance = tool->grabSensitivity();
182 QPointF border = tool->canvas()->viewConverter()->viewToDocument(QPointF(grabDistance, grabDistance));
183 filterRect.adjust(-border.x(), -border.y(), border.x(), border.y());
184 // map event point from document to shape coordinates
185 QPointF shapePoint = transform.map(mousePosition);
186 // check if the mouse is inside/near our filter rect
187 if (filterRect.contains(shapePoint)) {
188 if (qAbs(shapePoint.x() - filterRect.left()) <= border.x()) {
189 return MoveLeft;
190 } else if (qAbs(shapePoint.x() - filterRect.right()) <= border.x()) {
191 return MoveRight;
192 } else if (qAbs(shapePoint.y() - filterRect.top()) <= border.y()) {
193 return MoveTop;
194 } else if (qAbs(shapePoint.y() - filterRect.bottom()) <= border.y()) {
195 return MoveBottom;
196 } else {
197 return MoveAll;
198 }
199 } else {
200 return None;
201 }
202 }
203 return None;
204 }
205
206 KoResourceSelector *filterSelector;
207 KComboBox *configSelector;
208 QStackedWidget *configStack;
209 QDoubleSpinBox *posX;
210 QDoubleSpinBox *posY;
211 QDoubleSpinBox *posW;
212 QDoubleSpinBox *posH;
213 QToolButton *clearButton;
214 KoFilterEffect *currentEffect;
215 KoFilterEffectConfigWidgetBase *currentPanel;
216 KoShape *currentShape;
217 };
218
KarbonFilterEffectsTool(KoCanvasBase * canvas)219 KarbonFilterEffectsTool::KarbonFilterEffectsTool(KoCanvasBase *canvas)
220 : KoInteractionTool(canvas)
221 , d(new Private())
222 {
223 connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()),
224 this, SLOT(selectionChanged()));
225 connect(canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()),
226 this, SLOT(selectionChanged()));
227 }
228
~KarbonFilterEffectsTool()229 KarbonFilterEffectsTool::~KarbonFilterEffectsTool()
230 {
231 delete d;
232 }
233
paint(QPainter & painter,const KoViewConverter & converter)234 void KarbonFilterEffectsTool::paint(QPainter &painter, const KoViewConverter &converter)
235 {
236 if (d->currentShape && d->currentShape->filterEffectStack()) {
237 painter.save();
238 // apply the shape transformation
239
240 QTransform transform = d->currentShape->absoluteTransformation();
241 painter.setTransform(transform * converter.documentToView(), true);
242 // get the size rect of the shape
243 QRectF sizeRect(QPointF(), d->currentShape->size());
244 // get the clipping rect of the filter stack
245 KoFilterEffectStack *filterStack = d->currentShape->filterEffectStack();
246 QRectF clipRect = filterStack->clipRectForBoundingRect(sizeRect);
247 // finally paint the clipping rect
248 painter.setBrush(Qt::NoBrush);
249 painter.setPen(Qt::blue);
250 painter.drawRect(clipRect);
251
252 if (currentStrategy()) {
253 currentStrategy()->paint(painter, converter);
254 } else if (d->currentEffect) {
255 QRectF filterRect = d->currentEffect->filterRectForBoundingRect(sizeRect);
256 // paint the filter subregion rect
257 painter.setBrush(Qt::NoBrush);
258 painter.setPen(Qt::red);
259 painter.drawRect(filterRect);
260 }
261
262 painter.restore();
263 }
264 }
265
repaintDecorations()266 void KarbonFilterEffectsTool::repaintDecorations()
267 {
268 if (d->currentShape && d->currentShape->filterEffectStack()) {
269 QRectF bb = d->currentShape->boundingRect();
270 const int radius = handleRadius();
271 canvas()->updateCanvas(bb.adjusted(-radius, -radius, radius, radius));
272 }
273 }
274
activate(ToolActivation toolActivation,const QSet<KoShape * > & shapes)275 void KarbonFilterEffectsTool::activate(ToolActivation toolActivation, const QSet<KoShape *> &shapes)
276 {
277 Q_UNUSED(toolActivation);
278 if (shapes.isEmpty()) {
279 emit done();
280 return;
281 }
282
283 d->currentShape = canvas()->selectedShapesProxy()->selection()->firstSelectedShape();
284 d->fillConfigSelector(d->currentShape, this);
285 }
286
mouseMoveEvent(KoPointerEvent * event)287 void KarbonFilterEffectsTool::mouseMoveEvent(KoPointerEvent *event)
288 {
289 if (currentStrategy()) {
290 KoInteractionTool::mouseMoveEvent(event);
291 } else {
292 EditMode mode = d->editModeFromMousePosition(event->point, this);
293 switch (mode) {
294 case MoveAll:
295 useCursor(Qt::SizeAllCursor);
296 break;
297 case MoveLeft:
298 case MoveRight:
299 useCursor(Qt::SizeHorCursor);
300 break;
301 case MoveTop:
302 case MoveBottom:
303 useCursor(Qt::SizeVerCursor);
304 break;
305 case None:
306 useCursor(Qt::ArrowCursor);
307 break;
308 }
309 }
310 }
311
createStrategy(KoPointerEvent * event)312 KoInteractionStrategy *KarbonFilterEffectsTool::createStrategy(KoPointerEvent *event)
313 {
314 EditMode mode = d->editModeFromMousePosition(event->point, this);
315 if (mode == None) {
316 return 0;
317 }
318
319 return new FilterRegionEditStrategy(this, d->currentShape, d->currentEffect, mode);
320 }
321
presetSelected(KoResource * resource)322 void KarbonFilterEffectsTool::presetSelected(KoResource *resource)
323 {
324 if (!d->currentShape) {
325 return;
326 }
327
328 FilterEffectResource *effectResource = dynamic_cast<FilterEffectResource *>(resource);
329 if (!effectResource) {
330 return;
331 }
332
333 KoFilterEffectStack *filterStack = effectResource->toFilterStack();
334 if (!filterStack) {
335 return;
336 }
337
338 canvas()->addCommand(new FilterStackSetCommand(filterStack, d->currentShape));
339 d->fillConfigSelector(d->currentShape, this);
340 }
341
editFilter()342 void KarbonFilterEffectsTool::editFilter()
343 {
344 QPointer<QDialog> dlg = new QDialog();
345 dlg->setWindowTitle(i18n("Filter Effect Editor"));
346 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
347 QWidget *mainWidget = new QWidget(0);
348 QVBoxLayout *mainLayout = new QVBoxLayout;
349 dlg->setLayout(mainLayout);
350 mainLayout->addWidget(mainWidget);
351 connect(buttonBox->button(QDialogButtonBox::Close), SIGNAL(clicked()), dlg, SLOT(close()));
352
353 FilterEffectEditWidget *editor = new FilterEffectEditWidget(dlg);
354 editor->editShape(d->currentShape, canvas());
355
356 mainLayout->addWidget(editor);
357 mainLayout->addWidget(buttonBox);
358 dlg->exec();
359 delete dlg;
360
361 d->fillConfigSelector(d->currentShape, this);
362 }
363
clearFilter()364 void KarbonFilterEffectsTool::clearFilter()
365 {
366 if (!d->currentShape) {
367 return;
368 }
369 if (!d->currentShape->filterEffectStack()) {
370 return;
371 }
372
373 canvas()->addCommand(new FilterStackSetCommand(0, d->currentShape));
374
375 d->fillConfigSelector(d->currentShape, this);
376 }
377
filterChanged()378 void KarbonFilterEffectsTool::filterChanged()
379 {
380 if (!d->currentShape) {
381 return;
382 }
383
384 d->currentShape->update();
385 }
386
filterSelected(int index)387 void KarbonFilterEffectsTool::filterSelected(int index)
388 {
389 if (!d->currentShape || ! d->currentShape->filterEffectStack()) {
390 return;
391 }
392
393 KoFilterEffect *effect = 0;
394 QList<KoFilterEffect *> filterEffects = d->currentShape->filterEffectStack()->filterEffects();
395 if (index >= 0 && index < filterEffects.count()) {
396 effect = filterEffects[index];
397 }
398
399 d->addWidgetForEffect(effect, this);
400
401 repaintDecorations();
402 }
403
selectionChanged()404 void KarbonFilterEffectsTool::selectionChanged()
405 {
406 d->currentShape = canvas()->selectedShapesProxy()->selection()->firstSelectedShape();
407 d->fillConfigSelector(d->currentShape, this);
408 }
409
regionXChanged(double x)410 void KarbonFilterEffectsTool::regionXChanged(double x)
411 {
412 if (!d->currentEffect) {
413 return;
414 }
415
416 QRectF region = d->currentEffect->filterRect();
417 region.setX(x / 100.0);
418 canvas()->addCommand(new FilterRegionChangeCommand(d->currentEffect, region, d->currentShape));
419 }
420
regionYChanged(double y)421 void KarbonFilterEffectsTool::regionYChanged(double y)
422 {
423 if (!d->currentEffect) {
424 return;
425 }
426
427 QRectF region = d->currentEffect->filterRect();
428 region.setY(y / 100.0);
429 canvas()->addCommand(new FilterRegionChangeCommand(d->currentEffect, region, d->currentShape));
430 }
431
regionWidthChanged(double width)432 void KarbonFilterEffectsTool::regionWidthChanged(double width)
433 {
434 if (!d->currentEffect) {
435 return;
436 }
437
438 QRectF region = d->currentEffect->filterRect();
439 region.setWidth(width / 100.0);
440 canvas()->addCommand(new FilterRegionChangeCommand(d->currentEffect, region, d->currentShape));
441 }
442
regionHeightChanged(double height)443 void KarbonFilterEffectsTool::regionHeightChanged(double height)
444 {
445 if (!d->currentEffect) {
446 return;
447 }
448
449 QRectF region = d->currentEffect->filterRect();
450 region.setHeight(height / 100.0);
451 canvas()->addCommand(new FilterRegionChangeCommand(d->currentEffect, region, d->currentShape));
452 }
453
createOptionWidgets()454 QList<QPointer<QWidget> > KarbonFilterEffectsTool::createOptionWidgets()
455 {
456 QList<QPointer<QWidget> > widgets;
457
458 FilterResourceServerProvider *serverProvider = FilterResourceServerProvider::instance();
459 KoResourceServer<FilterEffectResource> *server = serverProvider->filterEffectServer();
460 QSharedPointer<KoAbstractResourceServerAdapter> adapter(new KoResourceServerAdapter<FilterEffectResource>(server));
461
462 //---------------------------------------------------------------------
463
464 QWidget *addFilterWidget = new QWidget();
465 addFilterWidget->setObjectName("AddEffect");
466 QGridLayout *addFilterLayout = new QGridLayout(addFilterWidget);
467
468 d->filterSelector = new KoResourceSelector(addFilterWidget);
469 d->filterSelector->setResourceAdapter(adapter);
470 d->filterSelector->setDisplayMode(KoResourceSelector::TextMode);
471 d->filterSelector->setColumnCount(1);
472 addFilterLayout->addWidget(new QLabel(i18n("Effects"), addFilterWidget), 0, 0);
473 addFilterLayout->addWidget(d->filterSelector, 0, 1);
474 connect(d->filterSelector, SIGNAL(resourceSelected(KoResource*)),
475 this, SLOT(presetSelected(KoResource*)));
476
477 connect(d->filterSelector, SIGNAL(resourceApplied(KoResource*)),
478 this, SLOT(presetSelected(KoResource*)));
479
480 QToolButton *editButton = new QToolButton(addFilterWidget);
481 editButton->setIcon(koIcon("view-filter"));
482 editButton->setToolTip(i18n("View and edit filter"));
483 addFilterLayout->addWidget(editButton, 0, 2);
484 connect(editButton, SIGNAL(clicked()), this, SLOT(editFilter()));
485
486 d->clearButton = new QToolButton(addFilterWidget);
487 d->clearButton->setIcon(koIcon("edit-delete"));
488 d->clearButton->setToolTip(i18n("Remove filter from object"));
489 addFilterLayout->addWidget(d->clearButton, 0, 3);
490 connect(d->clearButton, SIGNAL(clicked()), this, SLOT(clearFilter()));
491
492 addFilterWidget->setWindowTitle(i18n("Add Filter"));
493 widgets.append(addFilterWidget);
494
495 //---------------------------------------------------------------------
496
497 QWidget *configFilterWidget = new QWidget();
498 configFilterWidget->setObjectName("ConfigEffect");
499 QGridLayout *configFilterLayout = new QGridLayout(configFilterWidget);
500
501 d->configSelector = new KComboBox(configFilterWidget);
502 configFilterLayout->addWidget(d->configSelector, 0, 0);
503 connect(d->configSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(filterSelected(int)));
504
505 d->configStack = new QStackedWidget(configFilterWidget);
506 configFilterLayout->addWidget(d->configStack, 1, 0);
507 configFilterLayout->setContentsMargins(0, 0, 0, 0);
508
509 configFilterWidget->setWindowTitle(i18n("Effect Properties"));
510 widgets.append(configFilterWidget);
511
512 //---------------------------------------------------------------------
513
514 QWidget *filterRegionWidget = new QWidget();
515 filterRegionWidget->setObjectName("EffectRegion");
516 QGridLayout *filterRegionLayout = new QGridLayout(filterRegionWidget);
517
518 d->posX = new KisDoubleParseSpinBox(filterRegionWidget);
519 d->posX->setSuffix(i18n("%"));
520 connect(d->posX, SIGNAL(valueChanged(double)), this, SLOT(regionXChanged(double)));
521 filterRegionLayout->addWidget(new QLabel(i18n("X:")), 0, 0);
522 filterRegionLayout->addWidget(d->posX, 0, 1);
523
524 d->posY = new KisDoubleParseSpinBox(filterRegionWidget);
525 d->posY->setSuffix(i18n("%"));
526 connect(d->posY, SIGNAL(valueChanged(double)), this, SLOT(regionYChanged(double)));
527 filterRegionLayout->addWidget(new QLabel(i18n("Y:")), 1, 0);
528 filterRegionLayout->addWidget(d->posY, 1, 1);
529
530 d->posW = new KisDoubleParseSpinBox(filterRegionWidget);
531 d->posW->setSuffix(i18n("%"));
532 connect(d->posW, SIGNAL(valueChanged(double)), this, SLOT(regionWidthChanged(double)));
533 filterRegionLayout->addWidget(new QLabel(i18n("W:")), 0, 2);
534 filterRegionLayout->addWidget(d->posW, 0, 3);
535
536 d->posH = new KisDoubleParseSpinBox(filterRegionWidget);
537 d->posH->setSuffix(i18n("%"));
538 connect(d->posH, SIGNAL(valueChanged(double)), this, SLOT(regionHeightChanged(double)));
539 filterRegionLayout->addWidget(new QLabel(i18n("H:")), 1, 2);
540 filterRegionLayout->addWidget(d->posH, 1, 3);
541 filterRegionLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding), 2, 0);
542 filterRegionLayout->setContentsMargins(0, 0, 0, 0);
543
544 filterRegionWidget->setWindowTitle(i18n("Effect Region"));
545 widgets.append(filterRegionWidget);
546
547 //---------------------------------------------------------------------
548
549 d->fillConfigSelector(d->currentShape, this);
550
551 return widgets;
552 }
553
554