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

List:       kde-core-devel
Subject:    Kicker keyboard navigation patch and kicker keyboard focus problem
From:       Hamish Rodda <meddie () yoyo ! cc ! monash ! edu ! au>
Date:       2001-12-26 7:30:08
[Download RAW message or body]

Dear list,

I have implemented keyboard navigation of the kicker menu - see the attatched 
patch (but note focus problem below).  I'm posting this here to see if 
everyone is ok with applying it before 3.0; I'm happy for it to wait if need 
be.

The navigation works as follows. The first item from the top with the matching 
first letter is selected. If it is the only match on the menu, it is 
executed; otherwise, further presses of the same key cycle between matching 
entries.

There is one problem at the moment. The menu currently loses keyboard focus 
after the first keypress - this appears to be a regression of 
http://lists.kde.org/?l=kde-core-devel&m=98120358401987&w=2 . Even grabbing 
the keyboard with grabKeyboard() doesn't fix this.  I haven't been able to 
figure this one out - could someone more knowledgeable about kwin take a 
look?

For those who want to test the navigation patch now, because of the issue 
above you will need to make a  change to 
kdebase/kicker/core/container_panel.cpp - in PanelContainer::PanelContainer, 
comment out KWin::setType( winId(), NET::Dock );  This will allow kicker 
itself to take focus, and the keyboard navigation will work. To give kicker 
focus, change virtual desktops with the mouse, or use the scrollwheel over 
kicker. Note that this produces side-effects (kicker itself can be 
minimized).

Notes:
- performance is fine on a fast computer. I don't have a slow one to test with 
unfortunately - post if there are issues.
- different response patterns would not be hard to implement, just describe 
them to me (this could be configurable).
- a few implementation questions are in the patch's comments.
- if there are locale problems please post.

Cheers,

Hamish

PS. you may remember I wanted to work on X translucency effects; it was above 
me and I wanted to learn c++ more than c...
["keyboard.patch" (text/x-diff)]

Index: ui/service_mnu.cpp
===================================================================
RCS file: /home/kde/kdebase/kicker/ui/service_mnu.cpp,v
retrieving revision 1.30
diff -u -3 -p -u -r1.30 service_mnu.cpp
--- ui/service_mnu.cpp	2001/12/08 06:28:04	1.30
+++ ui/service_mnu.cpp	2001/12/26 07:52:59
@@ -368,6 +368,130 @@ void PanelServiceMenu::mouseMoveEvent(QM
     startPos_ = QPoint(-1,-1);
 }

+/**
+ * This is re-implemented for keyboard navigation.
+ */
+void PanelServiceMenu::keyPressEvent(QKeyEvent* e)
+{
+	/********************************************************************
+	 * The following tries to be somewhat efficient...
+	 *    ... but not so efficient that the code is (too) complex.
+	 *
+	 * FIXME should we cater for multiple keystrokes in the same event?
+	 *
+	 * Todo:
+	 *  - Verify this works in interesting locales
+	 *  - Different reaction patterns on request
+	 *******************************************************************/
+
+	int i = 0;
+
+	// this was chosen to honour unicode strokes correctly - is this correct?
+	QString lower = e->text().lower();
+
+	// check for common commands dealt with by QPopup
+	int key = e->key();
+	if (key == Key_Escape || key == Key_Return || key == Key_Enter
+			|| key == Key_Up || key == Key_Down || key == Key_Left
+			|| key == Key_Right || key == Key_F1) {
+		e->ignore();
+		// continue event processing by Qpopup
+		KPanelMenu::keyPressEvent(e);
+		return;
+	}
+
+	if (lastChar == lower) {
+		// same character pressed as last time
+		if (noMatches) {
+			e->ignore();
+			KPanelMenu::keyPressEvent(e);
+			return;
+		} else if (moreThanOne) {
+			// we had to check for this last time
+			setActiveItem(nextHitIndex);
+
+			lastHitIndex = nextHitIndex;
+			i = nextHitIndex + 1;
+		} else {
+			// already on the only matching entry
+			// this will only be reached with:
+			//  1) a non-auto-executing policy
+			//  2) the user returning to this menu, and pressing the same key
+			// For now, execute it again
+			setActiveItem(lastHitIndex);
+			return;
+		}
+	} else {
+		// reset all variables
+		lastHitIndex = nextHitIndex = -1;
+		moreThanOne = noMatches =  false;
+
+		// remember this keypress for next time
+		lastChar = lower;
+	}
+
+	QString upper = e->text().upper();
+
+	bool firstpass = true;
+	// loop menu items: -1 is to not do a search on the tearoff
+	for (; i < count() - 1; i++) {
+		// compare typed text with text of this entry
+		int j = idAt(i);
+		if (text(j).startsWith(upper) || text(j).startsWith(lower)) {
+			// match
+			if (moreThanOne) {
+				// save for next time
+				nextHitIndex = i;
+				return;
+			} else if (firstpass) {
+				// we don't know if there is another hit or not yet
+				// we need to know so that we can execute the item if
+				// it is unique, so continue on...
+				firstpass = false;
+				lastHitIndex = i;
+			} else {
+				// We've found a second item. select it only.
+				setActiveItem(lastHitIndex);
+
+				moreThanOne = true;
+				nextHitIndex = i;
+				return;
+			}
+		}
+	}
+
+	if (moreThanOne) {
+		// the next entry must be up further. Find it next time.
+		// this causes a reset on the next pass.
+		lastChar = QString::null;
+		return;
+	} else if (!firstpass) {
+		// we found a unique entry, activate it
+		setActiveItem(lastHitIndex);
+
+		// Create and pass an Enter keystroke to actually activate the item.
+		// This is the best method I could find...
+		QKeyEvent *enterPress =
+			new QKeyEvent(QKeyEvent::KeyPress, Key_Enter,
+					Key_Enter, 0, QString(QChar(Key_Enter)));
+		QKeyEvent *enterRelease =
+			new QKeyEvent(QKeyEvent::KeyRelease, Key_Enter,
+					Key_Enter, 0, QString(QChar(Key_Enter)));
+
+		// FIXME is this trouble where keyevents could be waiting?
+		// is sendEvent() more appropriate?
+		QApplication::postEvent(this, enterPress);
+		QApplication::postEvent(this, enterRelease);
+
+		return;
+	}
+
+	// no matches whatsoever, clean up
+	noMatches = true;
+	e->ignore();
+	KPanelMenu::keyPressEvent(e);
+}
+
 PanelServiceMenu *PanelServiceMenu::newSubMenu(const QString & label, const QString & relPath,
                                                QWidget * parent, const char * name)
 {
Index: ui/service_mnu.h
===================================================================
RCS file: /home/kde/kdebase/kicker/ui/service_mnu.h,v
retrieving revision 1.13
diff -u -3 -p -u -r1.13 service_mnu.h
--- ui/service_mnu.h	2001/12/08 06:28:04	1.13
+++ ui/service_mnu.h	2001/12/26 07:52:59
@@ -82,6 +82,16 @@ protected:
     virtual void mousePressEvent(QMouseEvent *);
     virtual void mouseMoveEvent(QMouseEvent *);
     virtual void closeEvent(QCloseEvent *);
+	virtual void keyPressEvent(QKeyEvent* e);
+
+	// variables for keyboard navigation
+	QString lastChar;
+	// variables below apply only when last character typed == new character being processed
+	int lastHitIndex;
+	int nextHitIndex;
+	bool moreThanOne;
+	bool noMatches;
+

     QString relPath_;



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

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