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