Keyboard shortcut 'stealing' patch

Zac Hansen xaxxon at chopper.slackworks.com
Mon Mar 17 16:56:44 GMT 2003


This patch modifies the behaviour when you try to set a keyboard shortcut 
with a key sequence that is already bound to another command.

***DISCLAIMER***
I'm not claiming this code to be correct, only that in the test cases I 
was able to come up with did it work (as far as I could tell).  This is 
only the second patch I've done for KDE and don't understand the full 
workings.  I tested this mostly by using konsole and kcontrol.  Please, if 
anyone knows anything I'm messing up, let me know and I'll fix it.
****************


The previous behaviour was that it would pop up an information dialog 
telling you what command the sequence was bound to (but no easy way to get 
to it if you didn't know where it was), and telling you to use another 
sequence.

The new behaviour pops up a warningYesNo box telling you the same 
information, but asking if you want to steal the sequence from the other 
command.  As is the standard behaviour with a warningYesNo box, "No" is 
the default choiYASYce, so there is little chance of accidentally stealing 
the 
shortcut.  


The patch also adds misc comments I added while sifting thorugh the code.  
I believe them to be accurate.. but I did them as I was learning the code, 
so they may be inaccurate.

kstdaccel.h:
added forceInitialization function to re-load a key-bninding if it's been 
changed.

kstdaccel.cpp:
code for forceInitialization

kkeydialog.h:
indentation fixes
PromptForSteal function that presents a warningYesNo asking if the user 
wants to steal, replacing _warning

kkeydialog.cpp
indentation fixes (people have tabs set to 8, and then indent matching 
with spaces to 8.  which messes up everyone who has tabs set to not-8)
comments
Code for actually doing the stealing.  

If the shortcut belongs to a standard application shortcut, it writes the 
entry out to the Shortcuts section of the config file, and then calls 
forceInitialization() to re-load the keyboard shortcut for the standard 
application shortcut.

If the shortcut belongs to global shortcut, it then clears out the global 
shortcut entry and writes it out.  Since global shortcuts don't affect the 
local program, no need to re-init anything.

if it's a local preference, just change the preference.  Stuff will get 
taken care of on program exit.  


<patch attached>



--Zac
xaxxon at slackworks.com



-------------- next part --------------
--- /home/kdedevel/kde-cvs3/kdelibs/./kdecore/kstdaccel.h	2003-02-25 17:36:38.000000000 +0000
+++ ./kdecore/kstdaccel.h	2003-03-14 11:53:54.000000000 +0000
@@ -75,6 +75,10 @@ namespace KStdAccel
    */
   const KShortcut& shortcut(StdAccel id);
 
+  // forces initialization or re-initialization of the shortcut.  Useful if you've
+  //   changed something
+  void forceInitialization ( StdAccel id );
+
   /**
    * Returns a unique name for the given accel.
    * @param id the id of the accelerator
--- /home/kdedevel/kde-cvs3/kdelibs/./kdeui/kkeydialog.h	2003-03-12 19:36:32.000000000 +0000
+++ ./kdeui/kkeydialog.h	2003-03-14 11:26:22.000000000 +0000
@@ -140,7 +140,13 @@ class KKeyChooser : public QWidget
 	void fontChange( const QFont& _font );
 	void setShortcut( const KShortcut& cut );
 	bool isKeyPresent( const KShortcut& cut, bool warnuser = true );
-        bool isKeyPresentLocally( const KShortcut& cut, KKeyChooserItem* ignoreItem, const QString& warnText );
+	bool isKeyPresentLocally( const KShortcut& cut, KKeyChooserItem* ignoreItem, const QString& warnText );
+
+	// puts up a dialog asking the user if he/she wants to steal an existing
+	//   keyboard shortcut away from its previous assignment -- replaces
+	//   _warning ( )
+	bool PromptForSteal ( const KKeySequence & seq, QString sAction, QString sTitle );
+	
 	void _warning( const KKeySequence& seq, QString sAction, QString sTitle );
 
  protected slots:
@@ -149,8 +155,8 @@ class KKeyChooser : public QWidget
 	void slotCustomKey();
 	void slotListItemSelected( QListViewItem *item );
 	void capturedShortcut( const KShortcut& cut );
-        void slotSettingsChanged( int );
-        void slotListItemDoubleClicked ( QListViewItem * ipoQListViewItem, const QPoint & ipoQPoint, int c );
+	void slotSettingsChanged( int );
+	void slotListItemDoubleClicked ( QListViewItem * ipoQListViewItem, const QPoint & ipoQPoint, int c );
 
  protected:
 	ActionType m_type;
--- /home/kdedevel/kde-cvs3/kdelibs/./kdecore/kstdaccel.cpp	2003-02-25 17:36:38.000000000 +0000
+++ ./kdecore/kstdaccel.cpp	2003-03-14 11:27:26.000000000 +0000
@@ -128,6 +128,13 @@ static void initialize( StdAccel id )
 	pInfo->bInitialized = true;
 }
 
+void forceInitialization ( StdAccel id )
+{
+
+	initialize ( id );
+
+}
+
 QString name( StdAccel id )
 {
 	KStdAccelInfo* pInfo = infoPtr( id );
--- /home/kdedevel/kde-cvs3/kdelibs/./kdeui/kkeydialog.cpp	2003-03-12 19:36:31.000000000 +0000
+++ ./kdeui/kkeydialog.cpp	2003-03-16 05:44:30.662848016 +0000
@@ -322,7 +322,7 @@ void KKeyChooser::initGUI( ActionType ty
 
   // handle double clicking an item
   connect ( d->pList, SIGNAL ( doubleClicked ( QListViewItem *, const QPoint &, int ) ),
-                       SLOT ( slotListItemDoubleClicked ( QListViewItem *, const QPoint &, int ) ) );
+			SLOT ( slotListItemDoubleClicked ( QListViewItem *, const QPoint &, int ) ) );
 
   //
   // CREATE CHOOSE KEY GROUP
@@ -531,9 +531,9 @@ void KKeyChooser::slotCustomKey()
 
 void KKeyChooser::readGlobalKeys()
 {
-        d->mapGlobals.clear();
-        if( m_type == Global )
-            return; // they will be checked normally, because we're configuring them
+	d->mapGlobals.clear();
+	if( m_type == Global )
+		return; // they will be checked normally, because we're configuring them
 	QMap<QString, QString> mapEntry = KGlobal::config()->entryMap( "Global Shortcuts" );
 	QMap<QString, QString>::Iterator it( mapEntry.begin() );
 	for( uint i = 0; it != mapEntry.end(); ++it, i++ )
@@ -688,72 +688,156 @@ static int keyConflict( const KShortcut&
 	return -1;
 }
 
+
+// Checks shortcut "cut" against standard application shortcuts, global shortcuts, 
+//   
 bool KKeyChooser::isKeyPresent( const KShortcut& cut, bool bWarnUser )
 {
 	KKeyChooserItem* pItem = dynamic_cast<KKeyChooserItem*>(d->pList->currentItem());
 
+
 	// If editing global shortcuts, check them for conflicts with the stdaccels.
 	if( m_type == ApplicationGlobal || m_type == Global ) {
-		// For each key sequence in the shortcut,
+
+		// check all KSequences in the KShortcut
 		for( uint i = 0; i < cut.count(); i++ ) {
+
 			const KKeySequence& seq = cut.seq(i);
+			
 
 			KStdAccel::StdAccel id = KStdAccel::findStdAccel( seq );
 			if( id != KStdAccel::AccelNone
 			    && keyConflict( cut, KStdAccel::shortcut( id ) ) > -1 ) {
-				if( bWarnUser )
-					_warning( seq, KStdAccel::label(id), i18n("Conflict with Standard Application Shortcut") );
-				return true;
-			}
-		}
-	}
+				if( bWarnUser ) {
+
+					bool bSteal = PromptForSteal ( seq, KStdAccel::label(id), i18n("Conflict with Standard Application Shortcut") );
+
+					// if they don't want to steal, return a conflict
+					if ( bSteal == false ) {
+
+ 						return true;
+
+					} else {
+						
+						// write this to the Shortcut subject
+						KConfig * poConfig = KGlobal::config ( );
+						KConfigGroupSaver oConfigGroupSaver ( poConfig, "Shortcuts" );
+
+						oConfigGroupSaver.config ( )->writeEntry ( KStdAccel::label(id), "", true, true, false );
+						poConfig->sync ( );
+
+						// gotta tell the standard shortcuts to reload themselves
+						KStdAccel::forceInitialization ( id );
+
+					}
+
+				} // endif bWarnUser
 
+			} // endif di != KStdAcces::AccelNone
+
+		} // endif for i = 0 to cut.count ( )
+
+	} // end if m_type == ApplicationGlobal or Global
+
+	
 	QMap<QString, KShortcut>::ConstIterator it;
 	for( it = d->mapGlobals.begin(); it != d->mapGlobals.end(); ++it ) {
+		
+		// check for a conflict against this shortcut
 		int iSeq = keyConflict( cut, (*it) );
+		
+		// if a non-negative result was returned, it's the position of the sequence 
+		//   in this shortcut that conflicts
 		if( iSeq > -1 ) {
 			if( m_type != Global || it.key() != pItem->actionName() ) {
-				if( bWarnUser )
-					_warning( cut.seq(iSeq), it.key(), i18n("Conflict with Global Shortcuts") );
-				return true;
+				if( bWarnUser ) {
+					bool bSteal = PromptForSteal ( cut.seq(iSeq), it.key(), i18n("Conflict with Global Shortcuts") );
+
+					// if they don't want to steal it, report a conflict
+					if ( bSteal == false ) {
+
+						return true;
+
+					} else {
+
+						// update the global copy
+						KConfig * poConfig = KGlobal::config ( );
+
+						KConfigGroupSaver oConfigGroupSaver ( poConfig, "Global Shortcuts" );
+						oConfigGroupSaver.config ( )->writeEntry ( it.key ( ), "", true, true, false );
+						poConfig->sync ( ); // write it to the backend
+						
+ 					}
+				}
 			}
 		}
 	}
-        
-        if( isKeyPresentLocally( cut, pItem, bWarnUser ? i18n("Key Conflict") : QString::null ))
-            return true;
-
-        // check also other Global KKeyChooser's
-        if( m_type == Global && globalChoosers != NULL ) {
-            for( QValueList< KKeyChooser* >::ConstIterator it = globalChoosers->begin();
-                 it != globalChoosers->end();
-                 ++it ) {
-                if( (*it) != this && (*it)->isKeyPresentLocally( cut, NULL,
-                    bWarnUser ? i18n("Key Conflict") : QString::null))
-                    return true;
-            }
-        }
+
+	if( isKeyPresentLocally( cut, pItem, bWarnUser ? i18n("Conflict with Local Shortcut") : QString::null )) {
+
+		return true;
+
+	}
+	
+	// check also other Global KKeyChooser's
+	if( m_type == Global && globalChoosers != NULL ) {
+
+		// go through all the KKeyChosers
+		for( QValueList< KKeyChooser* >::ConstIterator it = globalChoosers->begin();
+			 it != globalChoosers->end();
+			 ++it ) {
+
+
+			// if we're not looking at ourselves and it's present in the
+			//   current KKeyChooser, return a conflict
+			if( (*it) != this && (*it)->isKeyPresentLocally( cut, NULL,
+															 bWarnUser ? i18n("Conflict with Other Global Shortcut") : QString::null))
+				return true;
+		}
+	}
 	return false;
 }
 
+
+
 bool KKeyChooser::isKeyPresentLocally( const KShortcut& cut, KKeyChooserItem* ignoreItem, const QString& warnText )
 {
-	// Search for shortcut conflicts with other actions in the
-	//  lists we're configuring.
-	for( QListViewItemIterator it( d->pList ); it.current(); ++it ) {
+	// check for conflicts between "cut" and all the shortcuts in "this"
+
+
+	// go through all teh shortcuts in "this"
+	for ( QListViewItemIterator it( d->pList ); it.current(); ++it ) {
 		KKeyChooserItem* pItem2 = dynamic_cast<KKeyChooserItem*>(it.current());
+
+		// check this KKeyChooserItem against cut if we're not ignoring it
 		if( pItem2 && pItem2 != ignoreItem ) {
 			int iSeq = keyConflict( cut, pItem2->shortcut() );
 			if( iSeq > -1 ) {
-				if( !warnText.isNull() )
-					_warning( cut.seq(iSeq), pItem2->text(0), warnText );
-				return true;
+				if( !warnText.isNull() ) {
+					bool bSteal = PromptForSteal ( cut.seq(iSeq), pItem2->text(0), warnText );
+
+					// If they didn't want to steal it, leave everything alone
+					if ( bSteal == false ) {
+
+						return true;
+
+					} else { 
+
+						// Otherwise, they want to steal it, so clear out the old one 
+						//   and keep looking for conflicting ones.  There SHOULDN'T be
+						//   more than one conflicting, but no harm looking
+						KShortcut & roShortcut = pItem2->shortcut ( );
+						pItem2->setShortcut ( KShortcut ( ) );
+
+					}
+				}
 			}
 		}
 	}
-        return false;            
+	return false;
 }
 
+
 /* antlarr: KDE 4: make them const QString & */
 void KKeyChooser::_warning( const KKeySequence& cut, QString sAction, QString sTitle )
 {
@@ -768,6 +852,25 @@ void KKeyChooser::_warning( const KKeySe
 	KMessageBox::sorry( this, s, sTitle );
 }
 
+/* antlarr: KDE 4: make them const QString & */
+bool KKeyChooser::PromptForSteal( const KKeySequence& cut, QString sAction, QString sTitle )
+{
+	sAction = sAction.stripWhiteSpace();
+
+	QString s =
+		i18n("The '%1' key combination has already been allocated "
+		"to the \"%2\" action.\n"
+		"Do you want to steal it?  Selecting \"yes\" will remove the shortcut "
+		"from the current command and assign it to the new command.").
+		arg(cut.toString()).arg(sAction);
+
+	int nSteal = KMessageBox::warningYesNo( this, s, sTitle );
+	return nSteal == KMessageBox::Yes;
+	
+}
+
+
+
 //---------------------------------------------------
 KKeyChooserItem::KKeyChooserItem( KListView* parent, QListViewItem* after, KShortcutList* pList, uint iAction )
 :	KListViewItem( parent, after )


More information about the kde-core-devel mailing list