/***************************************************************************
- * Copyright (C) 2005-08 by the Quassel Project *
+ * Copyright (C) 2005-2020 by the Quassel Project *
* devel@quassel-irc.org *
* *
* This program is free software; you can redistribute it and/or modify *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************
+ * Parts of this implementation are based on KDE's KActionCollection. *
***************************************************************************/
+
+#include "actioncollection.h"
+
+#include <QAction>
+#include <QDebug>
+#include <QMetaMethod>
+
+#include "action.h"
+#include "uisettings.h"
+
+void ActionCollection::addActions(const std::vector<std::pair<QString, Action*>>& actions)
+{
+ for (auto&& p : actions) {
+ addAction(p.first, p.second);
+ }
+}
+
+#ifndef HAVE_KDE
+
+int ActionCollection::count() const
+{
+ return actions().count();
+}
+
+bool ActionCollection::isEmpty() const
+{
+ return actions().count();
+}
+
+void ActionCollection::clear()
+{
+ _actionByName.clear();
+ qDeleteAll(_actions);
+ _actions.clear();
+}
+
+QAction* ActionCollection::action(const QString& name) const
+{
+ return _actionByName.value(name, 0);
+}
+
+QList<QAction*> ActionCollection::actions() const
+{
+ return _actions;
+}
+
+QAction* ActionCollection::addAction(const QString& name, QAction* action)
+{
+ if (!action)
+ return action;
+
+ const QString origName = action->objectName();
+ QString indexName = name;
+
+ if (indexName.isEmpty())
+ indexName = action->objectName();
+ else
+ action->setObjectName(indexName);
+ if (indexName.isEmpty())
+ indexName = indexName.asprintf("unnamed-%p", (void*)action);
+
+ // do we already have this action?
+ if (_actionByName.value(indexName, 0) == action)
+ return action;
+ // or maybe another action under this name?
+ if (QAction* oldAction = _actionByName.value(indexName))
+ takeAction(oldAction);
+
+ // do we already have this action under a different name?
+ int oldIndex = _actions.indexOf(action);
+ if (oldIndex != -1) {
+ _actionByName.remove(origName);
+ _actions.removeAt(oldIndex);
+ }
+
+ // add action
+ _actionByName.insert(indexName, action);
+ _actions.append(action);
+
+ foreach (QWidget* widget, _associatedWidgets) {
+ widget->addAction(action);
+ }
+
+ connect(action, &QObject::destroyed, this, &ActionCollection::actionDestroyed);
+ if (_connectHovered)
+ connect(action, &QAction::hovered, this, &ActionCollection::slotActionHovered);
+ if (_connectTriggered)
+ connect(action, &QAction::triggered, this, &ActionCollection::slotActionTriggered);
+
+ emit inserted(action);
+ return action;
+}
+
+void ActionCollection::removeAction(QAction* action)
+{
+ delete takeAction(action);
+}
+
+QAction* ActionCollection::takeAction(QAction* action)
+{
+ if (!unlistAction(action))
+ return nullptr;
+
+ foreach (QWidget* widget, _associatedWidgets) {
+ widget->removeAction(action);
+ }
+
+ action->disconnect(this);
+ return action;
+}
+
+void ActionCollection::readSettings()
+{
+ ShortcutSettings s;
+ QStringList savedShortcuts = s.savedShortcuts();
+
+ foreach (const QString& name, _actionByName.keys()) {
+ if (!savedShortcuts.contains(name))
+ continue;
+ auto* action = qobject_cast<Action*>(_actionByName.value(name));
+ if (action)
+ action->setShortcut(s.loadShortcut(name), Action::ActiveShortcut);
+ }
+}
+
+void ActionCollection::writeSettings() const
+{
+ ShortcutSettings s;
+ foreach (const QString& name, _actionByName.keys()) {
+ auto* action = qobject_cast<Action*>(_actionByName.value(name));
+ if (!action)
+ continue;
+ if (!action->isShortcutConfigurable())
+ continue;
+ if (action->shortcut(Action::ActiveShortcut) == action->shortcut(Action::DefaultShortcut))
+ continue;
+ s.saveShortcut(name, action->shortcut(Action::ActiveShortcut));
+ }
+}
+
+void ActionCollection::slotActionTriggered()
+{
+ auto* action = qobject_cast<QAction*>(sender());
+ if (action)
+ emit actionTriggered(action);
+}
+
+void ActionCollection::slotActionHovered()
+{
+ auto* action = qobject_cast<QAction*>(sender());
+ if (action)
+ emit actionHovered(action);
+}
+
+void ActionCollection::actionDestroyed(QObject* obj)
+{
+ // remember that this is not an QAction anymore at this point
+ auto* action = static_cast<QAction*>(obj);
+
+ unlistAction(action);
+}
+
+void ActionCollection::connectNotify(const QMetaMethod& signal)
+{
+ if (_connectHovered && _connectTriggered)
+ return;
+
+ if (QMetaMethod::fromSignal(&ActionCollection::actionHovered) == signal) {
+ if (!_connectHovered) {
+ _connectHovered = true;
+ foreach (QAction* action, actions())
+ connect(action, &QAction::hovered, this, &ActionCollection::slotActionHovered);
+ }
+ }
+ else if (QMetaMethod::fromSignal(&ActionCollection::actionTriggered) == signal) {
+ if (!_connectTriggered) {
+ _connectTriggered = true;
+ foreach (QAction* action, actions())
+ connect(action, &QAction::triggered, this, &ActionCollection::slotActionTriggered);
+ }
+ }
+
+ QObject::connectNotify(signal);
+}
+
+void ActionCollection::associateWidget(QWidget* widget) const
+{
+ foreach (QAction* action, actions()) {
+ if (!widget->actions().contains(action))
+ widget->addAction(action);
+ }
+}
+
+void ActionCollection::addAssociatedWidget(QWidget* widget)
+{
+ if (!_associatedWidgets.contains(widget)) {
+ widget->addActions(actions());
+ _associatedWidgets.append(widget);
+ connect(widget, &QObject::destroyed, this, &ActionCollection::associatedWidgetDestroyed);
+ }
+}
+
+void ActionCollection::removeAssociatedWidget(QWidget* widget)
+{
+ foreach (QAction* action, actions())
+ widget->removeAction(action);
+ _associatedWidgets.removeAll(widget);
+ disconnect(widget, &QObject::destroyed, this, &ActionCollection::associatedWidgetDestroyed);
+}
+
+QList<QWidget*> ActionCollection::associatedWidgets() const
+{
+ return _associatedWidgets;
+}
+
+void ActionCollection::clearAssociatedWidgets()
+{
+ foreach (QWidget* widget, _associatedWidgets)
+ foreach (QAction* action, actions())
+ widget->removeAction(action);
+
+ _associatedWidgets.clear();
+}
+
+void ActionCollection::associatedWidgetDestroyed(QObject* obj)
+{
+ _associatedWidgets.removeAll(static_cast<QWidget*>(obj));
+}
+
+bool ActionCollection::unlistAction(QAction* action)
+{
+ // This might be called with a partly destroyed QAction!
+
+ int index = _actions.indexOf(action);
+ if (index == -1)
+ return false;
+
+ QString name = action->objectName();
+ _actionByName.remove(name);
+ _actions.removeAt(index);
+
+ // TODO: remove from ActionCategory if we ever get that
+
+ return true;
+}
+
+#endif /* HAVE_KDE */