1 /*
2  *  Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program 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
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "kis_brush_hud.h"
20 
21 #include <QGuiApplication>
22 #include <QVBoxLayout>
23 #include <QHBoxLayout>
24 #include <QPointer>
25 #include <QLabel>
26 #include <QPainter>
27 #include <QPaintEvent>
28 #include <QScrollArea>
29 #include <QEvent>
30 #include <QToolButton>
31 #include <QAction>
32 
33 #include "kis_uniform_paintop_property.h"
34 #include "kis_slider_based_paintop_property.h"
35 #include "kis_uniform_paintop_property_widget.h"
36 #include "kis_canvas_resource_provider.h"
37 #include "kis_paintop_preset.h"
38 #include "kis_paintop_settings.h"
39 #include "kis_signal_auto_connection.h"
40 #include "kis_paintop_settings_update_proxy.h"
41 #include "kis_icon_utils.h"
42 #include "kis_dlg_brush_hud_config.h"
43 #include "kis_brush_hud_properties_config.h"
44 #include "kis_elided_label.h"
45 
46 #include "kis_canvas2.h"
47 #include "KisViewManager.h"
48 #include "kactioncollection.h"
49 
50 #include "kis_debug.h"
51 
52 
53 struct KisBrushHud::Private
54 {
55     QPointer<KisElidedLabel> lblPresetName;
56     QPointer<QLabel> lblPresetIcon;
57     QPointer<QWidget> wdgProperties;
58     QPointer<QScrollArea> wdgPropertiesArea;
59     QPointer<QVBoxLayout> propertiesLayout;
60     QPointer<QToolButton> btnReloadPreset;
61     QPointer<QToolButton> btnConfigure;
62 
63     KisCanvasResourceProvider *provider;
64 
65     KisSignalAutoConnectionsStore connections;
66     KisSignalAutoConnectionsStore presetConnections;
67 
68     KisPaintOpPresetSP currentPreset;
69 };
70 
KisBrushHud(KisCanvasResourceProvider * provider,QWidget * parent)71 KisBrushHud::KisBrushHud(KisCanvasResourceProvider *provider, QWidget *parent)
72     : QWidget(parent, Qt::FramelessWindowHint),
73       m_d(new Private)
74 {
75     m_d->provider = provider;
76 
77     QVBoxLayout *layout = new QVBoxLayout();
78 
79     QHBoxLayout *labelLayout = new QHBoxLayout();
80     m_d->lblPresetIcon = new QLabel(this);
81     const QSize iconSize = QSize(22,22);
82     m_d->lblPresetIcon->setMinimumSize(iconSize);
83     m_d->lblPresetIcon->setMaximumSize(iconSize);
84     m_d->lblPresetIcon->setScaledContents(true);
85 
86     m_d->lblPresetName = new KisElidedLabel("<Preset Name>", Qt::ElideMiddle, this);
87 
88     m_d->btnReloadPreset = new QToolButton(this);
89     m_d->btnReloadPreset->setAutoRaise(true);
90 
91     m_d->btnConfigure = new QToolButton(this);
92     m_d->btnConfigure->setAutoRaise(true);
93 
94     connect(m_d->btnReloadPreset, SIGNAL(clicked()), SLOT(slotReloadPreset()));
95     connect(m_d->btnConfigure, SIGNAL(clicked()), SLOT(slotConfigBrushHud()));
96 
97     labelLayout->addWidget(m_d->lblPresetIcon);
98     labelLayout->addWidget(m_d->lblPresetName);
99     labelLayout->addWidget(m_d->btnReloadPreset);
100     labelLayout->addWidget(m_d->btnConfigure);
101 
102     layout->addLayout(labelLayout);
103 
104     m_d->wdgPropertiesArea = new QScrollArea(this);
105     m_d->wdgPropertiesArea->setAlignment(Qt::AlignLeft | Qt::AlignTop);
106     m_d->wdgPropertiesArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
107 
108     m_d->wdgPropertiesArea->setWidgetResizable(true);
109 
110     m_d->wdgProperties = new QWidget(this);
111     m_d->propertiesLayout = new QVBoxLayout(this);
112     m_d->propertiesLayout->setSpacing(0);
113     m_d->propertiesLayout->setContentsMargins(0, 0, 22, 0);
114     m_d->propertiesLayout->setSizeConstraint(QLayout::SetMinimumSize);
115 
116     // not adding any widgets until explicitly requested
117 
118     m_d->wdgProperties->setLayout(m_d->propertiesLayout);
119     m_d->wdgPropertiesArea->setWidget(m_d->wdgProperties);
120     layout->addWidget(m_d->wdgPropertiesArea);
121 
122     // unfortunately the sizeHint() function of QScrollArea is pretty broken
123     // and it would add another event loop iteration to react to it anyway,
124     // so let's just catch LayoutRequest events from the properties widget directly
125     m_d->wdgProperties->installEventFilter(this);
126 
127     updateIcons();
128 
129     setLayout(layout);
130     setCursor(Qt::ArrowCursor);
131 
132     // Prevent tablet events from being captured by the canvas
133     setAttribute(Qt::WA_NoMousePropagation, true);
134 }
135 
136 
137 
~KisBrushHud()138 KisBrushHud::~KisBrushHud()
139 {
140 }
141 
sizeHint() const142 QSize KisBrushHud::sizeHint() const
143 {
144     QSize size = QWidget::sizeHint();
145     return QSize(size.width(), parentWidget()->height());
146 }
147 
updateIcons()148 void KisBrushHud::updateIcons()
149 {
150     this->setPalette(qApp->palette());
151     for(int i=0; i<this->children().size(); i++) {
152         QWidget *w = qobject_cast<QWidget*>(this->children().at(i));
153         if (w) {
154             w->setPalette(qApp->palette());
155         }
156     }
157     for(int i=0; i<m_d->wdgProperties->children().size(); i++) {
158         KisUniformPaintOpPropertyWidget *w = qobject_cast<KisUniformPaintOpPropertyWidget*>(m_d->wdgProperties->children().at(i));
159         if (w) {
160             w->slotThemeChanged(qApp->palette());
161         }
162     }
163     m_d->btnReloadPreset->setIcon(KisIconUtils::loadIcon("view-refresh"));
164     m_d->btnConfigure->setIcon(KisIconUtils::loadIcon("applications-system"));
165 }
166 
slotReloadProperties()167 void KisBrushHud::slotReloadProperties()
168 {
169     m_d->presetConnections.clear();
170     clearProperties();
171     updateProperties();
172 }
173 
clearProperties() const174 void KisBrushHud::clearProperties() const
175 {
176     while (m_d->propertiesLayout->count()) {
177         QLayoutItem *item = m_d->propertiesLayout->takeAt(0);
178 
179         QWidget *w = item->widget();
180         if (w) {
181             w->deleteLater();
182         }
183 
184         delete item;
185     }
186 
187     m_d->currentPreset.clear();
188 }
189 
updateProperties()190 void KisBrushHud::updateProperties()
191 {
192     KisPaintOpPresetSP preset = m_d->provider->currentPreset();
193 
194     if (preset == m_d->currentPreset) return;
195 
196     m_d->presetConnections.clear();
197     clearProperties();
198 
199     m_d->currentPreset = preset;
200     m_d->presetConnections.addConnection(
201         m_d->currentPreset->updateProxy(), SIGNAL(sigUniformPropertiesChanged()),
202         this, SLOT(slotReloadProperties()));
203 
204     m_d->lblPresetIcon->setPixmap(QPixmap::fromImage(preset->image()));
205     m_d->lblPresetName->setLongText(preset->name());
206 
207     QList<KisUniformPaintOpPropertySP> properties;
208 
209     {
210         QList<KisUniformPaintOpPropertySP> allProperties = preset->uniformProperties();
211         QList<KisUniformPaintOpPropertySP> discardedProperties;
212 
213         KisBrushHudPropertiesConfig cfg;
214         cfg.filterProperties(preset->paintOp().id(),
215                              allProperties,
216                              &properties,
217                              &discardedProperties);
218     }
219 
220     Q_FOREACH(auto property, properties) {
221         QWidget *w = 0;
222 
223         if (!property->isVisible()) continue;
224 
225         if (property->type() == KisUniformPaintOpProperty::Int) {
226             w = new KisUniformPaintOpPropertyIntSlider(property, m_d->wdgProperties);
227         } else if (property->type() == KisUniformPaintOpProperty::Double) {
228             w = new KisUniformPaintOpPropertyDoubleSlider(property, m_d->wdgProperties);
229         } else if (property->type() == KisUniformPaintOpProperty::Bool) {
230             w = new KisUniformPaintOpPropertyCheckBox(property, m_d->wdgProperties);
231         } else if (property->type() == KisUniformPaintOpProperty::Combo) {
232             w = new KisUniformPaintOpPropertyComboBox(property, m_d->wdgProperties);
233         }
234 
235         if (w) {
236             w->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
237             m_d->propertiesLayout->addWidget(w);
238         }
239     }
240 
241     m_d->propertiesLayout->addStretch();
242 }
243 
showEvent(QShowEvent * event)244 void KisBrushHud::showEvent(QShowEvent *event)
245 {
246     m_d->connections.clear();
247     m_d->connections.addUniqueConnection(
248         m_d->provider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
249         this, SLOT(slotCanvasResourceChanged(int,QVariant)));
250 
251     updateProperties();
252 
253     QWidget::showEvent(event);
254 }
255 
hideEvent(QHideEvent * event)256 void KisBrushHud::hideEvent(QHideEvent *event)
257 {
258     m_d->connections.clear();
259     QWidget::hideEvent(event);
260 
261     clearProperties();
262 }
263 
slotCanvasResourceChanged(int key,const QVariant & resource)264 void KisBrushHud::slotCanvasResourceChanged(int key, const QVariant &resource)
265 {
266     Q_UNUSED(resource);
267 
268     if (key == KisCanvasResourceProvider::CurrentPaintOpPreset) {
269         updateProperties();
270     }
271 }
272 
paintEvent(QPaintEvent * event)273 void KisBrushHud::paintEvent(QPaintEvent *event)
274 {
275     QColor bgColor = palette().color(QPalette::Window);
276 
277     QPainter painter(this);
278     painter.fillRect(rect() & event->rect(), bgColor);
279     painter.end();
280 
281     QWidget::paintEvent(event);
282 }
283 
event(QEvent * event)284 bool KisBrushHud::event(QEvent *event)
285 {
286     switch (event->type()) {
287     case QEvent::TabletPress:
288     case QEvent::TabletMove:
289     case QEvent::TabletRelease:
290         // Allow the tablet event to be translated to a mouse event on certain platforms
291         break;
292     case QEvent::MouseButtonPress:
293     case QEvent::MouseMove:
294     case QEvent::MouseButtonRelease:
295     case QEvent::Wheel:
296         event->accept();
297         return true;
298     case QEvent::LayoutRequest:
299         // resize when our layout determined a new recommended size
300         resize(sizeHint());
301         return true;
302     default:
303         break;
304     }
305 
306     return QWidget::event(event);
307 }
308 
eventFilter(QObject * watched,QEvent * event)309 bool KisBrushHud::eventFilter(QObject *watched, QEvent *event)
310 {
311     // LayoutRequest event is sent from a layout to its parent widget
312     // when size requirements have been determined, i.e. sizeHint is available
313     if (watched == m_d->wdgProperties && event->type() == QEvent::LayoutRequest)
314     {
315         int totalMargin = 2 * m_d->wdgPropertiesArea->frameWidth();
316         m_d->wdgPropertiesArea->setMinimumWidth(m_d->wdgProperties->sizeHint().width() + totalMargin);
317     }
318     return false;
319 }
320 
slotConfigBrushHud()321 void KisBrushHud::slotConfigBrushHud()
322 {
323     if (!m_d->currentPreset) return;
324 
325     KisDlgConfigureBrushHud dlg(m_d->currentPreset);
326     dlg.exec();
327 
328     slotReloadProperties();
329 }
330 
slotReloadPreset()331 void KisBrushHud::slotReloadPreset()
332 {
333     KisCanvas2* canvas = dynamic_cast<KisCanvas2*>(m_d->provider->canvas());
334     KIS_ASSERT_RECOVER_RETURN(canvas);
335     canvas->viewManager()->actionCollection()->action("reload_preset_action")->trigger();
336 }
337