1/*
2* Copyright 2011 by Aaron Seigo <aseigo@kde.org>
3*
4* This program is free software; you can redistribute it and/or modify
5* it under the terms of the GNU Library General Public License version 2,
6* or (at your option) any later version.
7*
8* This program is distributed in the hope that it will be useful,
9* but WITHOUT ANY WARRANTY; without even the implied warranty of
10* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11* GNU General Public License for more details
12*
13* You should have received a copy of the GNU Library General Public
14* License along with this program; if not, write to the
15* Free Software Foundation, Inc.,
16* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17*/
18
19#include "shadows.h"
20
21#include <QWidget>
22#include <QPainter>
23
24#ifdef Q_WS_X11
25#include <QX11Info>
26#include <X11/Xatom.h>
27#include <X11/Xlib.h>
28#include <fixx11h.h>
29#endif
30
31#include <kdebug.h>
32#include <kglobal.h>
33
34class Shadows::Private
35{
36public:
37 Private(Shadows *shadows)
38 : q(shadows),
39 m_managePixmaps(false)
40 {
41 }
42
43 ~Private()
44 {
45 clearPixmaps();
46 }
47
48 void clearPixmaps();
49 void setupPixmaps();
50 void initPixmap(const QString &element);
51 QPixmap initEmptyPixmap(const QSize &size);
52 void updateShadow(const QWidget *window, Plasma::FrameSvg::EnabledBorders);
53 void clearShadow(const QWidget *window);
54 void updateShadows();
55 void windowDestroyed(QObject *deletedObject);
56 void setupData(Plasma::FrameSvg::EnabledBorders enabledBorders);
57
58 Shadows *q;
59 QList<QPixmap> m_shadowPixmaps;
60
61 QPixmap m_emptyCornerPix;
62 QPixmap m_emptyCornerLeftPix;
63 QPixmap m_emptyCornerTopPix;
64 QPixmap m_emptyCornerRightPix;
65 QPixmap m_emptyCornerBottomPix;
66 QPixmap m_emptyVerticalPix;
67 QPixmap m_emptyHorizontalPix;
68
69 QHash<Plasma::FrameSvg::EnabledBorders, QVector<unsigned long> > data;
70 QHash<const QWidget *, Plasma::FrameSvg::EnabledBorders> m_windows;
71 bool m_managePixmaps;
72};
73
74class ShadowsSingleton
75{
76public:
77 ShadowsSingleton()
78 {
79 }
80
81 Shadows self;
82};
83
84K_GLOBAL_STATIC(ShadowsSingleton, privateShadowsSelf)
85
86Shadows::Shadows(QObject *parent, const QString &prefix)
87 : Plasma::Svg(parent),
88 d(new Private(this))
89{
90 setImagePath(prefix);
91 connect(this, SIGNAL(repaintNeeded()), this, SLOT(updateShadows()));
92}
93
94Shadows *Shadows::self()
95{
96 return &privateShadowsSelf->self;
97}
98
99void Shadows::addWindow(const QWidget *window, Plasma::FrameSvg::EnabledBorders enabledBorders)
100{
101 if (!window || !window->isWindow()) {
102 return;
103 }
104
105 d->m_windows[window] = enabledBorders;
106 d->updateShadow(window, enabledBorders);
107 connect(window, SIGNAL(destroyed(QObject*)),
108 this, SLOT(windowDestroyed(QObject*)), Qt::UniqueConnection);
109}
110
111void Shadows::removeWindow(const QWidget *window)
112{
113 if (!d->m_windows.contains(window)) {
114 return;
115 }
116
117 d->m_windows.remove(window);
118 disconnect(window, 0, this, 0);
119 d->clearShadow(window);
120
121 if (d->m_windows.isEmpty()) {
122 d->clearPixmaps();
123 }
124}
125
126void Shadows::Private::windowDestroyed(QObject *deletedObject)
127{
128 m_windows.remove(static_cast<QWidget *>(deletedObject));
129
130 if (m_windows.isEmpty()) {
131 clearPixmaps();
132 }
133}
134
135void Shadows::Private::updateShadows()
136{
137 setupPixmaps();
138 QHash<const QWidget *, Plasma::FrameSvg::EnabledBorders>::const_iterator i;
139 for (i = m_windows.constBegin(); i != m_windows.constEnd(); ++i) {
140 updateShadow(i.key(), i.value());
141 }
142}
143
144void Shadows::Private::initPixmap(const QString &element)
145{
146#ifdef Q_WS_X11
147 QPixmap pix = q->pixmap(element);
148 if (!pix.isNull() && pix.handle() == 0) {
149 Pixmap xPix = XCreatePixmap(QX11Info::display(), QX11Info::appRootWindow(), pix.width(), pix.height(), 32);
150 QPixmap tempPix = QPixmap::fromX11Pixmap(xPix, QPixmap::ExplicitlyShared);
151 tempPix.fill(Qt::transparent);
152 QPainter p(&tempPix);
153 p.drawPixmap(QPoint(0, 0), pix);
154 m_shadowPixmaps << tempPix;
155 m_managePixmaps = true;
156 } else {
157 m_shadowPixmaps << pix;
158 }
159#endif
160}
161
162QPixmap Shadows::Private::initEmptyPixmap(const QSize &size)
163{
164 Pixmap emptyXPix = XCreatePixmap(QX11Info::display(), QX11Info::appRootWindow(), size.width(), size.height(), 32);
165 QPixmap tempEmptyPix = QPixmap::fromX11Pixmap(emptyXPix, QPixmap::ExplicitlyShared);
166 tempEmptyPix.fill(Qt::transparent);
167 return tempEmptyPix;
168}
169
170void Shadows::Private::setupPixmaps()
171{
172 clearPixmaps();
173 initPixmap("shadow-top");
174 initPixmap("shadow-topright");
175 initPixmap("shadow-right");
176 initPixmap("shadow-bottomright");
177 initPixmap("shadow-bottom");
178 initPixmap("shadow-bottomleft");
179 initPixmap("shadow-left");
180 initPixmap("shadow-topleft");
181
182 m_emptyCornerPix = initEmptyPixmap(QSize(1,1));
183 m_emptyCornerLeftPix = initEmptyPixmap(QSize(q->elementSize("shadow-topleft").width(), 1));
184 m_emptyCornerTopPix = initEmptyPixmap(QSize(1, q->elementSize("shadow-topleft").height()));
185 m_emptyCornerRightPix = initEmptyPixmap(QSize(q->elementSize("shadow-bottomright").width(), 1));
186 m_emptyCornerBottomPix = initEmptyPixmap(QSize(1, q->elementSize("shadow-bottomright").height()));
187 m_emptyVerticalPix = initEmptyPixmap(QSize(1, q->elementSize("shadow-left").height()));
188 m_emptyHorizontalPix = initEmptyPixmap(QSize(q->elementSize("shadow-top").width(), 1));
189
190}
191
192
193void Shadows::Private::setupData(Plasma::FrameSvg::EnabledBorders enabledBorders)
194{
195#ifdef Q_WS_X11
196 //shadow-top
197 if (enabledBorders & Plasma::FrameSvg::TopBorder) {
198 data[enabledBorders] << m_shadowPixmaps[0].handle();
199 } else {
200 data[enabledBorders] << m_emptyHorizontalPix.handle();
201 }
202
203 //shadow-topright
204 if (enabledBorders & Plasma::FrameSvg::TopBorder &&
205 enabledBorders & Plasma::FrameSvg::RightBorder) {
206 data[enabledBorders] << m_shadowPixmaps[1].handle();
207 } else if (enabledBorders & Plasma::FrameSvg::TopBorder) {
208 data[enabledBorders] << m_emptyCornerTopPix.handle();
209 } else if (enabledBorders & Plasma::FrameSvg::RightBorder) {
210 data[enabledBorders] << m_emptyCornerRightPix.handle();
211 } else {
212 data[enabledBorders] << m_emptyCornerPix.handle();
213 }
214
215 //shadow-right
216 if (enabledBorders & Plasma::FrameSvg::RightBorder) {
217 data[enabledBorders] << m_shadowPixmaps[2].handle();
218 } else {
219 data[enabledBorders] << m_emptyVerticalPix.handle();
220 }
221
222 //shadow-bottomright
223 if (enabledBorders & Plasma::FrameSvg::BottomBorder &&
224 enabledBorders & Plasma::FrameSvg::RightBorder) {
225 data[enabledBorders] << m_shadowPixmaps[3].handle();
226 } else if (enabledBorders & Plasma::FrameSvg::BottomBorder) {
227 data[enabledBorders] << m_emptyCornerBottomPix.handle();
228 } else if (enabledBorders & Plasma::FrameSvg::RightBorder) {
229 data[enabledBorders] << m_emptyCornerRightPix.handle();
230 } else {
231 data[enabledBorders] << m_emptyCornerPix.handle();
232 }
233
234 //shadow-bottom
235 if (enabledBorders & Plasma::FrameSvg::BottomBorder) {
236 data[enabledBorders] << m_shadowPixmaps[4].handle();
237 } else {
238 data[enabledBorders] << m_emptyHorizontalPix.handle();
239 }
240
241 //shadow-bottomleft
242 if (enabledBorders & Plasma::FrameSvg::BottomBorder &&
243 enabledBorders & Plasma::FrameSvg::LeftBorder) {
244 data[enabledBorders] << m_shadowPixmaps[5].handle();
245 } else if (enabledBorders & Plasma::FrameSvg::BottomBorder) {
246 data[enabledBorders] << m_emptyCornerBottomPix.handle();
247 } else if (enabledBorders & Plasma::FrameSvg::LeftBorder) {
248 data[enabledBorders] << m_emptyCornerLeftPix.handle();
249 } else {
250 data[enabledBorders] << m_emptyCornerPix.handle();
251 }
252
253 //shadow-left
254 if (enabledBorders & Plasma::FrameSvg::LeftBorder) {
255 data[enabledBorders] << m_shadowPixmaps[6].handle();
256 } else {
257 data[enabledBorders] << m_emptyVerticalPix.handle();
258 }
259
260 //shadow-topleft
261 if (enabledBorders & Plasma::FrameSvg::TopBorder &&
262 enabledBorders & Plasma::FrameSvg::LeftBorder) {
263 data[enabledBorders] << m_shadowPixmaps[7].handle();
264 } else if (enabledBorders & Plasma::FrameSvg::TopBorder) {
265 data[enabledBorders] << m_emptyCornerTopPix.handle();
266 } else if (enabledBorders & Plasma::FrameSvg::LeftBorder) {
267 data[enabledBorders] << m_emptyCornerLeftPix.handle();
268 } else {
269 data[enabledBorders] << m_emptyCornerPix.handle();
270 }
271#endif
272
273 int left, top, right, bottom = 0;
274
275 QSize marginHint;
276 if (enabledBorders & Plasma::FrameSvg::TopBorder) {
277 marginHint = q->elementSize("shadow-hint-top-margin");
278 if (marginHint.isValid()) {
279 top = marginHint.height();
280 } else {
281 top = m_shadowPixmaps[0].height(); // top
282 }
283 } else {
284 top = 1;
285 }
286
287 if (enabledBorders & Plasma::FrameSvg::RightBorder) {
288 marginHint = q->elementSize("shadow-hint-right-margin");
289 if (marginHint.isValid()) {
290 right = marginHint.width();
291 } else {
292 right = m_shadowPixmaps[2].width(); // right
293 }
294 } else {
295 right = 1;
296 }
297
298 if (enabledBorders & Plasma::FrameSvg::BottomBorder) {
299 marginHint = q->elementSize("shadow-hint-bottom-margin");
300 if (marginHint.isValid()) {
301 bottom = marginHint.height();
302 } else {
303 bottom = m_shadowPixmaps[4].height(); // bottom
304 }
305 } else {
306 bottom = 1;
307 }
308
309 if (enabledBorders & Plasma::FrameSvg::LeftBorder) {
310 marginHint = q->elementSize("shadow-hint-left-margin");
311 if (marginHint.isValid()) {
312 left = marginHint.width();
313 } else {
314 left = m_shadowPixmaps[6].width(); // left
315 }
316 } else {
317 left = 1;
318 }
319
320 data[enabledBorders] << top << right << bottom << left;
321}
322
323void Shadows::Private::clearPixmaps()
324{
325#ifdef Q_WS_X11
326 if (m_managePixmaps) {
327 foreach (const QPixmap &pixmap, m_shadowPixmaps) {
328 XFreePixmap(QX11Info::display(), pixmap.handle());
329 }
330
331 XFreePixmap(QX11Info::display(), m_emptyCornerPix.handle());
332 XFreePixmap(QX11Info::display(), m_emptyCornerBottomPix.handle());
333 XFreePixmap(QX11Info::display(), m_emptyCornerLeftPix.handle());
334 XFreePixmap(QX11Info::display(), m_emptyCornerRightPix.handle());
335 XFreePixmap(QX11Info::display(), m_emptyCornerTopPix.handle());
336 XFreePixmap(QX11Info::display(), m_emptyVerticalPix.handle());
337 XFreePixmap(QX11Info::display(), m_emptyHorizontalPix.handle());
338
339 m_managePixmaps = false;
340 }
341#endif
342 m_shadowPixmaps.clear();
343 data.clear();
344}
345
346void Shadows::Private::updateShadow(const QWidget *window, Plasma::FrameSvg::EnabledBorders enabledBorders)
347{
348#ifdef Q_WS_X11
349 if (m_shadowPixmaps.isEmpty()) {
350 setupPixmaps();
351 }
352
353 if (!data.contains(enabledBorders)) {
354 setupData(enabledBorders);
355 }
356
357 Display *dpy = QX11Info::display();
358 Atom atom = XInternAtom(dpy, "_KDE_NET_WM_SHADOW", False);
359
360 //kDebug() << "going to set the shadow of" << winId() << "to" << data;
361 XChangeProperty(dpy, window->winId(), atom, XA_CARDINAL, 32, PropModeReplace,
362 reinterpret_cast<const unsigned char *>(data[enabledBorders].constData()), data[enabledBorders].size());
363#endif
364}
365
366void Shadows::Private::clearShadow(const QWidget *window)
367{
368#ifdef Q_WS_X11
369 Display *dpy = QX11Info::display();
370 Atom atom = XInternAtom(dpy, "_KDE_NET_WM_SHADOW", False);
371 XDeleteProperty(dpy, window->winId(), atom);
372#endif
373}
374
375bool Shadows::enabled() const
376{
377 return hasElement("shadow-left");
378}
379
380#include "shadows.moc"
381
382