2 * Copyright © 2006-2007 Fredrik Höglund <fredrik@kde.org>
4 * Parts of this file are based on code from xserver/dix/glyphcurs.c
7 * Copyright © 1987, 1998 The Open Group
8 * Copyright © 1987 Digital Equipment Corporation
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public
12 * License version 2 or at your option version 3 as published
13 * by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; see the file COPYING. If not, write to
22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
34 #include <X11/Xutil.h>
35 #include <X11/cursorfont.h>
36 #include <X11/Xlibint.h>
38 #include "legacytheme.h"
44 // Borrowed from xc/lib/Xcursor/library.c
45 static const char *standard_names
[] = {
47 "X_cursor", "arrow", "based_arrow_down", "based_arrow_up",
48 "boat", "bogosity", "bottom_left_corner", "bottom_right_corner",
49 "bottom_side", "bottom_tee", "box_spiral", "center_ptr",
50 "circle", "clock", "coffee_mug", "cross",
53 "cross_reverse", "crosshair", "diamond_cross", "dot",
54 "dotbox", "double_arrow", "draft_large", "draft_small",
55 "draped_box", "exchange", "fleur", "gobbler",
56 "gumby", "hand1", "hand2", "heart",
59 "icon", "iron_cross", "left_ptr", "left_side",
60 "left_tee", "leftbutton", "ll_angle", "lr_angle",
61 "man", "middlebutton", "mouse", "pencil",
62 "pirate", "plus", "question_arrow", "right_ptr",
65 "right_side", "right_tee", "rightbutton", "rtl_logo",
66 "sailboat", "sb_down_arrow", "sb_h_double_arrow", "sb_left_arrow",
67 "sb_right_arrow", "sb_up_arrow", "sb_v_double_arrow", "shuttle",
68 "sizing", "spider", "spraycan", "star",
71 "target", "tcross", "top_left_arrow", "top_left_corner",
72 "top_right_corner", "top_side", "top_tee", "trek",
73 "ul_angle", "umbrella", "ur_angle", "watch",
81 CursorBitmap(const char * const *xpm
, const QPoint
&hotspot
)
82 : xpm(xpm
), hotspot(hotspot
) {}
83 const char * const *xpm
;
95 class LegacyTheme::Private
98 static int cursorShape(const QString
&name
);
99 static CursorMetrics
cursorMetrics(int shape
);
100 static QImage
fontImage(const QString
&name
, int *xhot
= 0, int *yhot
= 0);
101 static QImage
bitmapImage(const QString
&name
, int *xhot
= 0, int *yhot
= 0);
104 static QHash
<QString
, int> shapes
;
105 static QHash
<QString
, CursorBitmap
*> bitmaps
;
106 static XFontStruct
*xfs
;
109 QHash
<QString
, int> LegacyTheme::Private::shapes
;
110 QHash
<QString
, CursorBitmap
*> LegacyTheme::Private::bitmaps
;
111 XFontStruct
*LegacyTheme::Private::xfs
= NULL
;
114 int LegacyTheme::Private::cursorShape(const QString
&name
)
116 // A font cursor is created from two glyphs; a shape glyph and a mask glyph
117 // stored in pairs in the font, with the shape glyph first. There's only one
118 // name for each pair. This function always returns the index for the
120 if (shapes
.isEmpty())
122 int num
= XC_num_glyphs
/ 2;
123 shapes
.reserve(num
+ 5);
125 for (int i
= 0; i
< num
; ++i
)
126 shapes
.insert(standard_names
[i
], i
<< 1);
128 // Qt uses alternative names for some core cursors
129 shapes
.insert("size_all", XC_fleur
);
130 shapes
.insert("up_arrow", XC_center_ptr
);
131 shapes
.insert("ibeam", XC_xterm
);
132 shapes
.insert("wait", XC_watch
);
133 shapes
.insert("pointing_hand", XC_hand2
);
136 return shapes
.value(name
, -1);
140 CursorMetrics
LegacyTheme::Private::cursorMetrics(int shape
)
142 CursorMetrics metrics
;
144 // Get the metrics for the mask glyph
145 XCharStruct xcs
= xfs
->per_char
[shape
+ 1];
147 // Compute the width, height and cursor hotspot from the glyph metrics.
148 // Note that the X11 definition of right bearing is the right-ward distance
149 // from the X origin to the X coordinate of the rightmost pixel in the glyph.
150 // In QFontMetrics the right bearing is defined as the left-ward distance
151 // from the X origin of the hypothetical subsequent glyph to the X coordinate
152 // of the rightmost pixel in this glyph.
153 metrics
.width
= xcs
.rbearing
- xcs
.lbearing
;
154 metrics
.height
= xcs
.ascent
+ xcs
.descent
;
156 // The cursor hotspot is defined as the X and Y origin of the glyph.
157 if (xcs
.lbearing
< 0) {
158 metrics
.xhot
= -xcs
.lbearing
;
159 if (xcs
.rbearing
< 0) // rbearing can only be < 0 when lbearing < 0
160 metrics
.width
-= xcs
.rbearing
;
161 } else { // If the ink starts to the right of the X coordinate.
162 metrics
.width
+= xcs
.lbearing
; // With cursors this is probably never the case in practice,
163 metrics
.xhot
= 0; // since it would put the hotspot outside the image.
166 if (xcs
.ascent
> 0) {
167 metrics
.yhot
= xcs
.ascent
;
168 if (xcs
.descent
< 0) // descent can only be < 0 when ascent > 0
169 metrics
.height
-= xcs
.descent
;
170 } else { // If the ink starts below the baseline.
171 metrics
.height
-= xcs
.ascent
; // With cursors this is probably never the case in practice,
172 metrics
.yhot
= 0; // since it would put the hotspot outside the image.
179 QImage
LegacyTheme::Private::fontImage(const QString
&name
, int *xhot_return
, int *yhot_return
)
181 // Note that the reason we need this function is that XcursorLibraryLoadImage()
182 // doesn't work with the core theme, and X11 doesn't provide any other means to
183 // obtain the image of a cursor other than that of the active one.
184 Display
*dpy
= QX11Info::display();
187 Q_ASSERT(name
.length() > 0);
189 // Make sure the cursor font is loaded
190 if (dpy
->cursor_font
== None
)
191 dpy
->cursor_font
= XLoadFont(dpy
, CURSORFONT
);
193 // Query the font metrics for the cursor font
194 if (dpy
->cursor_font
&& !xfs
)
195 xfs
= XQueryFont(dpy
, dpy
->cursor_font
);
197 // Get the glyph shape index for the cursor name
198 int shape
= cursorShape(name
);
200 // If we there's no matching cursor in the font, if the font couldn't be loaded,
201 // or the font metrics couldn't be queried, return a NULL image.
202 if (shape
== -1 || dpy
->cursor_font
== None
|| xfs
== NULL
)
205 // Get the cursor metrics for the shape
206 CursorMetrics m
= cursorMetrics(shape
);
208 // Get the 16 bit bitmap and mask glyph indexes
209 char source2b
[2], mask2b
[2];
210 source2b
[0] = uchar(shape
>> 8);
211 source2b
[1] = uchar(shape
& 0xff);
213 mask2b
[0] = uchar((shape
+ 1) >> 8);
214 mask2b
[1] = uchar((shape
+ 1) & 0xff);
216 // Create an 8 bit pixmap and draw the glyphs on the pixmap
217 Pixmap pm
= XCreatePixmap(dpy
, QX11Info::appRootWindow(), m
.width
, m
.height
, 8);
218 GC gc
= XCreateGC(dpy
, pm
, 0, NULL
);
219 XSetFont(dpy
, gc
, dpy
->cursor_font
);
221 enum Colors
{ BackgroundColor
= 0, MaskColor
= 1, ShapeColor
= 2 };
223 // Clear the pixmap to transparent
224 XSetForeground(dpy
, gc
, BackgroundColor
);
225 XFillRectangle(dpy
, pm
, gc
, 0, 0, m
.width
, m
.height
);
228 XSetForeground(dpy
, gc
, MaskColor
);
229 XDrawString16(dpy
, pm
, gc
, m
.xhot
, m
.yhot
, (XChar2b
*)mask2b
, 1);
232 XSetForeground(dpy
, gc
, ShapeColor
);
233 XDrawString16(dpy
, pm
, gc
, m
.xhot
, m
.yhot
, (XChar2b
*)source2b
, 1);
236 // Convert the pixmap to an XImage
237 XImage
*ximage
= XGetImage(dpy
, pm
, 0, 0, m
.width
, m
.height
, AllPlanes
, ZPixmap
);
238 XFreePixmap(dpy
, pm
);
240 // Background color, mask color, shape color
241 static const quint32 color
[] =
243 0x00000000, // black, fully transparent
244 0xffffffff, // white, fully opaque
245 0xff000000, // black, fully opaque
248 // Convert the XImage to a QImage
249 image
= QImage(ximage
->width
, ximage
->height
, QImage::Format_ARGB32_Premultiplied
);
250 for (int y
= 0; y
< ximage
->height
; y
++)
252 quint8
*s
= reinterpret_cast<quint8
*>(ximage
->data
+ (y
* ximage
->bytes_per_line
));
253 quint32
*d
= reinterpret_cast<quint32
*>(image
.scanLine(y
));
255 for (int x
= 0; x
< ximage
->width
; x
++)
256 *(d
++) = color
[*(s
++)];
262 XDestroyImage(ximage
);
264 // Return the cursor hotspot to the caller
266 *xhot_return
= m
.xhot
;
269 *yhot_return
= m
.yhot
;
275 QImage
LegacyTheme::Private::bitmapImage(const QString
&name
, int *xhot_return
, int *yhot_return
)
277 const CursorBitmap
*bitmap
;
280 if (bitmaps
.isEmpty())
282 // These bitmap images are created from the XPM's in bitmaps.h.
284 bitmaps
.insert("size_ver", new CursorBitmap(size_ver_xpm
, QPoint( 8, 8)));
285 bitmaps
.insert("size_hor", new CursorBitmap(size_hor_xpm
, QPoint( 8, 8)));
286 bitmaps
.insert("size_bdiag", new CursorBitmap(size_bdiag_xpm
, QPoint( 8, 8)));
287 bitmaps
.insert("size_fdiag", new CursorBitmap(size_fdiag_xpm
, QPoint( 8, 8)));
288 bitmaps
.insert("left_ptr_watch", new CursorBitmap(busy_xpm
, QPoint( 0, 0)));
289 bitmaps
.insert("forbidden", new CursorBitmap(forbidden_xpm
, QPoint(10, 10)));
290 //bitmaps.insert("hand2", new CursorBitmap(kde_hand_xpm, QPoint( 7, 0)));
291 //bitmaps.insert("pointing_hand", new CursorBitmap(kde_hand_xpm, QPoint( 7, 0)));
292 bitmaps
.insert("whats_this", new CursorBitmap(whats_this_xpm
, QPoint( 0, 0)));
293 bitmaps
.insert("split_h", new CursorBitmap(split_h_xpm
, QPoint(16, 16)));
294 bitmaps
.insert("split_v", new CursorBitmap(split_v_xpm
, QPoint(16, 16)));
295 bitmaps
.insert("openhand", new CursorBitmap(openhand_xpm
, QPoint( 8, 8)));
296 bitmaps
.insert("closedhand", new CursorBitmap(closedhand_xpm
, QPoint( 8, 8)));
299 if ((bitmap
= bitmaps
.value(name
)))
301 image
= QPixmap(bitmap
->xpm
).toImage()
302 .convertToFormat(QImage::Format_ARGB32_Premultiplied
);
304 // Return the hotspot to the caller
306 *xhot_return
= bitmap
->hotspot
.x();
309 *yhot_return
= bitmap
->hotspot
.y();
317 // ---------------------------------------------------------------------------
321 LegacyTheme::LegacyTheme()
322 : CursorTheme(i18n("KDE Classic"), i18n("The default cursor theme in KDE 2 and 3"))
324 setName("#kde_legacy#");
328 LegacyTheme::~LegacyTheme()
333 QImage
LegacyTheme::loadImage(const QString
&name
, int) const
337 // Try to load the image from a bitmap first
338 image
= Private::bitmapImage(name
);
340 // If that fails, try to load it from the cursor font
342 image
= Private::fontImage(name
);
344 // Autocrop the image if we created it from a bitmap
345 image
= autoCropImage(image
);
351 QCursor
LegacyTheme::loadCursor(const QString
&name
, int) const
354 int xhot
= 0, yhot
= 0;
356 // Try to load the image from a bitmap first
357 image
= Private::bitmapImage(name
, &xhot
, &yhot
);
359 // If that fails, try to load it from the cursor font
361 image
= Private::fontImage(name
, &xhot
, &yhot
);
363 // Return the default cursor if that fails as well
367 QPixmap pixmap
= QPixmap::fromImage(image
);
368 QCursor
cursor(pixmap
, xhot
, yhot
);
370 setCursorName(cursor
, name
);