[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-usability
Subject:    RFC: Restricting mouse in popup menus
From:       Lubos Lunak <l.lunak () suse ! cz>
Date:       2005-05-17 12:43:15
Message-ID: 200505171443.15667.l.lunak () suse ! cz
[Download RAW message or body]

Hello,

 I'd like to get some feedback on the attached Qt patches :). The idea is 
based on a feature from the window manager called WindowLab (ggl:windowlab). 
In short, when a popup menu is open, the mouse movement is restricted only to 
the area of the popup - this allows navigating in the menus only using 
vertical movement and clicking.

 In not so short: When a popup is opened, the mouse area is restricted to its 
geometry plus 2 pixels above. The mouse pointer is initially positioned in 
that area above the popup, and clicking there closes it. Submenus don't open 
automatically, they always have to be clicked. The patch also significantly 
increases the mouse acceleration threshold, so that the mouse is less 
sensitive in the popup.

 This should have some advantages:

- The already mentioned vertical-only movement. Since the mouse is restricted 
to the popup geometry, you cannot move it out by going far to the left or 
right. Opening a submenu restricts the y-position to the submenu's geometry, 
closing the submenu moves it back. It's not possible to accidentally open 
another submenu while trying to move the mouse to the right to a submenu.

- Since the starting position is always at the topleft corner above the popup, 
one cannot accidentally click the bottommost Quit/Close/Whatever entry as 
sometimes happens with popups opened close to the bottom screen edge. This 
also means the navigation in the popup is always the same.

 The main disadvantage that I've found out so far is that it feels a bit odd, 
at least for the first five minutes, but since it seems to fit well together 
with the standalone menubar feature, which is also KDE-only, I guess this 
should not be a big problem ;).

 There are two #define's in the code that select whether to change the mouse 
acceleration threshold (CHANGE_ACCEL) and whether to restore the mouse 
position after closing a popup (RESET_MOUSE_POS). I'm not sure if they're 
better turned on or off.

 If people like this, I intend to clean up the patches and make it an optional 
KDE feature.

-- 
Lubos Lunak
KDE developer
---------------------------------------------------------------------
SuSE CR, s.r.o.  e-mail: l.lunak@suse.cz , l.lunak@kde.org
Drahobejlova 27  tel: +420 2 9654 2373
190 00 Praha 9   fax: +420 2 9654 2374
Czech Republic   http://www.suse.cz/

["qapplication_x11.cpp.patch" (text/x-diff)]

--- qapplication_x11.cpp.sav	2005-05-05 13:45:18.000000000 +0200
+++ qapplication_x11.cpp	2005-05-17 13:30:25.000000000 +0200
@@ -87,6 +87,7 @@
 #include "qsettings.h"
 #include "qstylefactory.h"
 #include "qfileinfo.h"
+#include "qpopupmenu.h"
 
 // Input method stuff - UNFINISHED
 #include "qinputcontext_p.h"
