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 * const 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);