actionmanager.cpp 19 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2 3 4
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
hjk's avatar
hjk committed
7
** Contact: Nokia Corporation (info@qt.nokia.com)
con's avatar
con committed
8
**
9
**
10
** GNU Lesser General Public License Usage
11
**
hjk's avatar
hjk committed
12 13 14 15 16 17
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21 22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23 24 25 26 27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
con's avatar
con committed
28 29
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
con's avatar
con committed
30
**
31
**************************************************************************/
hjk's avatar
hjk committed
32

33
#include "actionmanager_p.h"
con's avatar
con committed
34
#include "mainwindow.h"
35
#include "actioncontainer_p.h"
con's avatar
con committed
36
#include "command_p.h"
con's avatar
con committed
37 38 39
#include "uniqueidmanager.h"

#include <coreplugin/coreconstants.h>
40
#include <coreplugin/icore.h>
41
#include <utils/qtcassert.h>
con's avatar
con committed
42 43 44 45 46 47 48 49 50 51 52 53 54

#include <QtCore/QDebug>
#include <QtCore/QSettings>
#include <QtGui/QMenu>
#include <QtGui/QAction>
#include <QtGui/QShortcut>
#include <QtGui/QMenuBar>

namespace {
    enum { warnAboutFindFailures = 0 };
}

/*!
55
    \class Core::ActionManager
con's avatar
con committed
56 57
    \mainclass

58 59
    \brief The action manager is responsible for registration of menus and
    menu items and keyboard shortcuts.
con's avatar
con committed
60

61
    The ActionManager is the central bookkeeper of actions and their shortcuts and layout.
62 63 64
    You get the only implementation of this class from the core interface
    ICore::actionManager() method, e.g.
    \code
65
        Core::ICore::instance()->actionManager()
66
    \endcode
con's avatar
con committed
67 68 69 70 71

    The main reasons for the need of this class is to provide a central place where the user
    can specify all his keyboard shortcuts, and to provide a solution for actions that should
    behave differently in different contexts (like the copy/replace/undo/redo actions).

72 73 74
    \section1 Contexts

    All actions that are registered with the same string ID (but different context lists)
75
    are considered to be overloads of the same command, represented by an instance
con's avatar
con committed
76
    of the Command class.
77 78 79 80 81 82 83 84 85 86 87 88
    Exactly only one of the registered actions with the same ID is active at any time.
    Which action this is, is defined by the context list that the actions were registered
    with:

    If the current focus widget was registered via \l{ICore::addContextObject()},
    all the contexts returned by its IContext object are active. In addition all
    contexts set via \l{ICore::addAdditionalContext()} are active as well. If one
    of the actions was registered for one of these active contexts, it is the one
    active action, and receives \c triggered and \c toggled signals. Also the
    appearance of the visible action for this ID might be adapted to this
    active action (depending on the settings of the corresponding \l{Command} object).

con's avatar
con committed
89
    The action that is visible to the user is the one returned by Command::action().
90
    If you provide yourself a user visible representation of your action you need
con's avatar
con committed
91
    to use Command::action() for this.
92 93 94
    When this action is invoked by the user,
    the signal is forwarded to the registered action that is valid for the current context.

95 96 97
    \section1 Registering Actions

    To register a globally active action "My Action"
98 99
    put the following in your plugin's IPlugin::initialize method:
    \code
100
        Core::ActionManager *am = Core::ICore::instance()->actionManager();
101
        QAction *myAction = new QAction(tr("My Action"), this);
con's avatar
con committed
102
        Core::Command *cmd = am->registerAction(myAction,
103
                                                 "myplugin.myaction",
104
                                                 Core::Context(C_GLOBAL));
105 106 107 108 109 110
        cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Alt+u")));
        connect(myAction, SIGNAL(triggered()), this, SLOT(performMyAction()));
    \endcode

    So the \c connect is done to your own QAction instance. If you create e.g.
    a tool button that should represent the action you add the action
con's avatar
con committed
111
    from Command::action() to it:
112 113 114 115 116 117 118 119
    \code
        QToolButton *myButton = new QToolButton(someParentWidget);
        myButton->setDefaultAction(cmd->action());
    \endcode

    Also use the ActionManager to add items to registered
    action containers like the applications menu bar or menus in that menu bar.
    To do this, you register your action via the
120
    registerAction methods, get the action container for a specific ID (like specified in
121
    the Core::Constants namespace) with a call of
con's avatar
con committed
122 123
    actionContainer(const QString&) and add your command to this container.

124 125 126 127 128
    Following the example adding "My Action" to the "Tools" menu would be done by
    \code
        am->actionContainer(Core::M_TOOLS)->addAction(cmd);
    \endcode

129
    \section1 Important Guidelines:
con's avatar
con committed
130 131
    \list
    \o Always register your actions and shortcuts!
132 133
    \o Register your actions and shortcuts during your plugin's \l{ExtensionSystem::IPlugin::initialize()}
       or \l{ExtensionSystem::IPlugin::extensionsInitialized()} methods, otherwise the shortcuts won't appear
134
       in the keyboard settings dialog from the beginning.
135 136 137
    \o When registering an action with \c{cmd=registerAction(action, id, contexts)} be sure to connect
       your own action \c{connect(action, SIGNAL...)} but make \c{cmd->action()} visible to the user, i.e.
       \c{widget->addAction(cmd->action())}.
con's avatar
con committed
138 139 140
    \o Use this class to add actions to the applications menus
    \endlist

141
    \sa Core::ICore
con's avatar
con committed
142
    \sa Core::Command
143
    \sa Core::ActionContainer
144
    \sa Core::IContext
con's avatar
con committed
145 146 147
*/

