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 | |
34 | class Shadows::Private |
35 | { |
36 | public: |
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 | |
74 | class ShadowsSingleton |
75 | { |
76 | public: |
77 | ShadowsSingleton() |
78 | { |
79 | } |
80 | |
81 | Shadows self; |
82 | }; |
83 | |
84 | K_GLOBAL_STATIC(ShadowsSingleton, privateShadowsSelf) |
85 | |
86 | Shadows::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 | |
94 | Shadows *Shadows::self() |
95 | { |
96 | return &privateShadowsSelf->self; |
97 | } |
98 | |
99 | void 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 | |
111 | void 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 | |
126 | void 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 | |
135 | void 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 | |
144 | void 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 | |
162 | QPixmap 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 | |
170 | void 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 | |
193 | void 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 | |
323 | void 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 | |
346 | void 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 | |
366 | void 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 | |
375 | bool Shadows::enabled() const |
376 | { |
377 | return hasElement("shadow-left" ); |
378 | } |
379 | |
380 | #include "shadows.moc" |
381 | |
382 | |