actionmanager.cpp 18.3 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
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
con's avatar
con committed
9
** No Commercial Usage
10
**
con's avatar
con committed
11 12 13 14
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
15
**
16
** GNU Lesser General Public License Usage
17
**
18 19 20 21 22 23
** Alternatively, 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.
24
**
con's avatar
con committed
25 26 27 28 29 30
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
con's avatar
con committed
31
**
32
**************************************************************************/
hjk's avatar
hjk committed
33

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

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

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

namespace {
    enum { warnAboutFindFailures = 0 };
}

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

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

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

    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).

73 74 75
    \section1 Contexts

    All actions that are registered with the same string ID (but different context lists)
76
    are considered to be overloads of the same command, represented by an instance
con's avatar
con committed
77
    of the Command class.
78 79 80 81 82 83 84 85 86 87 88 89
    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
90
    The action that is visible to the user is the one returned by Command::action().
91
    If you provide yourself a user visible representation of your action you need
con's avatar
con committed
92
    to use Command::action() for this.
93 94 95
    When this action is invoked by the user,
    the signal is forwarded to the registered action that is valid for the current context.

96 97 98
    \section1 Registering Actions

    To register a globally active action "My Action"
99 100
    put the following in your plugin's IPlugin::initialize method:
    \code
101
        Core::ActionManager *am = Core::ICore::instance()->actionManager();
102
        QAction *myAction = new QAction(tr("My Action"), this);
con's avatar
con committed
103
        Core::Command *cmd = am->registerAction(myAction,
104
                                                 "myplugin.myaction",
105
                                                 Core::Context(C_GLOBAL));
106 107 108 109 110 111
        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
112
    from Command::action() to it:
113 114 115 116 117 118 119 120
    \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
121
    registerAction methods, get the action container for a specific ID (like specified in
122
    the Core::Constants namespace) with a call of
con's avatar
con committed
123 124
    actionContainer(const QString&) and add your command to this container.

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

130
    \section1 Important Guidelines:
con's avatar
con committed
131 132
    \list
    \o Always register your actions and shortcuts!
133 134
    \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
135
       in the keyboard settings dialog from the beginning.
136 137 138
    \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
139 140 141
    \o Use this class to add actions to the applications menus
    \endlist

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

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

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

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

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

/*!
169
    \fn Command *ActionManager::registerAction(QAction *action, const QString &id, const Context &context, bool scriptable)
170 171 172 173 174 175 176
    \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.
177 178
    A scriptable action can be called from a script without the need for the user
    to interact with it.
con's avatar
con committed
179 180 181
*/

/*!
182
    \fn Command *ActionManager::registerShortcut(QShortcut *shortcut, const QString &id, const Context &context, bool scriptable)
183 184 185 186 187 188 189
    \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.
190 191
    A scriptable shortcut can be called from a script without the need for the user
    to interact with it.
con's avatar
con committed
192 193 194
*/

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

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

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

207 208 209
    \sa ActionManager::createMenu()
    \sa ActionManager::createMenuBar()
*/
210 211 212 213 214 215 216 217 218 219 220

/*!
    \fn Command *ActionManager::unregisterAction(QAction *action, const QString &id)
    \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
221
/*!
222 223
    \fn ActionManager::ActionManager(QObject *parent)
    \internal
con's avatar
con committed
224 225
*/
/*!
226 227
    \fn ActionManager::~ActionManager()
    \internal
con's avatar
con committed
228 229 230 231 232
*/

using namespace Core;
using namespace Core::Internal;

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

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

241 242
ActionManagerPrivate::ActionManagerPrivate(MainWindow *mainWnd)
  : ActionManager(mainWnd),
con's avatar
con committed
243 244
    m_mainWnd(mainWnd)
{
245
    UniqueIDManager *uidmgr = UniqueIDManager::instance();
con's avatar
con committed
246 247 248 249 250 251 252
    m_defaultGroups << uidmgr->uniqueIdentifier(Constants::G_DEFAULT_ONE);
    m_defaultGroups << uidmgr->uniqueIdentifier(Constants::G_DEFAULT_TWO);
    m_defaultGroups << uidmgr->uniqueIdentifier(Constants::G_DEFAULT_THREE);
    m_instance = this;

}

253
ActionManagerPrivate::~ActionManagerPrivate()
con's avatar
con committed
254 255 256 257 258
{
    qDeleteAll(m_idCmdMap.values());
    qDeleteAll(m_idContainerMap.values());
}

259
ActionManagerPrivate *ActionManagerPrivate::instance()
con's avatar
con committed
260 261 262 263
{
    return m_instance;
}

264
QList<int> ActionManagerPrivate::defaultGroups() const
con's avatar
con committed
265 266 267 268
{
    return m_defaultGroups;
}

269
QList<Command *> ActionManagerPrivate::commands() const
con's avatar
con committed
270
{
271 272
    // transform list of CommandPrivate into list of Command
    QList<Command *> result;
hjk's avatar
hjk committed
273
    foreach (Command *cmd, m_idCmdMap.values())
274 275
        result << cmd;
    return result;
con's avatar
con committed
276 277
}

278
bool ActionManagerPrivate::hasContext(int context) const
con's avatar
con committed
279
{
hjk's avatar
hjk committed
280
    return m_context.contains(context);
con's avatar
con committed
281 282
}

283 284 285 286 287 288 289 290 291
QDebug operator<<(QDebug in, const Context &context)
{
    UniqueIDManager *uidm = UniqueIDManager::instance();
    in << "CONTEXT: ";
    foreach (int c, context)
        in << "   " << c << uidm->stringForUniqueIdentifier(c);
    return in;
}

292
void ActionManagerPrivate::setContext(const Context &context)
con's avatar
con committed
293 294 295 296 297 298 299 300 301 302
{
    // 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);
}

303
bool ActionManagerPrivate::hasContext(const Context &context) const
con's avatar
con committed
304
{
hjk's avatar
hjk committed
305 306
    for (int i = 0; i < m_context.size(); ++i) {
        if (context.contains(m_context.at(i)))
con's avatar
con committed
307 308 309 310 311
            return true;
    }
    return false;
}

hjk's avatar
hjk committed
312
ActionContainer *ActionManagerPrivate::createMenu(const Id &id)
con's avatar
con committed
313
{
314
    const int uid = UniqueIDManager::instance()->uniqueIdentifier(id);
con's avatar
con committed
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
    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);

    return mc;
}

hjk's avatar
hjk committed
330
ActionContainer *ActionManagerPrivate::createMenuBar(const Id &id)
con's avatar
con committed
331
{
332
    const int uid = UniqueIDManager::instance()->uniqueIdentifier(id);
con's avatar
con committed
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
    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);

    return mbc;
}

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);
con's avatar
con committed
375 376 377 378 379
    }

    return a;
}

380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
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) {
        qWarning() << "registerAction: id" << id << "is registered with a different command type.";
        return;
    }
    a->removeOverrideAction(action);
    if (a->isEmpty()) {
        // clean up
        m_mainWnd->removeAction(a->action());
        delete a->action();
        m_idCmdMap.remove(uid);
        delete a;
    }
    emit commandListChanged();
}

402
Command *ActionManagerPrivate::registerShortcut(QShortcut *shortcut, const Id &id, const Context &context, bool scriptable)
con's avatar
con committed
403 404
{
    Shortcut *sc = 0;
405
    int uid = UniqueIDManager::instance()->uniqueIdentifier(id);
con's avatar
con committed
406
    if (CommandPrivate *c = m_idCmdMap.value(uid, 0)) {
con's avatar
con committed
407 408
        sc = qobject_cast<Shortcut *>(c);
        if (!sc) {
con's avatar
con committed
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
            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);
427
    sc->setScriptable(scriptable);
con's avatar
con committed
428

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

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

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

hjk's avatar
hjk committed
451
ActionContainer *ActionManagerPrivate::actionContainer(const Id &id) const
con's avatar
con committed
452
{
453 454 455
    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
456
        if (warnAboutFindFailures)
457
            qWarning() << "ActionManagerPrivate::actionContainer(): failed to find :" << id << '/' << uid;
con's avatar
con committed
458 459 460 461 462
        return 0;
    }
    return it.value();
}

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

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

con's avatar
con committed
485 486 487 488
static const char *settingsGroup = "KeyBindings";
static const char *idKey = "ID";
static const char *sequenceKey = "Keysequence";

489
void ActionManagerPrivate::initialize()
con's avatar
con committed
490
{
491
    QSettings *settings = Core::ICore::instance()->settings();
con's avatar
con committed
492 493 494 495 496
    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());
497
        const int id = UniqueIDManager::instance()->uniqueIdentifier(sid);
con's avatar
con committed
498

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

506
void ActionManagerPrivate::saveSettings(QSettings *settings)
con's avatar
con committed
507 508 509 510 511 512 513
{
    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
514
        CommandPrivate *cmd = j.value();
con's avatar
con committed
515 516
        QKeySequence key = cmd->keySequence();
        if (key != cmd->defaultKeySequence()) {
517
            const QString sid = UniqueIDManager::instance()->stringForUniqueIdentifier(id);
con's avatar
con committed
518 519 520 521 522 523 524 525 526
            settings->setArrayIndex(count);
            settings->setValue(QLatin1String(idKey), sid);
            settings->setValue(QLatin1String(sequenceKey), key.toString());
            count++;
        }
    }

    settings->endArray();
}