/*!
148
    \fn ActionContainer *ActionManager::createMenu(const Id &id)
149
    \brief Creates a new menu with the given string \a id.
con's avatar
con committed
150

151
    Returns a new ActionContainer that you can use to get the QMenu instance
152
    or to add menu items to the menu. The ActionManager owns
153
    the returned ActionContainer.
154
    Add your menu to some other menu or a menu bar via the
155
    ActionManager::actionContainer and ActionContainer::addMenu methods.
con's avatar
con committed
156 157 158
*/

/*!
159
    \fn ActionContainer *ActionManager::createMenuBar(const Id &id)
160
    \brief Creates a new menu bar with the given string \a id.
con's avatar
con committed
161

162
    Returns a new ActionContainer that you can use to get the QMenuBar instance
163
    or to add menus to the menu bar. The ActionManager owns
164
    the returned ActionContainer.
con's avatar
con committed
165 166 167
*/

/*!
168
    \fn Command *ActionManager::registerAction(QAction *action, const Id &id, const Context &context, bool scriptable)
169 170 171 172 173 174 175
    \brief Makes an \a action known to the system under the specified string \a id.

    Returns a command object that represents the action in the application and is
    owned by the ActionManager. You can registered several actions with the
    same \a id as long as the \a context is different. In this case
    a trigger of the actual action is forwarded to the registered QAction
    for the currently active context.
176 177
    A scriptable action can be called from a script without the need for the user
    to interact with it.
con's avatar
con committed
178 179 180
*/

/*!
181
    \fn Command *ActionManager::registerShortcut(QShortcut *shortcut, const QString &id, const Context &context, bool scriptable)
182 183 184 185 186 187 188
    \brief Makes a \a shortcut known to the system under the specified string \a id.

    Returns a command object that represents the shortcut in the application and is
    owned by the ActionManager. You can registered several shortcuts with the
    same \a id as long as the \a context is different. In this case
    a trigger of the actual shortcut is forwarded to the registered QShortcut
    for the currently active context.
189 190
    A scriptable shortcut can be called from a script without the need for the user
    to interact with it.
con's avatar
con committed
191 192 193
*/

