diff --git a/applet.cpp b/applet.cpp index 82ce051c5..06c3ffa27 100644 --- a/applet.cpp +++ b/applet.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -53,6 +54,7 @@ #include #include #include +#include #include @@ -650,6 +652,34 @@ void Applet::flushPendingConstraintsEvents() Plasma::Constraints c = d->pendingConstraints; 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) { d->needsConfigOverlay->setGeometry(QRectF(QPointF(0, 0), boundingRect().size())); // FIXME:: rather horrible hack to work around the fact we don't have spacers @@ -703,6 +733,16 @@ QList Applet::contextualActions() return d->script ? d->script->contextualActions() : QList(); } +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) { QPainter *p; @@ -821,6 +861,16 @@ Containment* Applet::containment() const return c; } +void Applet::addAssociatedWidget(QWidget *widget) +{ + d->actions.addAssociatedWidget(widget); +} + +void Applet::removeAssociatedWidget(QWidget *widget) +{ + d->actions.removeAssociatedWidget(widget); +} + Location Applet::location() const { Containment* c = containment(); @@ -886,7 +936,32 @@ bool Applet::hasConfigurationInterface() const void Applet::setHasConfigurationInterface(bool hasInterface) { + if (d->hasConfigurationInterface == hasInterface) { + return; + } 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 ) @@ -946,6 +1021,12 @@ void Applet::mousePressEvent(QGraphicsSceneMouseEvent *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); } @@ -1145,6 +1226,7 @@ Applet* Applet::load(const KPluginInfo& info, uint appletId, const QVariantList& QVariant Applet::itemChange(GraphicsItemChange change, const QVariant &value) { + //kDebug() << change; switch (change) { case ItemPositionChange: if (d->shadow) { @@ -1287,6 +1369,7 @@ Applet::Private::Private(KService::Ptr service, int uniqueID, Applet *applet) pendingConstraints(NoConstraint), aspectRatioMode(Plasma::KeepAspectRatio), immutability(Mutable), + actions(applet), constraintsTimerId(0), hasConfigurationInterface(false), failed(false), diff --git a/applet.h b/applet.h index ff64f37e6..0d686025f 100644 --- a/applet.h +++ b/applet.h @@ -36,6 +36,7 @@ class KConfigDialog; class QGraphicsView; +class KActionCollection; namespace Plasma { @@ -414,6 +415,16 @@ class PLASMA_EXPORT Applet : public QGraphicsWidget **/ virtual QList 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 * and if it has a drop shadow @@ -448,6 +459,18 @@ class PLASMA_EXPORT Applet : public QGraphicsWidget **/ 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 serviceId the name of the .desktop file containing the diff --git a/applet_p.h b/applet_p.h index 2ed12c7a6..931e7e255 100644 --- a/applet_p.h +++ b/applet_p.h @@ -22,6 +22,8 @@ #ifndef PLASMA_APPLET_P_H #define PLASMA_APPLET_P_H +#include + namespace Plasma { @@ -87,6 +89,7 @@ public: Plasma::AspectRatioMode aspectRatioMode; QGraphicsView* ghostView; ImmutabilityType immutability; + KActionCollection actions; int constraintsTimerId; bool hasConfigurationInterface : 1; bool failed : 1; diff --git a/containment.cpp b/containment.cpp index fde0a3b89..2be274fa7 100644 --- a/containment.cpp +++ b/containment.cpp @@ -116,6 +116,59 @@ void Containment::init() 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(); } @@ -276,12 +329,11 @@ void Containment::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) } if (applet->hasConfigurationInterface()) { - QAction* configureApplet = new QAction(i18n("%1 Settings", applet->name()), &desktopMenu); - configureApplet->setIcon(KIcon("configure")); - connect(configureApplet, SIGNAL(triggered(bool)), - applet, SLOT(showConfigurationInterface())); - desktopMenu.addAction(configureApplet); - hasEntries = true; + QAction* configureApplet = applet->d->actions.action("configure"); + if (configureApplet) { + desktopMenu.addAction(configureApplet); + hasEntries = true; + } } QList containmentActions = contextualActions(); @@ -302,14 +354,18 @@ void Containment::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) } } - if (scene() && !static_cast(scene())->immutability() != Mutable) { + if (scene() && static_cast(scene())->immutability() == Mutable) { if (hasEntries) { desktopMenu.addSeparator(); } - QAction* closeApplet = new QAction(i18n("Remove this %1", applet->name()), &desktopMenu); - closeApplet->setIcon(KIcon("edit-delete")); - connect(closeApplet, SIGNAL(triggered(bool)), applet, SLOT(destroy())); + QAction* closeApplet = applet->d->actions.action("remove"); + if (! closeApplet) { //unlikely but not impossible + 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); hasEntries = true; } @@ -716,6 +772,7 @@ bool Containment::sceneEventFilter(QGraphicsItem *watched, QEvent *event) QVariant Containment::itemChange(GraphicsItemChange change, const QVariant &value) { Q_UNUSED(value) + //FIXME if the applet is moved to another containment we need to unfocus it if (isContainment() && (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(q)->d->actions; +} + +void Containment::Private::focusApplet(Plasma::Applet *applet) +{ + kDebug(); + if (focusedApplet == applet) { + return; + } + + QList 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 @@ -808,7 +943,7 @@ void Containment::Private::toggleDesktopImmutability() } } - setLockToolText(); + //setLockToolText(); } void Containment::Private::zoomIn() @@ -896,6 +1031,17 @@ void Containment::Private::containmentConstraintsEvent(Plasma::Constraints const //kDebug() << "got containmentConstraintsEvent" << constraints << (QObject*)toolBox; if (constraints & Plasma::ImmutableConstraint) { 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 foreach (Applet *a, applets) { @@ -1001,6 +1147,9 @@ void Containment::Private::appletDestroyed(QObject* object) // so this unsafe looking code is actually just fine. Applet* applet = static_cast(object); applets.removeAll(applet); + if (focusedApplet == applet) { + focusedApplet = 0; + } emit q->appletRemoved(applet); emit q->configNeedsSaving(); } diff --git a/containment.h b/containment.h index 1459b96a0..9ffd3b97f 100644 --- a/containment.h +++ b/containment.h @@ -260,6 +260,18 @@ class PLASMA_EXPORT Containment : public Applet */ 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: /** * 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(); + /** + * switch keyboard focus to the next of our applets + */ + void focusNextApplet(); + + /** + * switch keyboard focus to the previous one of our applets + */ + void focusPreviousApplet(); + protected: /** * Sets the type of this containment. diff --git a/containment_p.h b/containment_p.h index 41aa606df..a76996cab 100644 --- a/containment_p.h +++ b/containment_p.h @@ -37,6 +37,7 @@ public: : q(c), formFactor(Planar), location(Floating), + focusedApplet(0), screen(-1), // no screen toolBox(0), type(Containment::NoContainmentType), @@ -82,10 +83,18 @@ public: const QRectF &geometry = QRectF(-1, -1, -1, -1), uint id = 0, bool delayedInit = false); + KActionCollection& actions(); + + /** + * give keyboard focus to applet within this containment + */ + void focusApplet(Plasma::Applet *applet); + Containment *q; FormFactor formFactor; Location location; Applet::List applets; + Applet *focusedApplet; QMap handles; int screen; Toolbox *toolBox; diff --git a/view.cpp b/view.cpp index e56f24c2c..fb43a13c9 100644 --- a/view.cpp +++ b/view.cpp @@ -21,6 +21,7 @@ #include #include +#include #include "corona.h" #include "containment.h" @@ -175,13 +176,18 @@ void View::setContainment(Containment *containment) if (d->containment) { disconnect(d->containment, SIGNAL(geometryChanged()), this, SLOT(updateSceneRect())); screen = d->containment->screen(); + //remove all the old containment's actions + d->containment->removeAssociatedWidget(this); } d->containment = containment; if (! containment) { return; } - + + //add keyboard-shortcut actions + d->containment->addAssociatedWidget(this); + if (screen > -1) { containment->setScreen(screen); }