kdelibs/kdeui/util/kcursor.cpp

420 lines
12 KiB
C++
Raw Normal View History

2014-11-13 01:04:59 +02:00
/* This file is part of the KDE libraries
Copyright (C) 1998 Kurt Granroth (granroth@kde.org)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kcursor.h"
#include "kcursor_p.h"
#include <QBitmap>
#include <QCursor>
#include <QEvent>
#include <QAbstractScrollArea>
#include <QTimer>
#include <QWidget>
#include <QFile>
#include <QX11Info>
2014-11-13 01:04:59 +02:00
#include <kglobal.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <kdebug.h>
2014-11-13 01:04:59 +02:00
#include <config.h>
#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#ifdef HAVE_XCURSOR
# include <X11/Xcursor/Xcursor.h>
#endif
#ifdef HAVE_XFIXES
# include <X11/extensions/Xfixes.h>
#endif
#include <fixx11h.h>
namespace
{
// Borrowed from xc/lib/Xcursor/library.c
static const char * const standard_names[] = {
/* 0 */
"X_cursor", "arrow", "based_arrow_down", "based_arrow_up",
"boat", "bogosity", "bottom_left_corner", "bottom_right_corner",
"bottom_side", "bottom_tee", "box_spiral", "center_ptr",
"circle", "clock", "coffee_mug", "cross",
/* 32 */
"cross_reverse", "crosshair", "diamond_cross", "dot",
"dotbox", "double_arrow", "draft_large", "draft_small",
"draped_box", "exchange", "fleur", "gobbler",
"gumby", "hand1", "hand2", "heart",
/* 64 */
"icon", "iron_cross", "left_ptr", "left_side",
"left_tee", "leftbutton", "ll_angle", "lr_angle",
"man", "middlebutton", "mouse", "pencil",
"pirate", "plus", "question_arrow", "right_ptr",
/* 96 */
"right_side", "right_tee", "rightbutton", "rtl_logo",
"sailboat", "sb_down_arrow", "sb_h_double_arrow", "sb_left_arrow",
"sb_right_arrow", "sb_up_arrow", "sb_v_double_arrow", "shuttle",
"sizing", "spider", "spraycan", "star",
/* 128 */
"target", "tcross", "top_left_arrow", "top_left_corner",
"top_right_corner", "top_side", "top_tee", "trek",
"ul_angle", "umbrella", "ur_angle", "watch",
"xterm",
};
static Qt::HANDLE x11LoadXcursor(const QString &name)
{
#ifdef HAVE_XCURSOR
return XcursorLibraryLoadCursor(QX11Info::display(), QFile::encodeName(name));
#else
return 0;
#endif
}
static int x11CursorShape(const QString &name)
{
static QHash<QString, int> shapes;
// A font cursor is created from two glyphs; a shape glyph and a mask glyph
// stored in pairs in the font, with the shape glyph first. There's only one
// name for each pair. This function always returns the index for the
// shape glyph.
if (shapes.isEmpty()) {
2014-11-13 01:04:59 +02:00
int num = XC_num_glyphs / 2;
shapes.reserve(num + 5);
for (int i = 0; i < num; ++i) {
2014-11-13 01:04:59 +02:00
shapes.insert(standard_names[i], i << 1);
}
2014-11-13 01:04:59 +02:00
// Qt uses alternative names for some core cursors
shapes.insert("size_all", XC_fleur);
shapes.insert("up_arrow", XC_center_ptr);
shapes.insert("ibeam", XC_xterm);
shapes.insert("wait", XC_watch);
shapes.insert("pointing_hand", XC_hand2);
}
return shapes.value(name, -1);
}
static Qt::HANDLE x11LoadFontCursor(const QString &name)
{
int shape = x11CursorShape(name);
if (shape != -1) {
2014-11-13 01:04:59 +02:00
return XCreateFontCursor(QX11Info::display(), shape);
}
2014-11-13 01:04:59 +02:00
return 0;
}
bool x11HaveXfixes()
{
bool result = false;
#ifdef HAVE_XFIXES
int event_base, error_base;
if (XFixesQueryExtension(QX11Info::display(), &event_base, &error_base)) {
int major = 0;
int minor = 0;
2014-11-13 01:04:59 +02:00
XFixesQueryVersion(QX11Info::display(), &major, &minor);
result = (major >= 2);
}
#endif
return result;
}
static void x11SetCursorName(Qt::HANDLE handle, const QString &name)
{
#ifdef HAVE_XFIXES
static bool haveXfixes = x11HaveXfixes();
if (haveXfixes) {
const QByteArray namebytes = QFile::encodeName(name);
XFixesSetCursorName(QX11Info::display(), handle, namebytes.constData());
}
2014-11-13 01:04:59 +02:00
#endif
}
}
QCursor KCursor::fromName(const QString &name, Qt::CursorShape fallback)
2014-11-13 01:04:59 +02:00
{
Qt::HANDLE handle = x11LoadXcursor(name);
if (!handle) {
2014-11-13 01:04:59 +02:00
handle = x11LoadFontCursor(name);
}
if (handle) {
QCursor result(handle);
x11SetCursorName(result.handle(), name);
return result;
}
2014-11-13 01:04:59 +02:00
return QCursor(fallback);
2014-11-13 01:04:59 +02:00
}
void KCursor::setAutoHideCursor(QWidget *w, bool enable,
bool customEventFilter)
2014-11-13 01:04:59 +02:00
{
KCursorPrivate::self()->setAutoHideCursor(w, enable, customEventFilter);
2014-11-13 01:04:59 +02:00
}
void KCursor::autoHideEventFilter(QObject *o, QEvent *e)
2014-11-13 01:04:59 +02:00
{
KCursorPrivate::self()->eventFilter(o, e);
2014-11-13 01:04:59 +02:00
}
void KCursor::setHideCursorDelay(int ms)
2014-11-13 01:04:59 +02:00
{
KCursorPrivate::self()->hideCursorDelay = ms;
}
int KCursor::hideCursorDelay()
{
return KCursorPrivate::self()->hideCursorDelay;
}
// **************************************************************************
KCursorPrivateAutoHideEventFilter::KCursorPrivateAutoHideEventFilter(QWidget* widget)
: m_widget(widget)
, m_wasMouseTracking(m_widget->hasMouseTracking())
, m_isCursorHidden(false)
, m_isOwnCursor(false)
2014-11-13 01:04:59 +02:00
{
mouseWidget()->setMouseTracking(true);
connect(&m_autoHideTimer, SIGNAL(timeout()), this, SLOT(hideCursor()));
2014-11-13 01:04:59 +02:00
}
KCursorPrivateAutoHideEventFilter::~KCursorPrivateAutoHideEventFilter()
{
if (m_widget) {
mouseWidget()->setMouseTracking(m_wasMouseTracking);
}
2014-11-13 01:04:59 +02:00
}
void KCursorPrivateAutoHideEventFilter::resetWidget()
{
m_widget = nullptr;
2014-11-13 01:04:59 +02:00
}
void KCursorPrivateAutoHideEventFilter::hideCursor()
{
m_autoHideTimer.stop();
if (m_isCursorHidden) {
2014-11-13 01:04:59 +02:00
return;
}
2014-11-13 01:04:59 +02:00
m_isCursorHidden = true;
QWidget* w = mouseWidget();
m_isOwnCursor = w->testAttribute(Qt::WA_SetCursor);
if (m_isOwnCursor) {
2014-11-13 01:04:59 +02:00
m_oldCursor = w->cursor();
}
2014-11-13 01:04:59 +02:00
w->setCursor(QCursor(Qt::BlankCursor));
2014-11-13 01:04:59 +02:00
}
void KCursorPrivateAutoHideEventFilter::unhideCursor()
{
m_autoHideTimer.stop();
if (!m_isCursorHidden) {
2014-11-13 01:04:59 +02:00
return;
}
2014-11-13 01:04:59 +02:00
m_isCursorHidden = false;
QWidget* w = mouseWidget();
if (w->cursor().shape() != Qt::BlankCursor) {
// someone messed with the cursor already
return;
}
2014-11-13 01:04:59 +02:00
if (m_isOwnCursor) {
w->setCursor(m_oldCursor);
} else {
2014-11-13 01:04:59 +02:00
w->unsetCursor();
}
2014-11-13 01:04:59 +02:00
}
// The widget which gets mouse events, and that shows the cursor
// (that is the viewport, for a QAbstractScrollArea)
QWidget* KCursorPrivateAutoHideEventFilter::mouseWidget() const
{
QWidget* w = m_widget;
// Is w a QAbstractScrollArea ? Call setCursor on the viewport in that case.
QAbstractScrollArea * sv = qobject_cast<QAbstractScrollArea*>(w);
if (sv) {
2014-11-13 01:04:59 +02:00
w = sv->viewport();
}
2014-11-13 01:04:59 +02:00
return w;
}
bool KCursorPrivateAutoHideEventFilter::eventFilter(QObject *o, QEvent *e)
2014-11-13 01:04:59 +02:00
{
Q_UNUSED(o);
// o is m_widget or its viewport
// Q_ASSERT(o == m_widget);
switch (e->type()) {
case QEvent::Leave:
case QEvent::FocusOut:
case QEvent::WindowDeactivate: {
unhideCursor();
break;
}
case QEvent::KeyPress:
case QEvent::ShortcutOverride: {
hideCursor();
break;
}
case QEvent::Enter:
case QEvent::FocusIn:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
case QEvent::Show:
case QEvent::Hide:
case QEvent::Wheel: {
unhideCursor();
if (m_widget->hasFocus()) {
m_autoHideTimer.setSingleShot(true);
m_autoHideTimer.start( KCursorPrivate::self()->hideCursorDelay);
}
break;
}
default: {
break;
2014-11-13 01:04:59 +02:00
}
}
return false;
}
KCursorPrivate * KCursorPrivate::s_self = nullptr;
2014-11-13 01:04:59 +02:00
KCursorPrivate * KCursorPrivate::self()
{
if (!s_self) {
s_self = new KCursorPrivate();
}
2014-11-13 01:04:59 +02:00
// WABA: Don't delete KCursorPrivate, it serves no real purpose.
// Even worse it causes crashes because it seems to get deleted
// during ~QApplication and ~QApplication doesn't seem to like it
// when we delete a QCursor. No idea if that is a bug itself.
return s_self;
}
KCursorPrivate::KCursorPrivate()
{
hideCursorDelay = 5000; // 5s default value
KConfigGroup cg(KGlobal::config(), QLatin1String("KDE"));
enabled = cg.readEntry(QLatin1String("Autohiding cursor enabled"), true);
2014-11-13 01:04:59 +02:00
}
KCursorPrivate::~KCursorPrivate()
{
}
void KCursorPrivate::setAutoHideCursor(QWidget *w, bool enable, bool customEventFilter)
2014-11-13 01:04:59 +02:00
{
if (!w || !enabled) {
2014-11-13 01:04:59 +02:00
return;
}
2014-11-13 01:04:59 +02:00
QWidget* viewport = 0;
QAbstractScrollArea* sv = qobject_cast<QAbstractScrollArea*>(w);
if (sv) {
2014-11-13 01:04:59 +02:00
viewport = sv->viewport();
}
2014-11-13 01:04:59 +02:00
if (enable) {
if (m_eventFilters.contains(w)) {
2014-11-13 01:04:59 +02:00
return;
}
KCursorPrivateAutoHideEventFilter* filter = new KCursorPrivateAutoHideEventFilter(w);
m_eventFilters.insert(w, filter);
2014-11-13 01:04:59 +02:00
if (viewport) {
m_eventFilters.insert(viewport, filter);
2014-11-13 01:04:59 +02:00
connect(viewport, SIGNAL(destroyed(QObject*)), this, SLOT(slotViewportDestroyed(QObject*)));
}
if (!customEventFilter) {
// for key events
w->installEventFilter(filter);
if (viewport) {
// for mouse events
viewport->installEventFilter(filter);
}
2014-11-13 01:04:59 +02:00
}
connect(w, SIGNAL(destroyed(QObject*)), this, SLOT(slotWidgetDestroyed(QObject*)));
} else {
KCursorPrivateAutoHideEventFilter* filter = m_eventFilters.take(w);
if (!filter) {
2014-11-13 01:04:59 +02:00
return;
}
w->removeEventFilter(filter);
2014-11-13 01:04:59 +02:00
if (viewport) {
m_eventFilters.remove(viewport);
2014-11-13 01:04:59 +02:00
disconnect(viewport, SIGNAL(destroyed(QObject*)), this, SLOT(slotViewportDestroyed(QObject*)));
viewport->removeEventFilter(filter);
2014-11-13 01:04:59 +02:00
}
delete filter;
disconnect(w, SIGNAL(destroyed(QObject*)), this, SLOT(slotWidgetDestroyed(QObject*)));
2014-11-13 01:04:59 +02:00
}
}
bool KCursorPrivate::eventFilter(QObject *o, QEvent *e)
2014-11-13 01:04:59 +02:00
{
if (!enabled) {
2014-11-13 01:04:59 +02:00
return false;
}
2014-11-13 01:04:59 +02:00
KCursorPrivateAutoHideEventFilter* filter = m_eventFilters.value(o);
Q_ASSERT(filter != nullptr);
if (!filter) {
2014-11-13 01:04:59 +02:00
return false;
}
2014-11-13 01:04:59 +02:00
return filter->eventFilter(o, e);
2014-11-13 01:04:59 +02:00
}
void KCursorPrivate::slotViewportDestroyed(QObject *o)
{
m_eventFilters.remove(o);
}
void KCursorPrivate::slotWidgetDestroyed(QObject *o)
2014-11-13 01:04:59 +02:00
{
KCursorPrivateAutoHideEventFilter* filter = m_eventFilters.take(o);
Q_ASSERT(filter != nullptr);
2014-11-13 01:04:59 +02:00
filter->resetWidget(); // so that dtor doesn't access it
delete filter;
}
#include "moc_kcursor_p.cpp"