/*!
194
    \fn Command *ActionManager::command(const Id &id) const
con's avatar
con committed
195
    \brief Returns the Command object that is known to the system
196 197 198
    under the given string \a id.

    \sa ActionManager::registerAction()
con's avatar
con committed
199 200 201
*/

/*!
202
    \fn ActionContainer *ActionManager::actionContainer(const Id &id) const
203 204
    \brief Returns the IActionContainter object that is know to the system
    under the given string \a id.
con's avatar
con committed
205

206 207 208
    \sa ActionManager::createMenu()
    \sa ActionManager::createMenuBar()
*/
209 210

/*!
211
    \fn Command *ActionManager::unregisterAction(QAction *action, const Id &id)
212 213 214 215 216 217 218 219
    \brief Removes the knowledge about an \a action under the specified string \a id.

    Usually you do not need to unregister actions. The only valid use case for unregistering
    actions, is for actions that represent user definable actions, like for the custom Locator
    filters. If the user removes such an action, it also has to be unregistered from the action manager,
    to make it disappear from shortcut settings etc.
*/

con's avatar
con committed
220
/*!
221 222
    \fn ActionManager::ActionManager(QObject *parent)
    \internal
con's avatar
con committed
223 224
*/
/*!
225 226
    \fn ActionManager::~ActionManager()
    \internal
con's avatar
con committed
227 228 229 230 231
*/

using namespace Core;
using namespace Core::Internal;

232
ActionManagerPrivate* ActionManagerPrivate::m_instance = 0;
con's avatar
con committed
233 234

/*!
235 236 237
    \class ActionManagerPrivate
    \inheaderfile actionmanager_p.h
    \internal
con's avatar
con committed
238 239
*/

240 241
ActionManagerPrivate::ActionManagerPrivate(MainWindow *mainWnd)
  : ActionManager(mainWnd),
con's avatar
con committed
242 243 244 245 246
    m_mainWnd(mainWnd)
{
    m_instance = this;
}

247
ActionManagerPrivate::~ActionManagerPrivate()
con's avatar
con committed
248
{
249
    // first delete containers to avoid them reacting to command deletion
250 251
    foreach (ActionContainerPrivate *container, m_idContainerMap)
        disconnect(container, SIGNAL(destroyed()), this, SLOT(containerDestroyed()));
con's avatar
con committed
252
    qDeleteAll(m_idContainerMap.values());
253
    qDeleteAll(m_idCmdMap.values());
con's avatar
con committed
254 255
}

256
ActionManagerPrivate *ActionManagerPrivate::instance()
con's avatar
con committed
257 258 259 260
{
    return m_instance;
}

261
QList<Command *> ActionManagerPrivate::commands() const
con's avatar
con committed
262
{
263 264
    // transform list of CommandPrivate into list of Command
    QList<Command *> result;
hjk's avatar
hjk committed
265
    foreach (Command *cmd, m_idCmdMap.values())
266 267
        result << cmd;
    return result;
con's avatar
con committed
268 269
}

270
bool ActionManagerPrivate::hasContext(int context) const
con's avatar
con committed
271
{
hjk's avatar
hjk committed
272
    return m_context.contains(context);
con's avatar
con committed
273 274
}

275 276 277 278 279 280 281 282 283
QDebug operator<<(QDebug in, const Context &context)
{
    UniqueIDManager *uidm = UniqueIDManager::instance();
    in << "CONTEXT: ";
    foreach (int c, context)
        in << "   " << c << uidm->stringForUniqueIdentifier(c);
    return in;
}

284
void ActionManagerPrivate::setContext(const Context &context)
con's avatar
con committed
285 286 287 288 289 290 291 292 293 294
{
    // here are possibilities for speed optimization if necessary:
    // let commands (de-)register themselves for contexts
    // and only update commands that are either in old or new contexts
    m_context = context;
    const IdCmdMap::const_iterator cmdcend = m_idCmdMap.constEnd();
    for (IdCmdMap::const_iterator it = m_idCmdMap.constBegin(); it != cmdcend; ++it)
        it.value()->setCurrentContext(m_context);
}

