diff --git a/CMakeLists.txt b/CMakeLists.txt index 121b54ba6..62d01202f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,6 +149,8 @@ SET( CANTATA_SRCS libmaia/maiaFault.cpp libmaia/maiaXmlRpcClient.cpp devices/utils.cpp + support/action.cpp + support/actioncollection.cpp ) SET( CANTATA_MOC_HDRS @@ -208,6 +210,8 @@ SET( CANTATA_MOC_HDRS libmaia/maiaObject.h libmaia/maiaFault.h libmaia/maiaXmlRpcClient.h + support/action.h + support/actioncollection.h ) SET( CANTATA_UIS @@ -291,6 +295,7 @@ include_directories( ${CMAKE_SOURCE_DIR}/libmaia ${CMAKE_SOURCE_DIR}/http ${CMAKE_SOURCE_DIR}/devices ${CMAKE_SOURCE_DIR}/dynamic + ${CMAKE_SOURCE_DIR}/support ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ) @@ -440,10 +445,10 @@ IF( ENABLE_KDE_SUPPORT ) install( FILES cantata.notifyrc DESTINATION ${DATA_INSTALL_DIR}/cantata ) ELSE( ENABLE_KDE_SUPPORT ) SET( CANTATA_SRCS ${CANTATA_SRCS} widgets/dirrequester.cpp widgets/lineedit.cpp network/networkproxyfactory.cpp network/proxysettings.cpp widgets/kmessagewidget.cpp - widgets/dialog.cpp widgets/messagebox.cpp widgets/pagewidget.cpp ) + widgets/dialog.cpp widgets/messagebox.cpp widgets/pagewidget.cpp support/shortcutsmodel.cpp support/shortcutssettingspage.cpp support/keysequencewidget.cpp ) SET( CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} application.h widgets/dirrequester.h widgets/lineedit.h network/proxysettings.h widgets/kmessagewidget.h widgets/urllabel.h - widgets/dialog.h ) - SET( CANTATA_UIS ${CANTATA_UIS} network/proxysettings.ui ) + widgets/dialog.h support/shortcutsmodel.h support/shortcutssettingspage.h support/keysequencewidget.h ) + SET( CANTATA_UIS ${CANTATA_UIS} network/proxysettings.ui support/shortcutssettingspage.ui ) SET( CANTATA_RCS ${CANTATA_RCS} cantata_qt.qrc ) QT4_ADD_RESOURCES( CANTATA_RC_SRCS ${CANTATA_RCS} ) QT4_WRAP_UI( CANTATA_UI_HDRS ${CANTATA_UIS} ) diff --git a/ChangeLog b/ChangeLog index c6ee1b2e6..9f8af0944 100644 --- a/ChangeLog +++ b/ChangeLog @@ -44,6 +44,8 @@ view is set to use icon/list style. 24. Add 'Add to playlist' action to playqueue context menu. 25. Fix retrieval of covers from file-system based devices. +26. Support for modifiable keyboard shortcuts in Qt-only builds. (Code stolen + from Quassel!) 0.8.3.1 ------- diff --git a/devices/devicespage.cpp b/devices/devicespage.cpp index 3e8fef399..df308bfc6 100644 --- a/devices/devicespage.cpp +++ b/devices/devicespage.cpp @@ -30,13 +30,13 @@ #include "messagebox.h" #include "localize.h" #include "icons.h" +#include "mainwindow.h" +#include "action.h" +#include "actioncollection.h" #include #include #ifdef ENABLE_KDE_SUPPORT #include -#include -#else -#include #endif #ifdef ENABLE_REMOTE_DEVICES #include "remotedevicepropertiesdialog.h" @@ -56,16 +56,16 @@ DevicesPage::DevicesPage(MainWindow *p) { setupUi(this); - configureAction = p->createAction("configuredevice", i18n("Configure Device"), 0); + configureAction = ActionCollection::get()->createAction("configuredevice", i18n("Configure Device"), 0); configureAction->setIcon(Icons::configureIcon); - refreshAction = p->createAction("refreshdevice", i18n("Refresh Device"), "view-refresh"); - copyAction = p->createAction("copytolibrary", i18n("Copy To Library"), "document-import"); + refreshAction = ActionCollection::get()->createAction("refreshdevice", i18n("Refresh Device"), "view-refresh"); + copyAction = ActionCollection::get()->createAction("copytolibrary", i18n("Copy To Library"), "document-import"); copyToLibraryButton->setDefaultAction(copyAction); - syncAction = p->createAction("syncdevice", i18n("Sync"), "folder-sync"); + syncAction = ActionCollection::get()->createAction("syncdevice", i18n("Sync"), "folder-sync"); connect(syncAction, SIGNAL(triggered()), this, SLOT(sync())); #ifdef ENABLE_REMOTE_DEVICES - forgetDeviceAction=p->createAction("forgetdevice", i18n("Forget Device"), "list-remove"); - toggleDeviceAction=p->createAction("toggledevice", i18n("Toggle Device"), "network-connect"); + forgetDeviceAction=ActionCollection::get()->createAction("forgetdevice", i18n("Forget Device"), "list-remove"); + toggleDeviceAction=ActionCollection::get()->createAction("toggledevice", i18n("Toggle Device"), "network-connect"); connect(forgetDeviceAction, SIGNAL(triggered()), this, SLOT(forgetRemoteDevice())); connect(toggleDeviceAction, SIGNAL(triggered()), this, SLOT(toggleDevice())); #endif @@ -105,7 +105,7 @@ DevicesPage::DevicesPage(MainWindow *p) menu->addAction(refreshAction); menu->addSeparator(); #ifdef ENABLE_REMOTE_DEVICES - Action *addRemote=p->createAction("adddevice", i18n("Add Device"), "network-server"); + Action *addRemote=ActionCollection::get()->createAction("adddevice", i18n("Add Device"), "network-server"); connect(addRemote, SIGNAL(triggered()), this, SLOT(addRemoteDevice())); menu->addAction(addRemote); menu->addAction(forgetDeviceAction); diff --git a/devices/devicespage.h b/devices/devicespage.h index 9399fea1c..8f0823c7e 100644 --- a/devices/devicespage.h +++ b/devices/devicespage.h @@ -25,7 +25,6 @@ #define DEVICESPAGE_H #include "ui_devicespage.h" -#include "mainwindow.h" #include "musiclibraryproxymodel.h" #include "device.h" #include "config.h" @@ -33,6 +32,9 @@ #include "remotefsdevice.h" #endif +class MainWindow; +class Action; + class DevicesPage : public QWidget, public Ui::DevicesPage { Q_OBJECT @@ -81,4 +83,3 @@ private: }; #endif - diff --git a/dynamic/dynamicpage.cpp b/dynamic/dynamicpage.cpp index 3724183b4..15443841f 100644 --- a/dynamic/dynamicpage.cpp +++ b/dynamic/dynamicpage.cpp @@ -26,24 +26,22 @@ #include "dynamicrulesdialog.h" #include "localize.h" #include "icon.h" +#include "mainwindow.h" +#include "action.h" +#include "actioncollection.h" #include #include -#ifdef ENABLE_KDE_SUPPORT -#include -#else -#include -#endif DynamicPage::DynamicPage(MainWindow *p) : QWidget(p) { setupUi(this); - refreshAction = p->createAction("refreshdynamic", i18n("Refresh Dynamic Rules"), "view-refresh"); - addAction = p->createAction("adddynamic", i18n("Add Dynamic Rules"), "list-add"); - editAction = p->createAction("editdynamic", i18n("Edit Dynamic Rules"), "document-edit"); - removeAction = p->createAction("removedynamic", i18n("Remove Dynamic Rules"), "list-remove"); - startAction = p->createAction("startdynamic", i18n("Start Dynamic Mode"), "media-playback-start"); - stopAction = p->createAction("stopdynamic", i18n("Stop Dynamic Mode"), "process-stop"); + refreshAction = ActionCollection::get()->createAction("refreshdynamic", i18n("Refresh Dynamic Rules"), "view-refresh"); + addAction = ActionCollection::get()->createAction("adddynamic", i18n("Add Dynamic Rules"), "list-add"); + editAction = ActionCollection::get()->createAction("editdynamic", i18n("Edit Dynamic Rules"), "document-edit"); + removeAction = ActionCollection::get()->createAction("removedynamic", i18n("Remove Dynamic Rules"), "list-remove"); + startAction = ActionCollection::get()->createAction("startdynamic", i18n("Start Dynamic Mode"), "media-playback-start"); + stopAction = ActionCollection::get()->createAction("stopdynamic", i18n("Stop Dynamic Mode"), "process-stop"); toggleAction = new Action(this); Icon::init(refreshBtn); diff --git a/dynamic/dynamicpage.h b/dynamic/dynamicpage.h index 975a8a7af..e36923555 100644 --- a/dynamic/dynamicpage.h +++ b/dynamic/dynamicpage.h @@ -25,9 +25,11 @@ #define DYNAMICPAGE_H #include "ui_dynamicpage.h" -#include "mainwindow.h" #include "dynamicproxymodel.h" +class MainWindow; +class Action; + class DynamicPage : public QWidget, public Ui::DynamicPage { Q_OBJECT diff --git a/gui/albumspage.cpp b/gui/albumspage.cpp index 6c35dc1c7..e16bcdaf9 100644 --- a/gui/albumspage.cpp +++ b/gui/albumspage.cpp @@ -30,13 +30,10 @@ #include "messagebox.h" #include "settings.h" #include "icon.h" +#include "mainwindow.h" +#include "action.h" #include #include -#ifdef ENABLE_KDE_SUPPORT -#include -#else -#include -#endif AlbumsPage::AlbumsPage(MainWindow *p) : QWidget(p) diff --git a/gui/albumspage.h b/gui/albumspage.h index e7e4c8a71..dae505194 100644 --- a/gui/albumspage.h +++ b/gui/albumspage.h @@ -25,9 +25,11 @@ #define ALBUMSPAGE_H #include "ui_albumspage.h" -#include "mainwindow.h" #include "albumsproxymodel.h" +#include "config.h" +class MainWindow; +class Action; class MusicLibraryItemRoot; class AlbumsPage : public QWidget, public Ui::AlbumsPage diff --git a/gui/folderpage.cpp b/gui/folderpage.cpp index 7b1e4ca29..710b25f94 100644 --- a/gui/folderpage.cpp +++ b/gui/folderpage.cpp @@ -28,14 +28,14 @@ #include "icon.h" #include "localize.h" #include "messagebox.h" +#include "mainwindow.h" +#include "action.h" +#include "actioncollection.h" #include #include #include #ifdef ENABLE_KDE_SUPPORT -#include #include -#else -#include #endif FolderPage::FolderPage(MainWindow *p) @@ -47,7 +47,7 @@ FolderPage::FolderPage(MainWindow *p) replacePlayQueue->setDefaultAction(p->replacePlayQueueAction); libraryUpdate->setDefaultAction(p->refreshAction); #ifdef ENABLE_KDE_SUPPORT - browseAction = p->createAction("openfilemanager", i18n("Open File Manager"), "system-file-manager"); + browseAction = ActionCollection::get()->createAction("openfilemanager", i18n("Open File Manager"), "system-file-manager"); #endif Icon::init(addToPlayQueue); Icon::init(replacePlayQueue); diff --git a/gui/folderpage.h b/gui/folderpage.h index af2923675..b900f4785 100644 --- a/gui/folderpage.h +++ b/gui/folderpage.h @@ -25,9 +25,12 @@ #define FOLDERPAGE_H #include "ui_folderpage.h" -#include "mainwindow.h" #include "dirviewmodel.h" #include "dirviewproxymodel.h" +#include "config.h" + +class MainWindow; +class Action; class FolderPage : public QWidget, public Ui::FolderPage { diff --git a/gui/librarypage.cpp b/gui/librarypage.cpp index 8862e6f48..aaba3056b 100644 --- a/gui/librarypage.cpp +++ b/gui/librarypage.cpp @@ -33,15 +33,13 @@ #include "messagebox.h" #include "settings.h" #include "icon.h" +#include "mainwindow.h" +#include "action.h" #include #include #ifdef ENABLE_KDE_SUPPORT -#include #include -#include #include -#else -#include #endif LibraryPage::LibraryPage(MainWindow *p) diff --git a/gui/librarypage.h b/gui/librarypage.h index defe67ea3..797cd0774 100644 --- a/gui/librarypage.h +++ b/gui/librarypage.h @@ -25,8 +25,11 @@ #define LIBRARYPAGE_H #include "ui_librarypage.h" -#include "mainwindow.h" #include "musiclibraryproxymodel.h" +#include "config.h" + +class MainWindow; +class Action; class LibraryPage : public QWidget, public Ui::LibraryPage { diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index a52480812..98d7d35d1 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #else #include #include "networkproxyfactory.h" @@ -112,6 +113,8 @@ #include "actionitemdelegate.h" #include "icons.h" #include "volumecontrol.h" +#include "action.h" +#include "actioncollection.h" #ifdef Q_OS_WIN static void raiseWindow(QWidget *w); #endif @@ -221,12 +224,6 @@ static int nextKey(int &key) { return k; } -#ifdef ENABLE_KDE_SUPPORT -#define SET_SHORTCUT(ACT, S) ACT->setShortcut(S) -#else -#define SET_SHORTCUT(ACT, S) ACT->setShortcut(S); addAction(ACT); ACT->setShortcutContext(Qt::ApplicationShortcut) -#endif - MainWindow::MainWindow(QWidget *parent) #ifdef ENABLE_KDE_SUPPORT : KXmlGuiWindow(parent) @@ -268,6 +265,8 @@ MainWindow::MainWindow(QWidget *parent) , phononStream(0) #endif { + ActionCollection::setMainWidget(this); + #ifndef Q_OS_WIN new CantataAdaptor(this); #ifndef ENABLE_KDE_SUPPORT @@ -293,104 +292,103 @@ MainWindow::MainWindow(QWidget *parent) MPDParseUtils::setGroupMultiple(Settings::self()->groupMultiple()); #ifdef ENABLE_KDE_SUPPORT - prefAction=KStandardAction::preferences(this, SLOT(showPreferencesDialog()), actionCollection()); - quitAction=KStandardAction::quit(kapp, SLOT(quit()), actionCollection()); + prefAction=(Action *)KStandardAction::preferences(this, SLOT(showPreferencesDialog()), ActionCollection::get()); + quitAction=(Action *)KStandardAction::quit(kapp, SLOT(quit()), ActionCollection::get()); #else setWindowIcon(Icons::appIcon); QNetworkProxyFactory::setApplicationProxyFactory(NetworkProxyFactory::Instance()); - quitAction = new QAction(i18n("&Quit"), this); + quitAction = ActionCollection::get()->createAction("quit", i18n("Quit"), "application-exit"); connect(quitAction, SIGNAL(triggered(bool)), qApp, SLOT(quit())); - quitAction->setIcon(Icon("application-exit")); quitAction->setShortcut(QKeySequence::Quit); #if !defined Q_OS_WIN - restoreAction = new QAction(i18n("&Show Window"), this); + restoreAction = ActionCollection::get()->createAction("showwindow", i18n("Show Window")); connect(restoreAction, SIGNAL(triggered(bool)), this, SLOT(restoreWindow())); #endif // !Q_OS_WIN #endif // ENABLE_KDE_SUPPORT - smallPlaybackButtonsAction = createAction("smallplaybackbuttons", i18n("Small Playback Buttons")); - smallControlButtonsAction = createAction("smallcontrolbuttons", i18n("Small Control Buttons")); - connectAction = createAction("connect",i18n("Connect")); + smallPlaybackButtonsAction = ActionCollection::get()->createAction("smallplaybackbuttons", i18n("Small Playback Buttons")); + smallControlButtonsAction = ActionCollection::get()->createAction("smallcontrolbuttons", i18n("Small Control Buttons")); + connectAction = ActionCollection::get()->createAction("connect",i18n("Connect")); connectAction->setIcon(Icons::connectIcon); - connectionsAction = createAction("connections", i18n("Connection"), "network-server"); - outputsAction = createAction("outputs", i18n("Outputs")); + connectionsAction = ActionCollection::get()->createAction("connections", i18n("Connection"), "network-server"); + outputsAction = ActionCollection::get()->createAction("outputs", i18n("Outputs")); outputsAction->setIcon(Icons::speakerIcon); - refreshAction = createAction("refresh", i18n("Refresh Database"), "view-refresh"); - prevTrackAction = createAction("prevtrack", i18n("Previous Track"), "media-skip-backward"); - nextTrackAction = createAction("nexttrack", i18n("Next Track"), "media-skip-forward"); - playPauseTrackAction = createAction("playpausetrack", i18n("Play/Pause")); - stopTrackAction = createAction("stoptrack", i18n("Stop"), "media-playback-stop"); - increaseVolumeAction = createAction("increasevolume", i18n("Increase Volume")); - decreaseVolumeAction = createAction("decreasevolume", i18n("Decrease Volume")); - addToPlayQueueAction = createAction("addtoplaylist", i18n("Add To Play Queue"), "list-add"); - addToStoredPlaylistAction = createAction("addtostoredplaylist", i18n("Add To Playlist")); - addPlayQueueToStoredPlaylistAction = createAction("addpqtostoredplaylist", i18n("Add To Stored Playlist")); + refreshAction = ActionCollection::get()->createAction("refresh", i18n("Refresh Database"), "view-refresh"); + prevTrackAction = ActionCollection::get()->createAction("prevtrack", i18n("Previous Track"), "media-skip-backward"); + nextTrackAction = ActionCollection::get()->createAction("nexttrack", i18n("Next Track"), "media-skip-forward"); + playPauseTrackAction = ActionCollection::get()->createAction("playpausetrack", i18n("Play/Pause")); + stopTrackAction = ActionCollection::get()->createAction("stoptrack", i18n("Stop"), "media-playback-stop"); + increaseVolumeAction = ActionCollection::get()->createAction("increasevolume", i18n("Increase Volume")); + decreaseVolumeAction = ActionCollection::get()->createAction("decreasevolume", i18n("Decrease Volume")); + addToPlayQueueAction = ActionCollection::get()->createAction("addtoplaylist", i18n("Add To Play Queue"), "list-add"); + addToStoredPlaylistAction = ActionCollection::get()->createAction("addtostoredplaylist", i18n("Add To Playlist")); + addPlayQueueToStoredPlaylistAction = ActionCollection::get()->createAction("addpqtostoredplaylist", i18n("Add To Stored Playlist")); #ifdef ENABLE_REPLAYGAIN_SUPPORT - replaygainAction = createAction("replaygain", i18n("ReplayGain"), "audio-x-generic"); + replaygainAction = ActionCollection::get()->createAction("replaygain", i18n("ReplayGain"), "audio-x-generic"); #endif - backAction = createAction("back", i18n("Back"), "go-previous"); - removeAction = createAction("removeitems", i18n("Remove"), "list-remove"); - replacePlayQueueAction = createAction("replaceplaylist", i18n("Replace Play Queue"), "media-playback-start"); - removeFromPlayQueueAction = createAction("removefromplaylist", i18n("Remove From Play Queue"), "list-remove"); - copyTrackInfoAction = createAction("copytrackinfo", i18n("Copy Track Info")); - cropPlayQueueAction = createAction("cropplaylist", i18n("Crop")); - shufflePlayQueueAction = createAction("shuffleplaylist", i18n("Shuffle")); - savePlayQueueAction = createAction("saveplaylist", i18n("Save As"), "document-save-as"); - clearPlayQueueAction = createAction("clearplaylist", i18n("Clear"), "edit-clear-list"); + backAction = ActionCollection::get()->createAction("back", i18n("Back"), "go-previous"); + removeAction = ActionCollection::get()->createAction("removeitems", i18n("Remove"), "list-remove"); + replacePlayQueueAction = ActionCollection::get()->createAction("replaceplaylist", i18n("Replace Play Queue"), "media-playback-start"); + removeFromPlayQueueAction = ActionCollection::get()->createAction("removefromplaylist", i18n("Remove From Play Queue"), "list-remove"); + copyTrackInfoAction = ActionCollection::get()->createAction("copytrackinfo", i18n("Copy Track Info")); + cropPlayQueueAction = ActionCollection::get()->createAction("cropplaylist", i18n("Crop")); + shufflePlayQueueAction = ActionCollection::get()->createAction("shuffleplaylist", i18n("Shuffle")); + savePlayQueueAction = ActionCollection::get()->createAction("saveplaylist", i18n("Save As"), "document-save-as"); + clearPlayQueueAction = ActionCollection::get()->createAction("clearplaylist", i18n("Clear"), "edit-clear-list"); #if !defined ENABLE_KDE_SUPPORT && !defined Q_OS_WIN if (clearPlayQueueAction->icon().isNull()) { clearPlayQueueAction->setIcon(Icon("edit-delete")); } #endif - expandInterfaceAction = createAction("expandinterface", i18n("Expanded Interface"), "view-media-playlist"); - randomPlayQueueAction = createAction("randomplaylist", i18n("Random")); - repeatPlayQueueAction = createAction("repeatplaylist", i18n("Repeat")); - singlePlayQueueAction = createAction("singleplaylist", i18n("Single"), 0, i18n("When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled.")); - consumePlayQueueAction = createAction("consumeplaylist", i18n("Consume"), 0, i18n("When consume is activated, a song is removed from the play queue after it has been played.")); - addWithPriorityAction = createAction("addwithprio", i18n("Add With Priority")); - setPriorityAction = createAction("setprio", i18n("Set Priority")); - addPrioHighestAction = createAction("highestprio", i18n("Highest Priority (255)")); - addPrioHighAction = createAction("highprio", i18n("High Priority (200)")); - addPrioMediumAction = createAction("mediumprio", i18n("Medium Priority (125)")); - addPrioLowAction = createAction("lowprio", i18n("Low Priority (50)")); - addPrioDefaultAction = createAction("defaultprio", i18n("Default Priority (0)")); - addPrioCustomAction = createAction("customprio", i18n("Custom Priority...")); + expandInterfaceAction = ActionCollection::get()->createAction("expandinterface", i18n("Expanded Interface"), "view-media-playlist"); + randomPlayQueueAction = ActionCollection::get()->createAction("randomplaylist", i18n("Random")); + repeatPlayQueueAction = ActionCollection::get()->createAction("repeatplaylist", i18n("Repeat")); + singlePlayQueueAction = ActionCollection::get()->createAction("singleplaylist", i18n("Single"), 0, i18n("When 'Single' is activated, playback is stopped after current song, or song is repeated if 'Repeat' is enabled.")); + consumePlayQueueAction = ActionCollection::get()->createAction("consumeplaylist", i18n("Consume"), 0, i18n("When consume is activated, a song is removed from the play queue after it has been played.")); + addWithPriorityAction = ActionCollection::get()->createAction("addwithprio", i18n("Add With Priority")); + setPriorityAction = ActionCollection::get()->createAction("setprio", i18n("Set Priority")); + addPrioHighestAction = ActionCollection::get()->createAction("highestprio", i18n("Highest Priority (255)")); + addPrioHighAction = ActionCollection::get()->createAction("highprio", i18n("High Priority (200)")); + addPrioMediumAction = ActionCollection::get()->createAction("mediumprio", i18n("Medium Priority (125)")); + addPrioLowAction = ActionCollection::get()->createAction("lowprio", i18n("Low Priority (50)")); + addPrioDefaultAction = ActionCollection::get()->createAction("defaultprio", i18n("Default Priority (0)")); + addPrioCustomAction = ActionCollection::get()->createAction("customprio", i18n("Custom Priority...")); #ifdef PHONON_FOUND - streamPlayAction = createAction("streamplay", i18n("Play Stream"), 0, i18n("When 'Play Stream' is activated, the enabled stream is played locally.")); + streamPlayAction = ActionCollection::get()->createAction("streamplay", i18n("Play Stream"), 0, i18n("When 'Play Stream' is activated, the enabled stream is played locally.")); streamPlayAction->setIcon(Icons::streamIcon); #endif - locateTrackAction = createAction("locatetrack", i18n("Locate In Library"), "edit-find"); + locateTrackAction = ActionCollection::get()->createAction("locatetrack", i18n("Locate In Library"), "edit-find"); #ifdef TAGLIB_FOUND - organiseFilesAction = createAction("organizefiles", i18n("Organize Files"), "inode-directory"); - editTagsAction = createAction("edittags", i18n("Edit Tags"), "document-edit"); - editPlayQueueTagsAction = createAction("editpqtags", i18n("Edit Song Tags"), "document-edit"); + organiseFilesAction = ActionCollection::get()->createAction("organizefiles", i18n("Organize Files"), "inode-directory"); + editTagsAction = ActionCollection::get()->createAction("edittags", i18n("Edit Tags"), "document-edit"); + editPlayQueueTagsAction = ActionCollection::get()->createAction("editpqtags", i18n("Edit Song Tags"), "document-edit"); #endif - showPlayQueueAction = createAction("showplayqueue", i18n("Play Queue"), "media-playback-start"); - libraryTabAction = createAction("showlibrarytab", i18n("Library")); - albumsTabAction = createAction("showalbumstab", i18n("Albums")); + showPlayQueueAction = ActionCollection::get()->createAction("showplayqueue", i18n("Play Queue"), "media-playback-start"); + libraryTabAction = ActionCollection::get()->createAction("showlibrarytab", i18n("Library")); + albumsTabAction = ActionCollection::get()->createAction("showalbumstab", i18n("Albums")); albumsTabAction->setIcon(Icons::albumIcon); - foldersTabAction = createAction("showfolderstab", i18n("Folders"), "inode-directory"); - playlistsTabAction = createAction("showplayliststab", i18n("Playlists")); + foldersTabAction = ActionCollection::get()->createAction("showfolderstab", i18n("Folders"), "inode-directory"); + playlistsTabAction = ActionCollection::get()->createAction("showplayliststab", i18n("Playlists")); playlistsTabAction->setIcon(Icons::playlistIcon); - dynamicTabAction = createAction("showdynamictab", i18n("Dynamic")); + dynamicTabAction = ActionCollection::get()->createAction("showdynamictab", i18n("Dynamic")); dynamicTabAction->setIcon(Icons::dynamicIcon); - lyricsTabAction = createAction("showlyricstab", i18n("Lyrics")); + lyricsTabAction = ActionCollection::get()->createAction("showlyricstab", i18n("Lyrics")); lyricsTabAction->setIcon(Icons::lyricsIcon); - streamsTabAction = createAction("showstreamstab", i18n("Streams"), 0); + streamsTabAction = ActionCollection::get()->createAction("showstreamstab", i18n("Streams"), 0); streamsTabAction->setIcon(Icons::streamIcon); #ifdef ENABLE_WEBKIT - infoTabAction = createAction("showinfotab", i18n("Info")); + infoTabAction = ActionCollection::get()->createAction("showinfotab", i18n("Info")); #endif - serverInfoTabAction = createAction("showserverinfotab", i18n("Server Info"), "network-server"); + serverInfoTabAction = ActionCollection::get()->createAction("showserverinfotab", i18n("Server Info"), "network-server"); #ifdef ENABLE_DEVICES_SUPPORT - devicesTabAction = createAction("showdevicestab", i18n("Devices"), "multimedia-player"); - copyToDeviceAction = createAction("copytodevice", i18n("Copy To Device"), "multimedia-player"); - deleteSongsAction = createAction("deletesongs", i18n("Delete Songs"), "edit-delete"); + devicesTabAction = ActionCollection::get()->createAction("showdevicestab", i18n("Devices"), "multimedia-player"); + copyToDeviceAction = ActionCollection::get()->createAction("copytodevice", i18n("Copy To Device"), "multimedia-player"); + deleteSongsAction = ActionCollection::get()->createAction("deletesongs", i18n("Delete Songs"), "edit-delete"); #endif - searchAction = createAction("search", i18n("Search"), "edit-find"); - expandAllAction = createAction("expandall", i18n("Expand All")); - collapseAllAction = createAction("collapseall", i18n("Collapse All")); + searchAction = ActionCollection::get()->createAction("search", i18n("Search"), "edit-find"); + expandAllAction = ActionCollection::get()->createAction("expandall", i18n("Expand All")); + collapseAllAction = ActionCollection::get()->createAction("collapseall", i18n("Collapse All")); #if defined ENABLE_KDE_SUPPORT prevTrackAction->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_Left)); @@ -401,27 +399,27 @@ MainWindow::MainWindow(QWidget *parent) decreaseVolumeAction->setGlobalShortcut(KShortcut(Qt::META + Qt::Key_Down)); #endif - SET_SHORTCUT(copyTrackInfoAction, QKeySequence::Copy); - SET_SHORTCUT(backAction, QKeySequence::Back); - SET_SHORTCUT(searchAction, Qt::ControlModifier+Qt::Key_F); - SET_SHORTCUT(expandAllAction, Qt::ControlModifier+Qt::Key_Plus); - SET_SHORTCUT(collapseAllAction, Qt::ControlModifier+Qt::Key_Minus); + copyTrackInfoAction->setShortcut(QKeySequence::Copy); + backAction->setShortcut(QKeySequence::Back); + searchAction->setShortcut(Qt::ControlModifier+Qt::Key_F); + expandAllAction->setShortcut(Qt::ControlModifier+Qt::Key_Plus); + collapseAllAction->setShortcut(Qt::ControlModifier+Qt::Key_Minus); int pageKey=Qt::Key_1; - SET_SHORTCUT(showPlayQueueAction, Qt::AltModifier+Qt::Key_Q); - SET_SHORTCUT(libraryTabAction, Qt::AltModifier+nextKey(pageKey)); - SET_SHORTCUT(albumsTabAction, Qt::AltModifier+nextKey(pageKey)); - SET_SHORTCUT(foldersTabAction, Qt::AltModifier+nextKey(pageKey)); - SET_SHORTCUT(playlistsTabAction, Qt::AltModifier+nextKey(pageKey)); - SET_SHORTCUT(dynamicTabAction, Qt::AltModifier+nextKey(pageKey)); - SET_SHORTCUT(streamsTabAction, Qt::AltModifier+nextKey(pageKey)); - SET_SHORTCUT(lyricsTabAction, Qt::AltModifier+nextKey(pageKey)); + showPlayQueueAction->setShortcut(Qt::AltModifier+Qt::Key_Q); + libraryTabAction->setShortcut(Qt::AltModifier+nextKey(pageKey)); + albumsTabAction->setShortcut(Qt::AltModifier+nextKey(pageKey)); + foldersTabAction->setShortcut(Qt::AltModifier+nextKey(pageKey)); + playlistsTabAction->setShortcut(Qt::AltModifier+nextKey(pageKey)); + dynamicTabAction->setShortcut(Qt::AltModifier+nextKey(pageKey)); + streamsTabAction->setShortcut(Qt::AltModifier+nextKey(pageKey)); + lyricsTabAction->setShortcut(Qt::AltModifier+nextKey(pageKey)); #ifdef ENABLE_WEBKIT - SET_SHORTCUT(infoTabAction, Qt::AltModifier+nextKey(pageKey)); + infoTabAction->setShortcut(Qt::AltModifier+nextKey(pageKey)); #endif // ENABLE_WEBKIT - SET_SHORTCUT(serverInfoTabAction, Qt::AltModifier+nextKey(pageKey)); + serverInfoTabAction->setShortcut(Qt::AltModifier+nextKey(pageKey)); #ifdef ENABLE_DEVICES_SUPPORT - SET_SHORTCUT(devicesTabAction, Qt::AltModifier+nextKey(pageKey)); + devicesTabAction->setShortcut(Qt::AltModifier+nextKey(pageKey)); #endif // ENABLE_DEVICES_SUPPORT volumeSliderEventHandler = new VolumeSliderEventHandler(this); @@ -631,15 +629,20 @@ MainWindow::MainWindow(QWidget *parent) mainMenu->addAction(outputsAction); #ifdef ENABLE_KDE_SUPPORT mainMenu->addAction(prefAction); - mainMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::KeyBindings))); + shortcutsAction=(Action *)KStandardAction::keyBindings(this, SLOT(configureShortcuts()), ActionCollection::get()); + mainMenu->addAction(shortcutsAction); mainMenu->addSeparator(); mainMenu->addMenu(helpMenu()); #else - prefAction=mainMenu->addAction(i18n("Configure Cantata..."), this, SLOT(showPreferencesDialog())); + prefAction=ActionCollection::get()->createAction("configure", i18n("Configure Cantata...")); + connect(prefAction, SIGNAL(triggered(bool)),this, SLOT(showPreferencesDialog())); prefAction->setIcon(Icons::configureIcon); + mainMenu->addAction(prefAction); mainMenu->addSeparator(); - Action *aboutAction=mainMenu->addAction(i18nc("Qt-only", "About Cantata..."), this, SLOT(showAboutDialog())); + Action *aboutAction=ActionCollection::get()->createAction("about", i18nc("Qt-only", "About Cantata...")); + connect(aboutAction, SIGNAL(triggered(bool)),this, SLOT(showAboutDialog())); aboutAction->setIcon(Icons::appIcon); + mainMenu->addAction(aboutAction); #endif mainMenu->addSeparator(); mainMenu->addAction(quitAction); @@ -899,6 +902,7 @@ MainWindow::MainWindow(QWidget *parent) QTimer::singleShot(250, this, SLOT(toggleDockManager())); } #endif + ActionCollection::get()->readSettings(); } MainWindow::~MainWindow() @@ -1210,6 +1214,22 @@ void MainWindow::refresh() #define DIALOG_ERROR MessageBox::error(this, i18n("Action is not currently possible, due to other open dialogs.")); return +#ifdef ENABLE_KDE_SUPPORT +void MainWindow::configureShortcuts() +{ + KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsDisallowed, this); + dlg.addCollection(ActionCollection::get()); + connect(&dlg, SIGNAL(okClicked()), this, SLOT(saveShortcuts())); + dlg.exec(); +} + +void MainWindow::saveShortcuts() +{ + ActionCollection::get()->writeSettings(); +} + +#endif + void MainWindow::showPreferencesDialog() { static bool showing=false; @@ -1309,7 +1329,7 @@ void MainWindow::outputsUpdated(const QList &outputs) act->setData(o.id); act->setCheckable(true); act->setChecked(o.enabled); - SET_SHORTCUT(act, Qt::MetaModifier+nextKey(i)); + act->setShortcut(Qt::MetaModifier+nextKey(i)); } } else { foreach (const Output &o, outputs) { @@ -1353,7 +1373,7 @@ void MainWindow::updateConnectionsMenu() act->setCheckable(true); act->setChecked(d.name==current); act->setActionGroup(connectionsGroup); - SET_SHORTCUT(act, Qt::ControlModifier+nextKey(i)); + act->setShortcut(Qt::ControlModifier+nextKey(i)); } } } @@ -2366,7 +2386,7 @@ void MainWindow::togglePlayQueue() } } else { // Widths also sometimes expands, so make sure this is no larger than it was before... - collapsedSize=QSize(collapsedSize.isValid() ? collapsedSize.width() : (size().width()>prevWidth ? prevWidth : size().width()), compactHeight); + collapsedSize=QSize(collapsedSize.isValid() ? collapsedSize.width() : (size().width()>prevWidth ? prevWidth : size().width()), compactHeight); resize(collapsedSize); setFixedHeight(size().height()); } @@ -3045,28 +3065,6 @@ void MainWindow::replayGain() } #endif -Action * MainWindow::createAction(const QString &name, const QString &text, const char *icon, const QString &whatsThis) -{ - #ifdef ENABLE_KDE_SUPPORT - Action *act = actionCollection()->addAction(name); - act->setText(text); - if (0!=icon) { - act->setIcon(Icon(icon)); - } - #else - Q_UNUSED(name) - Action *act = new Action(name, this); - act->setText(text); - if (0!=icon) { - act->setIcon('m'==icon[0] && 'e'==icon[1] && 'd'==icon[2] && 'i'==icon[3] && 'a'==icon[4] && '-'==icon[5] ? Icon::getMediaIcon(icon) : Icon(icon)); - } - #endif - if (!whatsThis.isEmpty()) { - act->setWhatsThis(whatsThis); - } - return act; -} - int MainWindow::currentTrackPosition() const { return positionSlider->value(); diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 83f306844..b39f7a4c3 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -52,15 +52,13 @@ #endif #ifdef ENABLE_KDE_SUPPORT -class KAction; class KStatusNotifierItem; class KMenu; class KNotification; -#define Action KAction -#else -#define Action QAction #endif +class Action; +class ActionCollection; class MainWindow; class LibraryPage; class AlbumsPage; @@ -186,7 +184,6 @@ public: void load(const QList &urls); #endif - Action * createAction(const QString &name, const QString &text, const char *icon=0, const QString &whatsThis=QString()); int currentTrackPosition() const; QString coverFile() const; @@ -226,6 +223,10 @@ public Q_SLOTS: void restoreWindow(); private Q_SLOTS: + #ifdef ENABLE_KDE_SUPPORT + void configureShortcuts(); + void saveShortcuts(); + #endif void setMpdVolume(int ); void playbackButtonsMenu(); void controlButtonsMenu(); @@ -364,6 +365,9 @@ private: VolumeSliderEventHandler *volumeSliderEventHandler; VolumeControl *volumeControl; Action *prefAction; + #ifdef ENABLE_KDE_SUPPORT + Action *shortcutsAction; + #endif Action *connectAction; Action *connectionsAction; Action *outputsAction; diff --git a/gui/playlistspage.cpp b/gui/playlistspage.cpp index b2de9f13f..645118bfc 100644 --- a/gui/playlistspage.cpp +++ b/gui/playlistspage.cpp @@ -28,11 +28,11 @@ #include "inputdialog.h" #include "localize.h" #include "icon.h" +#include "mainwindow.h" +#include "action.h" +#include "actioncollection.h" #include -#ifdef ENABLE_KDE_SUPPORT -#include -#else -#include +#ifndef ENABLE_KDE_SUPPORT #include #endif @@ -41,7 +41,7 @@ PlaylistsPage::PlaylistsPage(MainWindow *p) , mw(p) { setupUi(this); - renamePlaylistAction = p->createAction("renameplaylist", i18n("Rename"), "edit-rename"); + renamePlaylistAction = ActionCollection::get()->createAction("renameplaylist", i18n("Rename"), "edit-rename"); replacePlayQueue->setDefaultAction(p->replacePlayQueueAction); libraryUpdate->setDefaultAction(p->refreshAction); connect(genreCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(searchItems())); diff --git a/gui/playlistspage.h b/gui/playlistspage.h index 50fd44b69..ed6d06857 100644 --- a/gui/playlistspage.h +++ b/gui/playlistspage.h @@ -25,9 +25,11 @@ #define PLAYLISTSPAGE_H #include "ui_playlistspage.h" -#include "mainwindow.h" #include "playlistsproxymodel.h" +class MainWindow; +class Action; + class PlaylistsPage : public QWidget, public Ui::PlaylistsPage { Q_OBJECT diff --git a/gui/preferencesdialog.cpp b/gui/preferencesdialog.cpp index 0845324b9..3306a1f4f 100644 --- a/gui/preferencesdialog.cpp +++ b/gui/preferencesdialog.cpp @@ -40,6 +40,8 @@ #include "pagewidget.h" #ifndef ENABLE_KDE_SUPPORT #include "proxysettings.h" +#include "shortcutssettingspage.h" +#include "actioncollection.h" #endif PreferencesDialog::PreferencesDialog(QWidget *parent, LyricsPage *lp) @@ -81,6 +83,10 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, LyricsPage *lp) proxy = new ProxySettings(widget); proxy->load(); widget->addPage(proxy, i18nc("Qt-only", "Proxy"), Icon("preferences-system-network"), i18nc("Qt-only", "Proxy Settings")); + QHash map; + map.insert("Cantata", ActionCollection::get()); + shortcuts = new ShortcutsSettingsPage(map, widget); + widget->addPage(shortcuts, i18nc("Qt-only", "Shortcuts"), Icon("keyboard"), i18nc("Qt-only", "Keyboard Shortcut Settings")); #endif widget->allPagesAdded(); setCaption(i18n("Configure")); @@ -102,6 +108,7 @@ void PreferencesDialog::writeSettings() #endif #ifndef ENABLE_KDE_SUPPORT proxy->save(); + shortcuts->save(); #endif Settings::self()->saveLyricProviders(lyrics->EnabledProviders()); Settings::self()->save(); diff --git a/gui/preferencesdialog.h b/gui/preferencesdialog.h index b02bd45bf..076d595bb 100644 --- a/gui/preferencesdialog.h +++ b/gui/preferencesdialog.h @@ -28,6 +28,7 @@ #include "dialog.h" #ifndef ENABLE_KDE_SUPPORT class ProxySettings; +class ShortcutsSettingsPage; #endif class ServerSettings; @@ -71,6 +72,7 @@ private: #endif #ifndef ENABLE_KDE_SUPPORT ProxySettings *proxy; + ShortcutsSettingsPage *shortcuts; #endif }; diff --git a/gui/serverinfopage.cpp b/gui/serverinfopage.cpp index c48439373..c5b14ec36 100644 --- a/gui/serverinfopage.cpp +++ b/gui/serverinfopage.cpp @@ -28,17 +28,14 @@ #include "mpdparseutils.h" #include "localize.h" #include "icon.h" -#ifdef ENABLE_KDE_SUPPORT -#include -#else -#include -#endif +#include "action.h" +#include "actioncollection.h" -ServerInfoPage::ServerInfoPage(MainWindow *p) +ServerInfoPage::ServerInfoPage(QWidget *p) : QWidget(p) { setupUi(this); - updateAction = p->createAction("updatempdinfo", i18n("Update MPD Information"), "view-refresh"); + updateAction = ActionCollection::get()->createAction("updatempdinfo", i18n("Update MPD Information"), "view-refresh"); updateInfo->setDefaultAction(updateAction); connect(updateAction, SIGNAL(triggered(bool)), MPDConnection::self(), SLOT(getStats())); connect(MPDStats::self(), SIGNAL(updated()), SLOT(statsUpdated())); diff --git a/gui/serverinfopage.h b/gui/serverinfopage.h index 74d60f58e..04703f4ac 100644 --- a/gui/serverinfopage.h +++ b/gui/serverinfopage.h @@ -27,8 +27,9 @@ #include #include #include "ui_serverinfopage.h" -#include "mainwindow.h" +class Action; +class Action; class MPDStats; class ServerInfoPage : public QWidget, public Ui::ServerInfoPage @@ -36,7 +37,7 @@ class ServerInfoPage : public QWidget, public Ui::ServerInfoPage Q_OBJECT public: - ServerInfoPage(MainWindow *p); + ServerInfoPage(QWidget *p); ~ServerInfoPage(); void clear(); diff --git a/gui/streamspage.cpp b/gui/streamspage.cpp index ef3705079..cc889176d 100644 --- a/gui/streamspage.cpp +++ b/gui/streamspage.cpp @@ -28,12 +28,13 @@ #include "messagebox.h" #include "localize.h" #include "icon.h" +#include "mainwindow.h" +#include "action.h" +#include "actioncollection.h" #include #ifdef ENABLE_KDE_SUPPORT -#include #include #else -#include #include #include #endif @@ -44,10 +45,10 @@ StreamsPage::StreamsPage(MainWindow *p) , mw(p) { setupUi(this); - importAction = p->createAction("importstreams", i18n("Import Streams"), "document-import"); - exportAction = p->createAction("exportstreams", i18n("Export Streams"), "document-export"); - addAction = p->createAction("addstream", i18n("Add Stream"), "list-add"); - editAction = p->createAction("editstream", i18n("Edit"), "document-edit"); + importAction = ActionCollection::get()->createAction("importstreams", i18n("Import Streams"), "document-import"); + exportAction = ActionCollection::get()->createAction("exportstreams", i18n("Export Streams"), "document-export"); + addAction = ActionCollection::get()->createAction("addstream", i18n("Add Stream"), "list-add"); + editAction = ActionCollection::get()->createAction("editstream", i18n("Edit"), "document-edit"); replacePlayQueue->setDefaultAction(p->replacePlayQueueAction); // connect(view, SIGNAL(itemsSelected(bool)), addToPlaylist, SLOT(setEnabled(bool))); diff --git a/gui/streamspage.h b/gui/streamspage.h index eebc163ee..632caba01 100644 --- a/gui/streamspage.h +++ b/gui/streamspage.h @@ -25,10 +25,12 @@ #define STREAMSPAGE_H #include "ui_streamspage.h" -#include "mainwindow.h" #include "streamsmodel.h" #include "streamsproxymodel.h" +class MainWindow; +class Action; + class StreamsPage : public QWidget, public Ui::StreamsPage { Q_OBJECT diff --git a/lyrics/lyricspage.cpp b/lyrics/lyricspage.cpp index ebad69335..c15a4e080 100644 --- a/lyrics/lyricspage.cpp +++ b/lyrics/lyricspage.cpp @@ -37,6 +37,8 @@ #endif #include "icon.h" #include "utils.h" +#include "action.h" +#include "actioncollection.h" #include #include #include @@ -46,11 +48,6 @@ #include #include #include -#ifdef ENABLE_KDE_SUPPORT -#include -#else -#include -#endif static const QLatin1String constLyricsDir("lyrics/"); const QLatin1String LyricsPage::constExtension(".lyrics"); @@ -68,7 +65,7 @@ bool CompareLyricProviders(const UltimateLyricsProvider* a, const UltimateLyrics return a->relevance() < b->relevance(); } -LyricsPage::LyricsPage(MainWindow *p) +LyricsPage::LyricsPage(QWidget *p) : QWidget(p) // , reader(new UltimateLyricsReader(this)) , currentRequest(0) @@ -87,12 +84,12 @@ LyricsPage::LyricsPage(MainWindow *p) // QFutureWatcher *watcher = new QFutureWatcher(this); // watcher->setFuture(future); // connect(watcher, SIGNAL(finished()), SLOT(ultimateLyricsParsed())); - refreshAction = p->createAction("refreshlyrics", i18n("Refresh"), "view-refresh"); - searchAction = p->createAction("searchlyrics", i18n("Search For Lyrics"), "edit-find"); - editAction = p->createAction("editlyrics", i18n("Edit Lyrics"), "document-edit"); - saveAction = p->createAction("savelyrics", i18n("Save Lyrics"), "document-save"); - cancelAction = p->createAction("canceleditlyrics", i18n("Cancel Editing Lyrics"), "dialog-cancel"); - delAction = p->createAction("dellyrics", i18n("Delete Lyrics File"), "edit-delete"); + refreshAction = ActionCollection::get()->createAction("refreshlyrics", i18n("Refresh"), "view-refresh"); + searchAction = ActionCollection::get()->createAction("searchlyrics", i18n("Search For Lyrics"), "edit-find"); + editAction = ActionCollection::get()->createAction("editlyrics", i18n("Edit Lyrics"), "document-edit"); + saveAction = ActionCollection::get()->createAction("savelyrics", i18n("Save Lyrics"), "document-save"); + cancelAction = ActionCollection::get()->createAction("canceleditlyrics", i18n("Cancel Editing Lyrics"), "dialog-cancel"); + delAction = ActionCollection::get()->createAction("dellyrics", i18n("Delete Lyrics File"), "edit-delete"); connect(refreshAction, SIGNAL(triggered()), SLOT(update())); connect(searchAction, SIGNAL(triggered()), SLOT(search())); diff --git a/lyrics/lyricspage.h b/lyrics/lyricspage.h index fed4f8e7e..4754729b6 100644 --- a/lyrics/lyricspage.h +++ b/lyrics/lyricspage.h @@ -29,12 +29,12 @@ #include "song.h" #include "ui_lyricspage.h" #include "textbrowser.h" -#include "mainwindow.h" class UltimateLyricsProvider; // class UltimateLyricsReader; class UltimateLyricsProvider; class QImage; +class Action; class LyricsPage : public QWidget, public Ui::LyricsPage { @@ -49,7 +49,7 @@ class LyricsPage : public QWidget, public Ui::LyricsPage public: static const QLatin1String constExtension; - LyricsPage(MainWindow *p); + LyricsPage(QWidget *p); ~LyricsPage(); void saveSettings(); diff --git a/support/action.cpp b/support/action.cpp new file mode 100644 index 000000000..5d9d67ec2 --- /dev/null +++ b/support/action.cpp @@ -0,0 +1,113 @@ +/*************************************************************************** + * Copyright (C) 2005-09 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + *************************************************************************** + * Parts of this implementation are taken from KDE's kaction.cpp * + ***************************************************************************/ + +#include "action.h" +#include + +Action::Action(QObject *parent) +#ifdef ENABLE_KDE_SUPPORT +: KAction(parent) +#else +: QAction(parent) +#endif +{ + #ifndef ENABLE_KDE_SUPPORT + init(); + #endif +} + +Action::Action(const QString &text, QObject *parent, const QObject *receiver, const char *slot, const QKeySequence &shortcut) +#ifdef ENABLE_KDE_SUPPORT +: KAction(parent) +#else +: QAction(parent) +#endif +{ + #ifndef ENABLE_KDE_SUPPORT + init(); + #endif + setText(text); + setShortcut(shortcut); + if(receiver && slot) + connect(this, SIGNAL(triggered()), receiver, slot); +} + +Action::Action(const QIcon &icon, const QString &text, QObject *parent, const QObject *receiver, const char *slot, const QKeySequence &shortcut) +#ifdef ENABLE_KDE_SUPPORT +: KAction(parent) +#else +: QAction(parent) +#endif +{ + #ifndef ENABLE_KDE_SUPPORT + init(); + #endif + setIcon(icon); + setText(text); + setShortcut(shortcut); + if(receiver && slot) + connect(this, SIGNAL(triggered()), receiver, slot); +} + +#ifndef ENABLE_KDE_SUPPORT +void Action::init() { + connect(this, SIGNAL(triggered(bool)), this, SLOT(slotTriggered())); + + setProperty("isShortcutConfigurable", true); +} + +void Action::slotTriggered() { + emit triggered(QApplication::mouseButtons(), QApplication::keyboardModifiers()); +} + +bool Action::isShortcutConfigurable() const { + return property("isShortcutConfigurable").toBool(); +} + +void Action::setShortcutConfigurable(bool b) { + setProperty("isShortcutConfigurable", b); +} + +QKeySequence Action::shortcut(ShortcutTypes type) const { + Q_ASSERT(type); + if(type == DefaultShortcut) + return property("defaultShortcut").value(); + + if(shortcuts().count()) return shortcuts().value(0); + return QKeySequence(); +} + +void Action::setShortcut(const QShortcut &shortcut, ShortcutTypes type) { + setShortcut(shortcut.key(), type); +} + +void Action::setShortcut(const QKeySequence &key, ShortcutTypes type) { + Q_ASSERT(type); + + if(type & DefaultShortcut) + setProperty("defaultShortcut", key); + + if(type & ActiveShortcut) + QAction::setShortcut(key); +} + +#endif // !ENABLE_KDE_SUPPORT diff --git a/support/action.h b/support/action.h new file mode 100644 index 000000000..8d1b36f68 --- /dev/null +++ b/support/action.h @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (C) 2005-09 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + *************************************************************************** + * Parts of this API have been shamelessly stolen from KDE's kaction.h * + ***************************************************************************/ + +#ifndef ACTION_H_ +#define ACTION_H_ + +#ifdef ENABLE_KDE_SUPPORT +#include +class Action : public KAction { + Q_OBJECT + + public: + explicit Action(QObject *parent); + Action(const QString &text, QObject *parent, const QObject *receiver = 0, const char *slot = 0, const QKeySequence &shortcut = 0); + Action(const QIcon &icon, const QString &text, QObject *parent, const QObject *receiver = 0, const char *slot = 0, const QKeySequence &shortcut = 0); +}; +#else +#include +#include + +/// A specialized QWidgetAction, enhanced by some KDE features +/** This declares/implements a subset of KAction's API (notably we've left out global shortcuts + * for now), which should make it easy to plug in KDE support later on. + */ +class Action : public QAction { + Q_OBJECT + + Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut) + Q_PROPERTY(bool shortcutConfigurable READ isShortcutConfigurable WRITE setShortcutConfigurable) + Q_FLAGS(ShortcutType) + + public: + enum ShortcutType { + ActiveShortcut = 0x01, + DefaultShortcut = 0x02 + }; + Q_DECLARE_FLAGS(ShortcutTypes, ShortcutType) + + explicit Action(QObject *parent); + Action(const QString &text, QObject *parent, const QObject *receiver = 0, const char *slot = 0, const QKeySequence &shortcut = 0); + Action(const QIcon &icon, const QString &text, QObject *parent, const QObject *receiver = 0, const char *slot = 0, const QKeySequence &shortcut = 0); + + QKeySequence shortcut(ShortcutTypes types = ActiveShortcut) const; + void setShortcut(const QShortcut &shortcut, ShortcutTypes type = ShortcutTypes(ActiveShortcut | DefaultShortcut)); + void setShortcut(const QKeySequence &shortcut, ShortcutTypes type = ShortcutTypes(ActiveShortcut | DefaultShortcut)); + + bool isShortcutConfigurable() const; + void setShortcutConfigurable(bool configurable); + + signals: + void triggered(Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers); + + private: + void init(); + + private slots: + void slotTriggered(); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Action::ShortcutTypes) +#endif + +#endif diff --git a/support/actioncollection.cpp b/support/actioncollection.cpp new file mode 100644 index 000000000..8a0988ec4 --- /dev/null +++ b/support/actioncollection.cpp @@ -0,0 +1,293 @@ +/*************************************************************************** + * Copyright (C) 2005-09 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + *************************************************************************** + * Parts of this implementation are based on KDE's KActionCollection. * + ***************************************************************************/ + +#include "actioncollection.h" +#include "action.h" +#include "icon.h" +#include + +static const char *constProp="Category"; +static ActionCollection *coll=0; +static QWidget *mainWidget=0; +void ActionCollection::setMainWidget(QWidget *w) +{ + mainWidget=w; +} + +ActionCollection * ActionCollection::get() +{ + if (!coll) { + coll=new ActionCollection(mainWidget); + coll->setProperty(constProp, QCoreApplication::applicationName()); + if (mainWidget) { + coll->addAssociatedWidget(mainWidget); + } + } + return coll; +} + +Action * ActionCollection::createAction(const QString &name, const QString &text, const char *icon, const QString &whatsThis) +{ + Action *act = (Action *)addAction(name); + act->setText(text); + if (0!=icon) { + act->setIcon(Icon(icon)); + } + if (!whatsThis.isEmpty()) { + act->setWhatsThis(whatsThis); + } + return act; +} + +#ifndef ENABLE_KDE_SUPPORT + +#include + +ActionCollection::ActionCollection(QObject *parent) : QObject(parent) { + _connectTriggered = _connectHovered = false; +} + +ActionCollection::~ActionCollection() { +} + +void ActionCollection::clear() { + _actionByName.clear(); + qDeleteAll(_actions); + _actions.clear(); +} + +QAction *ActionCollection::action(const QString &name) const { + return _actionByName.value(name, 0); +} + +QList ActionCollection::actions() const { + return _actions; +} + +Action *ActionCollection::addAction(const QString &name, Action *action) { + QAction *act = addAction(name, static_cast(action)); + Q_UNUSED(act); + Q_ASSERT(act == action); + return action; +} + +Action *ActionCollection::addAction(const QString &name, const QObject *receiver, const char *member) { + Action *a = new Action(this); + if(receiver && member) + connect(a, SIGNAL(triggered(bool)), receiver, member); + return addAction(name, a); +} + +QAction *ActionCollection::addAction(const QString &name, QAction *action) { + if(!action) + return action; + + const QString origName = action->objectName(); + QString indexName = name; + + if(indexName.isEmpty()) + indexName = action->objectName(); + else + action->setObjectName(indexName); + if(indexName.isEmpty()) + indexName = indexName.sprintf("unnamed-%p", (void *)action); + + // do we already have this action? + if(_actionByName.value(indexName, 0) == action) + return action; + // or maybe another action under this name? + if(QAction *oldAction = _actionByName.value(indexName)) + takeAction(oldAction); + + // do we already have this action under a different name? + int oldIndex = _actions.indexOf(action); + if(oldIndex != -1) { + _actionByName.remove(origName); + _actions.removeAt(oldIndex); + } + + // add action + _actionByName.insert(indexName, action); + _actions.append(action); + + foreach(QWidget *widget, _associatedWidgets) { + widget->addAction(action); + } + + connect(action, SIGNAL(destroyed(QObject *)), SLOT(actionDestroyed(QObject *))); + if(_connectHovered) + connect(action, SIGNAL(hovered()), SLOT(slotActionHovered())); + if(_connectTriggered) + connect(action, SIGNAL(triggered(bool)), SLOT(slotActionTriggered())); + + emit inserted(action); + return action; +} + +void ActionCollection::removeAction(QAction *action) { + delete takeAction(action); +} + +QAction *ActionCollection::takeAction(QAction *action) { + if(!unlistAction(action)) + return 0; + + foreach(QWidget *widget, _associatedWidgets) { + widget->removeAction(action); + } + + action->disconnect(this); + return action; +} + +QString ActionCollection::configKey() const +{ + QVariant v=property(constProp); + if (v.isValid() && !v.toString().isEmpty()) { + return QLatin1String("Shortcuts-")+v.toString(); + } + return QLatin1String("Shortcuts"); +} + +void ActionCollection::readSettings() { + QSettings s; + s.beginGroup(configKey()); + QStringList savedShortcuts = s.childKeys(); + + foreach(const QString &name, _actionByName.keys()) { + if(!savedShortcuts.contains(name)) + continue; + Action *action = qobject_cast(_actionByName.value(name)); + if(action) + action->setShortcut(s.value(name, QKeySequence()).value(), Action::ActiveShortcut); + } +} + +void ActionCollection::writeSettings() const { + QSettings s; + s.beginGroup(configKey()); + foreach(const QString &name, _actionByName.keys()) { + Action *action = qobject_cast(_actionByName.value(name)); + if(!action) + continue; + if(!action->isShortcutConfigurable()) + continue; + if(action->shortcut(Action::ActiveShortcut) == action->shortcut(Action::DefaultShortcut)) + continue; + s.setValue(name, action->shortcut(Action::ActiveShortcut)); + } +} + +void ActionCollection::slotActionTriggered() { + QAction *action = qobject_cast(sender()); + if(action) + emit actionTriggered(action); +} + +void ActionCollection::slotActionHovered() { + QAction *action = qobject_cast(sender()); + if(action) + emit actionHovered(action); +} + +void ActionCollection::actionDestroyed(QObject *obj) { + // remember that this is not an QAction anymore at this point + QAction *action = static_cast(obj); + + unlistAction(action); +} + +void ActionCollection::connectNotify(const char *signal) { + if(_connectHovered && _connectTriggered) + return; + + if(QMetaObject::normalizedSignature(SIGNAL(actionHovered(QAction*))) == signal) { + if(!_connectHovered) { + _connectHovered = true; + foreach (QAction* action, actions()) + connect(action, SIGNAL(hovered()), SLOT(slotActionHovered())); + } + } else if(QMetaObject::normalizedSignature(SIGNAL(actionTriggered(QAction*))) == signal) { + if(!_connectTriggered) { + _connectTriggered = true; + foreach (QAction* action, actions()) + connect(action, SIGNAL(triggered(bool)), SLOT(slotActionTriggered())); + } + } + + QObject::connectNotify(signal); +} + +void ActionCollection::associateWidget(QWidget *widget) const { + foreach(QAction *action, actions()) { + if(!widget->actions().contains(action)) + widget->addAction(action); + } +} + +void ActionCollection::addAssociatedWidget(QWidget *widget) { + if(!_associatedWidgets.contains(widget)) { + widget->addActions(actions()); + _associatedWidgets.append(widget); + connect(widget, SIGNAL(destroyed(QObject *)), SLOT(associatedWidgetDestroyed(QObject *))); + } +} + +void ActionCollection::removeAssociatedWidget(QWidget *widget) { + foreach(QAction *action, actions()) + widget->removeAction(action); + _associatedWidgets.removeAll(widget); + disconnect(widget, SIGNAL(destroyed(QObject *)), this, SLOT(associatedWidgetDestroyed(QObject *))); +} + +QList ActionCollection::associatedWidgets() const { + return _associatedWidgets; +} + +void ActionCollection::clearAssociatedWidgets() { + foreach(QWidget *widget, _associatedWidgets) + foreach(QAction *action, actions()) + widget->removeAction(action); + + _associatedWidgets.clear(); +} + +void ActionCollection::associatedWidgetDestroyed(QObject *obj) { + _associatedWidgets.removeAll(static_cast(obj)); +} + +bool ActionCollection::unlistAction(QAction *action) { + // This might be called with a partly destroyed QAction! + + int index = _actions.indexOf(action); + if(index == -1) return false; + + QString name = action->objectName(); + _actionByName.remove(name); + _actions.removeAt(index); + + // TODO: remove from ActionCategory if we ever get that + + return true; +} + +#endif /* HAVE_KDE */ diff --git a/support/actioncollection.h b/support/actioncollection.h new file mode 100644 index 000000000..6020d854a --- /dev/null +++ b/support/actioncollection.h @@ -0,0 +1,135 @@ +/*************************************************************************** + * Copyright (C) 2005-09 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + *************************************************************************** + * This is a subset of the API of KDE's KActionCollection. * + ***************************************************************************/ + +#ifndef ACTIONCOLLECTION_H_ +#define ACTIONCOLLECTION_H_ + +class Action; + +#ifdef ENABLE_KDE_SUPPORT +#include + +class ActionCollection : public KActionCollection { + Q_OBJECT + + public: + explicit ActionCollection(QObject *parent) : KActionCollection(parent) {}; + + static void setMainWidget(QWidget *w); + static ActionCollection * get(); + Action * createAction(const QString &name, const QString &text, const char *icon=0, const QString &whatsThis=QString()); +}; + +#else + +#include +#include +#include +#include + +class QWidget; +class QAction; + +class ActionCollection : public QObject { + Q_OBJECT + + public: + explicit ActionCollection(QObject *parent); + virtual ~ActionCollection(); + + static void setMainWidget(QWidget *w); + static ActionCollection * get(); + Action * createAction(const QString &name, const QString &text, const char *icon=0, const QString &whatsThis=QString()); + + /// Clears the entire action collection, deleting all actions. + void clear(); + + /// Associate all action in this collection to the given \a widget. + /** Not that this only adds all current actions in the collection to that widget; + * subsequently added actions won't be added automatically. + */ + void associateWidget(QWidget *widget) const; + + /// Associate all actions in this collection to the given \a widget. + /** Subsequently added actions will be automagically associated with this widget as well. + */ + void addAssociatedWidget(QWidget *widget); + + void removeAssociatedWidget(QWidget *widget); + QList associatedWidgets() const; + void clearAssociatedWidgets(); + + void readSettings(); + void writeSettings() const; + + int count() const { return actions().count(); } + bool isEmpty() const { return 0==actions().count(); } + + QAction *action(int index) const; + QAction *action(const QString &name) const; + QList actions() const; + + QAction *addAction(const QString &name, QAction *action); + Action *addAction(const QString &name, Action *action); + Action *addAction(const QString &name, const QObject *receiver = 0, const char *member = 0); + void removeAction(QAction *action); + QAction *takeAction(QAction *action); + + /// Create new action under the given name, add it to the collection and connect its triggered(bool) signal to the specified receiver. + template + ActionType *add(const QString &name, const QObject *receiver = 0, const char *member = 0) { + ActionType *a = new ActionType(this); + if(receiver && member) + connect(a, SIGNAL(triggered(bool)), receiver, member); + addAction(name, a); + return a; + } + + signals: + void inserted(QAction *action); + void actionHovered(QAction *action); + void actionTriggered(QAction *action); + + protected slots: + virtual void connectNotify(const char *signal); + virtual void slotActionTriggered(); + + private slots: + void slotActionHovered(); + void actionDestroyed(QObject *); + void associatedWidgetDestroyed(QObject *); + + private: + bool unlistAction(QAction *); + QString configKey() const; + + QMap _actionByName; + QList _actions; + QList _associatedWidgets; + + bool _connectHovered; + bool _connectTriggered; +}; + +#endif + +#endif diff --git a/support/keysequencewidget.cpp b/support/keysequencewidget.cpp new file mode 100644 index 000000000..953760484 --- /dev/null +++ b/support/keysequencewidget.cpp @@ -0,0 +1,377 @@ +/*************************************************************************** + * Copyright (C) 2010 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This class has been inspired by KDE's KKeySequenceWidget and uses * + * some code snippets of its implementation, part of kdelibs. * + * The original file is * + * Copyright (C) 1998 Mark Donohoe * + * Copyright (C) 2001 Ellis Whitehead * + * Copyright (C) 2007 Andreas Hartmetz * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include +#include +#include +#include +#include + +// This defines the unicode symbols for special keys (kCommandUnicode and friends) +#ifdef Q_WS_MAC +# include +#endif + +#include "icon.h" +#include "action.h" +#include "actioncollection.h" +#include "keysequencewidget.h" + +KeySequenceButton::KeySequenceButton(KeySequenceWidget *d_, QWidget *parent) + : QPushButton(parent), + d(d_) +{ + +} + +bool KeySequenceButton::event(QEvent *e) { + if(d->isRecording() && e->type() == QEvent::KeyPress) { + keyPressEvent(static_cast(e)); + return true; + } + + // The shortcut 'alt+c' ( or any other dialog local action shortcut ) + // ended the recording and triggered the action associated with the + // action. In case of 'alt+c' ending the dialog. It seems that those + // ShortcutOverride events get sent even if grabKeyboard() is active. + if(d->isRecording() && e->type() == QEvent::ShortcutOverride) { + e->accept(); + return true; + } + + return QPushButton::event(e); +} + +void KeySequenceButton::keyPressEvent(QKeyEvent *e) { + int keyQt = e->key(); + if(keyQt == -1) { + // Qt sometimes returns garbage keycodes, I observed -1, if it doesn't know a key. + // We cannot do anything useful with those (several keys have -1, indistinguishable) + // and QKeySequence.toString() will also yield a garbage string. + QMessageBox::information(this, + tr("The key you just pressed is not supported by Qt."), + tr("Unsupported Key")); + return d->cancelRecording(); + } + + uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META); + + //don't have the return or space key appear as first key of the sequence when they + //were pressed to start editing - catch and them and imitate their effect + if(!d->isRecording() && ((keyQt == Qt::Key_Return || keyQt == Qt::Key_Space))) { + d->startRecording(); + d->_modifierKeys = newModifiers; + d->updateShortcutDisplay(); + return; + } + + // We get events even if recording isn't active. + if(!d->isRecording()) + return QPushButton::keyPressEvent(e); + + e->accept(); + d->_modifierKeys = newModifiers; + + switch(keyQt) { + case Qt::Key_AltGr: //or else we get unicode salad + return; + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Alt: + case Qt::Key_Meta: + case Qt::Key_Menu: //unused (yes, but why?) + d->updateShortcutDisplay(); + break; + + default: + if(!(d->_modifierKeys & ~Qt::SHIFT)) { + // It's the first key and no modifier pressed. Check if this is + // allowed + if(!d->isOkWhenModifierless(keyQt)) + return; + } + + // We now have a valid key press. + if(keyQt) { + if((keyQt == Qt::Key_Backtab) && (d->_modifierKeys & Qt::SHIFT)) { + keyQt = Qt::Key_Tab | d->_modifierKeys; + } + else if(d->isShiftAsModifierAllowed(keyQt)) { + keyQt |= d->_modifierKeys; + } else + keyQt |= (d->_modifierKeys & ~Qt::SHIFT); + + d->_keySequence = QKeySequence(keyQt); + d->doneRecording(); + } + } +} + +void KeySequenceButton::keyReleaseEvent(QKeyEvent *e) { + if(e->key() == -1) { + // ignore garbage, see keyPressEvent() + return; + } + + if(!d->isRecording()) + return QPushButton::keyReleaseEvent(e); + + e->accept(); + + uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META); + + // if a modifier that belongs to the shortcut was released... + if((newModifiers & d->_modifierKeys) < d->_modifierKeys) { + d->_modifierKeys = newModifiers; + d->updateShortcutDisplay(); + } +} + +/******************************************************************************/ + +KeySequenceWidget::KeySequenceWidget(QWidget *parent) + : QWidget(parent), + _shortcutsModel(0), + _isRecording(false), + _modifierKeys(0) +{ + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setMargin(0); + + _keyButton = new KeySequenceButton(this, this); + _keyButton->setFocusPolicy(Qt::StrongFocus); + //TODO: _keyButton->setIcon(SmallIcon("configure")); + _keyButton->setToolTip(tr("Click on the button, then enter the shortcut like you would in the program.\nExample for Ctrl+a: hold the Ctrl key and press a.")); + layout->addWidget(_keyButton); + + _clearButton = new QToolButton(this); + layout->addWidget(_clearButton); + + Icon icon(qApp->isLeftToRight() ? "edit-clear-locationbar-rtl" : "edit-clear-locationbar-ltr"); + + if (icon.isNull()) { + icon=Icon("edit-clear"); + } + _clearButton->setIcon(icon); + + setLayout(layout); + + connect(_keyButton, SIGNAL(clicked()), SLOT(startRecording())); + connect(_keyButton, SIGNAL(clicked()), SIGNAL(clicked())); + connect(_clearButton, SIGNAL(clicked()), SLOT(clear())); + connect(_clearButton, SIGNAL(clicked()), SIGNAL(clicked())); +} + +void KeySequenceWidget::setModel(ShortcutsModel *model) { + Q_ASSERT(!_shortcutsModel); + _shortcutsModel = model; +} + +bool KeySequenceWidget::isOkWhenModifierless(int keyQt) const { + //this whole function is a hack, but especially the first line of code + if(QKeySequence(keyQt).toString().length() == 1) + return false; + + switch(keyQt) { + case Qt::Key_Return: + case Qt::Key_Space: + case Qt::Key_Tab: + case Qt::Key_Backtab: //does this ever happen? + case Qt::Key_Backspace: + case Qt::Key_Delete: + return false; + default: + return true; + } +} + +bool KeySequenceWidget::isShiftAsModifierAllowed(int keyQt) const { + // Shift only works as a modifier with certain keys. It's not possible + // to enter the SHIFT+5 key sequence for me because this is handled as + // '%' by qt on my keyboard. + // The working keys are all hardcoded here :-( + if(keyQt >= Qt::Key_F1 && keyQt <= Qt::Key_F35) + return true; + + if(QChar(keyQt).isLetter()) + return true; + + switch(keyQt) { + case Qt::Key_Return: + case Qt::Key_Space: + case Qt::Key_Backspace: + case Qt::Key_Escape: + case Qt::Key_Print: + case Qt::Key_ScrollLock: + case Qt::Key_Pause: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_Insert: + case Qt::Key_Delete: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + return true; + + default: + return false; + } +} + +void KeySequenceWidget::updateShortcutDisplay() { + QString s = _keySequence.toString(QKeySequence::NativeText); + s.replace('&', QLatin1String("&&")); + + if(_isRecording) { + if(_modifierKeys) { +#ifdef Q_WS_MAC + if(_modifierKeys & Qt::META) s += QChar(kControlUnicode); + if(_modifierKeys & Qt::ALT) s += QChar(kOptionUnicode); + if(_modifierKeys & Qt::SHIFT) s += QChar(kShiftUnicode); + if(_modifierKeys & Qt::CTRL) s += QChar(kCommandUnicode); +#else + if(_modifierKeys & Qt::META) s += tr("Meta", "Meta key") + '+'; + if(_modifierKeys & Qt::CTRL) s += tr("Ctrl", "Ctrl key") + '+'; + if(_modifierKeys & Qt::ALT) s += tr("Alt", "Alt key") + '+'; + if(_modifierKeys & Qt::SHIFT) s += tr("Shift", "Shift key") + '+'; +#endif + } else { + s = tr("Input", "What the user inputs now will be taken as the new shortcut"); + } + // make it clear that input is still going on + s.append(" ..."); + } + + if(s.isEmpty()) { + s = tr("None", "No shortcut defined"); + } + + s.prepend(' '); + s.append(' '); + _keyButton->setText(s); +} + +void KeySequenceWidget::startRecording() { + _modifierKeys = 0; + _oldKeySequence = _keySequence; + _keySequence = QKeySequence(); + _conflictingIndex = QModelIndex(); + _isRecording = true; + _keyButton->grabKeyboard(); + +// if(!QWidget::keyboardGrabber()) { +// qWarning() << "Failed to grab the keyboard! Most likely qt's nograb option is active"; +// } + + _keyButton->setDown(true); + updateShortcutDisplay(); +} + + +void KeySequenceWidget::doneRecording() { + bool wasRecording = _isRecording; + _isRecording = false; + _keyButton->releaseKeyboard(); + _keyButton->setDown(false); + + if(!wasRecording || _keySequence == _oldKeySequence) { + // The sequence hasn't changed + updateShortcutDisplay(); + return; + } + + if(!isKeySequenceAvailable(_keySequence)) { + _keySequence = _oldKeySequence; + } else if(wasRecording) { + emit keySequenceChanged(_keySequence, _conflictingIndex); + } + updateShortcutDisplay(); +} + +void KeySequenceWidget::cancelRecording() { + _keySequence = _oldKeySequence; + doneRecording(); +} + +void KeySequenceWidget::setKeySequence(const QKeySequence &seq) { + // oldKeySequence holds the key sequence before recording started, if setKeySequence() + // is called while not recording then set oldKeySequence to the existing sequence so + // that the keySequenceChanged() signal is emitted if the new and previous key + // sequences are different + if(!isRecording()) + _oldKeySequence = _keySequence; + + _keySequence = seq; + _clearButton->setVisible(!_keySequence.isEmpty()); + doneRecording(); +} + +void KeySequenceWidget::clear() { + setKeySequence(QKeySequence()); + // setKeySequence() won't emit a signal when we're not recording + emit keySequenceChanged(QKeySequence()); +} + +bool KeySequenceWidget::isKeySequenceAvailable(const QKeySequence &seq) { + if(seq.isEmpty()) + return true; + + // We need to access the root model, not the filtered one + for(int cat = 0; cat < _shortcutsModel->rowCount(); cat++) { + QModelIndex catIdx = _shortcutsModel->index(cat, 0); + for(int r = 0; r < _shortcutsModel->rowCount(catIdx); r++) { + QModelIndex actIdx = _shortcutsModel->index(r, 0, catIdx); + Q_ASSERT(actIdx.isValid()); + if(actIdx.data(ShortcutsModel::ActiveShortcutRole).value() != seq) + continue; + + if(!actIdx.data(ShortcutsModel::IsConfigurableRole).toBool()) { + QMessageBox::warning(this, tr("Shortcut Conflict"), + tr("The \"%1\" shortcut is already in use, and cannot be configured.\nPlease choose another one.").arg(seq.toString(QKeySequence::NativeText)), + QMessageBox::Ok); + return false; + } + + QMessageBox box(QMessageBox::Warning, tr("Shortcut Conflict"), + (tr("The \"%1\" shortcut is ambiguous with the shortcut for the following action:") + + "
  • %2

