/* Copyright (C) 1997-2000 Nicolas Hadacek Copyright (C) 1998 Mark Donohoe Copyright (C) 1998 Matthias Ettrich Copyright (c) 2001,2002 Ellis Whitehead This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 "tdeaccelbase.h" #include #include #include #include #include "kckey.h" #include #include #include #include "kkeyserver.h" #include #include "tdeshortcutmenu.h" //--------------------------------------------------------------------- // class TDEAccelBase::ActionInfo //--------------------------------------------------------------------- //--------------------------------------------------------------------- // class TDEAccelBase //--------------------------------------------------------------------- TDEAccelBase::TDEAccelBase( int fInitCode ) : m_rgActions( this ) { kdDebug(125) << "TDEAccelBase(): this = " << this << endl; m_bNativeKeys = fInitCode & NATIVE_KEYS; m_bEnabled = true; m_sConfigGroup = "Shortcuts"; m_bConfigIsGlobal = false; m_bAutoUpdate = false; mtemp_pActionRemoving = 0; } TDEAccelBase::~TDEAccelBase() { kdDebug(125) << "~TDEAccelBase(): this = " << this << endl; } uint TDEAccelBase::actionCount() const { return m_rgActions.count(); } TDEAccelActions& TDEAccelBase::actions() { return m_rgActions; } bool TDEAccelBase::isEnabled() const { return m_bEnabled; } // see TDEGlobalAccel::blockShortcuts() stuff - it's to temporarily block // all global shortcuts, so that the key grabs are released, but from the app's // point of view the TDEGlobalAccel is still enabled, so TDEGlobalAccel needs // to disable key grabbing even if enabled bool TDEAccelBase::isEnabledInternal() const { return isEnabled(); } TDEAccelAction* TDEAccelBase::actionPtr( const TQString& sAction ) { return m_rgActions.actionPtr( sAction ); } const TDEAccelAction* TDEAccelBase::actionPtr( const TQString& sAction ) const { return m_rgActions.actionPtr( sAction ); } TDEAccelAction* TDEAccelBase::actionPtr( const KKeyServer::Key& key ) { if( !m_mapKeyToAction.contains( key ) ) return 0; // Note: If more than one action is connected to a single key, nil will be returned. return m_mapKeyToAction[key].pAction; } TDEAccelAction* TDEAccelBase::actionPtr( const KKey& key ) { KKeyServer::Key k2; k2.init( key, !m_bNativeKeys ); return actionPtr( k2 ); } void TDEAccelBase::setConfigGroup( const TQString& sConfigGroup ) { m_sConfigGroup = sConfigGroup; } void TDEAccelBase::setConfigGlobal( bool global ) { m_bConfigIsGlobal = global; } bool TDEAccelBase::setActionEnabled( const TQString& sAction, bool bEnable ) { TDEAccelAction* pAction = actionPtr( sAction ); if( pAction ) { if( pAction->m_bEnabled != bEnable ) { kdDebug(125) << "TDEAccelBase::setActionEnabled( " << sAction << ", " << bEnable << " )" << endl; pAction->m_bEnabled = bEnable; if( m_bAutoUpdate ) { // FIXME: the action may already have it's connections inserted! if( bEnable ) insertConnection( pAction ); else if( pAction->isConnected() ) removeConnection( pAction ); } } return true; } return false; } bool TDEAccelBase::setAutoUpdate( bool bAuto ) { kdDebug(125) << "TDEAccelBase::setAutoUpdate( " << bAuto << " ): m_bAutoUpdate on entrance = " << m_bAutoUpdate << endl; bool b = m_bAutoUpdate; if( !m_bAutoUpdate && bAuto ) updateConnections(); m_bAutoUpdate = bAuto; return b; } TDEAccelAction* TDEAccelBase::insert( const TQString& sAction, const TQString& sDesc, const TQString& sHelp, const TDEShortcut& rgCutDefaults3, const TDEShortcut& rgCutDefaults4, const TQObject* pObjSlot, const char* psMethodSlot, bool bConfigurable, bool bEnabled ) { kdDebug(125) << "TDEAccelBase::insert() begin" << endl; kdDebug(125) << "\t" << sAction << ": " << rgCutDefaults3.toString() << ": " << rgCutDefaults4.toString() << endl; TDEAccelAction* pAction = m_rgActions.insert( sAction, sDesc, sHelp, rgCutDefaults3, rgCutDefaults4, pObjSlot, psMethodSlot, bConfigurable, bEnabled ); if( pAction && m_bAutoUpdate ) insertConnection( pAction ); //kdDebug(125) << "TDEAccelBase::insert() end" << endl; return pAction; } TDEAccelAction* TDEAccelBase::insert( const TQString& sName, const TQString& sDesc ) { return m_rgActions.insert( sName, sDesc ); } bool TDEAccelBase::remove( const TQString& sAction ) { return m_rgActions.remove( sAction ); } void TDEAccelBase::slotRemoveAction( TDEAccelAction* pAction ) { removeConnection( pAction ); } bool TDEAccelBase::setActionSlot( const TQString& sAction, const TQObject* pObjSlot, const char* psMethodSlot ) { kdDebug(125) << "TDEAccelBase::setActionSlot( " << sAction << ", " << pObjSlot << ", " << psMethodSlot << " )\n"; TDEAccelAction* pAction = m_rgActions.actionPtr( sAction ); if( pAction ) { // If there was a previous connection, remove it. if( m_bAutoUpdate && pAction->isConnected() ) { kdDebug(125) << "\tm_pObjSlot = " << pAction->m_pObjSlot << " m_psMethodSlot = " << pAction->m_psMethodSlot << endl; removeConnection( pAction ); } pAction->m_pObjSlot = pObjSlot; pAction->m_psMethodSlot = psMethodSlot; // If we're setting a connection, if( m_bAutoUpdate && pObjSlot && psMethodSlot ) insertConnection( pAction ); return true; } else return false; } /* TDEAccelBase Run Command=Meta+Enter;Alt+F2 TDEAccelAction = "Run Command" 1) TDEAccelKeySeries = "Meta+Enter" 1a) Meta+Enter 1b) Meta+Keypad_Enter 2) TDEAccelKeySeries = "Alt+F2" 1a) Alt+F2 Konqueror=Meta+I,I TDEAccelAction = "Konqueror" 1) TDEAccelKeySeries = "Meta+I,I" 1a) Meta+I 2a) I Something=Meta+Asterisk,X TDEAccelAction = "Something" 1) TDEAccelKeySeries = "Meta+Asterisk,X" 1a) Meta+Shift+8 1b) Meta+Keypad_8 2a) X read in a config entry split by ';' find key sequences to disconnect find new key sequences to connect check for conflicts with implicit keys disconnect conflicting implicit keys connect new key sequences */ /* { For { for( TDEAccelAction::iterator itAction = m_rgActions.begin(); itAction != m_rgActions.end(); ++itAction ) { TDEAccelAction& action = *itAction; for( TDEAccelSeries::iterator itSeries = action.m_rgSeries.begin(); itSeries != action.m_rgSeries.end(); ++itSeries ) { TDEAccelSeries& series = *itSeries; if( } } } Sort by: iVariation, iSequence, iSeries, iAction 1) TDEAccelAction = "Run Command" 1) TDEAccelKeySeries = "Meta+Enter" 1a) Meta+Enter 1b) Meta+Keypad_Enter 2) TDEAccelKeySeries = "Alt+F2" 1a) Alt+F2 2) TDEAccelAction = "Enter Calculation" 1) TDEAccelKeySeries = "Meta+Keypad_Enter" 1a) Meta+Keypad_Enter List = Meta+Enter -> 1, 1, 1a Meta+Keypad_Enter -> 2, 1, 1a Alt+F2 -> 1, 2, 1a [Meta+Keypad_Enter] -> [1, 1, 1b] } */ #ifdef Q_WS_X11 struct TDEAccelBase::X { uint iAction, iSeq, iVari; KKeyServer::Key key; X() {} X( uint _iAction, uint _iSeq, uint _iVari, const KKeyServer::Key& _key ) { iAction = _iAction; iSeq = _iSeq; iVari = _iVari; key = _key; } int compare( const X& x ) { int n = key.compare( x.key ); if( n != 0 ) return n; if( iVari != x.iVari ) return iVari - x.iVari; if( iSeq != x.iSeq ) return iSeq - x.iSeq; return 0; } bool operator <( const X& x ) { return compare( x ) < 0; } bool operator >( const X& x ) { return compare( x ) > 0; } bool operator <=( const X& x ) { return compare( x ) <= 0; } }; #endif //Q_WS_X11 /* #1 Ctrl+A #2 Ctrl+A #3 Ctrl+B ------ Ctrl+A => Null Ctrl+B => #3 #1 Ctrl+A #1 Ctrl+B;Ctrl+A ------ Ctrl+A => #1 Ctrl+B => #2 #1 Ctrl+A #1 Ctrl+B,C #1 Ctrl+B,D ------ Ctrl+A => #1 Ctrl+B => Null #1 Ctrl+A #2 Ctrl+Plus(Ctrl+KP_Add) ------ Ctrl+A => #1 Ctrl+Plus => #2 Ctrl+KP_Add => #2 #1 Ctrl+Plus(Ctrl+KP_Add) #2 Ctrl+KP_Add ------ Ctrl+Plus => #1 Ctrl+KP_Add => #2 #1 Ctrl+Plus(Ctrl+KP_Add) #2 Ctrl+A;Ctrl+KP_Add ------ Ctrl+A => #2 Ctrl+Plus => #1 Ctrl+KP_Add => #2 */ bool TDEAccelBase::updateConnections() { #ifdef Q_WS_X11 kdDebug(125) << "TDEAccelBase::updateConnections() this = " << this << endl; // Retrieve the list of keys to be connected, sorted by priority. // (key, variation, seq) TQValueVector rgKeys; createKeyList( rgKeys ); m_rgActionsNonUnique.clear(); KKeyToActionMap mapKeyToAction; for( uint i = 0; i < rgKeys.size(); i++ ) { X& x = rgKeys[i]; KKeyServer::Key& key = x.key; ActionInfo info; bool bNonUnique = false; info.pAction = m_rgActions.actionPtr( x.iAction ); info.iSeq = x.iSeq; info.iVariation = x.iVari; // If this is a multi-key shortcut, if( info.pAction->shortcut().seq(info.iSeq).count() > 1 ) bNonUnique = true; // If this key is requested by more than one action, else if( i < rgKeys.size() - 1 && key == rgKeys[i+1].key ) { // If multiple actions requesting this key // have the same priority as the first one, if( info.iVariation == rgKeys[i+1].iVari && info.iSeq == rgKeys[i+1].iSeq ) bNonUnique = true; kdDebug(125) << "key conflict = " << key.key().toStringInternal() << " action1 = " << info.pAction->name() << " action2 = " << m_rgActions.actionPtr( rgKeys[i+1].iAction )->name() << " non-unique = " << bNonUnique << endl; // Skip over the other records with this same key. while( i < rgKeys.size() - 1 && key == rgKeys[i+1].key ) i++; } if( bNonUnique ) { // Remove connection to single action if there is one if( m_mapKeyToAction.contains( key ) ) { TDEAccelAction* pAction = m_mapKeyToAction[key].pAction; if( pAction ) { m_mapKeyToAction.remove( key ); disconnectKey( *pAction, key ); pAction->decConnections(); m_rgActionsNonUnique.append( pAction ); } } // Indicate that no single action is associated with this key. m_rgActionsNonUnique.append( info.pAction ); info.pAction = 0; } kdDebug(125) << "mapKeyToAction[" << key.key().toStringInternal() << "] = " << info.pAction << endl; mapKeyToAction[key] = info; } // Disconnect keys which no longer have bindings: for( KKeyToActionMap::iterator it = m_mapKeyToAction.begin(); it != m_mapKeyToAction.end(); ++it ) { const KKeyServer::Key& key = it.key(); TDEAccelAction* pAction = (*it).pAction; // If this key is longer used or it points to a different action now, if( !mapKeyToAction.contains( key ) || mapKeyToAction[key].pAction != pAction ) { if( pAction ) { disconnectKey( *pAction, key ); pAction->decConnections(); } else disconnectKey( key ); } } // Connect any unconnected keys: // In other words, connect any keys which are present in the // new action map, but which are _not_ present in the old one. for( KKeyToActionMap::iterator it = mapKeyToAction.begin(); it != mapKeyToAction.end(); ++it ) { const KKeyServer::Key& key = it.key(); TDEAccelAction* pAction = (*it).pAction; if( !m_mapKeyToAction.contains( key ) || m_mapKeyToAction[key].pAction != pAction ) { // TODO: Decide what to do if connect fails. // Probably should remove this item from map. if( pAction ) { if( connectKey( *pAction, key ) ) pAction->incConnections(); } else connectKey( key ); } } // Store new map. m_mapKeyToAction = mapKeyToAction; #ifndef NDEBUG for( KKeyToActionMap::iterator it = m_mapKeyToAction.begin(); it != m_mapKeyToAction.end(); ++it ) { kdDebug(125) << "Key: " << it.key().key().toStringInternal() << " => '" << (((*it).pAction) ? (*it).pAction->name() : TQString::null) << "'" << endl; } #endif #endif //Q_WS_X11 return true; } #ifdef Q_WS_X11 // Construct a list of keys to be connected, sorted highest priority first. void TDEAccelBase::createKeyList( TQValueVector& rgKeys ) { kdDebug(125) << "TDEAccelBase::createKeyList()" << endl; if( !isEnabledInternal()) { return; } // create the list // For each action for( uint iAction = 0; iAction < m_rgActions.count(); iAction++ ) { TDEAccelAction* pAction = m_rgActions.actionPtr( iAction ); if( pAction && pAction->m_pObjSlot && pAction->m_psMethodSlot && pAction != mtemp_pActionRemoving ) { // For each key sequence associated with action for( uint iSeq = 0; iSeq < pAction->shortcut().count(); iSeq++ ) { const KKeySequence& seq = pAction->shortcut().seq(iSeq); if( seq.count() > 0 ) { KKeyServer::Variations vars; vars.init( seq.key(0), !m_bNativeKeys ); for( uint iVari = 0; iVari < vars.count(); iVari++ ) { if( vars.key(iVari).code() && vars.key(iVari).sym() ) { rgKeys.push_back( X( iAction, iSeq, iVari, vars.key( iVari ) ) ); } kdDebug(125) << "\t" << pAction->name() << ": " << vars.key(iVari).key().toStringInternal() << " [action specified: " << pAction->toStringInternal() << "]" << endl; } } //else { // kdDebug(125) << "\t*" << pAction->name() << ":" << endl; // } } } } // sort by priority: iVariation[of first key], iSequence, iAction qHeapSort( rgKeys.begin(), rgKeys.end() ); } #endif //Q_WS_X11 bool TDEAccelBase::insertConnection( TDEAccelAction* pAction ) { if( !pAction->m_pObjSlot || !pAction->m_psMethodSlot ) return true; kdDebug(125) << "TDEAccelBase::insertConnection( " << pAction << "=\"" << pAction->m_sName << "\"; shortcut = " << pAction->shortcut().toStringInternal() << " ) this = " << this << endl; // For each sequence associated with the given action: for( uint iSeq = 0; iSeq < pAction->shortcut().count(); iSeq++ ) { // Get the first key of the sequence. KKeyServer::Variations vars; vars.init( pAction->shortcut().seq(iSeq).key(0), !m_bNativeKeys ); for( uint iVari = 0; iVari < vars.count(); iVari++ ) { const KKeyServer::Key& key = vars.key( iVari ); //if( !key.isNull() ) { if( key.sym() ) { if( !m_mapKeyToAction.contains( key ) ) { // If this is a single-key shortcut, if( pAction->shortcut().seq(iSeq).count() == 1 ) { m_mapKeyToAction[key] = ActionInfo( pAction, iSeq, iVari ); if( connectKey( *pAction, key ) ) pAction->incConnections(); } // Else this is a multi-key shortcut, else { m_mapKeyToAction[key] = ActionInfo( 0, 0, 0 ); // Insert into non-unique list if it's not already there. if( m_rgActionsNonUnique.findIndex( pAction ) == -1 ) m_rgActionsNonUnique.append( pAction ); if( connectKey( key ) ) pAction->incConnections(); } } else { // There is a key conflict. A full update // check is necessary. // TODO: make this more efficient where possible. if( m_mapKeyToAction[key].pAction != pAction && m_mapKeyToAction[key].pAction != 0 ) { kdDebug(125) << "Key conflict with action = " << m_mapKeyToAction[key].pAction->name() << " key = " << key.key().toStringInternal() << " : call updateConnections()" << endl; return updateConnections(); } } } } } //kdDebug(125) << "\tActions = " << m_rgActions.size() << endl; //for( TDEAccelActions::const_iterator it = m_rgActions.begin(); it != m_rgActions.end(); ++it ) { // kdDebug(125) << "\t" << &(*it) << " '" << (*it).m_sName << "'" << endl; //} //kdDebug(125) << "\tKeys = " << m_mapKeyToAction.size() << endl; //for( KKeyToActionMap::iterator it = m_mapKeyToAction.begin(); it != m_mapKeyToAction.end(); ++it ) { // //kdDebug(125) << "\tKey: " << it.key().toString() << " => '" << (*it)->m_sName << "'" << endl; // kdDebug(125) << "\tKey: " << it.key().toString() << " => '" << *it << "'" << endl; // kdDebug(125) << "\t\t'" << (*it)->m_sName << "'" << endl; //} return true; } bool TDEAccelBase::removeConnection( TDEAccelAction* pAction ) { kdDebug(125) << "TDEAccelBase::removeConnection( " << pAction << " = \"" << pAction->m_sName << "\"; shortcut = " << pAction->m_cut.toStringInternal() << " ): this = " << this << endl; //for( KKeyToActionMap::iterator it = m_mapKeyToAction.begin(); it != m_mapKeyToAction.end(); ++it ) // kdDebug(125) << "\tKey: " << it.key().toString() << " => '" << (*it)->m_sName << "'" << " " << *it << endl; if( m_rgActionsNonUnique.findIndex( pAction ) >= 0 ) { mtemp_pActionRemoving = pAction; bool b = updateConnections(); mtemp_pActionRemoving = 0; return b; } KKeyToActionMap::iterator it = m_mapKeyToAction.begin(); while( it != m_mapKeyToAction.end() ) { KKeyServer::Key key = it.key(); ActionInfo* pInfo = &(*it); // If the given action is connected to this key, if( pAction == pInfo->pAction ) { disconnectKey( *pAction, key ); pAction->decConnections(); KKeyToActionMap::iterator itRemove = it++; m_mapKeyToAction.remove( itRemove ); } else ++it; } return true; } bool TDEAccelBase::setShortcut( const TQString& sAction, const TDEShortcut& cut ) { TDEAccelAction* pAction = actionPtr( sAction ); if( pAction ) { if( m_bAutoUpdate ) removeConnection( pAction ); pAction->setShortcut( cut ); if( m_bAutoUpdate && !pAction->shortcut().isNull() ) insertConnection( pAction ); return true; } else return false; } void TDEAccelBase::readSettings( TDEConfigBase* pConfig ) { m_rgActions.readActions( m_sConfigGroup, pConfig ); if( m_bAutoUpdate ) updateConnections(); } void TDEAccelBase::writeSettings( TDEConfigBase* pConfig ) const { m_rgActions.writeActions( m_sConfigGroup, pConfig, m_bConfigIsGlobal, m_bConfigIsGlobal ); } TQPopupMenu* TDEAccelBase::createPopupMenu( TQWidget* pParent, const KKeySequence& seq ) { TDEShortcutMenu* pMenu = new TDEShortcutMenu( pParent, &actions(), seq ); bool bActionInserted = false; bool bInsertSeparator = false; for( uint i = 0; i < actionCount(); i++ ) { const TDEAccelAction* pAction = actions().actionPtr( i ); if( !pAction->isEnabled() ) continue; // If an action has already been inserted into the menu // and we have a label instead of an action here, // then indicate that we should insert a separator before the next menu entry. if( bActionInserted && !pAction->isConfigurable() && pAction->name().contains( ':' ) ) bInsertSeparator = true; for( uint iSeq = 0; iSeq < pAction->shortcut().count(); iSeq++ ) { const KKeySequence& seqAction = pAction->shortcut().seq(iSeq); if( seqAction.startsWith( seq ) ) { if( bInsertSeparator ) { pMenu->insertSeparator(); bInsertSeparator = false; } pMenu->insertAction( i, seqAction ); //kdDebug(125) << "sLabel = " << sLabel << ", seq = " << (TQString)seqMenu.qt() << ", i = " << i << endl; //kdDebug(125) << "pMenu->accel(" << i << ") = " << (TQString)pMenu->accel(i) << endl; bActionInserted = true; break; } } } pMenu->updateShortcuts(); return pMenu; }