configurable keyboard shortcuts

svn path=/trunk/KDE/kdelibs/; revision=963051
This commit is contained in:
Chani Armitage 2009-05-03 20:22:14 +00:00
parent bf83f9d006
commit c756fd29c4
7 changed files with 265 additions and 96 deletions

View File

@ -1016,30 +1016,45 @@ void Applet::flushPendingConstraintsEvents()
//FIXME desktop containments can't be removed while in use.
//it's kinda silly to have a keyboard shortcut for something that can only be used when the
//shortcut isn't active.
KAction *closeApplet = new KAction(this);
closeApplet->setIcon(KIcon("edit-delete"));
closeApplet->setEnabled(unlocked);
closeApplet->setVisible(unlocked);
closeApplet->setText(i18nc("%1 is the name of the applet", "Remove this %1", name()));
if (d->isContainment) {
closeApplet->setShortcut(QKeySequence("alt+d,alt+r"));
} else {
closeApplet->setShortcut(QKeySequence("alt+d,r"));
QAction *closeApplet = d->actions->action("remove");
if (closeApplet) {
closeApplet->setEnabled(unlocked);
closeApplet->setVisible(unlocked);
connect(closeApplet, SIGNAL(triggered(bool)), this, SLOT(selectItemToDestroy()));
}
QAction *configAction = d->actions->action("configure");
if (configAction) {
//XXX assumption: isContainment won't change after this
if (d->isContainment) {
connect(configAction, SIGNAL(triggered()), this, SLOT(requestConfiguration()));
} else {
connect(configAction, SIGNAL(triggered(bool)), this, SLOT(showConfigurationInterface()));
}
bool canConfig = unlocked || KAuthorized::authorize("PlasmaAllowConfigureWhenLocked");
canConfig = canConfig && (d->hasConfigurationInterface || d->isContainment);
configAction->setVisible(canConfig);
configAction->setEnabled(canConfig);
}
d->updateShortcuts();
Corona * corona = qobject_cast<Corona*>(scene());
if (corona) {
connect(corona, SIGNAL(shortcutsChanged()), this, SLOT(updateShortcuts()));
}
connect(closeApplet, SIGNAL(triggered(bool)), this, SLOT(selectItemToDestroy()));
d->actions.addAction("remove", closeApplet);
}
if (c & Plasma::ImmutableConstraint) {
bool unlocked = immutability() == Mutable;
QAction *action = d->actions.action("remove");
QAction *action = d->actions->action("remove");
if (action) {
action->setVisible(unlocked);
action->setEnabled(unlocked);
}
bool canConfig = unlocked || KAuthorized::authorize("PlasmaAllowConfigureWhenLocked");
action = d->actions.action("configure");
canConfig = canConfig && (d->hasConfigurationInterface || d->isContainment);
action = d->actions->action("configure");
if (action) {
action->setVisible(canConfig);
action->setEnabled(canConfig);
@ -1143,12 +1158,12 @@ QList<QAction*> Applet::contextualActions()
QAction *Applet::action(QString name) const
{
return d->actions.action(name);
return d->actions->action(name);
}
void Applet::addAction(QString name, QAction *action)
{
d->actions.addAction(name, action);
d->actions->addAction(name, action);
}
void Applet::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
@ -1273,7 +1288,7 @@ void Applet::setGlobalShortcut(const KShortcut &shortcut)
d->activationAction->setObjectName(QString("activate widget %1").arg(id())); // NO I18N
connect(d->activationAction, SIGNAL(triggered()), this, SIGNAL(activate()));
QList<QWidget *> widgets = d->actions.associatedWidgets();
QList<QWidget *> widgets = d->actions->associatedWidgets();
foreach (QWidget *w, widgets) {
w->addAction(d->activationAction);
}
@ -1307,12 +1322,12 @@ bool Applet::isPopupShowing() const
void Applet::addAssociatedWidget(QWidget *widget)
{
d->actions.addAssociatedWidget(widget);
d->actions->addAssociatedWidget(widget);
}
void Applet::removeAssociatedWidget(QWidget *widget)
{
d->actions.removeAssociatedWidget(widget);
d->actions->removeAssociatedWidget(widget);
}
Location Applet::location() const
@ -1369,6 +1384,8 @@ bool Applet::hasConfigurationInterface() const
return d->hasConfigurationInterface;
}
//it bugs me that this can get turned on and off at will. I don't see it being useful and it just
//makes more work for me and more code duplication.
void Applet::setHasConfigurationInterface(bool hasInterface)
{
if (d->hasConfigurationInterface == hasInterface) {
@ -1376,31 +1393,41 @@ void Applet::setHasConfigurationInterface(bool hasInterface)
}
d->hasConfigurationInterface = hasInterface;
//config action
//TODO respect security when it's implemented (4.2)
KAction *configAction = qobject_cast<KAction*>(d->actions.action("configure"));
if (hasInterface) {
if (!configAction) { //should be always true
configAction = new KAction(i18n("%1 Settings", name()), this);
configAction->setIcon(KIcon("configure"));
//configAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); //don't clash with other views
bool unlocked = immutability() == Mutable;
bool canConfig = unlocked || KAuthorized::authorize("PlasmaAllowConfigureWhenLocked");
configAction->setVisible(canConfig);
configAction->setEnabled(canConfig);
//XXX these shortcuts are also in setIsContainment. keep them in sync.
if (d->isContainment) {
configAction->setShortcut(QKeySequence("alt+d,alt+s"));
connect(configAction, SIGNAL(triggered()), this, SLOT(requestConfiguration()));
} else {
configAction->setShortcut(QKeySequence("alt+d,s"));
connect(configAction, SIGNAL(triggered(bool)), this, SLOT(showConfigurationInterface()));
}
d->actions.addAction("configure", configAction);
}
} else if (!d->isContainment && !qobject_cast<Plasma::Containment*>(this)) {
d->actions.removeAction(configAction);
//FIXME I'm pretty sure this line has issues but I'm preserving current behaviour for now
if (!hasInterface && (d->isContainment || qobject_cast<Plasma::Containment*>(this))) {
return;
}
//config action
KAction *configAction = qobject_cast<KAction*>(d->actions->action("configure"));
if (configAction) {
bool canConfig = false;
if (hasInterface) {
bool unlocked = immutability() == Mutable;
canConfig = unlocked || KAuthorized::authorize("PlasmaAllowConfigureWhenLocked");
}
configAction->setVisible(canConfig);
configAction->setEnabled(canConfig);
}
}
KActionCollection* AppletPrivate::defaultActions(QObject *parent)
{
KActionCollection *actions = new KActionCollection(parent);
actions->setConfigGroup("Shortcuts-Applet");
KAction *configAction = new KAction(i18n("Widget Settings"), actions);
configAction->setIcon(KIcon("configure"));
configAction->setShortcut(KShortcut("alt+d, s"));
actions->addAction("configure", configAction);
KAction *closeApplet = new KAction("Remove this Widget", actions);
closeApplet->setIcon(KIcon("edit-delete"));
closeApplet->setShortcut(KShortcut("alt+d, r"));
actions->addAction("remove", closeApplet);
return actions;
}
bool Applet::eventFilter(QObject *o, QEvent *e)
@ -1635,6 +1662,33 @@ void AppletPrivate::configDialogFinished()
q->configChanged();
}
void AppletPrivate::updateShortcuts()
{
if (isContainment) {
//a horrible hack to avoid clobbering corona settings
//we pull them out, then read, then put them back
QList<QString> names;
QList<QAction*> qactions;
names << "zoom out" << "add sibling containment" << "configure shortcuts" << "lock widgets";
foreach (const QString &name, names) {
QAction *a = actions->action(name);
actions->takeAction(a); //FIXME this is stupid, KActionCollection needs a takeAction(QString) method
qactions << a;
}
actions->readSettings();
for (int i=0; i<names.size(); ++i) {
QAction *a = qactions.at(i);
if (a) {
actions->addAction(names.at(i), a);
}
}
} else {
actions->readSettings();
}
}
void Applet::configChanged()
{
if (d->script) {
@ -2019,24 +2073,12 @@ void AppletPrivate::setIsContainment(bool nowIsContainment, bool forceUpdate)
}
isContainment = nowIsContainment;
//FIXME I do not like this function.
//currently it's only called before ctmt/applet init, with (true,true), and I'm going to assume it stays that way.
//if someone calls it at some other time it'll cause headaches. :P
delete mainConfig;
mainConfig = 0;
KAction *configAction = qobject_cast<KAction*>(actions.action("configure"));
if (configAction) {
QObject::disconnect(configAction, SIGNAL(triggered()), q, SLOT(requestConfiguration()));
QObject::disconnect(configAction, SIGNAL(triggered(bool)), q, SLOT(showConfigurationInterface()));
//XXX these shortcuts are also in setHasConfigurationInterface. keep them in sync.
if (nowIsContainment) {
//kDebug() << "I am a containment";
configAction->setShortcut(QKeySequence("alt+d,alt+s"));
QObject::connect(configAction, SIGNAL(triggered()), q, SLOT(requestConfiguration()));
} else {
configAction->setShortcut(QKeySequence("alt+d,s"));
QObject::connect(configAction, SIGNAL(triggered(bool)), q, SLOT(showConfigurationInterface()));
}
}
}
bool Applet::isContainment() const
@ -2062,7 +2104,7 @@ AppletPrivate::AppletPrivate(KService::Ptr service, int uniqueID, Applet *applet
pendingConstraints(NoConstraint),
aspectRatioMode(Plasma::KeepAspectRatio),
immutability(Mutable),
actions(applet),
actions(AppletPrivate::defaultActions(applet)),
activationAction(0),
shortcutEditor(0),
messageOverlayProxy(0),
@ -2179,7 +2221,13 @@ void AppletPrivate::init(const QString &packagePath)
}
q->setBackgroundHints(Applet::DefaultBackground);
q->setHasConfigurationInterface(true);
q->setHasConfigurationInterface(true); //FIXME why not default it to true in the constructor?
QAction *closeApplet = actions->action("remove");
closeApplet->setText(i18nc("%1 is the name of the applet", "Remove this %1", q->name()));
QAction *configAction = actions->action("configure");
configAction->setText(i18nc("%1 is the name of the applet", "%1 Settings", q->name()));
QObject::connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), q, SLOT(themeChanged()));
QObject::connect(q, SIGNAL(activate()), q, SLOT(setFocus()));
}

