add more spacing
[personal-kdebase.git] / workspace / kcontrol / input / xcursor / legacytheme.cpp
blob1e1ecadfbc387f5e509adcb79e68ac5d6cda1ed5
1 /*
2 * Copyright © 2006-2007 Fredrik Höglund <fredrik@kde.org>
4 * Parts of this file are based on code from xserver/dix/glyphcurs.c
5 * in X.org,
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.
26 #include <KLocale>
28 #include <QCursor>
29 #include <QImage>
30 #include <QHash>
31 #include <QX11Info>
33 #include <X11/Xlib.h>
34 #include <X11/Xutil.h>
35 #include <X11/cursorfont.h>
36 #include <X11/Xlibint.h>
38 #include "legacytheme.h"
39 #include "bitmaps.h"
42 namespace {
44 // Borrowed from xc/lib/Xcursor/library.c
45 static const char *standard_names[] = {
46 /* 0 */
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",
52 /* 32 */
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",
58 /* 64 */
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",
64 /* 96 */
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",
70 /* 128 */
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",
74 "xterm",
79 struct CursorBitmap
81 CursorBitmap(const char * const *xpm, const QPoint &hotspot)
82 : xpm(xpm), hotspot(hotspot) {}
83 const char * const *xpm;
84 QPoint hotspot;
88 struct CursorMetrics
90 int xhot, yhot;
91 int width, height;
95 class LegacyTheme::Private
97 public:
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);
103 private:
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
119 // shape glyph.
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.
175 return metrics;
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();
185 QImage image;
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)
203 return image;
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);
227 // Draw the mask
228 XSetForeground(dpy, gc, MaskColor);
229 XDrawString16(dpy, pm, gc, m.xhot, m.yhot, (XChar2b*)mask2b, 1);
231 // Draw the shape
232 XSetForeground(dpy, gc, ShapeColor );
233 XDrawString16(dpy, pm, gc, m.xhot, m.yhot, (XChar2b*)source2b, 1);
234 XFreeGC(dpy, gc);
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++)];
259 // Free the XImage
260 free(ximage->data);
261 ximage->data = NULL;
262 XDestroyImage(ximage);
264 // Return the cursor hotspot to the caller
265 if (xhot_return)
266 *xhot_return = m.xhot;
268 if (yhot_return)
269 *yhot_return = m.yhot;
271 return image;
275 QImage LegacyTheme::Private::bitmapImage(const QString &name, int *xhot_return, int *yhot_return)
277 const CursorBitmap *bitmap;
278 QImage image;
280 if (bitmaps.isEmpty())
282 // These bitmap images are created from the XPM's in bitmaps.h.
283 bitmaps.reserve(13);
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
305 if (xhot_return)
306 *xhot_return = bitmap->hotspot.x();
308 if (yhot_return)
309 *yhot_return = bitmap->hotspot.y();
312 return image;
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
335 QImage image;
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
341 if (image.isNull())
342 image = Private::fontImage(name);
343 else
344 // Autocrop the image if we created it from a bitmap
345 image = autoCropImage(image);
347 return image;
351 QCursor LegacyTheme::loadCursor(const QString &name, int) const
353 QImage image;
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
360 if (image.isNull())
361 image = Private::fontImage(name, &xhot, &yhot);
363 // Return the default cursor if that fails as well
364 if (image.isNull())
365 return QCursor();
367 QPixmap pixmap = QPixmap::fromImage(image);
368 QCursor cursor(pixmap, xhot, yhot);
370 setCursorName(cursor, name);
371 return cursor;