295
bool ActionManagerPrivate::hasContext(const Context &context) const
con's avatar
con committed
296
{
hjk's avatar
hjk committed
297 298
    for (int i = 0; i < m_context.size(); ++i) {
        if (context.contains(m_context.at(i)))
con's avatar
con committed
299 300 301 302 303
            return true;
    }
    return false;
}

hjk's avatar
hjk committed
304
ActionContainer *ActionManagerPrivate::createMenu(const Id &id)
con's avatar
con committed
305
{
306
    const int uid = UniqueIDManager::instance()->uniqueIdentifier(id);
con's avatar
con committed
307 308 309 310 311 312 313 314 315 316 317
    const IdContainerMap::const_iterator it = m_idContainerMap.constFind(uid);
    if (it !=  m_idContainerMap.constEnd())
        return it.value();

    QMenu *m = new QMenu(m_mainWnd);
    m->setObjectName(id);

    MenuActionContainer *mc = new MenuActionContainer(uid);
    mc->setMenu(m);

    m_idContainerMap.insert(uid, mc);
318
    connect(mc, SIGNAL(destroyed()), this, SLOT(containerDestroyed()));
con's avatar
con committed
319 320 321 322

    return mc;
}

hjk's avatar
hjk committed
323
ActionContainer *ActionManagerPrivate::createMenuBar(const Id &id)
con's avatar
con committed
324
{
325
    const int uid = UniqueIDManager::instance()->uniqueIdentifier(id);
con's avatar
con committed
326 327 328 329 330 331 332 333 334 335 336
    const IdContainerMap::const_iterator it = m_idContainerMap.constFind(uid);
    if (it !=  m_idContainerMap.constEnd())
        return it.value();

    QMenuBar *mb = new QMenuBar; // No parent (System menu bar on Mac OS X)
    mb->setObjectName(id);

    MenuBarActionContainer *mbc = new MenuBarActionContainer(uid);
    mbc->setMenuBar(mb);

    m_idContainerMap.insert(uid, mbc);
337
    connect(mbc, SIGNAL(destroyed()), this, SLOT(containerDestroyed()));
con's avatar
con committed
338 339 340 341

    return mbc;
}

342 343 344 345 346 347
void ActionManagerPrivate::containerDestroyed()
{
    ActionContainerPrivate *container = static_cast<ActionContainerPrivate *>(sender());
    m_idContainerMap.remove(m_idContainerMap.key(container));
}

348
Command *ActionManagerPrivate::registerAction(QAction *action, const Id &id, const Context &context, bool scriptable)
con's avatar
con committed
349
{
350 351
    Action *a = overridableAction(id);
    if (a) {
352
        a->addOverrideAction(action, context, scriptable);
353 354 355
        emit commandListChanged();
        emit commandAdded(id);
    }
con's avatar
con committed
356 357 358
    return a;
}

359
Action *ActionManagerPrivate::overridableAction(const Id &id)
con's avatar
con committed
360
{
361
    Action *a = 0;
362
    const int uid = UniqueIDManager::instance()->uniqueIdentifier(id);
con's avatar
con committed
363
    if (CommandPrivate *c = m_idCmdMap.value(uid, 0)) {
364
        a = qobject_cast<Action *>(c);
con's avatar
con committed
365
        if (!a) {
con's avatar
con committed
366
            qWarning() << "registerAction: id" << id << "is registered with a different command type.";
367
            return 0;
con's avatar
con committed
368
        }
con's avatar
con committed
369
    } else {
370
        a = new Action(uid);
con's avatar
con committed
371
        m_idCmdMap.insert(uid, a);
372 373 374
        m_mainWnd->addAction(a->action());
        a->action()->setObjectName(id);
        a->action()->setShortcutContext(Qt::ApplicationShortcut);
375
        a->setCurrentContext(m_context);
con's avatar
con committed
376 377 378 379 380
    }

    return a;
}

