Commit f5fbbbfd authored by Nicolas Arnaud-Cormos's avatar Nicolas Arnaud-Cormos Committed by Tobias Hunger

New plugin: plugin macro

This plugin let the user record and replay some macros:
 * 3 handlers: action, find and texteditor
 * almost all texteditor and cpp actions recorded
 * options page to set the directories where the macros are stored
 * optional dialog to give a name and comment at the end of a macro
 * locator integration to play a macro
 * shortcuts assignment to macro

The most important change outside the plugin macros is the isScriptable method
for command: All scriptable commands can be stored in a macro.

Merge-request: 2176
Reviewed-by: default avatarTobias Hunger <tobias.hunger@nokia.com>
parent 11e21e16
......@@ -166,7 +166,7 @@ namespace {
*/
/*!
\fn Command *ActionManager::registerAction(QAction *action, const QString &id, const Context &context)
\fn Command *ActionManager::registerAction(QAction *action, const QString &id, const Context &context, bool scriptable)
\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
......@@ -174,10 +174,12 @@ namespace {
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.
A scriptable action can be called from a script without the need for the user
to interact with it.
*/
/*!
\fn Command *ActionManager::registerShortcut(QShortcut *shortcut, const QString &id, const Context &context)
\fn Command *ActionManager::registerShortcut(QShortcut *shortcut, const QString &id, const Context &context, bool scriptable)
\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
......@@ -185,6 +187,8 @@ namespace {
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.
A scriptable shortcut can be called from a script without the need for the user
to interact with it.
*/
/*!
......@@ -341,14 +345,15 @@ ActionContainer *ActionManagerPrivate::createMenuBar(const Id &id)
return mbc;
}
Command *ActionManagerPrivate::registerAction(QAction *action, const Id &id, const Context &context)
Command *ActionManagerPrivate::registerAction(QAction *action, const Id &id, const Context &context, bool scriptable)
{
Action *a = 0;
Command *c = registerOverridableAction(action, id, false);
a = static_cast<Action *>(c);
if (a)
a->addOverrideAction(action, context);
a->addOverrideAction(action, context, scriptable);
emit commandListChanged();
emit commandAdded(id);
return a;
}
......@@ -417,7 +422,7 @@ void ActionManagerPrivate::unregisterAction(QAction *action, const Id &id)
emit commandListChanged();
}
Command *ActionManagerPrivate::registerShortcut(QShortcut *shortcut, const Id &id, const Context &context)
Command *ActionManagerPrivate::registerShortcut(QShortcut *shortcut, const Id &id, const Context &context, bool scriptable)
{
Shortcut *sc = 0;
int uid = UniqueIDManager::instance()->uniqueIdentifier(id);
......@@ -442,6 +447,7 @@ Command *ActionManagerPrivate::registerShortcut(QShortcut *shortcut, const Id &i
shortcut->setObjectName(id);
shortcut->setParent(m_mainWnd);
sc->setShortcut(shortcut);
sc->setScriptable(scriptable);
if (context.isEmpty())
sc->setContext(Context(0));
......@@ -449,6 +455,7 @@ Command *ActionManagerPrivate::registerShortcut(QShortcut *shortcut, const Id &i
sc->setContext(context);
emit commandListChanged();
emit commandAdded(id);
return sc;
}
......
......@@ -62,8 +62,8 @@ public:
virtual ActionContainer *createMenu(const Id &id) = 0;
virtual ActionContainer *createMenuBar(const Id &id) = 0;
virtual Command *registerAction(QAction *action, const Id &id, const Context &context) = 0;
virtual Command *registerShortcut(QShortcut *shortcut, const Id &id, const Context &context) = 0;
virtual Command *registerAction(QAction *action, const Id &id, const Context &context, bool scriptable = false) = 0;
virtual Command *registerShortcut(QShortcut *shortcut, const Id &id, const Context &context, bool scriptable = false) = 0;
virtual Command *command(const Id &id) const = 0;
virtual ActionContainer *actionContainer(const Id &id) const = 0;
......@@ -74,6 +74,7 @@ public:
signals:
void commandListChanged();
void commandAdded(const QString &id);
};
} // namespace Core
......
......@@ -89,9 +89,9 @@ public:
ActionContainer *createMenuBar(const Id &id);
Command *registerAction(QAction *action, const Id &id,
const Context &context);
const Context &context, bool scriptable=false);
Command *registerShortcut(QShortcut *shortcut, const Id &id,
const Context &context);
const Context &context, bool scriptable=false);
Core::Command *command(const Id &id) const;
Core::ActionContainer *actionContainer(const Id &id) const;
......
......@@ -191,6 +191,19 @@
context.
*/
/*!
\fn bool Command::isScriptable() const
Returns if the Command is scriptable. A scriptable command can be called
from a script without the need for the user to interact with it.
*/
/*!
\fn bool Command::isScriptable(const Context &) const
Returns if the Command is scriptable for the given context.
A scriptable command can be called from a script without the need for the user to
interact with it.
*/
/*!
\fn Command::~Command()
\internal
......@@ -285,7 +298,7 @@ QString CommandPrivate::stringWithAppendedShortcut(const QString &str) const
*/
Shortcut::Shortcut(int id)
: CommandPrivate(id), m_shortcut(0)
: CommandPrivate(id), m_shortcut(0), m_scriptable(false)
{
}
......@@ -363,6 +376,21 @@ bool Shortcut::isActive() const
return m_shortcut->isEnabled();
}
bool Shortcut::isScriptable() const
{
return m_scriptable;
}
bool Shortcut::isScriptable(const Core::Context &) const
{
return m_scriptable;
}
void Shortcut::setScriptable(bool value)
{
m_scriptable = value;
}
// ---------- Action ------------
/*!
......@@ -485,7 +513,7 @@ static inline QString msgActionWarning(QAction *newAction, int k, QAction *oldAc
return msg;
}
void Action::addOverrideAction(QAction *action, const Core::Context &context)
void Action::addOverrideAction(QAction *action, const Core::Context &context, bool scriptable)
{
if (context.isEmpty()) {
m_contextActionMap.insert(0, action);
......@@ -497,6 +525,7 @@ void Action::addOverrideAction(QAction *action, const Core::Context &context)
m_contextActionMap.insert(k, action);
}
}
m_scriptableMap[action] = scriptable;
}
void Action::removeOverrideAction(QAction *action)
......@@ -558,3 +587,21 @@ bool Action::isEmpty() const
return m_contextActionMap.isEmpty();
}
bool Action::isScriptable() const
{
return m_scriptableMap.values().contains(true);
}
bool Action::isScriptable(const Core::Context &context) const
{
if (context == m_context && m_scriptableMap.contains(m_currentAction))
return m_scriptableMap.value(m_currentAction);
for (int i = 0; i < context.size(); ++i) {
if (QAction *a = m_contextActionMap.value(context.at(i), 0)) {
if (m_scriptableMap.contains(a) && m_scriptableMap.value(a))
return true;
}
}
return false;
}
......@@ -85,6 +85,9 @@ public:
virtual QString stringWithAppendedShortcut(const QString &str) const = 0;
virtual bool isScriptable() const = 0;
virtual bool isScriptable(const Context &) const = 0;
signals:
void keySequenceChanged();
void activeStateChanged();
......
......@@ -40,6 +40,7 @@
#include <QtCore/QList>
#include <QtCore/QMultiMap>
#include <QtCore/QPointer>
#include <QtCore/QMap>
#include <QtGui/QKeySequence>
namespace Core {
......@@ -109,9 +110,15 @@ public:
bool setCurrentContext(const Context &context);
bool isActive() const;
bool isScriptable() const;
bool isScriptable(const Context &) const;
void setScriptable(bool value);
private:
QShortcut *m_shortcut;
QString m_defaultText;
bool m_scriptable;
};
class Action : public CommandPrivate
......@@ -133,10 +140,13 @@ public:
bool setCurrentContext(const Context &context);
bool isActive() const;
void addOverrideAction(QAction *action, const Context &context);
void addOverrideAction(QAction *action, const Context &context, bool scriptable);
void removeOverrideAction(QAction *action);
bool isEmpty() const;
bool isScriptable() const;
bool isScriptable(const Context &context) const;
protected:
void updateToolTipWithKeySequence();
......@@ -152,6 +162,7 @@ private:
QPointer<QAction> m_currentAction;
QMap<int, QPointer<QAction> > m_contextActionMap;
QMap<QAction*, bool> m_scriptableMap;
bool m_active;
bool m_contextInitialized;
};
......
......@@ -311,7 +311,7 @@ EditorManager::EditorManager(ICore *core, QWidget *parent) :
mwindow->addAction(cmd, Constants::G_WINDOW_NAVIGATE);
// Close Action
cmd = am->registerAction(m_d->m_closeCurrentEditorAction, Constants::CLOSE, editManagerContext);
cmd = am->registerAction(m_d->m_closeCurrentEditorAction, Constants::CLOSE, editManagerContext, true);
cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+W")));
cmd->setAttribute(Core::Command::CA_UpdateText);
cmd->setDefaultText(m_d->m_closeCurrentEditorAction->text());
......@@ -328,13 +328,13 @@ EditorManager::EditorManager(ICore *core, QWidget *parent) :
#endif
// Close All Action
cmd = am->registerAction(m_d->m_closeAllEditorsAction, Constants::CLOSEALL, editManagerContext);
cmd = am->registerAction(m_d->m_closeAllEditorsAction, Constants::CLOSEALL, editManagerContext, true);
cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Shift+W")));
mfile->addAction(cmd, Constants::G_FILE_CLOSE);
connect(m_d->m_closeAllEditorsAction, SIGNAL(triggered()), this, SLOT(closeAllEditors()));
// Close All Others Action
cmd = am->registerAction(m_d->m_closeOtherEditorsAction, Constants::CLOSEOTHERS, editManagerContext);
cmd = am->registerAction(m_d->m_closeOtherEditorsAction, Constants::CLOSEOTHERS, editManagerContext, true);
mfile->addAction(cmd, Constants::G_FILE_CLOSE);
cmd->setAttribute(Core::Command::CA_UpdateText);
connect(m_d->m_closeOtherEditorsAction, SIGNAL(triggered()), this, SLOT(closeOtherEditors()));
......
......@@ -254,7 +254,7 @@ bool CppPlugin::initialize(const QStringList & /*arguments*/, QString *errorMess
QAction *jumpToDefinition = new QAction(tr("Follow Symbol Under Cursor"), this);
cmd = am->registerAction(jumpToDefinition,
Constants::JUMP_TO_DEFINITION, context);
Constants::JUMP_TO_DEFINITION, context, true);
cmd->setDefaultKeySequence(QKeySequence(Qt::Key_F2));
connect(jumpToDefinition, SIGNAL(triggered()),
this, SLOT(jumpToDefinition()));
......@@ -263,7 +263,7 @@ bool CppPlugin::initialize(const QStringList & /*arguments*/, QString *errorMess
QAction *switchDeclarationDefinition = new QAction(tr("Switch Between Method Declaration/Definition"), this);
cmd = am->registerAction(switchDeclarationDefinition,
Constants::SWITCH_DECLARATION_DEFINITION, context);
Constants::SWITCH_DECLARATION_DEFINITION, context, true);
cmd->setDefaultKeySequence(QKeySequence("Shift+F2"));
connect(switchDeclarationDefinition, SIGNAL(triggered()),
this, SLOT(switchDeclarationDefinition()));
......
......@@ -137,7 +137,7 @@ bool CppToolsPlugin::initialize(const QStringList &arguments, QString *error)
Core::Context context(CppEditor::Constants::C_CPPEDITOR);
QAction *switchAction = new QAction(tr("Switch Header/Source"), this);
Core::Command *command = am->registerAction(switchAction, Constants::SWITCH_HEADER_SOURCE, context);
Core::Command *command = am->registerAction(switchAction, Constants::SWITCH_HEADER_SOURCE, context, true);
command->setDefaultKeySequence(QKeySequence(Qt::Key_F4));
mcpptools->addAction(command);
connect(switchAction, SIGNAL(triggered()), this, SLOT(switchHeaderSource()));
......
<plugin name=\"Macros\" version=\"$$QTCREATOR_VERSION\" compatVersion=\"$$QTCREATOR_VERSION\">
<vendor>Nokia Corporation</vendor>
<copyright>(C) 2010 Nicolas Arnaud-Cormos</copyright>
<license>
Commercial Usage
Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Nokia.
GNU Lesser General Public License Usage
Alternatively, this plugin may be used under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation. 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.
</license>
<category>Qt Creator</category>
<description>Macros in text editors.</description>
<url>http://qt.nokia.com</url>
<dependencyList>
<dependency name=\"Core\" version=\"$$QTCREATOR_VERSION\"/>
<dependency name=\"Locator\" version=\"$$QTCREATOR_VERSION\"/>
<dependency name=\"Find\" version=\"$$QTCREATOR_VERSION\"/>
<dependency name=\"TextEditor\" version=\"$$QTCREATOR_VERSION\"/>
</dependencyList>
</plugin>
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nicolas Arnaud-Cormos.
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "actionmacrohandler.h"
#include "macroevent.h"
#include "macro.h"
#include <texteditor/texteditorconstants.h>
#include <coreplugin/icore.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/uniqueidmanager.h>
#include <coreplugin/icontext.h>
#include <QObject>
#include <QEvent>
#include <QAction>
#include <QSignalMapper>
#include <QShortcut>
#include <QtAlgorithms>
#include <QStringList>
using namespace Macros;
using namespace Macros::Internal;
static const char EVENTNAME[] = "Action";
static quint8 ACTIONNAME = 0;
ActionMacroHandler::ActionMacroHandler():
m_mapper(new QSignalMapper(this))
{
connect(m_mapper, SIGNAL(mapped(const QString &)),
this, SLOT(addActionEvent(const QString &)));
const Core::ICore *core = Core::ICore::instance();
const Core::ActionManager *am = core->actionManager();
connect(am, SIGNAL(commandAdded(QString)),
this, SLOT(addCommand(QString)));
// Register all existing scriptable actions
QList<Core::Command *> commands = am->commands();
foreach (Core::Command *command, commands) {
if (command->isScriptable()) {
QString id = Core::UniqueIDManager::instance()->stringForUniqueIdentifier(command->id());
registerCommand(id);
}
}
}
bool ActionMacroHandler::canExecuteEvent(const MacroEvent &macroEvent)
{
return (macroEvent.id() == EVENTNAME);
}
bool ActionMacroHandler::executeEvent(const MacroEvent &macroEvent)
{
const Core::ICore *core = Core::ICore::instance();
const Core::ActionManager *am = core->actionManager();
QAction *action = am->command(macroEvent.value(ACTIONNAME).toString())->action();
if (!action)
return false;
action->trigger();
return true;
}
void ActionMacroHandler::addActionEvent(const QString &id)
{
if (!isRecording())
return;
const Core::ICore *core = Core::ICore::instance();
const Core::ActionManager *am = core->actionManager();
const Core::Command *cmd = am->command(id);
if (cmd->isScriptable(cmd->context())) {
MacroEvent e;
e.setId(EVENTNAME);
e.setValue(ACTIONNAME, id);
addMacroEvent(e);
}
}
void ActionMacroHandler::registerCommand(const QString &id)
{
if (!m_commandIds.contains(id)) {
m_commandIds.insert(id);
const Core::ICore *core = Core::ICore::instance();
const Core::ActionManager *am = core->actionManager();
QAction* action = am->command(id)->action();
if (action) {
connect(action, SIGNAL(triggered()), m_mapper, SLOT(map()));
m_mapper->setMapping(action, id);
return;
}
QShortcut* shortcut = am->command(id)->shortcut();
if (shortcut) {
connect(shortcut, SIGNAL(activated()), m_mapper, SLOT(map()));
m_mapper->setMapping(shortcut, id);
}
}
}
void ActionMacroHandler::addCommand(const QString &id)
{
const Core::ICore *core = Core::ICore::instance();
const Core::ActionManager *am = core->actionManager();
if (am->command(id)->isScriptable())
registerCommand(id);
}
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nicolas Arnaud-Cormos.
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef MACROSPLUGIN_ACTIONEVENTHANDLER_H
#define MACROSPLUGIN_ACTIONEVENTHANDLER_H
#include "imacrohandler.h"
#include <QSet>
class QAction;
class QSignalMapper;
namespace Macros {
namespace Internal {
class ActionMacroHandler : public IMacroHandler
{
Q_OBJECT
public:
ActionMacroHandler();
bool canExecuteEvent(const MacroEvent &macroEvent);
bool executeEvent(const MacroEvent &macroEvent);
private:
void registerCommand(const QString &id);
private slots:
void addCommand(const QString &id);
void addActionEvent(const QString &id);
private:
QSet<QString> m_commandIds;
QSignalMapper *m_mapper;
};
} // namespace Internal
} // namespace Macros
#endif // MACROSPLUGIN_ACTIONEVENTHANDLER_H
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nicolas Arnaud-Cormos.
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "findmacrohandler.h"
#include "macroevent.h"
#include "macro.h"
#include "macrotextfind.h"
#include <find/ifindsupport.h>
#include <coreplugin/icore.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
#include <aggregation/aggregate.h>
using namespace Macros;
using namespace Macros::Internal;
static const char EVENTNAME[] = "Find";
static const quint8 TYPE = 0;
static const quint8 BEFORE = 1;
static const quint8 AFTER = 2;
static const quint8 FLAGS = 3;
static const quint8 FINDINCREMENTAL = 0;
static const quint8 FINDSTEP = 1;
static const quint8 REPLACE = 2;
static const quint8 REPLACESTEP = 3;
static const quint8 REPLACEALL = 4;
static const quint8 RESET = 5;
FindMacroHandler::FindMacroHandler():
IMacroHandler()
{
const Core::EditorManager *editorManager = Core::EditorManager::instance();
connect(editorManager, SIGNAL(currentEditorChanged(Core::IEditor*)),
this, SLOT(changeEditor(Core::IEditor*)));
}
bool FindMacroHandler::canExecuteEvent(const MacroEvent &macroEvent)
{
return (macroEvent.id() == EVENTNAME);
}
bool FindMacroHandler::executeEvent(const MacroEvent &macroEvent)
{
Core::IEditor *editor = Core::EditorManager::instance()->currentEditor();
if (!editor)