mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-24 10:52:49 +00:00
419 lines
12 KiB
C++
419 lines
12 KiB
C++
/* 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>
|
|
#include <kglobal.h>
|
|
#include <ksharedconfig.h>
|
|
#include <kconfiggroup.h>
|
|
#include <kdebug.h>
|
|
|
|
#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()) {
|
|
int num = XC_num_glyphs / 2;
|
|
shapes.reserve(num + 5);
|
|
|
|
for (int i = 0; i < num; ++i) {
|
|
shapes.insert(standard_names[i], i << 1);
|
|
}
|
|
|
|
// 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) {
|
|
return XCreateFontCursor(QX11Info::display(), shape);
|
|
}
|
|
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;
|
|
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());
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
QCursor KCursor::fromName(const QString &name, Qt::CursorShape fallback)
|
|
{
|
|
Qt::HANDLE handle = x11LoadXcursor(name);
|
|
if (!handle) {
|
|
handle = x11LoadFontCursor(name);
|
|
}
|
|
if (handle) {
|
|
QCursor result(handle);
|
|
x11SetCursorName(result.handle(), name);
|
|
return result;
|
|
}
|
|
|
|
return QCursor(fallback);
|
|
}
|
|
|
|
void KCursor::setAutoHideCursor(QWidget *w, bool enable,
|
|
bool customEventFilter)
|
|
{
|
|
KCursorPrivate::self()->setAutoHideCursor(w, enable, customEventFilter);
|
|
}
|
|
|
|
void KCursor::autoHideEventFilter(QObject *o, QEvent *e)
|
|
{
|
|
KCursorPrivate::self()->eventFilter(o, e);
|
|
}
|
|
|
|
void KCursor::setHideCursorDelay(int ms)
|
|
{
|
|
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)
|
|
{
|
|
mouseWidget()->setMouseTracking(true);
|
|
connect(&m_autoHideTimer, SIGNAL(timeout()), this, SLOT(hideCursor()));
|
|
}
|
|
|
|
KCursorPrivateAutoHideEventFilter::~KCursorPrivateAutoHideEventFilter()
|
|
{
|
|
if (m_widget) {
|
|
mouseWidget()->setMouseTracking(m_wasMouseTracking);
|
|
}
|
|
}
|
|
|
|
void KCursorPrivateAutoHideEventFilter::resetWidget()
|
|
{
|
|
m_widget = nullptr;
|
|
}
|
|
|
|
void KCursorPrivateAutoHideEventFilter::hideCursor()
|
|
{
|
|
m_autoHideTimer.stop();
|
|
|
|
if (m_isCursorHidden) {
|
|
return;
|
|
}
|
|
|
|
m_isCursorHidden = true;
|
|
|
|
QWidget* w = mouseWidget();
|
|
|
|
m_isOwnCursor = w->testAttribute(Qt::WA_SetCursor);
|
|
if (m_isOwnCursor) {
|
|
m_oldCursor = w->cursor();
|
|
}
|
|
|
|
w->setCursor(QCursor(Qt::BlankCursor));
|
|
}
|
|
|
|
void KCursorPrivateAutoHideEventFilter::unhideCursor()
|
|
{
|
|
m_autoHideTimer.stop();
|
|
|
|
if (!m_isCursorHidden) {
|
|
return;
|
|
}
|
|
|
|
m_isCursorHidden = false;
|
|
|
|
QWidget* w = mouseWidget();
|
|
if (w->cursor().shape() != Qt::BlankCursor) {
|
|
// someone messed with the cursor already
|
|
return;
|
|
}
|
|
|
|
if (m_isOwnCursor) {
|
|
w->setCursor(m_oldCursor);
|
|
} else {
|
|
w->unsetCursor();
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
w = sv->viewport();
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
bool KCursorPrivateAutoHideEventFilter::eventFilter(QObject *o, QEvent *e)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
KCursorPrivate * KCursorPrivate::s_self = nullptr;
|
|
|
|
KCursorPrivate * KCursorPrivate::self()
|
|
{
|
|
if (!s_self) {
|
|
s_self = new KCursorPrivate();
|
|
}
|
|
// 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);
|
|
}
|
|
|
|
KCursorPrivate::~KCursorPrivate()
|
|
{
|
|
}
|
|
|
|
void KCursorPrivate::setAutoHideCursor(QWidget *w, bool enable, bool customEventFilter)
|
|
{
|
|
if (!w || !enabled) {
|
|
return;
|
|
}
|
|
|
|
QWidget* viewport = 0;
|
|
QAbstractScrollArea* sv = qobject_cast<QAbstractScrollArea*>(w);
|
|
if (sv) {
|
|
viewport = sv->viewport();
|
|
}
|
|
|
|
if (enable) {
|
|
if (m_eventFilters.contains(w)) {
|
|
return;
|
|
}
|
|
KCursorPrivateAutoHideEventFilter* filter = new KCursorPrivateAutoHideEventFilter(w);
|
|
m_eventFilters.insert(w, filter);
|
|
if (viewport) {
|
|
m_eventFilters.insert(viewport, filter);
|
|
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);
|
|
}
|
|
}
|
|
connect(w, SIGNAL(destroyed(QObject*)), this, SLOT(slotWidgetDestroyed(QObject*)));
|
|
} else {
|
|
KCursorPrivateAutoHideEventFilter* filter = m_eventFilters.take(w);
|
|
if (!filter) {
|
|
return;
|
|
}
|
|
w->removeEventFilter(filter);
|
|
if (viewport) {
|
|
m_eventFilters.remove(viewport);
|
|
disconnect(viewport, SIGNAL(destroyed(QObject*)), this, SLOT(slotViewportDestroyed(QObject*)));
|
|
viewport->removeEventFilter(filter);
|
|
}
|
|
delete filter;
|
|
disconnect(w, SIGNAL(destroyed(QObject*)), this, SLOT(slotWidgetDestroyed(QObject*)));
|
|
}
|
|
}
|
|
|
|
bool KCursorPrivate::eventFilter(QObject *o, QEvent *e)
|
|
{
|
|
if (!enabled) {
|
|
return false;
|
|
}
|
|
|
|
KCursorPrivateAutoHideEventFilter* filter = m_eventFilters.value(o);
|
|
Q_ASSERT(filter != nullptr);
|
|
if (!filter) {
|
|
return false;
|
|
}
|
|
|
|
return filter->eventFilter(o, e);
|
|
}
|
|
|
|
void KCursorPrivate::slotViewportDestroyed(QObject *o)
|
|
{
|
|
m_eventFilters.remove(o);
|
|
}
|
|
|
|
void KCursorPrivate::slotWidgetDestroyed(QObject *o)
|
|
{
|
|
KCursorPrivateAutoHideEventFilter* filter = m_eventFilters.take(o);
|
|
Q_ASSERT(filter != nullptr);
|
|
filter->resetWidget(); // so that dtor doesn't access it
|
|
delete filter;
|
|
}
|
|
|
|
#include "moc_kcursor_p.cpp"
|