@@ -3846,8 +3847,45 @@ bool qt_try_modal( QWidget *widget, XEve
 	    QWidget *widget	The popup widget to be removed
  *****************************************************************************/
 
+#define RESET_MOUSE_POS
+#define CHANGE_ACCEL
+
+static Window popupRegionWindow = None;
+static QValueList< QPoint >* popupPointerPositions;
+static void popupApplyRegion( QWidget* popup, bool set )
+{
+    QRect r( 0, 0, XDisplayWidth( popup->x11Display(), popup->x11Screen()),
+        XDisplayHeight( popup->x11Display(), popup->x11Screen()));
+    QPopupMenu* p = qt_cast< QPopupMenu* >( popup );
+    if( p ) {
+        r = p->contentsRect();
+        r.moveBy( p->x(), p->y());
+        r.addCoords( 0, -2, 0, 0 );
+        if( set ) {
+            if( !popupPointerPositions ) {
+                popupPointerPositions = new QValueList< QPoint >;
+                Q_CHECK_PTR( popupPointerPositions );
+            }
+            popupPointerPositions->append( QCursor::pos());
+        }
+    }
+    XMoveResizeWindow( popup->x11Display(), popupRegionWindow,
+        r.x(), r.y(), r.width(), r.height());
+    if( p ) {
+        if( set ) {
+            XWarpPointer( popup->x11Display(), None, popupRegionWindow, 0, 0, 0, 0, 0, 0 );
+        } else {
+            QPoint p = popupPointerPositions->back();
+            popupPointerPositions->pop_back();
+#ifdef RESET_MOUSE_POS
+            QCursor::setPos( p );
+#endif
+        }
+    }
+}
 
 static int openPopupCount = 0;
+static int accelthresh;
 void QApplication::openPopup( QWidget *popup )
 {
     openPopupCount++;
@@ -3858,6 +3896,15 @@ void QApplication::openPopup( QWidget *p
     popupWidgets->append( popup );		// add to end of list
 
     if ( popupWidgets->count() == 1 && !qt_nograb() ){ // grab mouse/keyboard
+        XSetWindowAttributes attrs;
+        attrs.override_redirect = True;
+        popupRegionWindow = XCreateWindow( popup->x11Display(),
+            RootWindow( popup->x11Display(), popup->x11Screen()),
+            0, 0, 1, 1, 0, 0,
+            InputOnly, CopyFromParent, CWOverrideRedirect, &attrs );
+        popupApplyRegion( popup, true );
+        XLowerWindow( popup->x11Display(), popupRegionWindow );
+        XMapWindow( popup->x11Display(), popupRegionWindow );
 	int r = XGrabKeyboard( popup->x11Display(), popup->winId(), FALSE,
 			       GrabModeSync, GrabModeAsync, CurrentTime );
 	if ( (popupGrabOk = (r == GrabSuccess)) ) {
@@ -3866,14 +3913,20 @@ void QApplication::openPopup( QWidget *p
 				     ButtonMotionMask | EnterWindowMask |
 				     LeaveWindowMask | PointerMotionMask),
 			      GrabModeSync, GrabModeAsync,
-			      None, None, CurrentTime );
+			      popupRegionWindow, None, CurrentTime );
 
 	    if ( (popupGrabOk = (r == GrabSuccess)) )
 		XAllowEvents( popup->x11Display(), SyncPointer, CurrentTime );
 	    else
 		XUngrabKeyboard( popup->x11Display(), CurrentTime );
-	}
+#ifdef CHANGE_ACCEL
+        int dummy;
+        XGetPointerControl( popup->x11Display(), &dummy, &dummy, &accelthresh );
+	XChangePointerControl( popup->x11Display(), False, True, 1, 1, 200 );
+#endif
+        }
     } else if ( popupGrabOk ) {
+        popupApplyRegion( popup, true );
 	XAllowEvents(  popup->x11Display(), SyncPointer, CurrentTime );
     }
 