View File

@ -943,6 +943,7 @@ class PLASMA_EXPORT Applet : public QGraphicsWidget
Q_PRIVATE_SLOT(d, void destroyMessageOverlay())
Q_PRIVATE_SLOT(d, void clearShortcutEditorPtr())
Q_PRIVATE_SLOT(d, void configDialogFinished())
Q_PRIVATE_SLOT(d, void updateShortcuts())
/**
* Reimplemented from QGraphicsItem

View File

@ -137,57 +137,55 @@ void Containment::init()
setContainmentType(DesktopContainment);
}
//common actions
//connect actions
ContainmentPrivate::addDefaultActions(d->actions());
bool unlocked = immutability() == Mutable;
KAction *appletBrowserAction = new KAction(i18n("Add Widgets..."), this);
appletBrowserAction->setIcon(KIcon("list-add"));
//fix the text of the actions that need name()
//btw, do we really want to use name() when it's a desktopcontainment?
QAction *closeApplet = d->actions()->action("remove");
closeApplet->setText(i18nc("%1 is the name of the applet", "Remove this %1", name()));
QAction *configAction = d->actions()->action("configure");
configAction->setText(i18nc("%1 is the name of the applet", "%1 Settings", name()));
QAction *appletBrowserAction = action("add widgets");
appletBrowserAction->setVisible(unlocked);
appletBrowserAction->setEnabled(unlocked);
connect(appletBrowserAction, SIGNAL(triggered()), this, SLOT(triggerShowAddWidgets()));
appletBrowserAction->setShortcut(QKeySequence("alt+d,a"));
d->actions().addAction("add widgets", appletBrowserAction);
KAction *action = new KAction(i18n("Next Widget"), this);
//no icon
connect(action, SIGNAL(triggered()), this, SLOT(focusNextApplet()));
action->setShortcut(QKeySequence("alt+d,n"));
d->actions().addAction("next applet", action);
action = new KAction(i18n("Previous Widget"), this);
//no icon
connect(action, SIGNAL(triggered()), this, SLOT(focusPreviousApplet()));
action->setShortcut(QKeySequence("alt+d,p"));
d->actions().addAction("previous applet", action);
QAction *act = action("next applet");
connect(act, SIGNAL(triggered()), this, SLOT(focusNextApplet()));
act = action("previous applet");
connect(act, SIGNAL(triggered()), this, SLOT(focusPreviousApplet()));
if (immutability() != SystemImmutable && corona()) {
QAction *lockDesktopAction = corona()->action("lock widgets");
//keep a pointer so nobody notices it moved to corona
if (lockDesktopAction) {
d->actions().addAction("lock widgets", lockDesktopAction);
d->actions()->addAction("lock widgets", lockDesktopAction);
}
}
if (d->type != PanelContainment &&
d->type != CustomPanelContainment) {
KAction *zoomAction = new KAction(i18n("Zoom In"), this);
zoomAction->setIcon(KIcon("zoom-in"));
connect(zoomAction, SIGNAL(triggered(bool)), this, SLOT(zoomIn()));
//two shortcuts because I hate ctrl-+ but others expect it
QList<QKeySequence> keys;
keys << QKeySequence("alt+d,+");
keys << QKeySequence("alt+d,=");
zoomAction->setShortcuts(keys);
d->actions().addAction("zoom in", zoomAction);
if (d->type == PanelContainment ||
d->type == CustomPanelContainment) {
d->actions()->removeAction(d->actions()->action("zoom in"));
} else {
QAction *zoomAction = action("zoom in");
connect(zoomAction, SIGNAL(triggered()), this, SLOT(zoomIn()));
if (corona()) {
QAction *action = corona()->action("zoom out");
if (action) {
d->actions().addAction("zoom out", action);
d->actions()->addAction("zoom out", action);
}
action = corona()->action("add sibling containment");
if (action) {
d->actions().addAction("add sibling containment", action);
d->actions()->addAction("add sibling containment", action);
}
//a stupid hack to make this one's keyboard shortcut work
action = corona()->action("configure shortcuts");
if (action) {
d->actions()->addAction("configure shortcuts", action);
}
}
@ -210,6 +208,45 @@ void Containment::init()
setDrawWallpaper(true);
}
}
}
void ContainmentPrivate::addDefaultActions(KActionCollection *actions)
{
actions->setConfigGroup("Shortcuts-Containment");
//adjust applet actions
KAction *appAction = qobject_cast<KAction*>(actions->action("remove"));
appAction->setShortcut(KShortcut("alt+d, alt+r"));
appAction->setText(i18n("Remove this Activity"));
appAction = qobject_cast<KAction*>(actions->action("configure"));
if (appAction) {
appAction->setShortcut(KShortcut("alt+d, alt+s"));
appAction->setText(i18n("Activity Settings"));
}
//add our own actions
KAction *appletBrowserAction = new KAction(i18n("Add Widgets..."), actions);
appletBrowserAction->setIcon(KIcon("list-add"));
appletBrowserAction->setShortcut(KShortcut("alt+d, a"));
actions->addAction("add widgets", appletBrowserAction);
KAction *action = new KAction(i18n("Next Widget"), actions);
//no icon
action->setShortcut(KShortcut("alt+d, n"));
actions->addAction("next applet", action);
action = new KAction(i18n("Previous Widget"), actions);
//no icon
action->setShortcut(KShortcut("alt+d, p"));
actions->addAction("previous applet", action);
KAction *zoomAction = new KAction(i18n("Zoom In"), actions);
zoomAction->setIcon(KIcon("zoom-in"));
//two shortcuts because I hate ctrl-+ but others expect it
zoomAction->setShortcuts(KShortcut("alt+d, +; alt+d, ="));
actions->addAction("zoom in", zoomAction);
}
// helper function for sorting the list of applets
@ -507,7 +544,7 @@ void ContainmentPrivate::appletActions(KMenu &desktopMenu, Applet *applet, bool
}
if (applet->hasConfigurationInterface()) {
QAction *configureApplet = applet->d->actions.action("configure");
QAction *configureApplet = applet->d->actions->action("configure");
if (configureApplet) {
desktopMenu.addAction(configureApplet);
}
@ -519,7 +556,7 @@ void ContainmentPrivate::appletActions(KMenu &desktopMenu, Applet *applet, bool
desktopMenu.addSeparator();
}
QAction *closeApplet = applet->d->actions.action("remove");
QAction *closeApplet = applet->d->actions->action("remove");
if (!closeApplet) { //unlikely but not impossible
closeApplet = new QAction(i18nc("%1 is the name of the applet", "Remove this %1", applet->name()), &desktopMenu);
closeApplet->setIcon(KIcon("edit-delete"));
@ -1450,7 +1487,7 @@ Context *ContainmentPrivate::context()
return con;
}
KActionCollection &ContainmentPrivate::actions()
KActionCollection* ContainmentPrivate::actions()
{
return static_cast<Applet*>(q)->d->actions;
}
@ -1461,7 +1498,7 @@ void ContainmentPrivate::focusApplet(Plasma::Applet *applet)
return;
}
QList<QWidget *> widgets = actions().associatedWidgets();
QList<QWidget *> widgets = actions()->associatedWidgets();
if (focusedApplet) {
foreach (QWidget *w, widgets) {
focusedApplet->removeAssociatedWidget(w);

View File

@ -37,10 +37,12 @@
#include <kmimetype.h>
#include <kactioncollection.h>
#include <kaction.h>
#include <kshortcutsdialog.h>
#include "containment.h"
#include "view.h"
#include "private/applet_p.h"
#include "private/containment_p.h"
#include "tooltipmanager.h"
using namespace Plasma;
@ -80,13 +82,48 @@ public:
QObject::connect(&configSyncTimer, SIGNAL(timeout()), q, SLOT(syncConfig()));
//some common actions
actions.setConfigGroup("Shortcuts");
KAction *lockAction = new KAction(i18n("Lock Widgets"), q);
lockAction->setIcon(KIcon("object-locked"));
QObject::connect(lockAction, SIGNAL(triggered(bool)),
q, SLOT(toggleImmutability()));
lockAction->setShortcut(QKeySequence("alt+d,l"));
lockAction->setShortcut(KShortcut("alt+d, l"));
lockAction->setShortcutContext(Qt::ApplicationShortcut);
actions.addAction("lock widgets", lockAction);
//FIXME this doesn't really belong here. desktop KCM maybe?
//but should the shortcuts be per-app or really-global?
//I don't know how to make kactioncollections use plasmarc
KAction *action = new KAction(i18n("Shortcuts..."), q);
action->setIcon(KIcon("configure"));
QObject::connect(action, SIGNAL(triggered()),
q, SLOT(showShortcutConfig()));
//action->setShortcut(KShortcut("ctrl+h"));
action->setShortcutContext(Qt::ApplicationShortcut);
actions.addAction("configure shortcuts", action);
actions.readSettings();
//fake containment/applet actions
KActionCollection *aActions = AppletPrivate::defaultActions(q);
KActionCollection *cActions = AppletPrivate::defaultActions(q); //containment has to start with applet stuff
ContainmentPrivate::addDefaultActions(cActions); //now it's really containment
//grab the current stuff
cActions->readSettings();
aActions->readSettings();
shortcutsDlg.setModal(false);
shortcutsDlg.addCollection(aActions);
shortcutsDlg.addCollection(cActions);
QObject::connect(&shortcutsDlg, SIGNAL(saved()), q, SIGNAL(shortcutsChanged()));
}
void showShortcutConfig()
{
//show a kshortcutsdialog with the actions
shortcutsDlg.configure();
}
void toggleImmutability()
@ -214,6 +251,7 @@ public:
QList<Containment*> containments;
QHash<uint, QGraphicsWidget*> offscreenWidgets;
KActionCollection actions;
KShortcutsDialog shortcutsDlg;
};
Corona::Corona(QObject *parent)
@ -661,6 +699,17 @@ void Corona::enableAction(const QString &name, bool enable)
}
}
void Corona::updateShortcuts()
{
d->actions.readSettings();
d->shortcutsDlg.addCollection(&d->actions);
}
void Corona::addShortcuts(KActionCollection *newShortcuts)
{
d->shortcutsDlg.addCollection(newShortcuts);
}
} // namespace Plasma
#include "corona.moc"

View File

@ -185,6 +185,22 @@ public:
*/
void enableAction(const QString &name, bool enable);
/**
* @since 4.3
* Updates keyboard shortcuts for all the corona's actions.
* If you've added actions to the corona you'll need to
* call this for them to be configurable.
*/
void updateShortcuts();
/**
* @since 4.3
* Adds a set of actions to the shortcut config dialog.
* don't use this on actions in the corona's own actioncollection,
* those are handled automatically. this is for stuff outside of that.
*/
void addShortcuts(KActionCollection *newShortcuts);
public Q_SLOTS:
/**
* Initializes the layout from a config file. This will first clear any existing
@ -281,6 +297,16 @@ Q_SIGNALS:
*/
void immutabilityChanged(Plasma::ImmutabilityType immutability);
/**
* @since 4.3
* emitted when the user changes keyboard shortcut settings
* connect to this if you've put some extra shortcuts in your app
* that are NOT in corona's actioncollection.
* if your code's not in shells/ it probably shouldn't be using this function.
* @see addShortcuts
*/
void shortcutsChanged();
protected:
/**
* Loads the default (system wide) layout for this user
@ -316,6 +342,7 @@ private:
Q_PRIVATE_SLOT(d, void offscreenWidgetDestroyed(QObject *))
Q_PRIVATE_SLOT(d, void syncConfig())
Q_PRIVATE_SLOT(d, void toggleImmutability())
Q_PRIVATE_SLOT(d, void showShortcutConfig())
friend class CoronaPrivate;
};

View File

@ -95,7 +95,9 @@ public:
KConfigDialog *generateGenericConfigDialog();
QString configDialogId() const;
QString configWindowTitle() const;
void updateShortcuts();
static KActionCollection* defaultActions(QObject *parent);
static QSet<QString> knownCategories();
static uint s_maxAppletId;
@ -123,7 +125,7 @@ public:
Plasma::Constraints pendingConstraints;
Plasma::AspectRatioMode aspectRatioMode;
ImmutabilityType immutability;
KActionCollection actions;
KActionCollection *actions;
KAction *activationAction;
KKeySequenceWidget *shortcutEditor; //TODO: subclass KConfigDialog and encapsulate this in there
QGraphicsProxyWidget *messageOverlayProxy;

View File

@ -89,7 +89,12 @@ public:
const QRectF &geometry = QRectF(-1, -1, -1, -1), uint id = 0,
bool delayedInit = false);
KActionCollection &actions();
KActionCollection *actions();
/**
* add the regular actions & keyboard shortcuts onto Applet's collection
*/
static void addDefaultActions(KActionCollection *actions);
/**
* give keyboard focus to applet within this containment