1 /*
2  *  kis_cmb_composite.cc - part of KImageShop/Krayon/Krita
3  *
4  *  Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org)
5  *  Copyright (c) 2011 Silvio Heinrich <plassy@web.de>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21 
22 #include "kis_cmb_composite.h"
23 
24 #include <KoCompositeOp.h>
25 #include <KoCompositeOpRegistry.h>
26 
27 #include "kis_composite_ops_model.h"
28 #include "kis_categorized_item_delegate.h"
29 #include <kis_action.h>
30 #include <QWheelEvent>
31 #include "kis_action_manager.h"
32 
33 //////////////////////////////////////////////////////////////////////////////////////////
34 // ---- KisCompositeOpListWidget ------------------------------------------------------ //
35 
KisCompositeOpListWidget(QWidget * parent)36 KisCompositeOpListWidget::KisCompositeOpListWidget(QWidget* parent):
37     KisCategorizedListView(parent),
38     m_model(new KisSortedCompositeOpListModel(false, this))
39 {
40     setModel(m_model);
41     setItemDelegate(new KisCategorizedItemDelegate(this));
42 }
43 
~KisCompositeOpListWidget()44 KisCompositeOpListWidget::~KisCompositeOpListWidget()
45 {
46 }
47 
selectedCompositeOp() const48 KoID KisCompositeOpListWidget::selectedCompositeOp() const {
49     KoID op;
50 
51     if (m_model->entryAt(op, currentIndex())) {
52         return op;
53     }
54 
55     return KoCompositeOpRegistry::instance().getDefaultCompositeOp();
56 }
57 
58 //////////////////////////////////////////////////////////////////////////////////////////
59 // ---- KisCompositeOpComboBox -------------------------------------------------------- //
60 
KisCompositeOpComboBox(QWidget * parent)61 KisCompositeOpComboBox::KisCompositeOpComboBox(QWidget* parent)
62     : KisCompositeOpComboBox(false, parent)
63 {
64 }
65 
KisCompositeOpComboBox(bool limitToLayerStyles,QWidget * parent)66 KisCompositeOpComboBox::KisCompositeOpComboBox(bool limitToLayerStyles, QWidget* parent)
67     : KisSqueezedComboBox(parent),
68       m_model(new KisSortedCompositeOpListModel(limitToLayerStyles, this)),
69       m_allowToHidePopup(true)
70 {
71     m_view = new KisCategorizedListView();
72     m_view->setCompositeBoxControl(true);
73 
74     setMaxVisibleItems(100);
75     setSizeAdjustPolicy(AdjustToContents);
76     m_view->setResizeMode(QListView::Adjust);
77 
78     setToolTip(i18n("Blending Mode"));
79 
80     setModel(m_model);
81     setView(m_view);
82     setItemDelegate(new KisCategorizedItemDelegate(this));
83 
84     connect(m_view, SIGNAL(sigCategoryToggled(QModelIndex,bool)), SLOT(slotCategoryToggled(QModelIndex,bool)));
85     connect(m_view, SIGNAL(sigEntryChecked(QModelIndex)), SLOT(slotEntryChecked(QModelIndex)));
86 
87     selectCompositeOp(KoCompositeOpRegistry::instance().getDefaultCompositeOp());
88 }
89 
~KisCompositeOpComboBox()90 KisCompositeOpComboBox::~KisCompositeOpComboBox()
91 {
92     delete m_view;
93 }
94 
connectBlendmodeActions(KisActionManager * manager)95 void KisCompositeOpComboBox::connectBlendmodeActions(KisActionManager *manager)
96 {
97     KisAction *action = 0;
98 
99     action = manager->createAction("Next Blending Mode");
100     connect(action, SIGNAL(triggered()), SLOT(slotNextBlendingMode()));
101 
102     action = manager->createAction("Previous Blending Mode");
103     connect(action, SIGNAL(triggered()), SLOT(slotPreviousBlendingMode()));
104 
105     action = manager->createAction("Select Normal Blending Mode");
106     connect(action, SIGNAL(triggered()), SLOT(slotNormal()));
107 
108     action = manager->createAction("Select Dissolve Blending Mode");
109     connect(action, SIGNAL(triggered()), SLOT(slotDissolve()));
110 
111     action = manager->createAction("Select Behind Blending Mode");
112     connect(action, SIGNAL(triggered()), SLOT(slotBehind()));
113 
114     action = manager->createAction("Select Clear Blending Mode");
115     connect(action, SIGNAL(triggered()), SLOT(slotClear()));
116 
117     action = manager->createAction("Select Darken Blending Mode");
118     connect(action, SIGNAL(triggered()), SLOT(slotDarken()));
119 
120     action = manager->createAction("Select Multiply Blending Mode");
121     connect(action, SIGNAL(triggered()), SLOT(slotMultiply()));
122 
123     action = manager->createAction("Select Color Burn Blending Mode");
124     connect(action, SIGNAL(triggered()), SLOT(slotColorBurn()));
125 
126     action = manager->createAction("Select Linear Burn Blending Mode");
127     connect(action, SIGNAL(triggered()), SLOT(slotLinearBurn()));
128 
129     action = manager->createAction("Select Lighten Blending Mode");
130     connect(action, SIGNAL(triggered()), SLOT(slotLighten()));
131 
132     action = manager->createAction("Select Screen Blending Mode");
133     connect(action, SIGNAL(triggered()), SLOT(slotScreen()));
134 
135     action = manager->createAction("Select Color Dodge Blending Mode");
136     connect(action, SIGNAL(triggered()), SLOT(slotColorDodge()));
137 
138     action = manager->createAction("Select Linear Dodge Blending Mode");
139     connect(action, SIGNAL(triggered()), SLOT(slotLinearDodge()));
140 
141     action = manager->createAction("Select Overlay Blending Mode");
142     connect(action, SIGNAL(triggered()), SLOT(slotOverlay()));
143 
144     action = manager->createAction("Select Hard Overlay Blending Mode");
145     connect(action, SIGNAL(triggered()), SLOT(slotHardOverlay()));
146 
147     action = manager->createAction("Select Soft Light Blending Mode");
148     connect(action, SIGNAL(triggered()), SLOT(slotSoftLight()));
149 
150     action = manager->createAction("Select Hard Light Blending Mode");
151     connect(action, SIGNAL(triggered()), SLOT(slotHardLight()));
152 
153     action = manager->createAction("Select Vivid Light Blending Mode");
154     connect(action, SIGNAL(triggered()), SLOT(slotVividLight()));
155 
156     action = manager->createAction("Select Linear Light Blending Mode");
157     connect(action, SIGNAL(triggered()), SLOT(slotLinearLight()));
158 
159     action = manager->createAction("Select Pin Light Blending Mode");
160     connect(action, SIGNAL(triggered()), SLOT(slotPinLight()));
161 
162     action = manager->createAction("Select Hard Mix Blending Mode");
163     connect(action, SIGNAL(triggered()), SLOT(slotHardMix()));
164 
165     action = manager->createAction("Select Difference Blending Mode");
166     connect(action, SIGNAL(triggered()), SLOT(slotDifference()));
167 
168     action = manager->createAction("Select Exclusion Blending Mode");
169     connect(action, SIGNAL(triggered()), SLOT(slotExclusion()));
170 
171     action = manager->createAction("Select Hue Blending Mode");
172     connect(action, SIGNAL(triggered()), SLOT(slotHue()));
173 
174     action = manager->createAction("Select Saturation Blending Mode");
175     connect(action, SIGNAL(triggered()), SLOT(slotSaturation()));
176 
177     action = manager->createAction("Select Color Blending Mode");
178     connect(action, SIGNAL(triggered()), SLOT(slotColor()));
179 
180     action = manager->createAction("Select Luminosity Blending Mode");
181     connect(action, SIGNAL(triggered()), SLOT(slotLuminosity()));
182 }
183 
validate(const KoColorSpace * cs)184 void KisCompositeOpComboBox::validate(const KoColorSpace *cs) {
185     m_model->validate(cs);
186 }
187 
selectCompositeOp(const KoID & op)188 void KisCompositeOpComboBox::selectCompositeOp(const KoID &op) {
189     KoID currentOp;
190     if (m_model->entryAt(currentOp, m_model->index(currentIndex(), 0)) &&
191             currentOp == op) {
192 
193         return;
194     }
195 
196     QModelIndex index = m_model->indexOf(op);
197 
198     setCurrentIndex(index.row());
199     emit activated(index.row());
200     emit activated(op.name());
201 }
202 
selectedCompositeOp() const203 KoID KisCompositeOpComboBox::selectedCompositeOp() const {
204     KoID op;
205 
206     if (m_model->entryAt(op, m_model->index(currentIndex(), 0))) {
207         return op;
208     }
209     return KoCompositeOpRegistry::instance().getDefaultCompositeOp();
210 }
211 
slotCategoryToggled(const QModelIndex & index,bool toggled)212 void KisCompositeOpComboBox::slotCategoryToggled(const QModelIndex& index, bool toggled)
213 {
214     Q_UNUSED(index);
215     Q_UNUSED(toggled);
216 
217     //NOTE: this will (should) fit the size of the
218     //      popup widget to the view
219     //      don't know if this is expected behaviour
220     //      on all supported platforms.
221     //      There is nothing written about this in the docs.
222     showPopup();
223 }
224 
slotEntryChecked(const QModelIndex & index)225 void KisCompositeOpComboBox::slotEntryChecked(const QModelIndex& index)
226 {
227     Q_UNUSED(index);
228     m_allowToHidePopup = false;
229 }
230 
hidePopup()231 void KisCompositeOpComboBox::hidePopup()
232 {
233     if (m_allowToHidePopup) {
234         QComboBox::hidePopup();
235     }
236     else  {
237         QComboBox::showPopup();
238     }
239 
240     m_allowToHidePopup = true;
241 }
242 
slotNextBlendingMode()243 void KisCompositeOpComboBox::slotNextBlendingMode()
244 {
245     selectNeighbouringBlendMode(true);
246 }
247 
slotPreviousBlendingMode()248 void KisCompositeOpComboBox::slotPreviousBlendingMode()
249 {
250     selectNeighbouringBlendMode(false);
251 }
252 
slotNormal()253 void KisCompositeOpComboBox::slotNormal()
254 {
255     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_OVER));
256 }
257 
slotDissolve()258 void KisCompositeOpComboBox::slotDissolve()
259 {
260     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_DISSOLVE));
261 }
262 
slotBehind()263 void KisCompositeOpComboBox::slotBehind()
264 {
265     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_BEHIND));
266 }
267 
slotClear()268 void KisCompositeOpComboBox::slotClear()
269 {
270     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_CLEAR));
271 }
272 
slotDarken()273 void KisCompositeOpComboBox::slotDarken()
274 {
275     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_DARKEN));
276 }
277 
slotMultiply()278 void KisCompositeOpComboBox::slotMultiply()
279 {
280     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_MULT));
281 }
282 
slotColorBurn()283 void KisCompositeOpComboBox::slotColorBurn()
284 {
285     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_BURN));
286 }
287 
slotLinearBurn()288 void KisCompositeOpComboBox::slotLinearBurn()
289 {
290     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LINEAR_BURN));
291 }
292 
slotLighten()293 void KisCompositeOpComboBox::slotLighten()
294 {
295     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LIGHTEN));
296 }
297 
slotScreen()298 void KisCompositeOpComboBox::slotScreen()
299 {
300     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_SCREEN));
301 }
302 
slotColorDodge()303 void KisCompositeOpComboBox::slotColorDodge()
304 {
305     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_DODGE));
306 }
307 
slotLinearDodge()308 void KisCompositeOpComboBox::slotLinearDodge()
309 {
310     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LINEAR_DODGE));
311 }
312 
slotOverlay()313 void KisCompositeOpComboBox::slotOverlay()
314 {
315     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_OVERLAY));
316 }
317 
slotHardOverlay()318 void KisCompositeOpComboBox::slotHardOverlay()
319 {
320     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_HARD_OVERLAY));
321 }
322 
slotSoftLight()323 void KisCompositeOpComboBox::slotSoftLight()
324 {
325     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_SOFT_LIGHT_PHOTOSHOP));
326 }
327 
slotHardLight()328 void KisCompositeOpComboBox::slotHardLight()
329 {
330     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_HARD_LIGHT));
331 }
332 
slotVividLight()333 void KisCompositeOpComboBox::slotVividLight()
334 {
335     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_VIVID_LIGHT));
336 }
337 
slotLinearLight()338 void KisCompositeOpComboBox::slotLinearLight()
339 {
340     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LINEAR_LIGHT));
341 }
342 
slotPinLight()343 void KisCompositeOpComboBox::slotPinLight()
344 {
345     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_PIN_LIGHT));
346 }
347 
slotHardMix()348 void KisCompositeOpComboBox::slotHardMix()
349 {
350     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_HARD_MIX_PHOTOSHOP));
351 }
352 
slotDifference()353 void KisCompositeOpComboBox::slotDifference()
354 {
355     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_DIFF));
356 }
357 
slotExclusion()358 void KisCompositeOpComboBox::slotExclusion()
359 {
360     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_EXCLUSION));
361 }
362 
slotHue()363 void KisCompositeOpComboBox::slotHue()
364 {
365     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_HUE));
366 }
367 
slotSaturation()368 void KisCompositeOpComboBox::slotSaturation()
369 {
370     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_SATURATION));
371 }
372 
slotColor()373 void KisCompositeOpComboBox::slotColor()
374 {
375     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_COLOR));
376 }
377 
slotLuminosity()378 void KisCompositeOpComboBox::slotLuminosity()
379 {
380     selectCompositeOp(KoCompositeOpRegistry::instance().getKoID(COMPOSITE_LUMINIZE));
381 }
382 
selectNeighbouringBlendMode(bool down)383 void KisCompositeOpComboBox::selectNeighbouringBlendMode(bool down)
384 {
385     const int rowCount = count();
386     int newIndex = currentIndex();
387 
388     QAbstractItemModel *model = this->model();
389     KoID op;
390 
391     if (!down) {
392         newIndex--;
393         while ((newIndex >= 0) &&
394                (!(model->flags(model->index(newIndex, modelColumn(), rootModelIndex())) & Qt::ItemIsEnabled) ||
395                 !m_model->entryAt(op, m_model->index(newIndex, modelColumn()))))
396 
397             newIndex--;
398     } else {
399         newIndex++;
400         while (newIndex < rowCount &&
401                (!(model->index(newIndex, modelColumn(), rootModelIndex()).flags() & Qt::ItemIsEnabled) ||
402                 !m_model->entryAt(op, m_model->index(newIndex, modelColumn()))))
403 
404             newIndex++;
405     }
406 
407     if (newIndex >= 0 && newIndex < rowCount && newIndex != currentIndex()) {
408         setCurrentIndex(newIndex);
409 
410         emit activated(newIndex);
411         if (m_model->entryAt(op, m_model->index(newIndex, 0))) {
412             emit activated(op.name());
413         }
414     }
415 }
416 
wheelEvent(QWheelEvent * e)417 void KisCompositeOpComboBox::wheelEvent(QWheelEvent *e)
418 {
419     /**
420      * This code is a copy of QComboBox::wheelEvent. It does the same thing,
421      * except that it skips "Category" items, by checking m_model->entryAt()
422      * on each step.
423      */
424 
425     QStyleOptionComboBox opt;
426     initStyleOption(&opt);
427 
428 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
429     if (style()->styleHint(QStyle::SH_ComboBox_AllowWheelScrolling, &opt, this)) {
430 #else
431     if (1) {
432 #endif
433 
434         if (e->delta() != 0) {
435             selectNeighbouringBlendMode(e->delta() < 0);
436         }
437 
438         e->accept();
439     } else {
440         KisSqueezedComboBox::wheelEvent(e);
441     }
442 }
443 
444 void KisCompositeOpComboBox::keyPressEvent(QKeyEvent *e)
445 {
446     /**
447      * This code is a copy of QComboBox::keyPressEvent. It does the same thing,
448      * except that it skips "Category" items, by checking m_model->entryAt()
449      * on each step.
450      */
451 
452     enum Move { NoMove=0 , MoveUp , MoveDown , MoveFirst , MoveLast};
453 
454     Move move = NoMove;
455     int newIndex = currentIndex();
456     switch (e->key()) {
457     case Qt::Key_Up:
458         if (e->modifiers() & Qt::ControlModifier)
459             break; // pass to line edit for auto completion
460         Q_FALLTHROUGH();
461     case Qt::Key_PageUp:
462         move = MoveUp;
463         break;
464     case Qt::Key_Down:
465         if (e->modifiers() & Qt::AltModifier) {
466             showPopup();
467             return;
468         } else if (e->modifiers() & Qt::ControlModifier)
469             break; // pass to line edit for auto completion
470         Q_FALLTHROUGH();
471     case Qt::Key_PageDown:
472         move = MoveDown;
473         break;
474     case Qt::Key_Home:
475         move = MoveFirst;
476         break;
477     case Qt::Key_End:
478         move = MoveLast;
479         break;
480     case Qt::Key_F4:
481         if (!e->modifiers()) {
482             showPopup();
483             return;
484         }
485         break;
486     case Qt::Key_Space:
487         showPopup();
488         return;
489     default:
490         break;
491     }
492 
493     const int rowCount = count();
494 
495     if (move != NoMove) {
496         KoID op;
497 
498         e->accept();
499         switch (move) {
500         case MoveFirst:
501             newIndex = -1;
502             Q_FALLTHROUGH();
503         case MoveDown:
504             newIndex++;
505             while (newIndex < rowCount &&
506                    (!(model()->index(newIndex, modelColumn(), rootModelIndex()).flags() & Qt::ItemIsEnabled) ||
507                     !m_model->entryAt(op, m_model->index(newIndex, modelColumn()))))
508                 newIndex++;
509             break;
510         case MoveLast:
511             newIndex = rowCount;
512             Q_FALLTHROUGH();
513         case MoveUp:
514             newIndex--;
515             while ((newIndex >= 0) &&
516                    (!(model()->flags(model()->index(newIndex, modelColumn(), rootModelIndex())) & Qt::ItemIsEnabled) ||
517                     !m_model->entryAt(op, m_model->index(newIndex, modelColumn()))))
518                 newIndex--;
519             break;
520         default:
521             e->ignore();
522             break;
523         }
524 
525         if (newIndex >= 0 && newIndex < rowCount && newIndex != currentIndex()) {
526             setCurrentIndex(newIndex);
527             emit activated(newIndex);
528 
529             if (m_model->entryAt(op, m_model->index(newIndex, 0))) {
530                 emit activated(op.name());
531             }
532         }
533     } else {
534         KisSqueezedComboBox::keyPressEvent(e);
535     }
536 }
537 
538 KisLayerStyleCompositeOpComboBox::KisLayerStyleCompositeOpComboBox(QWidget* parent)
539     : KisCompositeOpComboBox(true, parent)
540 {
541 }
542