@@ -3901,6 +3954,9 @@ void QApplication::closePopup( QWidget *
 	popupCloseDownMode = TRUE;		// control mouse events
 	delete popupWidgets;
 	popupWidgets = 0;
+#ifdef CHANGE_ACCEL
+	XChangePointerControl( popup->x11Display(), False, True, 1, 1, accelthresh );
+#endif
 	if ( !qt_nograb() && popupGrabOk ) {	// grabbing not disabled
 	    if ( mouseButtonState != 0
 		 || popup->geometry(). contains(QPoint(mouseGlobalXPos, mouseGlobalYPos) ) )
@@ -3912,6 +3968,9 @@ void QApplication::closePopup( QWidget *
 		XAllowEvents( popup->x11Display(), ReplayPointer,CurrentTime );
 	    }
 	    XUngrabPointer( popup->x11Display(), CurrentTime );
+            popupApplyRegion( popup, false );
+            XDestroyWindow( popup->x11Display(), popupRegionWindow );
+            popupRegionWindow = None;
 	    XFlush( popup->x11Display() );
 	}
 	if ( active_window ) {
@@ -3934,21 +3993,9 @@ void QApplication::closePopup( QWidget *
 	 else
 	     aw->setFocus();
 	 QFocusEvent::resetReason();
-	 if ( popupWidgets->count() == 1 && !qt_nograb() ){ // grab mouse/keyboard
-	     int r = XGrabKeyboard( aw->x11Display(), aw->winId(), FALSE,
-				    GrabModeSync, GrabModeAsync, CurrentTime );
-	     if ( (popupGrabOk = (r == GrabSuccess)) ) {
-		 r = XGrabPointer( aw->x11Display(), aw->winId(), TRUE,
-				   (uint)(ButtonPressMask | ButtonReleaseMask |
-					  ButtonMotionMask | EnterWindowMask |
-					  LeaveWindowMask | PointerMotionMask),
-				   GrabModeSync, GrabModeAsync,
-				   None, None, CurrentTime );
-
-		 if ( (popupGrabOk = (r == GrabSuccess)) )
-		     XAllowEvents( aw->x11Display(), SyncPointer, CurrentTime );
-	     }
-	 }
+         if ( popupGrabOk ) {
+             popupApplyRegion( aw, false );
+         }
      }
 }
 

["qpopupmenu.cpp.patch" (text/x-diff)]

--- qpopupmenu.cpp.sav	2005-05-05 13:46:05.000000000 +0200
+++ qpopupmenu.cpp	2005-05-17 13:30:43.000000000 +0200
@@ -851,6 +851,7 @@ bool QPopupMenu::tryMenuBar( QMouseEvent
 */
 bool QPopupMenu::tryMouseEvent( QPopupMenu *p, QMouseEvent * e)
 {
+    return false;
     if ( p == this )
 	return FALSE;
     QPoint pos = mapFromGlobal( e->globalPos() );
@@ -1598,7 +1599,7 @@ void QPopupMenu::mousePressEvent( QMouse
     int item = itemAtPos( e->pos() );
     if ( item == -1 ) {
 	if ( !rect().contains(e->pos()) && !tryMenuBar(e) ) {
-	    byeMenuBar();
+	    hide();
 	}
 	return;
     }
@@ -1633,6 +1634,9 @@ void QPopupMenu::mouseReleaseEvent( QMou
     if ( !parentMenu && !mouseBtDn && actItem < 0 && motion < 6 )
 	return;
 
+    if( !mouseBtDn )
+        return;
+
     mouseBtDn = FALSE;
 
     // if the user released the mouse outside the menu, pass control
@@ -1648,10 +1652,16 @@ void QPopupMenu::mouseReleaseEvent( QMou
     if ( actItem < 0 ) { // we do not have an active item
 	// if the release is inside without motion (happens with
 	// oversized popup menus on small screens), ignore it
-	if ( rect().contains( e->pos() ) && motion < 6 )
-	    return;
-	else
-	    byeMenuBar();
+//	if ( rect().contains( e->pos() ) && motion < 6 )
+//	    return;
+//	else
+//	    hide();
+	    QMenuData* p = parentMenu;
+	    hide();
+#ifndef QT_NO_MENUBAR
+	    if ( p && p->isMenuBar )
+		((QMenuBar*) p)->goodbye( TRUE );
+#endif
     } else {	// selected menu item!
 	register QMenuItem *mi = mitems->at(actItem);
 	if ( mi ->widget() ) {
@@ -1747,13 +1757,13 @@ void QPopupMenu::mouseMoveEvent( QMouseE
 	    updateRow( lastActItem );
         if ( lastActItem > 0 ||
 		    ( !rect().contains( e->pos() ) && !tryMenuBar( e ) ) ) {
-	    popupSubMenuLater(style().styleHint(QStyle::SH_PopupMenu_SubMenuPopupDelay,
-						this), this);
+//	    popupSubMenuLater(style().styleHint(QStyle::SH_PopupMenu_SubMenuPopupDelay,
+//						this), this);
 	}
     } else {					// mouse on valid item
 	// but did not register mouse press
-	if ( (e->state() & Qt::MouseButtonMask) && !mouseBtDn )
-	    mouseBtDn = TRUE; // so mouseReleaseEvent will pop down
+//	if ( (e->state() & Qt::MouseButtonMask) && !mouseBtDn )
+//	    mouseBtDn = TRUE; // so mouseReleaseEvent will pop down
 
 	register QMenuItem *mi = mitems->at( item );
 
@@ -1772,14 +1782,14 @@ void QPopupMenu::mouseMoveEvent( QMouseE
 	if ( style().styleHint(QStyle::SH_PopupMenu_SloppySubMenus, this) &&
 	     d->mouseMoveBuffer.contains( e->pos() ) ) {
 	    actItem = item;
-	    popupSubMenuLater( style().styleHint(QStyle::SH_PopupMenu_SubMenuPopupDelay, this) * 6,
-			       this );
+//	    popupSubMenuLater( style().styleHint(QStyle::SH_PopupMenu_SubMenuPopupDelay, this) * 6,
+//			       this );
 	    return;
 	}
 
 	if ( mi->popup() || ( popupActive >= 0 && popupActive != item ))
-	    popupSubMenuLater( style().styleHint(QStyle::SH_PopupMenu_SubMenuPopupDelay, this),
-			       this );
+;//	    popupSubMenuLater( style().styleHint(QStyle::SH_PopupMenu_SubMenuPopupDelay, this),
+//			       this );
 	else if ( singleSingleShot )
 	    singleSingleShot->stop();
 


_______________________________________________
kde-usability mailing list
kde-usability@kde.org
https://mail.kde.org/mailman/listinfo/kde-usability


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic