keyboard shortcuts.

the defaults are kinda lame right now, but I'm planning to do a lot more with this.
applet focus and the shorcut hooks work properly, however.

svn path=/trunk/KDE/kdebase/workspace/libs/plasma/; revision=808610
This commit is contained in:
Chani Armitage 2008-05-17 03:39:24 +00:00
parent 3bce3a18b8
commit 1d8eb91e89
7 changed files with 307 additions and 12 deletions

View File

@ -42,6 +42,7 @@
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QGraphicsView> #include <QGraphicsView>
#include <QGraphicsProxyWidget> #include <QGraphicsProxyWidget>
#include <QAction>
#include <KIcon> #include <KIcon>
#include <KColorScheme> #include <KColorScheme>
@ -53,6 +54,7 @@
#include <KService> #include <KService>
#include <KServiceTypeTrader> #include <KServiceTypeTrader>
#include <KWindowSystem> #include <KWindowSystem>
#include <KActionCollection>
#include <Solid/PowerManagement> #include <Solid/PowerManagement>
@ -650,6 +652,34 @@ void Applet::flushPendingConstraintsEvents()
Plasma::Constraints c = d->pendingConstraints; Plasma::Constraints c = d->pendingConstraints;
d->pendingConstraints = NoConstraint; d->pendingConstraints = NoConstraint;
if (c & Plasma::StartupCompletedConstraint) {
//common actions
bool unlocked = immutability() == Mutable;
//FIXME make it work for containments
//also, don't allow to delete the last desktop containment
//heck, can desktop ctmts even handle being deleted yet?
//so panel has a remove() that tears it down nicely. what does desktop have?
QAction* closeApplet = new QAction(i18n("Remove this %1", name()), this);
closeApplet->setIcon(KIcon("edit-delete"));
closeApplet->setEnabled(unlocked);
closeApplet->setVisible(unlocked);
closeApplet->setShortcutContext(Qt::WidgetWithChildrenShortcut); //don't clash with other views
if (! isContainment()) {
closeApplet->setShortcut(QKeySequence("ctrl+r"));
connect(closeApplet, SIGNAL(triggered(bool)), this, SLOT(destroy()));
}
d->actions.addAction("remove", closeApplet);
}
if (c & Plasma::ImmutableConstraint) {
bool unlocked = immutability() == Mutable;
QAction *action = d->actions.action("remove");
if (action) {
action->setVisible(unlocked);
action->setEnabled(unlocked);
}
}
if (c & Plasma::SizeConstraint && d->needsConfigOverlay) { if (c & Plasma::SizeConstraint && d->needsConfigOverlay) {
d->needsConfigOverlay->setGeometry(QRectF(QPointF(0, 0), boundingRect().size())); d->needsConfigOverlay->setGeometry(QRectF(QPointF(0, 0), boundingRect().size()));
// FIXME:: rather horrible hack to work around the fact we don't have spacers // FIXME:: rather horrible hack to work around the fact we don't have spacers
@ -703,6 +733,16 @@ QList<QAction*> Applet::contextualActions()
return d->script ? d->script->contextualActions() : QList<QAction*>(); return d->script ? d->script->contextualActions() : QList<QAction*>();
} }
QAction* Applet::action(QString name) const
{
return d->actions.action(name);
}
void Applet::addAction(QString name, QAction *action)
{
d->actions.addAction(name, action);
}
void Applet::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) void Applet::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{ {
QPainter *p; QPainter *p;
@ -821,6 +861,16 @@ Containment* Applet::containment() const
return c; return c;
} }
void Applet::addAssociatedWidget(QWidget *widget)
{
d->actions.addAssociatedWidget(widget);
}
void Applet::removeAssociatedWidget(QWidget *widget)
{
d->actions.removeAssociatedWidget(widget);
}
Location Applet::location() const Location Applet::location() const
{ {
Containment* c = containment(); Containment* c = containment();
@ -886,7 +936,32 @@ bool Applet::hasConfigurationInterface() const
void Applet::setHasConfigurationInterface(bool hasInterface) void Applet::setHasConfigurationInterface(bool hasInterface)
{ {
if (d->hasConfigurationInterface == hasInterface) {
return;
}
d->hasConfigurationInterface = hasInterface; d->hasConfigurationInterface = hasInterface;
//config action
//TODO respect security when it's implemented (4.2)
QAction *configAction = d->actions.action("configure");
if (hasInterface) {
if (! configAction) { //should be always true
configAction = new QAction(i18n("%1 Settings", name()), this);
configAction->setIcon(KIcon("configure"));
configAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); //don't clash with other views
if (isContainment()) {
//kDebug() << "I am a containment";
configAction->setShortcut(QKeySequence("ctrl+shift+s"));
} else {
configAction->setShortcut(QKeySequence("ctrl+s"));
}
//TODO how can we handle configuration of the shortcut in a way that spans all applets?
connect(configAction, SIGNAL(triggered(bool)),
this, SLOT(showConfigurationInterface()));
d->actions.addAction("configure", configAction);
}
} else {
d->actions.removeAction(configAction);
}
} }
bool Applet::eventFilter( QObject *o, QEvent * e ) bool Applet::eventFilter( QObject *o, QEvent * e )
@ -946,6 +1021,12 @@ void Applet::mousePressEvent(QGraphicsSceneMouseEvent *event)
void Applet::focusInEvent(QFocusEvent * event) void Applet::focusInEvent(QFocusEvent * event)
{ {
if (containment()) {
containment()->d->focusApplet(this);
//XXX if we are a containment we'll attempt to focus ourself
//which should be harmless
//focusing an applet may trigger this event again, but we won't be here more than twice
}
QGraphicsWidget::focusInEvent(event); QGraphicsWidget::focusInEvent(event);
} }
@ -1145,6 +1226,7 @@ Applet* Applet::load(const KPluginInfo& info, uint appletId, const QVariantList&
QVariant Applet::itemChange(GraphicsItemChange change, const QVariant &value) QVariant Applet::itemChange(GraphicsItemChange change, const QVariant &value)
{ {
//kDebug() << change;
switch (change) { switch (change) {
case ItemPositionChange: case ItemPositionChange:
if (d->shadow) { if (d->shadow) {
@ -1287,6 +1369,7 @@ Applet::Private::Private(KService::Ptr service, int uniqueID, Applet *applet)
pendingConstraints(NoConstraint), pendingConstraints(NoConstraint),
aspectRatioMode(Plasma::KeepAspectRatio), aspectRatioMode(Plasma::KeepAspectRatio),
immutability(Mutable), immutability(Mutable),
actions(applet),
constraintsTimerId(0), constraintsTimerId(0),
hasConfigurationInterface(false), hasConfigurationInterface(false),
failed(false), failed(false),

View File

@ -36,6 +36,7 @@
class KConfigDialog; class KConfigDialog;
class QGraphicsView; class QGraphicsView;
class KActionCollection;
namespace Plasma namespace Plasma
{ {
@ -414,6 +415,16 @@ class PLASMA_EXPORT Applet : public QGraphicsWidget
**/ **/
virtual QList<QAction*> contextualActions(); virtual QList<QAction*> contextualActions();
/**
* Returns the QAction with the given name from our collection
*/
QAction* action(QString name) const;
/**
* Adds the action to our collection under the given name
*/
void addAction(QString name, QAction *action);
/** /**
* @return BackgroundHints flags combination telling if the standard background is shown * @return BackgroundHints flags combination telling if the standard background is shown
* and if it has a drop shadow * and if it has a drop shadow
@ -448,6 +459,18 @@ class PLASMA_EXPORT Applet : public QGraphicsWidget
**/ **/
Containment* containment() const; Containment* containment() const;
/**
* associate actions with this widget, including ones added after this call.
* needed to make keyboard shortcuts work.
*/
virtual void addAssociatedWidget(QWidget *widget);
/**
* un-associate actions from this widget, including ones added after this call.
* needed to make keyboard shortcuts work.
*/
virtual void removeAssociatedWidget(QWidget *widget);
/** /**
* @param parent the QGraphicsItem this applet is parented to * @param parent the QGraphicsItem this applet is parented to
* @param serviceId the name of the .desktop file containing the * @param serviceId the name of the .desktop file containing the

View File

@ -22,6 +22,8 @@
#ifndef PLASMA_APPLET_P_H #ifndef PLASMA_APPLET_P_H
#define PLASMA_APPLET_P_H #define PLASMA_APPLET_P_H
#include <KActionCollection>
namespace Plasma namespace Plasma
{ {
@ -87,6 +89,7 @@ public:
Plasma::AspectRatioMode aspectRatioMode; Plasma::AspectRatioMode aspectRatioMode;
QGraphicsView* ghostView; QGraphicsView* ghostView;
ImmutabilityType immutability; ImmutabilityType immutability;
KActionCollection actions;
int constraintsTimerId; int constraintsTimerId;
bool hasConfigurationInterface : 1; bool hasConfigurationInterface : 1;
bool failed : 1; bool failed : 1;

View File

@ -116,6 +116,59 @@ void Containment::init()
setContainmentType(DesktopContainment); setContainmentType(DesktopContainment);
} }
//common actions
bool unlocked = immutability() == Mutable;
QAction *appletBrowserAction = new QAction(i18n("Add Widgets..."), this);
appletBrowserAction->setIcon(KIcon("list-add"));
appletBrowserAction->setVisible(unlocked);
appletBrowserAction->setEnabled(unlocked);
connect(appletBrowserAction, SIGNAL(triggered()), this, SLOT(triggerShowAddWidgets()));
appletBrowserAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
appletBrowserAction->setShortcut(QKeySequence("ctrl+a"));
d->actions().addAction("add widgets", appletBrowserAction);
QAction *action = new QAction(i18n("Next Applet"), this);
//no icon
connect(action, SIGNAL(triggered()), this, SLOT(focusNextApplet()));
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
action->setShortcut(QKeySequence("ctrl+n"));
d->actions().addAction("next applet", action);
action = new QAction(i18n("Previous Applet"), this);
//no icon
connect(action, SIGNAL(triggered()), this, SLOT(focusPreviousApplet()));
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
action->setShortcut(QKeySequence("ctrl+p"));
d->actions().addAction("previous applet", action);
if (immutability() != SystemImmutable) {
//FIXME I'm not certain this belongs in Containment
//but it sure is nice to have the keyboard shortcut in every containment by default
QAction *lockDesktopAction = new QAction(unlocked ? i18n("Lock Widgets") : i18n("Unlock Widgets"), this);
lockDesktopAction->setIcon(KIcon("object-locked"));
connect(lockDesktopAction, SIGNAL(triggered(bool)), this, SLOT(toggleDesktopImmutability()));
lockDesktopAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
lockDesktopAction->setShortcut(QKeySequence("ctrl+l"));
d->actions().addAction("lock widgets", lockDesktopAction);
}
if (d->type == DesktopContainment) { //this means custom containments are left out
QAction *zoomAction = new QAction(i18n("Zoom In"), this);
zoomAction->setIcon(KIcon("zoom-in"));
connect(zoomAction, SIGNAL(triggered(bool)), this, SLOT(zoomIn()));
zoomAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
zoomAction->setShortcut(QKeySequence("ctrl+="));
d->actions().addAction("zoom in", zoomAction);
zoomAction = new QAction(i18n("Zoom Out"), this);
zoomAction->setIcon(KIcon("zoom-out"));
connect(zoomAction, SIGNAL(triggered(bool)), this, SLOT(zoomOut()));
zoomAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
zoomAction->setShortcut(QKeySequence("ctrl+-"));
d->actions().addAction("zoom out", zoomAction);
}
Applet::init(); Applet::init();
} }
@ -276,12 +329,11 @@ void Containment::contextMenuEvent(QGraphicsSceneContextMenuEvent* event)
} }
if (applet->hasConfigurationInterface()) { if (applet->hasConfigurationInterface()) {
QAction* configureApplet = new QAction(i18n("%1 Settings", applet->name()), &desktopMenu); QAction* configureApplet = applet->d->actions.action("configure");
configureApplet->setIcon(KIcon("configure")); if (configureApplet) {
connect(configureApplet, SIGNAL(triggered(bool)), desktopMenu.addAction(configureApplet);
applet, SLOT(showConfigurationInterface())); hasEntries = true;
desktopMenu.addAction(configureApplet); }
hasEntries = true;
} }
QList<QAction*> containmentActions = contextualActions(); QList<QAction*> containmentActions = contextualActions();
@ -302,14 +354,18 @@ void Containment::contextMenuEvent(QGraphicsSceneContextMenuEvent* event)
} }
} }
if (scene() && !static_cast<Corona*>(scene())->immutability() != Mutable) { if (scene() && static_cast<Corona*>(scene())->immutability() == Mutable) {
if (hasEntries) { if (hasEntries) {
desktopMenu.addSeparator(); desktopMenu.addSeparator();
} }
QAction* closeApplet = new QAction(i18n("Remove this %1", applet->name()), &desktopMenu); QAction* closeApplet = applet->d->actions.action("remove");
closeApplet->setIcon(KIcon("edit-delete")); if (! closeApplet) { //unlikely but not impossible
connect(closeApplet, SIGNAL(triggered(bool)), applet, SLOT(destroy())); kDebug() << "wtf!! no remove action!!!!!!!!";
closeApplet = new QAction(i18n("Remove this %1", applet->name()), &desktopMenu);
closeApplet->setIcon(KIcon("edit-delete"));
connect(closeApplet, SIGNAL(triggered(bool)), applet, SLOT(destroy()));
}
desktopMenu.addAction(closeApplet); desktopMenu.addAction(closeApplet);
hasEntries = true; hasEntries = true;
} }
@ -716,6 +772,7 @@ bool Containment::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
QVariant Containment::itemChange(GraphicsItemChange change, const QVariant &value) QVariant Containment::itemChange(GraphicsItemChange change, const QVariant &value)
{ {
Q_UNUSED(value) Q_UNUSED(value)
//FIXME if the applet is moved to another containment we need to unfocus it
if (isContainment() && if (isContainment() &&
(change == QGraphicsItem::ItemSceneHasChanged || change == QGraphicsItem::ItemPositionHasChanged) && (change == QGraphicsItem::ItemSceneHasChanged || change == QGraphicsItem::ItemPositionHasChanged) &&
@ -789,6 +846,84 @@ void Containment::closeToolBox()
} }
} }
void Containment::addAssociatedWidget(QWidget *widget)
{
Applet::addAssociatedWidget(widget);
if (d->focusedApplet) {
d->focusedApplet->addAssociatedWidget(widget);
}
}
void Containment::removeAssociatedWidget(QWidget *widget)
{
Applet::removeAssociatedWidget(widget);
if (d->focusedApplet) {
d->focusedApplet->removeAssociatedWidget(widget);
}
}
KActionCollection& Containment::Private::actions()
{
return static_cast<Applet*>(q)->d->actions;
}
void Containment::Private::focusApplet(Plasma::Applet *applet)
{
kDebug();
if (focusedApplet == applet) {
return;
}
QList<QWidget *> widgets = actions().associatedWidgets();
if (focusedApplet) {
foreach (QWidget *w, widgets) {
focusedApplet->removeAssociatedWidget(w);
}
}
//but what if applet isn't really one of our applets?
//FIXME should we really unfocus the old applet?
if (applets.contains(applet)) {
kDebug() << "switching to" << applet->name();
focusedApplet = applet;
if (focusedApplet) {
foreach (QWidget *w, widgets) {
focusedApplet->addAssociatedWidget(w);
}
}
applet->setFocus(Qt::ShortcutFocusReason);
} else {
focusedApplet = 0;
}
}
void Containment::focusNextApplet()
{
kDebug();
if (d->applets.isEmpty()) {
return;
}
int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) + 1 : 0;
if (index >= d->applets.size()) {
index = 0;
}
kDebug() << "index" << index;
d->focusApplet(d->applets.at(index));
}
void Containment::focusPreviousApplet()
{
kDebug();
if (d->applets.isEmpty()) {
return;
}
int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) - 1 : -1;
if (index < 0) {
index = d->applets.size() - 1;
}
kDebug() << "index" << index;
d->focusApplet(d->applets.at(index));
}
// Private class implementation // Private class implementation
@ -808,7 +943,7 @@ void Containment::Private::toggleDesktopImmutability()
} }
} }
setLockToolText(); //setLockToolText();
} }
void Containment::Private::zoomIn() void Containment::Private::zoomIn()
@ -896,6 +1031,17 @@ void Containment::Private::containmentConstraintsEvent(Plasma::Constraints const
//kDebug() << "got containmentConstraintsEvent" << constraints << (QObject*)toolBox; //kDebug() << "got containmentConstraintsEvent" << constraints << (QObject*)toolBox;
if (constraints & Plasma::ImmutableConstraint) { if (constraints & Plasma::ImmutableConstraint) {
setLockToolText(); setLockToolText();
//update actions
bool unlocked = q->immutability() == Mutable;
QAction *action = actions().action("add widgets");
if (action) {
action->setVisible(unlocked);
action->setEnabled(unlocked);
}
action = actions().action("lock widgets");
if (action) {
action->setText(unlocked ? i18n("Lock Widgets") : i18n("Unlock Widgets"));
}
// tell the applets too // tell the applets too
foreach (Applet *a, applets) { foreach (Applet *a, applets) {
@ -1001,6 +1147,9 @@ void Containment::Private::appletDestroyed(QObject* object)
// so this unsafe looking code is actually just fine. // so this unsafe looking code is actually just fine.
Applet* applet = static_cast<Plasma::Applet*>(object); Applet* applet = static_cast<Plasma::Applet*>(object);
applets.removeAll(applet); applets.removeAll(applet);
if (focusedApplet == applet) {
focusedApplet = 0;
}
emit q->appletRemoved(applet); emit q->appletRemoved(applet);
emit q->configNeedsSaving(); emit q->configNeedsSaving();
} }

View File

@ -260,6 +260,18 @@ class PLASMA_EXPORT Containment : public Applet
*/ */
void closeToolBox(); void closeToolBox();
/**
* associate actions with this widget, including ones added after this call.
* needed to make keyboard shortcuts work.
*/
void addAssociatedWidget(QWidget *widget);
/**
* un-associate actions from this widget, including ones added after this call.
* needed to make keyboard shortcuts work.
*/
void removeAssociatedWidget(QWidget *widget);
Q_SIGNALS: Q_SIGNALS:
/** /**
* This signal is emitted when a new applet is created by the containment * This signal is emitted when a new applet is created by the containment
@ -328,6 +340,16 @@ class PLASMA_EXPORT Containment : public Applet
*/ */
void addSiblingContainment(); void addSiblingContainment();
/**
* switch keyboard focus to the next of our applets
*/
void focusNextApplet();
/**
* switch keyboard focus to the previous one of our applets
*/
void focusPreviousApplet();
protected: protected:
/** /**
* Sets the type of this containment. * Sets the type of this containment.

View File

@ -37,6 +37,7 @@ public:
: q(c), : q(c),
formFactor(Planar), formFactor(Planar),
location(Floating), location(Floating),
focusedApplet(0),
screen(-1), // no screen screen(-1), // no screen
toolBox(0), toolBox(0),
type(Containment::NoContainmentType), type(Containment::NoContainmentType),
@ -82,10 +83,18 @@ public:
const QRectF &geometry = QRectF(-1, -1, -1, -1), uint id = 0, const QRectF &geometry = QRectF(-1, -1, -1, -1), uint id = 0,
bool delayedInit = false); bool delayedInit = false);
KActionCollection& actions();
/**
* give keyboard focus to applet within this containment
*/
void focusApplet(Plasma::Applet *applet);
Containment *q; Containment *q;
FormFactor formFactor; FormFactor formFactor;
Location location; Location location;
Applet::List applets; Applet::List applets;
Applet *focusedApplet;
QMap<Applet*, AppletHandle*> handles; QMap<Applet*, AppletHandle*> handles;
int screen; int screen;
Toolbox *toolBox; Toolbox *toolBox;

View File

@ -21,6 +21,7 @@
#include <KGlobal> #include <KGlobal>
#include <KWindowSystem> #include <KWindowSystem>
#include <KActionCollection>
#include "corona.h" #include "corona.h"
#include "containment.h" #include "containment.h"
@ -175,6 +176,8 @@ void View::setContainment(Containment *containment)
if (d->containment) { if (d->containment) {
disconnect(d->containment, SIGNAL(geometryChanged()), this, SLOT(updateSceneRect())); disconnect(d->containment, SIGNAL(geometryChanged()), this, SLOT(updateSceneRect()));
screen = d->containment->screen(); screen = d->containment->screen();
//remove all the old containment's actions
d->containment->removeAssociatedWidget(this);
} }
d->containment = containment; d->containment = containment;
@ -182,6 +185,9 @@ void View::setContainment(Containment *containment)
return; return;
} }
//add keyboard-shortcut actions
d->containment->addAssociatedWidget(this);
if (screen > -1) { if (screen > -1) {
containment->setScreen(screen); containment->setScreen(screen);
} }