" + + tr("Do you want to reassign this shortcut to the selected action?") + ).arg(seq.toString(QKeySequence::NativeText), actIdx.data().toString()), + QMessageBox::Cancel, this); + box.addButton(tr("Reassign"), QMessageBox::AcceptRole); + if(box.exec() == QMessageBox::Cancel) + return false; + + _conflictingIndex = actIdx; + return true; + } + } + return true; +} diff --git a/support/keysequencewidget.h b/support/keysequencewidget.h new file mode 100644 index 000000000..39935ebc0 --- /dev/null +++ b/support/keysequencewidget.h @@ -0,0 +1,105 @@ +/*************************************************************************** + * Copyright (C) 2010 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This class has been inspired by KDE's KKeySequenceWidget and uses * + * some code snippets of its implementation, part of kdelibs. * + * The original file is * + * Copyright (C) 1998 Mark Donohoe * + * Copyright (C) 2001 Ellis Whitehead * + * Copyright (C) 2007 Andreas Hartmetz * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef KEYSEQUENCEWIDGET_H +#define KEYSEQUENCEWIDGET_H + +#include +#include +#include +#include "shortcutsmodel.h" + +class Action; +class ActionCollection; +class KeySequenceButton; +class QToolButton; + +class KeySequenceWidget : public QWidget { + Q_OBJECT +public: + KeySequenceWidget(QWidget *parent = 0); + + void setModel(ShortcutsModel *model); + +public slots: + void setKeySequence(const QKeySequence &seq); + +signals: + /** + * This signal is emitted when the current key sequence has changed by user input + * \param seq The key sequence the user has chosen + * \param conflicting The index of an action that needs to have its shortcut removed. The user has already been + * asked to agree (if he declines, this signal won't be emitted at all). + */ + void keySequenceChanged(const QKeySequence &seq, const QModelIndex &conflicting = QModelIndex()); + + void clicked(); + +private slots: + void updateShortcutDisplay(); + void startRecording(); + void cancelRecording(); + void clear(); + +private: + inline bool isRecording() const { return _isRecording; } + void doneRecording(); + + bool isOkWhenModifierless(int keyQt) const; + bool isShiftAsModifierAllowed(int keyQt) const; + bool isKeySequenceAvailable(const QKeySequence &seq); + + ShortcutsModel *_shortcutsModel; + bool _isRecording; + QKeySequence _keySequence, _oldKeySequence; + uint _modifierKeys; + QModelIndex _conflictingIndex; + + KeySequenceButton *_keyButton; + QToolButton *_clearButton; + + friend class KeySequenceButton; +}; + + +/*****************************************************************************/ + +class KeySequenceButton : public QPushButton { + Q_OBJECT +public: + explicit KeySequenceButton(KeySequenceWidget *d, QWidget *parent = 0); + +protected: + virtual bool event(QEvent *event); + virtual void keyPressEvent(QKeyEvent *event); + virtual void keyReleaseEvent(QKeyEvent *event); + +private: + KeySequenceWidget *d; +}; + +#endif // KEYSEQUENCEWIDGET_H diff --git a/support/shortcutsmodel.cpp b/support/shortcutsmodel.cpp new file mode 100644 index 000000000..340d889fb --- /dev/null +++ b/support/shortcutsmodel.cpp @@ -0,0 +1,247 @@ +/*************************************************************************** + * Copyright (C) 2010 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "shortcutsmodel.h" +#include "action.h" +#include "actioncollection.h" + +static QString stripAcceleratorMarkers(const QString &label_) { + QString label = label_; + int p = 0; + forever { + p = label.indexOf('&', p); + if(p < 0 || p + 1 >= label.length()) + break; + + if(label.at(p + 1).isLetterOrNumber() || label.at(p + 1) == '&') + label.remove(p, 1); + + ++p; + } + return label; +} + +ShortcutsModel::ShortcutsModel(const QHash &actionCollections, QObject *parent) + : QAbstractItemModel(parent), + _changedCount(0) +{ + for(int r = 0; r < actionCollections.values().count(); r++) { + ActionCollection *coll = actionCollections.values().at(r); + Item *item = new Item(); + item->row = r; + item->collection = coll; + for(int i = 0; i < coll->actions().count(); i++) { + Action *action = qobject_cast(coll->actions().at(i)); + if(!action) + continue; + Item *actionItem = new Item(); + actionItem->parentItem = item; + actionItem->row = i; + actionItem->collection = coll; + actionItem->action = action; + actionItem->shortcut = action->shortcut(); + item->actionItems.append(actionItem); + } + _categoryItems.append(item); + } +} + +ShortcutsModel::~ShortcutsModel() { + qDeleteAll(_categoryItems); +} + +QModelIndex ShortcutsModel::parent(const QModelIndex &child) const { + if(!child.isValid()) + return QModelIndex(); + + Item *item = static_cast(child.internalPointer()); + Q_ASSERT(item); + + if(!item->parentItem) + return QModelIndex(); + + return createIndex(item->parentItem->row, 0, item->parentItem); +} + +QModelIndex ShortcutsModel::index(int row, int column, const QModelIndex &parent) const { + + if(parent.isValid()) + return createIndex(row, column, static_cast(parent.internalPointer())->actionItems.at(row)); + + // top level category item + return createIndex(row, column, _categoryItems.at(row)); +} + +int ShortcutsModel::columnCount(const QModelIndex &parent) const { + return 2; + if(!parent.isValid()) + return 2; + + Item *item = static_cast(parent.internalPointer()); + Q_ASSERT(item); + + if(!item->parentItem) + return 2; + + return 2; +} + +int ShortcutsModel::rowCount(const QModelIndex &parent) const { + if(!parent.isValid()) + return _categoryItems.count(); + + Item *item = static_cast(parent.internalPointer()); + Q_ASSERT(item); + + if(!item->parentItem) + return item->actionItems.count(); + + return 0; +} + +QVariant ShortcutsModel::headerData(int section, Qt::Orientation orientation, int role) const { + if(orientation != Qt::Horizontal || role != Qt::DisplayRole) + return QVariant(); + switch(section) { + case 0: + return tr("Action"); + case 1: + return tr("Shortcut"); + default: + return QVariant(); + } +} + +QVariant ShortcutsModel::data(const QModelIndex &index, int role) const { + if(!index.isValid()) + return QVariant(); + + Item *item = static_cast(index.internalPointer()); + Q_ASSERT(item); + + if(!item->parentItem) { + if(index.column() != 0) + return QVariant(); + switch(role) { + case Qt::DisplayRole: + return item->collection->property("Category"); + default: + return QVariant(); + } + } + + Action *action = qobject_cast(item->action); + Q_ASSERT(action); + + switch(role) { + case Qt::DisplayRole: + switch(index.column()) { + case 0: + return stripAcceleratorMarkers(action->text()); + case 1: + return item->shortcut.toString(QKeySequence::NativeText); + default: + return QVariant(); + } + + case Qt::DecorationRole: + if(index.column() == 0) + return action->icon(); + return QVariant(); + + case ActionRole: + return QVariant::fromValue(action); + + case DefaultShortcutRole: + return action->shortcut(Action::DefaultShortcut); + case ActiveShortcutRole: + return item->shortcut; + + case IsConfigurableRole: + return action->isShortcutConfigurable(); + + default: + return QVariant(); + } +} + +bool ShortcutsModel::setData(const QModelIndex &index, const QVariant &value, int role) { + if(role != ActiveShortcutRole) + return false; + + if(!index.parent().isValid()) + return false; + + Item *item = static_cast(index.internalPointer()); + Q_ASSERT(item); + + QKeySequence newSeq = value.value(); + QKeySequence oldSeq = item->shortcut; + QKeySequence storedSeq = item->action->shortcut(Action::ActiveShortcut); + + item->shortcut = newSeq; + emit dataChanged(index, index.sibling(index.row(), 1)); + + if(oldSeq == storedSeq && newSeq != storedSeq) { + if(++_changedCount == 1) + emit hasChanged(true); + } else if(oldSeq != storedSeq && newSeq == storedSeq) { + if(--_changedCount == 0) + emit hasChanged(false); + } + + return true; +} + +void ShortcutsModel::load() { + foreach(Item *catItem, _categoryItems) { + foreach(Item *actItem, catItem->actionItems) { + actItem->shortcut = actItem->action->shortcut(Action::ActiveShortcut); + } + } + emit dataChanged(index(0, 1), index(rowCount()-1, 1)); + if(_changedCount != 0) { + _changedCount = 0; + emit hasChanged(false); + } +} + +void ShortcutsModel::commit() { + foreach(Item *catItem, _categoryItems) { + foreach(Item *actItem, catItem->actionItems) { + actItem->action->setShortcut(actItem->shortcut, Action::ActiveShortcut); + } + catItem->collection->writeSettings(); + } + if(_changedCount != 0) { + _changedCount = 0; + emit hasChanged(false); + } +} + +void ShortcutsModel::defaults() { + for(int cat = 0; cat < rowCount(); cat++) { + QModelIndex catidx = index(cat, 0); + for(int act = 0; act < rowCount(catidx); act++) { + QModelIndex actidx = index(act, 1, catidx); + setData(actidx, actidx.data(DefaultShortcutRole), ActiveShortcutRole); + } + } +} diff --git a/support/shortcutsmodel.h b/support/shortcutsmodel.h new file mode 100644 index 000000000..a52e073ca --- /dev/null +++ b/support/shortcutsmodel.h @@ -0,0 +1,96 @@ +/*************************************************************************** + * Copyright (C) 2010 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef SHORTCUTSMODEL_H +#define SHORTCUTSMODEL_H + +#include +#include + +class Action; +class ActionCollection; + +//! Model that exposes the actions from one or more ActionCollections +/** This model takes one or more ActionCollections and exposes their actions as model items. + * Note that the ShortcutsModel will not react to changes in the ActionCollection (e.g. adding, + * removing actions), because it is supposed to be used after all actions being defined. + */ +class ShortcutsModel : public QAbstractItemModel { + Q_OBJECT +public: + enum Role { + ActionRole = Qt::UserRole, + DefaultShortcutRole, + ActiveShortcutRole, + IsConfigurableRole + }; + + ShortcutsModel(const QHash &actionCollections, QObject *parent = 0); + ~ShortcutsModel(); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = ActiveShortcutRole); + +public slots: + //! Load shortcuts from the ActionCollections + /** Note that this will not rebuild the internal structure of the model, as we assume the + * ActionCollections to be static during the lifetime of the settingspage. This will merely + * re-read the shortcuts currently set in Quassel. + */ + void load(); + + //! Load default shortcuts from the ActionCollections + /** Note that this will not rebuild the internal structure of the model, as we assume the + * ActionCollections to be static during the lifetime of the settingspage. This will update + * the model's state from the ActionCollections' defaults. + */ + void defaults(); + + //! Commit the model changes to the ActionCollections + void commit(); + + inline bool hasChanged() const { return _changedCount; } + +signals: + //! Reflects the difference between model contents and the ActionCollections we loaded this from + void hasChanged(bool changed); + +private: + struct Item { + inline Item() { parentItem = 0; collection = 0; action = 0; } + inline ~Item() { qDeleteAll(actionItems); } + int row; + Item *parentItem; + ActionCollection *collection; + Action *action; + QKeySequence shortcut; + QList actionItems; + }; + + QList _categoryItems; + int _changedCount; +}; + +#endif // SHORTCUTSMODEL_H diff --git a/support/shortcutssettingspage.cpp b/support/shortcutssettingspage.cpp new file mode 100644 index 000000000..59a1026f1 --- /dev/null +++ b/support/shortcutssettingspage.cpp @@ -0,0 +1,137 @@ + +/*************************************************************************** + * Copyright (C) 2010 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include + +#include "shortcutssettingspage.h" +#include "action.h" +#include "actioncollection.h" +#include "shortcutsmodel.h" + +ShortcutsFilter::ShortcutsFilter(QObject *parent) : QSortFilterProxyModel(parent) { + setDynamicSortFilter(true); +} + +void ShortcutsFilter::setFilterString(const QString &filterString) { + _filterString = filterString; + invalidateFilter(); +} + +bool ShortcutsFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { + if(!source_parent.isValid()) + return true; + + QModelIndex index = source_parent.model()->index(source_row, 0, source_parent); + Q_ASSERT(index.isValid()); + if(!qobject_cast(index.data(ShortcutsModel::ActionRole).value())->isShortcutConfigurable()) + return false; + + for(int col = 0; col < source_parent.model()->columnCount(source_parent); col++) { + if(source_parent.model()->index(source_row, col, source_parent).data().toString().contains(_filterString, Qt::CaseInsensitive)) + return true; + } + return false; +} + +/****************************************************************************/ + +ShortcutsSettingsPage::ShortcutsSettingsPage(const QHash &actionCollections, QWidget *parent) + : QWidget(parent), + _shortcutsModel(new ShortcutsModel(actionCollections, this)), + _shortcutsFilter(new ShortcutsFilter(this)) +{ + setupUi(this); + + _shortcutsFilter->setSourceModel(_shortcutsModel); + shortcutsView->setModel(_shortcutsFilter); + shortcutsView->expandAll(); + shortcutsView->resizeColumnToContents(0); + shortcutsView->sortByColumn(0, Qt::AscendingOrder); + + keySequenceWidget->setModel(_shortcutsModel); + connect(keySequenceWidget, SIGNAL(keySequenceChanged(QKeySequence,QModelIndex)), SLOT(keySequenceChanged(QKeySequence,QModelIndex))); + + connect(shortcutsView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), SLOT(setWidgetStates())); + + setWidgetStates(); + + connect(useDefault, SIGNAL(clicked(bool)), SLOT(toggledCustomOrDefault())); + connect(useCustom, SIGNAL(clicked(bool)), SLOT(toggledCustomOrDefault())); + +// connect(_shortcutsModel, SIGNAL(hasChanged(bool)), SLOT(setChangedState(bool))); + + // fugly, but directly setting it from the ctor doesn't seem to work + QTimer::singleShot(0, searchEdit, SLOT(setFocus())); +} + +void ShortcutsSettingsPage::setWidgetStates() { + if(shortcutsView->currentIndex().isValid() && shortcutsView->currentIndex().parent().isValid()) { + QKeySequence active = shortcutsView->currentIndex().data(ShortcutsModel::ActiveShortcutRole).value(); + QKeySequence def = shortcutsView->currentIndex().data(ShortcutsModel::DefaultShortcutRole).value(); + defaultShortcut->setText(def.isEmpty()? tr("None") : def.toString(QKeySequence::NativeText)); + actionBox->setEnabled(true); + if(active == def) { + useDefault->setChecked(true); + keySequenceWidget->setKeySequence(QKeySequence()); + } else { + useCustom->setChecked(true); + keySequenceWidget->setKeySequence(active); + } + } else { + defaultShortcut->setText(tr("None")); + actionBox->setEnabled(false); + useDefault->setChecked(true); + keySequenceWidget->setKeySequence(QKeySequence()); + } +} + +void ShortcutsSettingsPage::on_searchEdit_textChanged(const QString &text) { + _shortcutsFilter->setFilterString(text); +} + +void ShortcutsSettingsPage::keySequenceChanged(const QKeySequence &seq, const QModelIndex &conflicting) { + if(conflicting.isValid()) + _shortcutsModel->setData(conflicting, QKeySequence(), ShortcutsModel::ActiveShortcutRole); + + QModelIndex rowIdx = _shortcutsFilter->mapToSource(shortcutsView->currentIndex()); + Q_ASSERT(rowIdx.isValid()); + _shortcutsModel->setData(rowIdx, seq, ShortcutsModel::ActiveShortcutRole); + setWidgetStates(); +} + +void ShortcutsSettingsPage::toggledCustomOrDefault() { + if(!shortcutsView->currentIndex().isValid()) + return; + + QModelIndex index = _shortcutsFilter->mapToSource(shortcutsView->currentIndex()); + Q_ASSERT(index.isValid()); + + if(useDefault->isChecked()) { + _shortcutsModel->setData(index, index.data(ShortcutsModel::DefaultShortcutRole)); + } else { + _shortcutsModel->setData(index, QKeySequence()); + } + setWidgetStates(); +} + +void ShortcutsSettingsPage::save() { + _shortcutsModel->commit(); +} diff --git a/support/shortcutssettingspage.h b/support/shortcutssettingspage.h new file mode 100644 index 000000000..202a5b1de --- /dev/null +++ b/support/shortcutssettingspage.h @@ -0,0 +1,66 @@ +/*************************************************************************** + * Copyright (C) 2010 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef SHORTCUTSSETTINGSPAGE_H +#define SHORTCUTSSETTINGSPAGE_H + +#include +#include "ui_shortcutssettingspage.h" + +class ActionCollection; +class ShortcutsModel; + +class ShortcutsFilter : public QSortFilterProxyModel { + Q_OBJECT +public: + ShortcutsFilter(QObject *parent = 0); + +public Q_SLOTS: + void setFilterString(const QString &filterString); + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + +private: + QString _filterString; +}; + +class ShortcutsSettingsPage : public QWidget, private Ui::ShortcutsSettingsPage { + Q_OBJECT +public: + ShortcutsSettingsPage(const QHash &actionCollections, QWidget *parent = 0); + + inline bool hasDefaults() const { return true; } + +public Q_SLOTS: + void save(); + +private Q_SLOTS: + void on_searchEdit_textChanged(const QString &text); + void keySequenceChanged(const QKeySequence &seq, const QModelIndex &conflicting); + void setWidgetStates(); + void toggledCustomOrDefault(); + +private: + ShortcutsModel *_shortcutsModel; + ShortcutsFilter *_shortcutsFilter; +}; + +#endif // SHORTCUTSSETTINGSPAGE_H diff --git a/support/shortcutssettingspage.ui b/support/shortcutssettingspage.ui new file mode 100644 index 000000000..ead27c7e9 --- /dev/null +++ b/support/shortcutssettingspage.ui @@ -0,0 +1,123 @@ + + + ShortcutsSettingsPage + + + + 0 + 0 + 497 + 481 + + + + Form + + + + + + + + Search: + + + + + + + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + true + + + true + + + false + + + true + + + true + + + true + + + + + + + Shortcut for Selected Action + + + + + + Default: + + + + + + + None + + + + + + + Custom: + + + + + + + + + + Qt::Horizontal + + + + 346 + 20 + + + + + + + + + + + + KeySequenceWidget + QWidget +
keysequencewidget.h
+ 1 +
+
+ + searchEdit + shortcutsView + useDefault + useCustom + + + +