1
// krazy:excludeall=qclasses
3
//////////////////////////////////////////////////////////////////////////////
4
// oxygenwindowmanager.cpp
5
// pass some window mouse press/release/move event actions to window manager
8
// Copyright (c) 2010 Hugo Pereira Da Costa <hugo@oxygen-icons.org>
10
// Largely inspired from BeSpin style
11
// Copyright (C) 2007 Thomas Luebking <thomas.luebking@web.de>
13
// Permission is hereby granted, free of charge, to any person obtaining a copy
14
// of this software and associated documentation files (the "Software"), to
15
// deal in the Software without restriction, including without limitation the
16
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
17
// sell copies of the Software, and to permit persons to whom the Software is
18
// furnished to do so, subject to the following conditions:
20
// The above copyright notice and this permission notice shall be included in
21
// all copies or substantial portions of the Software.
23
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
30
//////////////////////////////////////////////////////////////////////////////
32
#include "oxygenwindowmanager.h"
33
#include "oxygenwindowmanager.moc"
34
#include "oxygenpropertynames.h"
35
#include "oxygenstyleconfigdata.h"
37
#include <QtGui/QApplication>
38
#include <QtGui/QComboBox>
39
#include <QtGui/QDialog>
40
#include <QtGui/QDockWidget>
41
#include <QtGui/QGroupBox>
42
#include <QtGui/QLabel>
43
#include <QtGui/QListView>
44
#include <QtGui/QMainWindow>
45
#include <QtGui/QMenuBar>
46
#include <QtGui/QMouseEvent>
47
#include <QtGui/QProgressBar>
48
#include <QtGui/QStatusBar>
49
#include <QtGui/QStyle>
50
#include <QtGui/QStyleOptionGroupBox>
51
#include <QtGui/QTabBar>
52
#include <QtGui/QTabWidget>
53
#include <QtGui/QToolBar>
54
#include <QtGui/QToolButton>
55
#include <QtGui/QTreeView>
56
#include <QtGui/QGraphicsView>
58
#include <QtCore/QTextStream>
59
#include <QtGui/QTextDocument>
61
#include <KGlobalSettings>
65
#include <NETRootInfo>
71
//_____________________________________________________________
72
WindowManager::WindowManager( QObject* parent ):
75
_useWMMoveResize( true ),
76
_dragMode( StyleConfigData::WD_FULL ),
77
_dragDistance( KGlobalSettings::dndEventDelay() ),
78
_dragDelay( QApplication::startDragTime() ),
79
_dragAboutToStart( false ),
80
_dragInProgress( false ),
82
_cursorOverride( false )
85
// install application wise event filter
86
_appEventFilter = new AppEventFilter( this );
87
qApp->installEventFilter( _appEventFilter );
91
//_____________________________________________________________
92
void WindowManager::initialize( void )
95
setEnabled( StyleConfigData::windowDragEnabled() );
96
setDragMode( StyleConfigData::windowDragMode() );
97
setUseWMMoveResize( StyleConfigData::useWMMoveResize() );
99
setDragDistance( KGlobalSettings::dndEventDelay() );
100
setDragDelay( QApplication::startDragTime() );
102
initializeWhiteList();
103
initializeBlackList();
107
//_____________________________________________________________
108
void WindowManager::registerWidget( QWidget* widget )
111
if( isBlackListed( widget ) )
115
also install filter for blacklisted widgets
116
to be able to catch the relevant events and prevent
119
widget->removeEventFilter( this );
120
widget->installEventFilter( this );
122
} else if( isDragable( widget ) ) {
124
widget->removeEventFilter( this );
125
widget->installEventFilter( this );
131
//_____________________________________________________________
132
void WindowManager::unregisterWidget( QWidget* widget )
135
{ widget->removeEventFilter( this ); }
138
//_____________________________________________________________
139
void WindowManager::initializeWhiteList( void )
144
// add user specified whitelisted classnames
145
_whiteList.insert( ExceptionId( "MplayerWindow" ) );
146
_whiteList.insert( ExceptionId( "ViewSliders@kmix" ) );
147
_whiteList.insert( ExceptionId( "Sidebar_Widget@konqueror" ) );
149
foreach( const QString& exception, StyleConfigData::windowDragWhiteList() )
151
ExceptionId id( exception );
152
if( !id.className().isEmpty() )
153
{ _whiteList.insert( exception ); }
157
//_____________________________________________________________
158
void WindowManager::initializeBlackList( void )
162
_blackList.insert( ExceptionId( "CustomTrackView@kdenlive" ) );
163
_blackList.insert( ExceptionId( "MuseScore" ) );
164
_blackList.insert( ExceptionId( "KGameCanvasWidget" ) );
165
foreach( const QString& exception, StyleConfigData::windowDragBlackList() )
167
ExceptionId id( exception );
168
if( !id.className().isEmpty() )
169
{ _blackList.insert( exception ); }
174
//_____________________________________________________________
175
bool WindowManager::eventFilter( QObject* object, QEvent* event )
177
if( !enabled() ) return false;
179
switch ( event->type() )
181
case QEvent::MouseButtonPress:
182
return mousePressEvent( object, event );
185
case QEvent::MouseMove:
186
if ( object == _target.data() ) return mouseMoveEvent( object, event );
189
case QEvent::MouseButtonRelease:
190
if ( _target ) return mouseReleaseEvent( object, event );
202
//_____________________________________________________________
203
void WindowManager::timerEvent( QTimerEvent* event )
206
if( event->timerId() == _dragTimer.timerId() )
210
{ startDrag( _target.data(), _globalDragPoint ); }
214
return QObject::timerEvent( event );
220
//_____________________________________________________________
221
bool WindowManager::mousePressEvent( QObject* object, QEvent* event )
224
// cast event and check buttons/modifiers
225
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>( event );
226
if( !( mouseEvent->modifiers() == Qt::NoModifier && mouseEvent->button() == Qt::LeftButton ) )
230
if( isLocked() ) return false;
231
else setLocked( true );
234
QWidget *widget = static_cast<QWidget*>( object );
236
// check if widget can be dragged from current position
237
if( isBlackListed( widget ) || !canDrag( widget ) ) return false;
239
// retrieve widget's child at event position
240
QPoint position( mouseEvent->pos() );
241
QWidget* child = widget->childAt( position );
242
if( !canDrag( widget, child, position ) ) return false;
244
// save target and drag point
246
_dragPoint = position;
247
_globalDragPoint = mouseEvent->globalPos();
248
_dragAboutToStart = true;
250
// send a move event to the current child with same position
251
// if received, it is caught to actually start the drag
252
QPoint localPoint( _dragPoint );
253
if( child ) localPoint = child->mapFrom( widget, localPoint );
255
QMouseEvent localMouseEvent( QEvent::MouseMove, localPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
256
qApp->sendEvent( child, &localMouseEvent );
263
//_____________________________________________________________
264
bool WindowManager::mouseMoveEvent( QObject* object, QEvent* event )
270
if( _dragTimer.isActive() ) _dragTimer.stop();
272
// cast event and check drag distance
273
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>( event );
274
if( !_dragInProgress )
277
if( _dragAboutToStart )
279
if( mouseEvent->globalPos() == _globalDragPoint )
282
_dragAboutToStart = false;
283
if( _dragTimer.isActive() ) _dragTimer.stop();
284
_dragTimer.start( _dragDelay, this );
288
} else if( QPoint( mouseEvent->globalPos() - _globalDragPoint ).manhattanLength() >= _dragDistance )
289
{ _dragTimer.start( 0, this ); }
292
} else if( !useWMMoveResize() ) {
294
// use QWidget::move for the grabbing
295
/* this works only if the sending object and the target are identical */
296
QWidget* window( _target.data()->window() );
297
window->move( window->pos() + mouseEvent->pos() - _dragPoint );
304
//_____________________________________________________________
305
bool WindowManager::mouseReleaseEvent( QObject* object, QEvent* event )
313
//_____________________________________________________________
314
bool WindowManager::isDragable( QWidget* widget )
318
if( !widget ) return false;
320
// accepted default types
322
( qobject_cast<QDialog*>( widget ) && widget->isWindow() ) ||
323
( qobject_cast<QMainWindow*>( widget ) && widget->isWindow() ) ||
324
qobject_cast<QGroupBox*>( widget ) )
327
// more accepted types, provided they are not dock widget titles
328
if( ( qobject_cast<QMenuBar*>( widget ) ||
329
qobject_cast<QTabBar*>( widget ) ||
330
qobject_cast<QStatusBar*>( widget ) ||
331
qobject_cast<QToolBar*>( widget ) ) &&
332
!isDockWidgetTitle( widget ) )
335
if( widget->inherits( "KScreenSaver" ) && widget->inherits( "KCModule" ) )
338
if( isWhiteListed( widget ) )
342
if( QToolButton* toolButton = qobject_cast<QToolButton*>( widget ) )
343
{ if( toolButton->autoRaise() ) return true; }
347
one needs to check that
348
1/ the widget parent is a scrollarea
349
2/ it matches its parent viewport
350
3/ the parent is not blacklisted
352
if( QListView* listView = qobject_cast<QListView*>( widget->parentWidget() ) )
353
{ if( listView->viewport() == widget && !isBlackListed( listView ) ) return true; }
355
if( QTreeView* treeView = qobject_cast<QTreeView*>( widget->parentWidget() ) )
356
{ if( treeView->viewport() == widget && !isBlackListed( treeView ) ) return true; }
358
//if( QGraphicsView* graphicsView = qobject_cast<QGraphicsView*>( widget->parentWidget() ) )
359
//{ if( graphicsView->viewport() == widget && !isBlackListed( graphicsView ) ) return true; }
362
catch labels in status bars.
363
this is because of kstatusbar
364
who captures buttonPress/release events
366
if( QLabel* label = qobject_cast<QLabel*>( widget ) )
368
if( label->textInteractionFlags().testFlag( Qt::TextSelectableByMouse ) ) return false;
370
QWidget* parent = label->parentWidget();
373
if( qobject_cast<QStatusBar*>( parent ) ) return true;
374
parent = parent->parentWidget();
382
//_____________________________________________________________
383
bool WindowManager::isBlackListed( QWidget* widget )
386
// check against noAnimations propery
387
QVariant propertyValue( widget->property( PropertyNames::noWindowGrab ) );
388
if( propertyValue.isValid() && propertyValue.toBool() ) return true;
390
// list-based blacklisted widgets
391
QString appName( qApp->applicationName() );
392
foreach( const ExceptionId& id, _blackList )
394
if( !id.appName().isEmpty() && id.appName() != appName ) continue;
395
if( id.className() == "*" && !id.appName().isEmpty() )
397
// if application name matches and all classes are selected
398
// disable the grabbing entirely
402
if( widget->inherits( id.className().toLatin1() ) ) return true;
408
//_____________________________________________________________
409
bool WindowManager::isWhiteListed( QWidget* widget ) const
412
QString appName( qApp->applicationName() );
413
foreach( const ExceptionId& id, _whiteList )
415
if( !id.appName().isEmpty() && id.appName() != appName ) continue;
416
if( widget->inherits( id.className().toLatin1() ) ) return true;
422
//_____________________________________________________________
423
bool WindowManager::canDrag( QWidget* widget )
427
if( !enabled() ) return false;
429
// assume isDragable widget is already passed
430
// check some special cases where drag should not be effective
432
// check mouse grabber
433
if( QWidget::mouseGrabber() ) return false;
437
Assume that a changed cursor means that some action is in progress
438
and should prevent the drag
440
if( widget->cursor().shape() != Qt::ArrowCursor ) return false;
447
//_____________________________________________________________
448
bool WindowManager::canDrag( QWidget* widget, QWidget* child, const QPoint& position )
451
// retrieve child at given position and check cursor again
452
if( child && child->cursor().shape() != Qt::ArrowCursor ) return false;
455
check against children from which drag should never be enabled,
456
even if mousePress/Move has been passed to the parent
459
qobject_cast<QComboBox*>(child ) ||
460
qobject_cast<QProgressBar*>( child ) ) )
464
if( QToolButton* toolButton = qobject_cast<QToolButton*>( widget ) )
466
if( dragMode() == StyleConfigData::WD_MINIMAL && !qobject_cast<QToolBar*>(widget->parentWidget() ) ) return false;
467
return toolButton->autoRaise() && !toolButton->isEnabled();
471
if( QMenuBar* menuBar = qobject_cast<QMenuBar*>( widget ) )
474
// check if there is an active action
475
if( menuBar->activeAction() && menuBar->activeAction()->isEnabled() ) return false;
477
// check if action at position exists and is enabled
478
if( QAction* action = menuBar->actionAt( position ) )
480
if( action->isSeparator() ) return true;
481
if( action->isEnabled() ) return false;
484
// return true in all other cases
490
in MINIMAL mode, anything that has not been already accepted
491
and does not come from a toolbar is rejected
493
if( dragMode() == StyleConfigData::WD_MINIMAL )
495
if( qobject_cast<QToolBar*>( widget ) ) return true;
499
/* following checks are relevant only for WD_FULL mode */
501
// tabbar. Make sure no tab is under the cursor
502
if( QTabBar* tabBar = qobject_cast<QTabBar*>( widget ) )
503
{ return tabBar->tabAt( position ) == -1; }
507
prevent drag if unchecking grouboxes
509
if( QGroupBox *groupBox = qobject_cast<QGroupBox*>( widget ) )
511
// non checkable group boxes are always ok
512
if( !groupBox->isCheckable() ) return true;
514
// gather options to retrieve checkbox subcontrol rect
515
QStyleOptionGroupBox opt;
516
opt.initFrom( groupBox );
517
if( groupBox->isFlat() ) opt.features |= QStyleOptionFrameV2::Flat;
519
opt.midLineWidth = 0;
520
opt.text = groupBox->title();
521
opt.textAlignment = groupBox->alignment();
522
opt.subControls = (QStyle::SC_GroupBoxFrame | QStyle::SC_GroupBoxCheckBox);
523
if (!groupBox->title().isEmpty()) opt.subControls |= QStyle::SC_GroupBoxLabel;
525
opt.state |= (groupBox->isChecked() ? QStyle::State_On : QStyle::State_Off);
527
// check against groupbox checkbox
528
if( groupBox->style()->subControlRect(QStyle::CC_GroupBox, &opt, QStyle::SC_GroupBoxCheckBox, groupBox ).contains( position ) )
531
// check against groupbox label
532
if( !groupBox->title().isEmpty() && groupBox->style()->subControlRect(QStyle::CC_GroupBox, &opt, QStyle::SC_GroupBoxLabel, groupBox ).contains( position ) )
540
if( QLabel* label = qobject_cast<QLabel*>( widget ) )
541
{ if( label->textInteractionFlags().testFlag( Qt::TextSelectableByMouse ) ) return false; }
543
// abstract item views
544
QAbstractItemView* itemView( NULL );
546
( itemView = qobject_cast<QListView*>( widget->parentWidget() ) ) ||
547
( itemView = qobject_cast<QTreeView*>( widget->parentWidget() ) ) )
549
if( widget == itemView->viewport() )
552
if( itemView->frameShape() != QFrame::NoFrame ) return false;
554
itemView->selectionMode() != QAbstractItemView::NoSelection &&
555
itemView->selectionMode() != QAbstractItemView::SingleSelection &&
556
itemView->model() && itemView->model()->rowCount() ) return false;
557
else if( itemView->model() && itemView->indexAt( position ).isValid() ) return false;
560
} else if( ( itemView = qobject_cast<QAbstractItemView*>( widget->parentWidget() ) ) ) {
563
if( widget == itemView->viewport() )
566
if( itemView->frameShape() != QFrame::NoFrame ) return false;
567
else if( itemView->indexAt( position ).isValid() ) return false;
570
} else if( QGraphicsView* graphicsView = qobject_cast<QGraphicsView*>( widget->parentWidget() ) ) {
572
if( widget == graphicsView->viewport() )
575
if( graphicsView->frameShape() != QFrame::NoFrame ) return false;
576
else if( graphicsView->dragMode() != QGraphicsView::NoDrag ) return false;
577
else if( graphicsView->itemAt( position ) ) return false;
586
//____________________________________________________________
587
void WindowManager::resetDrag( void )
590
if( (!useWMMoveResize() ) && _target && _cursorOverride ) {
592
qApp->restoreOverrideCursor();
593
_cursorOverride = false;
598
if( _dragTimer.isActive() ) _dragTimer.stop();
599
_dragPoint = QPoint();
600
_globalDragPoint = QPoint();
601
_dragAboutToStart = false;
602
_dragInProgress = false;
606
//____________________________________________________________
607
void WindowManager::startDrag( QWidget* widget, const QPoint& position )
610
if( !( enabled() && widget ) ) return;
611
if( QWidget::mouseGrabber() ) return;
614
if( useWMMoveResize() )
618
XUngrabPointer(QX11Info::display(), QX11Info::appTime());
619
NETRootInfo rootInfo(QX11Info::display(), NET::WMMoveResize);
620
rootInfo.moveResizeRequest( widget->window()->winId(), position.x(), position.y(), NET::Move);
625
if( !useWMMoveResize() )
627
if( !_cursorOverride )
629
qApp->setOverrideCursor( Qt::SizeAllCursor );
630
_cursorOverride = true;
634
_dragInProgress = true;
640
//____________________________________________________________
641
bool WindowManager::supportWMMoveResize( void ) const
652
//____________________________________________________________
653
bool WindowManager::isDockWidgetTitle( const QWidget* widget ) const
656
if( !widget ) return false;
657
if( const QDockWidget* dockWidget = qobject_cast<const QDockWidget*>( widget->parent() ) )
660
return widget == dockWidget->titleBarWidget();
666
//____________________________________________________________
667
bool WindowManager::AppEventFilter::eventFilter( QObject* object, QEvent* event )
670
if( event->type() == QEvent::MouseButtonRelease )
674
if( _parent->_dragTimer.isActive() )
675
{ _parent->resetDrag(); }
678
if( _parent->isLocked() )
679
{ _parent->setLocked( false ); }
683
if( !_parent->enabled() ) return false;
686
if a drag is in progress, the widget will not receive any event
687
we trigger on the first MouseMove or MousePress events that are received
688
by any widget in the application to detect that the drag is finished
690
if( _parent->useWMMoveResize() && _parent->_dragInProgress && _parent->_target && ( event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonPress ) )
691
{ return appMouseEvent( object, event ); }
697
//_____________________________________________________________
698
bool WindowManager::AppEventFilter::appMouseEvent( QObject* object, QEvent* event )
703
// store target window (see later)
704
QWidget* window( _parent->_target.data()->window() );
707
post some mouseRelease event to the target, in order to counter balance
708
the mouse press that triggered the drag. Note that it triggers a resetDrag
710
QMouseEvent mouseEvent( QEvent::MouseButtonRelease, _parent->_dragPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
711
qApp->sendEvent( _parent->_target.data(), &mouseEvent );
713
if( event->type() == QEvent::MouseMove )
716
HACK: quickly move the main cursor out of the window and back
717
this is needed to get the focus right for the window children
718
the origin of this issue is unknown at the moment
720
const QPoint cursor = QCursor::pos();
721
QCursor::setPos(window->mapToGlobal( window->rect().topRight() ) + QPoint(1, 0) );
722
QCursor::setPos(cursor);