381 382 383 384 385 386 387 388
void ActionManagerPrivate::unregisterAction(QAction *action, const Id &id)
{
    Action *a = 0;
    const int uid = UniqueIDManager::instance()->uniqueIdentifier(id);
    CommandPrivate *c = m_idCmdMap.value(uid, 0);
    QTC_ASSERT(c, return);
    a = qobject_cast<Action *>(c);
    if (!a) {
389
        qWarning() << "unregisterAction: id" << id << "is registered with a different command type.";
390 391 392 393 394
        return;
    }
    a->removeOverrideAction(action);
    if (a->isEmpty()) {
        // clean up
395
        // ActionContainers listen to the commands' destroyed signals
396 397 398 399 400 401 402 403
        m_mainWnd->removeAction(a->action());
        delete a->action();
        m_idCmdMap.remove(uid);
        delete a;
    }
    emit commandListChanged();
}

404
Command *ActionManagerPrivate::registerShortcut(QShortcut *shortcut, const Id &id, const Context &context, bool scriptable)
con's avatar
con committed
405 406
{
    Shortcut *sc = 0;
407
    int uid = UniqueIDManager::instance()->uniqueIdentifier(id);
con's avatar
con committed
408
    if (CommandPrivate *c = m_idCmdMap.value(uid, 0)) {
con's avatar
con committed
409 410
        sc = qobject_cast<Shortcut *>(c);
        if (!sc) {
con's avatar
con committed
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
            qWarning() << "registerShortcut: id" << id << "is registered with a different command type.";
            return c;
        }
    } else {
        sc = new Shortcut(uid);
        m_idCmdMap.insert(uid, sc);
    }

    if (sc->shortcut()) {
        qWarning() << "registerShortcut: action already registered (id" << id << ".";
        return sc;
    }

    if (!hasContext(context))
        shortcut->setEnabled(false);
    shortcut->setObjectName(id);
    shortcut->setParent(m_mainWnd);
    sc->setShortcut(shortcut);
429
    sc->setScriptable(scriptable);
con's avatar
con committed
430

hjk's avatar
hjk committed
431
    if (context.isEmpty())
432
        sc->setContext(Context(0));
con's avatar
con committed
433 434 435
    else
        sc->setContext(context);

436
    emit commandListChanged();
437
    emit commandAdded(id);
con's avatar
con committed
438 439 440
    return sc;
}

hjk's avatar
hjk committed
441
Command *ActionManagerPrivate::command(const Id &id) const
con's avatar
con committed
442
{
443
    const int uid = UniqueIDManager::instance()->uniqueIdentifier(id);
con's avatar
con committed
444 445 446
    const IdCmdMap::const_iterator it = m_idCmdMap.constFind(uid);
    if (it == m_idCmdMap.constEnd()) {
        if (warnAboutFindFailures)
447
            qWarning() << "ActionManagerPrivate::command(): failed to find :" << id << '/' << uid;
con's avatar
con committed
448 449 450 451 452
        return 0;
    }
    return it.value();
}

hjk's avatar
hjk committed
453
ActionContainer *ActionManagerPrivate::actionContainer(const Id &id) const
con's avatar
con committed
454
{
455 456 457
    const int uid = UniqueIDManager::instance()->uniqueIdentifier(id);
    const IdContainerMap::const_iterator it = m_idContainerMap.constFind(uid);
    if (it == m_idContainerMap.constEnd()) {
con's avatar
con committed
458
        if (warnAboutFindFailures)
459
            qWarning() << "ActionManagerPrivate::actionContainer(): failed to find :" << id << '/' << uid;
con's avatar
con committed
460 461 462 463 464
        return 0;
    }
    return it.value();
}

con's avatar
con committed
465
Command *ActionManagerPrivate::command(int uid) const
con's avatar
con committed
466 467 468 469
{
    const IdCmdMap::const_iterator it = m_idCmdMap.constFind(uid);
    if (it == m_idCmdMap.constEnd()) {
        if (warnAboutFindFailures)
470
            qWarning() << "ActionManagerPrivate::command(): failed to find :" <<  UniqueIDManager::instance()->stringForUniqueIdentifier(uid) << '/' << uid;
con's avatar
con committed
471 472 473 474 475
        return 0;
    }
    return it.value();
}

476
ActionContainer *ActionManagerPrivate::actionContainer(int uid) const
con's avatar
con committed
477 478 479 480
{
    const IdContainerMap::const_iterator it = m_idContainerMap.constFind(uid);
    if (it == m_idContainerMap.constEnd()) {
        if (warnAboutFindFailures)
481
            qWarning() << "ActionManagerPrivate::actionContainer(): failed to find :" << UniqueIDManager::instance()->stringForUniqueIdentifier(uid) << uid;
con's avatar
con committed
482 483 484 485
        return 0;
    }
    return it.value();
}
hjk's avatar
hjk committed
486

Friedemann Kleint's avatar
Friedemann Kleint committed
487 488 489
static const char settingsGroup[] = "KeyBindings";
static const char idKey[] = "ID";
static const char sequenceKey[] = "Keysequence";
con's avatar
con committed
490

491
void ActionManagerPrivate::initialize()
con's avatar
con committed
492
{
493
    QSettings *settings = Core::ICore::instance()->settings();
con's avatar
con committed
494 495 496 497 498
    const int shortcuts = settings->beginReadArray(QLatin1String(settingsGroup));
    for (int i=0; i<shortcuts; ++i) {
        settings->setArrayIndex(i);
        const QString sid = settings->value(QLatin1String(idKey)).toString();
        const QKeySequence key(settings->value(QLatin1String(sequenceKey)).toString());
499
        const int id = UniqueIDManager::instance()->uniqueIdentifier(sid);
con's avatar
con committed
500

con's avatar
con committed
501
        Command *cmd = command(id);
con's avatar
con committed
502 503 504 505 506 507
        if (cmd)
            cmd->setKeySequence(key);
    }
    settings->endArray();
}

508
void ActionManagerPrivate::saveSettings(QSettings *settings)
con's avatar
con committed
509 510 511 512 513 514 515
{
    settings->beginWriteArray(QLatin1String(settingsGroup));
    int count = 0;

    const IdCmdMap::const_iterator cmdcend = m_idCmdMap.constEnd();
    for (IdCmdMap::const_iterator j = m_idCmdMap.constBegin(); j != cmdcend; ++j) {
        const int id = j.key();
con's avatar
con committed
516
        CommandPrivate *cmd = j.value();
con's avatar
con committed
517 518
        QKeySequence key = cmd->keySequence();
        if (key != cmd->defaultKeySequence()) {
519
            const QString sid = UniqueIDManager::instance()->stringForUniqueIdentifier(id);
con's avatar
con committed
520 521 522 523 524 525 526 527 528
            settings->setArrayIndex(count);
            settings->setValue(QLatin1String(idKey), sid);
            settings->setValue(QLatin1String(sequenceKey), key.toString());
            count++;
        }
    }

    settings->endArray();
}
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545

void ActionManagerPrivate::unregisterShortcut(const Core::Id &id)
{
    Shortcut *sc = 0;
    const int uid = UniqueIDManager::instance()->uniqueIdentifier(id);
    CommandPrivate *c = m_idCmdMap.value(uid, 0);
    QTC_ASSERT(c, return);
    sc = qobject_cast<Shortcut *>(c);
    if (!sc) {
        qWarning() << "unregisterShortcut: id" << id << "is registered with a different command type.";
        return;
    }
    delete sc->shortcut();
    m_idCmdMap.remove(uid);
    delete sc;
    emit commandListChanged();
}