You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdebase/kcontrol/input/xcursor/previewwidget.cpp

354 lines
9.3 KiB

/*
* Copyright (C) 2003 Fredrik Höglund <fredrik@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <kglobal.h>
#include <qwidget.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qstring.h>
#include <qcursor.h>
#include <kglobal.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xrender.h>
#include <X11/Xcursor/Xcursor.h>
#include "previewwidget.h"
extern bool qt_has_xft;
extern bool qt_use_xrender;
namespace {
// Preview cursors
const char *cursor_names[] =
{
"left_ptr",
"left_ptr_watch",
"watch",
"hand2",
"question_arrow",
"sb_h_double_arrow",
"sb_v_double_arrow",
"bottom_left_corner",
"bottom_right_corner",
"fleur",
"pirate",
"cross",
"X_cursor",
"right_ptr",
"right_side",
"right_tee",
"sb_right_arrow",
"sb_right_tee",
"base_arrow_down",
"base_arrow_up",
"bottom_side",
"bottom_tee",
"center_ptr",
"circle",
"dot",
"dot_box_mask",
"dot_box_mask",
"double_arrow",
"draped_box",
"left_side",
"left_tee",
"ll_angle",
"top_side",
"top_tee",
};
const int numCursors = 6; // The number of cursors in the above list to be previewed
const int previewSize = 24; // The cursor size to be used in the preview widget
const int cursorSpacing = 20;
}
class PreviewCursor
{
public:
PreviewCursor();
~PreviewCursor();
void load( const QString &, const QString & );
const Picture picture() const { return m_pict; }
const Cursor handle() const { return m_handle; }
const int width() const { return m_width; }
const int height() const { return m_height; }
private:
Picture createPicture( const XcursorImage* ) const;
void cropCursorImage( XcursorImage*& ) const;
Picture m_pict;
Cursor m_handle;
int m_width;
int m_height;
}; // class PreviewCursor
PreviewCursor::PreviewCursor() :
m_pict( 0 ), m_handle( 0 ), m_width( 0 ), m_height( 0 )
{
}
void PreviewCursor::load( const QString &name, const QString &theme )
{
Display *dpy = QPaintDevice::x11AppDisplay();
if ( m_pict ) XRenderFreePicture( dpy, m_pict );
if ( m_handle ) XFreeCursor( dpy, m_handle );
m_pict = 0;
m_handle = 0;
m_width = m_height = 0;
// Load the preview cursor image
XcursorImage *image =
XcursorLibraryLoadImage( name.latin1(), theme.latin1(), previewSize );
// If the theme doesn't have this cursor, load the default cursor for now
if ( !image )
image = XcursorLibraryLoadImage( "left_ptr", theme.latin1(), previewSize );
// TODO The old classic X cursors
if ( !image )
return;
// Auto-crop the image (some cursor themes use a fixed image size
// for all cursors, and doing this results in correctly centered images)
cropCursorImage( image );
m_pict = createPicture( image );
m_width = image->width;
m_height = image->height;
// Scale the image if its height is greater than 2x the requested size
if ( m_height > previewSize * 2.0 ) {
double factor = double( previewSize * 2.0 / m_height );
XTransform xform = {
{{ XDoubleToFixed(1.0), XDoubleToFixed(0), XDoubleToFixed(0) },
{ XDoubleToFixed(0), XDoubleToFixed(1.0), XDoubleToFixed(0) },
{ XDoubleToFixed(0), XDoubleToFixed(0), XDoubleToFixed(factor) }}
};
XRenderSetPictureTransform( dpy, m_pict, &xform );
m_width = int( m_width * factor );
m_height = int( m_height * factor );
}
// We don't need this image anymore
XcursorImageDestroy( image );
// Load the actual cursor we will use
int size = XcursorGetDefaultSize( dpy );
XcursorImages *images = XcursorLibraryLoadImages( name.latin1(), theme.latin1(), size );
if ( images ) {
m_handle = XcursorImagesLoadCursor( dpy, images );
XcursorImagesDestroy( images );
} else {
images = XcursorLibraryLoadImages( "left_ptr", theme.latin1(), size );
m_handle = XcursorImagesLoadCursor( dpy, images );
XcursorImagesDestroy( images );
}
}
PreviewCursor::~PreviewCursor()
{
if ( m_handle ) XFreeCursor( QPaintDevice::x11AppDisplay(), m_handle );
if ( m_pict ) XRenderFreePicture( QPaintDevice::x11AppDisplay(), m_pict );
}
Picture PreviewCursor::createPicture( const XcursorImage* image ) const
{
Display *dpy = QPaintDevice::x11AppDisplay();
XImage ximage;
ximage.width = image->width;
ximage.height = image->height;
ximage.xoffset = 0;
ximage.format = ZPixmap;
ximage.data = reinterpret_cast<char*>( image->pixels );
ximage.byte_order = ImageByteOrder( dpy );
ximage.bitmap_unit = 32;
ximage.bitmap_bit_order = ximage.byte_order;
ximage.bitmap_pad = 32;
ximage.depth = 32;
ximage.bits_per_pixel = 32;
ximage.bytes_per_line = image->width * 4;
ximage.red_mask = 0x00ff0000;
ximage.green_mask = 0x0000ff00;
ximage.blue_mask = 0x000000ff;
ximage.obdata = 0;
XInitImage( &ximage );
Pixmap pix = XCreatePixmap( dpy, DefaultRootWindow(dpy), ximage.width, ximage.height, 32 );
GC gc = XCreateGC( dpy, pix, 0, NULL );
XPutImage( dpy, pix, gc, &ximage, 0, 0, 0, 0, ximage.width, ximage.height );
XFreeGC( dpy, gc );
XRenderPictFormat *fmt = XRenderFindStandardFormat( dpy, PictStandardARGB32 );
Picture pict = XRenderCreatePicture( dpy, pix, fmt, 0, NULL );
XFreePixmap( dpy, pix );
return pict;
}
void PreviewCursor::cropCursorImage( XcursorImage *&image ) const
{
// Calculate the auto-crop rectangle
QRect r( QPoint( image->width, image->height ), QPoint() );
XcursorPixel *pixels = image->pixels;
for ( int y = 0; y < int(image->height); y++ ) {
for ( int x = 0; x < int(image->width); x++ ) {
if ( *(pixels++) >> 24 ) {
if ( x < r.left() ) r.setLeft( x );
if ( x > r.right() ) r.setRight( x );
if ( y < r.top() ) r.setTop( y );
if ( y > r.bottom() ) r.setBottom( y );
}
}
}
// Normalize the rectangle
r = r.normalize();
// Don't crop the image if the size isn't going to change
if ( r.width() == int( image->width ) && r.height() == int( image->height ) )
return;
// Create the new image
XcursorImage *cropped = XcursorImageCreate( r.width(), r.height() );
XcursorPixel *src = image->pixels + r.top() * image->width + r.left();
XcursorPixel *dst = cropped->pixels;
for ( int y = 0; y < r.height(); y++, src += (image->width - r.width()) ) {
for ( int x = 0; x < r.width(); x++ ) {
*(dst++) = *(src++);
}
}
// Destroy the original
XcursorImageDestroy( image );
image = cropped;
}
// ------------------------------------------------------------------------------------------------
PreviewWidget::PreviewWidget( QWidget *parent, const char *name )
: QWidget( parent, name )
{
cursors = new PreviewCursor* [ numCursors ];
for ( int i = 0; i < numCursors; i++ )
cursors[i] = new PreviewCursor;
current = -1;
setMouseTracking( true );
setFixedHeight( previewSize + 20 );
}
PreviewWidget::~PreviewWidget()
{
for ( int i = 0; i < numCursors; i++ )
delete cursors[i];
delete [] cursors;
}
void PreviewWidget::setTheme( const QString &theme )
{
setUpdatesEnabled( false );
int minHeight = previewSize + 20; // Minimum height of the preview widget
int maxHeight = height(); // Tallest cursor height
int maxWidth = previewSize; // Widest cursor width
for ( int i = 0; i < numCursors; i++ ) {
cursors[i]->load( cursor_names[i], theme.latin1() );
if ( cursors[i]->width() > maxWidth )
maxWidth = cursors[i]->width();
if ( cursors[i]->height() > maxHeight )
maxHeight = cursors[i]->height();
}
current = -1;
setFixedSize( ( maxWidth + cursorSpacing ) * numCursors, kMax( maxHeight, minHeight ) );
setUpdatesEnabled( true );
repaint( false );
}
void PreviewWidget::paintEvent( QPaintEvent * )
{
QPixmap buffer( size() );
QPainter p( &buffer );
p.fillRect( rect(), colorGroup().brush( QColorGroup::Background ) );
Picture dest;
if ( !qt_has_xft || !qt_use_xrender ) {
XRenderPictFormat *fmt = XRenderFindVisualFormat( x11Display(), (Visual*)buffer.x11Visual() );
dest = XRenderCreatePicture( x11Display(), buffer.handle(), fmt, 0, NULL );
} else
dest = buffer.x11RenderHandle();
int rwidth = width() / numCursors;
for ( int i = 0; i < numCursors; i++ ) {
if ( cursors[i]->picture() ) {
XRenderComposite( x11Display(), PictOpOver,
cursors[i]->picture(), 0, dest, 0, 0, 0, 0,
rwidth * i + (rwidth - cursors[i]->width()) / 2,
(height() - cursors[i]->height()) / 2,
cursors[i]->width(), cursors[i]->height() );
}
}
bitBlt( this, 0, 0, &buffer );
if ( !qt_has_xft || !qt_use_xrender )
XRenderFreePicture( x11Display(), dest );
}
void PreviewWidget::mouseMoveEvent( QMouseEvent *e )
{
int pos = e->x() / ( width() / numCursors );
if ( pos != current && pos < numCursors ) {
XDefineCursor( x11Display(), winId(), cursors[pos]->handle() );
current = pos;
}
}
// vim: set noet ts=4 sw=4: