kdelibs/kdeui/actions/kactioncollection.cpp

762 lines
23 KiB
C++
Raw Normal View History

2014-11-13 01:04:59 +02:00
/* This file is part of the KDE libraries
Copyright (C) 1999 Reginald Stadlbauer <reggie@kde.org>
(C) 1999 Simon Hausmann <hausmann@kde.org>
(C) 2000 Nicolas Hadacek <haadcek@kde.org>
(C) 2000 Kurt Granroth <granroth@kde.org>
(C) 2000 Michael Koch <koch@kde.org>
(C) 2001 Holger Freyther <freyther@kde.org>
(C) 2002 Ellis Whitehead <ellis@kde.org>
(C) 2002 Joseph Wenninger <jowenn@kde.org>
(C) 2005-2007 Hamish Rodda <rodda@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kactioncollection.h"
#include "kactioncategory.h"
#include "kxmlguiclient.h"
#include "kxmlguifactory.h"
#include "kdebug.h"
#include "kglobal.h"
#include "kaction.h"
#include "kaction_p.h"
#include <QtXml/qdom.h>
2014-11-13 01:04:59 +02:00
#include <QtCore/QSet>
#include <QtCore/QMap>
#include <QtCore/QList>
#include <QtGui/QAction>
#include <stdio.h>
#include "kcomponentdata.h"
#include "kconfiggroup.h"
class KActionCollectionPrivate
{
public:
KActionCollectionPrivate()
: m_parentGUIClient(0L),
configGroup("Shortcuts"),
configIsGlobal(false),
connectTriggered(false),
connectHovered(false),
q(0)
{
}
void setComponentForAction(KAction *kaction)
{ kaction->d->maybeSetComponentData(m_componentData); }
static QList<KActionCollection*> s_allCollections;
void _k_associatedWidgetDestroyed(QObject *obj);
void _k_actionDestroyed(QObject *obj);
bool writeKXMLGUIConfigFile();
KComponentData m_componentData;
//! Remove a action from our internal bookkeeping. Returns NULL if the
//! action doesn't belong to us.
QAction *unlistAction(QAction*);
QMap<QString, QAction*> actionByName;
QList<QAction*> actions;
const KXMLGUIClient *m_parentGUIClient;
QString configGroup;
bool configIsGlobal : 1;
bool connectTriggered : 1;
bool connectHovered : 1;
KActionCollection *q;
QList<QWidget*> associatedWidgets;
};
QList<KActionCollection*> KActionCollectionPrivate::s_allCollections;
KActionCollection::KActionCollection(QObject *parent, const KComponentData &cData)
: QObject( parent )
, d(new KActionCollectionPrivate)
{
d->q = this;
KActionCollectionPrivate::s_allCollections.append(this);
setComponentData(cData);
}
KActionCollection::KActionCollection( const KXMLGUIClient *parent )
: QObject( 0 )
, d(new KActionCollectionPrivate)
{
d->q = this;
KActionCollectionPrivate::s_allCollections.append(this);
d->m_parentGUIClient=parent;
d->m_componentData = parent->componentData();
}
KActionCollection::~KActionCollection()
{
KActionCollectionPrivate::s_allCollections.removeAll(this);
delete d;
}
void KActionCollection::clear()
{
d->actionByName.clear();
qDeleteAll(d->actions);
d->actions.clear();
}
QAction* KActionCollection::action(const QString &name) const
2014-11-13 01:04:59 +02:00
{
QAction* action = nullptr;
if (!name.isEmpty()) {
action = d->actionByName.value (name);
}
return action;
2014-11-13 01:04:59 +02:00
}
QAction* KActionCollection::action(int index) const
2014-11-13 01:04:59 +02:00
{
// ### investigate if any apps use this at all
return actions().value(index);
2014-11-13 01:04:59 +02:00
}
int KActionCollection::count() const
{
return d->actions.count();
2014-11-13 01:04:59 +02:00
}
bool KActionCollection::isEmpty() const
{
return count() == 0;
2014-11-13 01:04:59 +02:00
}
void KActionCollection::setComponentData(const KComponentData &cData)
{
if (count() > 0) {
// Its component name is part of an action's signature in the context of
// global shortcuts and the semantics of changing an existing action's
// signature are, as it seems, impossible to get right.
// As of now this only matters for global shortcuts. We could
// thus relax the requirement and only refuse to change the component data
// if we have actions with global shortcuts in this collection.
kWarning(129) << "this does not work on a KActionCollection containing actions!";
}
if (cData.isValid()) {
d->m_componentData = cData;
} else {
d->m_componentData = KGlobal::mainComponent();
}
}
KComponentData KActionCollection::componentData() const
{
return d->m_componentData;
}
const KXMLGUIClient *KActionCollection::parentGUIClient() const
{
return d->m_parentGUIClient;
2014-11-13 01:04:59 +02:00
}
QList<QAction*> KActionCollection::actions() const
{
return d->actions;
2014-11-13 01:04:59 +02:00
}
const QList< QAction* > KActionCollection::actionsWithoutGroup( ) const
{
QList<QAction*> ret;
foreach (QAction* action, d->actions)
if (!action->actionGroup())
ret.append(action);
return ret;
}
const QList< QActionGroup * > KActionCollection::actionGroups( ) const
{
QSet<QActionGroup*> set;
foreach (QAction* action, d->actions)
if (action->actionGroup())
set.insert(action->actionGroup());
return set.toList();
}
KAction *KActionCollection::addAction(const QString &name, KAction *action)
{
QAction* ret = addAction(name, static_cast<QAction*>(action));
Q_ASSERT(ret == action);
Q_UNUSED(ret); // fix compiler warning in release mode
return action;
}
QAction *KActionCollection::addAction(const QString &name, QAction *action)
{
if (!action)
return action;
const QString objectName = action->objectName();
QString indexName = name;
if (indexName.isEmpty()) {
// No name provided. Use the objectName.
indexName = objectName;
} else {
// A name was provided. Check against objectName.
if ((!objectName.isEmpty()) && (objectName != indexName)) {
// The user specified a new name and the action already has a
// different one. The objectName is used for saving shortcut
// settings to disk. Both for local and global shortcuts.
KAction *kaction = qobject_cast<KAction*>(action);
kDebug(125) << "Registering action " << objectName << " under new name " << indexName;
// If there is a global shortcuts it's a very bad idea.
if (kaction && kaction->isGlobalShortcutEnabled()) {
// In debug mode assert
Q_ASSERT(!kaction->isGlobalShortcutEnabled());
// In release mode keep the old name
kError() << "Changing action name from " << objectName << " to " << indexName << "\nignored because of active global shortcut.";
indexName = objectName;
}
}
// Set the new name
action->setObjectName(indexName);
}
// No name provided and the action had no name. Make one up. This will not
// work when trying to save shortcuts. Both local and global shortcuts.
if( indexName.isEmpty() ) {
indexName = indexName.sprintf("unnamed-%p", (void*)action);
action->setObjectName(indexName);
}
// From now on the objectName has to have a value. Else we cannot safely
// remove actions.
Q_ASSERT(!action->objectName().isEmpty());
// look if we already have THIS action under THIS name ;)
if (d->actionByName.value(indexName, 0) == action ) {
// This is not a multi map!
Q_ASSERT( d->actionByName.count(indexName)==1);
return action;
}
// Check if we have another action under this name
if (QAction *oldAction = d->actionByName.value(indexName)) {
takeAction(oldAction);
}
// Check if we have this action under a different name.
// Not using takeAction because we don't want to remove it from categories,
// and because it has the new name already.
const int oldIndex = d->actions.indexOf(action);
if (oldIndex != -1) {
d->actionByName.remove(d->actionByName.key(action));
d->actions.removeAt(oldIndex);
}
// Add action to our lists.
d->actionByName.insert(indexName, action);
d->actions.append(action);
foreach (QWidget* widget, d->associatedWidgets) {
widget->addAction(action);
}
connect(action, SIGNAL(destroyed(QObject*)), SLOT(_k_actionDestroyed(QObject*)));
// only our private class is a friend of KAction
if (KAction *kaction = qobject_cast<KAction *>(action)) {
2014-11-13 01:04:59 +02:00
d->setComponentForAction(kaction);
}
if (d->connectHovered)
connect(action, SIGNAL(hovered()), SLOT(slotActionHovered()));
if (d->connectTriggered)
connect(action, SIGNAL(triggered(bool)), SLOT(slotActionTriggered()));
emit inserted( action );
return action;
}
void KActionCollection::removeAction( QAction* action )
{
delete takeAction( action );
}
QAction* KActionCollection::takeAction(QAction *action)
{
if (!d->unlistAction(action))
return NULL;
// Remove the action from all widgets
foreach (QWidget* widget, d->associatedWidgets) {
widget->removeAction(action);
}
action->disconnect(this);
return action;
}
KAction *KActionCollection::addAction(KStandardAction::StandardAction actionType, const QObject *receiver, const char *member)
{
KAction *action = KStandardAction::create(actionType, receiver, member, this);
return action;
}
KAction *KActionCollection::addAction(KStandardAction::StandardAction actionType, const QString &name,
const QObject *receiver, const char *member)
{
// pass 0 as parent, because if the parent is a KActionCollection KStandardAction::create automatically
// adds the action to it under the default name. We would trigger the
// warning about renaming the action then.
KAction *action = KStandardAction::create(actionType, receiver, member, 0);
// Give it a parent for gc.
action->setParent(this);
// Remove the name to get rid of the "rename action" warning above
action->setObjectName(name);
// And now add it with the desired name.
return addAction(name, action);
}
KAction *KActionCollection::addAction(const QString &name, const QObject *receiver, const char *member)
{
KAction *a = new KAction(this);
if (receiver && member)
connect(a, SIGNAL(triggered(bool)), receiver, member);
return addAction(name, a);
}
QString KActionCollection::configGroup( ) const
{
return d->configGroup;
}
void KActionCollection::setConfigGroup( const QString & group )
{
d->configGroup = group;
}
bool KActionCollection::configIsGlobal() const
{
return d->configIsGlobal;
}
void KActionCollection::setConfigGlobal( bool global )
{
d->configIsGlobal = global;
}
void KActionCollection::importGlobalShortcuts( KConfigGroup* config )
{
Q_ASSERT(config);
if( !config || !config->exists()) {
return;
}
for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin();
it != d->actionByName.constEnd(); ++it) {
KAction *kaction = qobject_cast<KAction*>(it.value());
if (!kaction)
continue;
QString actionName = it.key();
if( kaction->isShortcutConfigurable() ) {
QString entry = config->readEntry(actionName, QString());
if( !entry.isEmpty() ) {
kaction->setGlobalShortcut( KShortcut(entry), KAction::ActiveShortcut, KAction::NoAutoloading );
} else {
kaction->setGlobalShortcut( kaction->shortcut(KAction::DefaultShortcut), KAction::ActiveShortcut, KAction::NoAutoloading );
}
}
}
}
void KActionCollection::readSettings( KConfigGroup* config )
{
KConfigGroup cg( KGlobal::config(), configGroup() );
if( !config )
config = &cg;
if( !config->exists()) {
return;
}
for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin();
it != d->actionByName.constEnd(); ++it) {
KAction *kaction = qobject_cast<KAction*>(it.value());
if (!kaction)
continue;
if( kaction->isShortcutConfigurable() ) {
QString actionName = it.key();
QString entry = config->readEntry(actionName, QString());
if( !entry.isEmpty() ) {
kaction->setShortcut( KShortcut(entry), KAction::ActiveShortcut );
} else {
kaction->setShortcut( kaction->shortcut(KAction::DefaultShortcut) );
}
}
}
//kDebug(125) << " done";
}
void KActionCollection::exportGlobalShortcuts(KConfigGroup *config, bool writeAll) const
2014-11-13 01:04:59 +02:00
{
Q_ASSERT(config);
if (!config) {
return;
}
2014-11-13 01:04:59 +02:00
for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin();
it != d->actionByName.constEnd(); ++it) {
KAction *kaction = qobject_cast<KAction*>(it.value());
if (!kaction) {
continue;
}
const QString actionName = it.key();
2014-11-13 01:04:59 +02:00
// If the action name starts with unnamed- spit out a warning. That name
// will change at will and will break loading writing
if (actionName.startsWith(QLatin1String("unnamed-"))) {
kError() << "Skipped exporting Shortcut for action without name " << kaction->text() << "!";
continue;
}
2014-11-13 01:04:59 +02:00
if (kaction->isShortcutConfigurable() && kaction->isGlobalShortcutEnabled() ) {
bool bConfigHasAction = !config->readEntry(actionName, QString()).isEmpty();
bool bSameAsDefault = (kaction->globalShortcut() == kaction->globalShortcut(KAction::DefaultShortcut));
// If we're using a global config or this setting
// differs from the default, then we want to write.
KConfigGroup::WriteConfigFlags flags = KConfigGroup::Persistent;
if (configIsGlobal()) {
flags |= KConfigGroup::Global;
}
if (writeAll || !bSameAsDefault) {
QString s = kaction->globalShortcut().toString();
if (s.isEmpty()) {
s = "none";
}
kDebug(125) << "\twriting " << actionName << " = " << s;
config->writeEntry(actionName, s, flags);
} else if (bConfigHasAction) {
// Otherwise, this key is the same as default but exists in config file. Remove it.
2014-11-13 01:04:59 +02:00
kDebug(125) << "\tremoving " << actionName << " because == default";
config->deleteEntry( actionName, flags );
}
}
}
2014-11-13 01:04:59 +02:00
config->sync();
2014-11-13 01:04:59 +02:00
}
bool KActionCollectionPrivate::writeKXMLGUIConfigFile()
{
const KXMLGUIClient *kxmlguiClient = q->parentGUIClient();
// return false if there is no KXMLGUIClient
if (!kxmlguiClient || kxmlguiClient->xmlFile().isEmpty()) {
return false;
}
kDebug(129) << "xmlFile=" << kxmlguiClient->xmlFile();
QString attrShortcut = QLatin1String("shortcut");
// Read XML file
QString sXml(KXMLGUIFactory::readConfigFile(kxmlguiClient->xmlFile(), q->componentData()));
QDomDocument doc;
doc.setContent(sXml);
2014-11-13 01:04:59 +02:00
// Process XML data
// Get hold of ActionProperties tag
QDomElement elem = KXMLGUIFactory::actionPropertiesElement(doc);
2014-11-13 01:04:59 +02:00
// now, iterate through our actions
for (QMap<QString, QAction *>::ConstIterator it = actionByName.constBegin();
it != actionByName.constEnd(); ++it) {
KAction *kaction = qobject_cast<KAction*>(it.value());
if (!kaction) {
continue;
}
2014-11-13 01:04:59 +02:00
const QString actionName = it.key();
2014-11-13 01:04:59 +02:00
// If the action name starts with unnamed- spit out a warning and ignore
// it. That name will change at will and will break loading writing
if (actionName.startsWith(QLatin1String("unnamed-"))) {
kError() << "Skipped writing shortcut for action " << actionName << "(" << kaction->text() << ")!";
continue;
}
2014-11-13 01:04:59 +02:00
bool bSameAsDefault = (kaction->shortcut() == kaction->shortcut(KAction::DefaultShortcut));
kDebug(129) << "name = " << actionName
<< " shortcut = " << kaction->shortcut(KAction::ActiveShortcut).toString()
<< " globalshortcut = " << kaction->globalShortcut(KAction::ActiveShortcut).toString()
<< " def = " << kaction->shortcut(KAction::DefaultShortcut).toString();
// now see if this element already exists
// and create it if necessary (unless bSameAsDefault)
QDomElement act_elem = KXMLGUIFactory::findActionByName(elem, actionName, !bSameAsDefault);
if (act_elem.isNull()) {
continue;
}
if (bSameAsDefault) {
act_elem.removeAttribute(attrShortcut);
// kDebug(129) << "act_elem.attributes().count() = " << act_elem.attributes().count();
if (act_elem.attributes().count() == 1) {
elem.removeChild(act_elem);
}
} else {
act_elem.setAttribute( attrShortcut, kaction->shortcut().toString() );
}
2014-11-13 01:04:59 +02:00
}
// Write back to XML file
KXMLGUIFactory::saveConfigFile(doc, kxmlguiClient->localXMLFile(), q->componentData());
return true;
}
void KActionCollection::writeSettings(KConfigGroup *config, bool writeAll, QAction *oneAction) const
2014-11-13 01:04:59 +02:00
{
// If the caller didn't provide a config group we try to save the KXMLGUI
// Configuration file. If that succeeds we are finished.
if (config == 0 && d->writeKXMLGUIConfigFile()) {
2014-11-13 01:04:59 +02:00
return;
}
KConfigGroup cg(KGlobal::config(), configGroup());
2014-11-13 01:04:59 +02:00
if (!config) {
config = &cg;
}
QList<QAction*> writeActions;
if (oneAction) {
writeActions.append(oneAction);
} else {
writeActions = actions();
}
for (QMap<QString, QAction *>::ConstIterator it = d->actionByName.constBegin();
it != d->actionByName.constEnd(); ++it) {
// Get the action. We only handle KActions so skip QActions
KAction *kaction = qobject_cast<KAction*>(it.value());
if (!kaction) {
continue;
}
const QString actionName = it.key();
2014-11-13 01:04:59 +02:00
// If the action name starts with unnamed- spit out a warning and ignore
// it. That name will change at will and will break loading writing
if (actionName.startsWith(QLatin1String("unnamed-"))) {
kError() << "Skipped saving Shortcut for action without name " << kaction->text() << "!";
continue;
}
// Write the shortcut
if (kaction->isShortcutConfigurable()) {
bool bConfigHasAction = !config->readEntry(actionName, QString()).isEmpty();
2014-11-13 01:04:59 +02:00
bool bSameAsDefault = (kaction->shortcut() == kaction->shortcut(KAction::DefaultShortcut));
// If we're using a global config or this setting
// differs from the default, then we want to write.
KConfigGroup::WriteConfigFlags flags = KConfigGroup::Persistent;
// Honor the configIsGlobal() setting
if (configIsGlobal()) {
flags |= KConfigGroup::Global;
}
if (writeAll || !bSameAsDefault) {
2014-11-13 01:04:59 +02:00
// We are instructed to write all shortcuts or the shortcut is
// not set to its default value. Write it
QString s = kaction->shortcut().toString();
if( s.isEmpty() )
s = "none";
kDebug(125) << "\twriting " << actionName << " = " << s;
config->writeEntry(actionName, s, flags);
2014-11-13 01:04:59 +02:00
} else if (bConfigHasAction) {
2014-11-13 01:04:59 +02:00
// Otherwise, this key is the same as default but exists in
// config file. Remove it.
kDebug(125) << "\tremoving " << actionName << " because == default";
config->deleteEntry(actionName, flags);
2014-11-13 01:04:59 +02:00
}
}
}
config->sync();
}
void KActionCollection::slotActionTriggered()
2014-11-13 01:04:59 +02:00
{
QAction* action = qobject_cast<QAction*>(sender());
if (action) {
emit actionTriggered(action);
}
2014-11-13 01:04:59 +02:00
}
void KActionCollection::slotActionHovered()
2014-11-13 01:04:59 +02:00
{
QAction* action = qobject_cast<QAction*>(sender());
if (action) {
emit actionHovered(action);
}
2014-11-13 01:04:59 +02:00
}
void KActionCollectionPrivate::_k_actionDestroyed(QObject *obj)
2014-11-13 01:04:59 +02:00
{
// obj isn't really a QAction anymore. So make sure we don't do fancy stuff
// with it.
QAction *action = static_cast<QAction*>(obj);
2014-11-13 01:04:59 +02:00
unlistAction(action);
2014-11-13 01:04:59 +02:00
}
void KActionCollection::connectNotify(const char *signal)
2014-11-13 01:04:59 +02:00
{
if (d->connectHovered && d->connectTriggered) {
return;
2014-11-13 01:04:59 +02:00
}
if (QMetaObject::normalizedSignature(SIGNAL(actionHovered(QAction*))) == signal) {
if (!d->connectHovered) {
d->connectHovered = true;
foreach (QAction* action, actions()) {
connect(action, SIGNAL(hovered()), SLOT(slotActionHovered()));
}
}
} else if (QMetaObject::normalizedSignature(SIGNAL(actionTriggered(QAction*))) == signal) {
if (!d->connectTriggered) {
d->connectTriggered = true;
foreach (QAction *action, actions()) {
connect(action, SIGNAL(triggered(bool)), SLOT(slotActionTriggered()));
}
}
2014-11-13 01:04:59 +02:00
}
QObject::connectNotify(signal);
2014-11-13 01:04:59 +02:00
}
const QList< KActionCollection*>& KActionCollection::allCollections()
2014-11-13 01:04:59 +02:00
{
return KActionCollectionPrivate::s_allCollections;
}
void KActionCollection::associateWidget(QWidget *widget) const
2014-11-13 01:04:59 +02:00
{
foreach (QAction *action, actions()) {
if (!widget->actions().contains(action)) {
2014-11-13 01:04:59 +02:00
widget->addAction(action);
}
2014-11-13 01:04:59 +02:00
}
}
void KActionCollection::addAssociatedWidget(QWidget *widget)
2014-11-13 01:04:59 +02:00
{
if (!d->associatedWidgets.contains(widget)) {
widget->addActions(actions());
2014-11-13 01:04:59 +02:00
d->associatedWidgets.append(widget);
connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(_k_associatedWidgetDestroyed(QObject*)));
}
2014-11-13 01:04:59 +02:00
}
void KActionCollection::removeAssociatedWidget(QWidget *widget)
2014-11-13 01:04:59 +02:00
{
foreach (QAction* action, actions()) {
widget->removeAction(action);
}
2014-11-13 01:04:59 +02:00
d->associatedWidgets.removeAll(widget);
disconnect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(_k_associatedWidgetDestroyed(QObject*)));
2014-11-13 01:04:59 +02:00
}
QAction* KActionCollectionPrivate::unlistAction(QAction *action)
2014-11-13 01:04:59 +02:00
{
// ATTENTION:
// This method is called with an QObject formerly known as a QAction
// during _k_actionDestroyed(). So don't do fancy stuff here that needs a
// real QAction!
2014-11-13 01:04:59 +02:00
// Get the index for the action
const int index = actions.indexOf(action);
2014-11-13 01:04:59 +02:00
// Action not found.
if (index==-1) {
return nullptr;
}
2014-11-13 01:04:59 +02:00
// An action collection can't have the same action twice.
Q_ASSERT(actions.indexOf(action,index + 1) == -1);
2014-11-13 01:04:59 +02:00
// Get the actions name
const QString name = action->objectName();
2014-11-13 01:04:59 +02:00
// Remove the action
actionByName.remove(name);
actions.removeAt(index);
2014-11-13 01:04:59 +02:00
// Remove the action from the categories. Should be only one
QList<KActionCategory*> categories = q->findChildren<KActionCategory*>();
foreach (KActionCategory *category, categories) {
category->unlistAction(action);
}
2014-11-13 01:04:59 +02:00
return action;
2014-11-13 01:04:59 +02:00
}
QList<QWidget*> KActionCollection::associatedWidgets() const
2014-11-13 01:04:59 +02:00
{
return d->associatedWidgets;
2014-11-13 01:04:59 +02:00
}
void KActionCollection::clearAssociatedWidgets()
{
foreach (QWidget* widget, d->associatedWidgets) {
foreach (QAction* action, actions()) {
widget->removeAction(action);
}
}
d->associatedWidgets.clear();
2014-11-13 01:04:59 +02:00
}
void KActionCollectionPrivate::_k_associatedWidgetDestroyed(QObject *obj)
{
associatedWidgets.removeAll(static_cast<QWidget*>(obj));
2014-11-13 01:04:59 +02:00
}
#include "moc_kactioncollection.cpp"