File indexing completed on 2024-05-12 15:27:01

0001 /***************************************************************************
0002     File                 : SignallingUndoCommand.cpp
0003     Project              : SciDAVis / LabPlot
0004     --------------------------------------------------------------------
0005     Copyright            : (C) 2010 Knut Franke
0006     Email (use @ for *)  : Knut.Franke*gmx.net
0007     Description          : An undo command calling a method/signal/slot on a
0008                            QObject on redo/undo.
0009 
0010  ***************************************************************************/
0011 
0012 /***************************************************************************
0013  *                                                                         *
0014  *  This program is free software; you can redistribute it and/or modify   *
0015  *  it under the terms of the GNU General Public License as published by   *
0016  *  the Free Software Foundation; either version 2 of the License, or      *
0017  *  (at your option) any later version.                                    *
0018  *                                                                         *
0019  *  This program is distributed in the hope that it will be useful,        *
0020  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
0021  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
0022  *  GNU General Public License for more details.                           *
0023  *                                                                         *
0024  *   You should have received a copy of the GNU General Public License     *
0025  *   along with this program; if not, write to the Free Software           *
0026  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
0027  *   Boston, MA  02110-1301  USA                                           *
0028  *                                                                         *
0029  ***************************************************************************/
0030 
0031 #include "SignallingUndoCommand.h"
0032 #include <QMetaObject>
0033 #include <QMetaType>
0034 
0035 /**
0036  * \class SignallingUndoCommand
0037  * \brief An undo command calling a method/signal/slot on a QObject on redo/undo.
0038  *
0039  * SignallingUndoCommand is a generic undo command which can be used in cases where undo/redo events
0040  * need to be forwarded to given methods, signals or slots of a QObject. That is, it behaves like a
0041  * cross between an undo command and a Qt signal-slot (or signal-signal) connection. Different
0042  * methods can be selected for undo and redo, but they are supposed to have the same signature.
0043  *
0044  * The intended use case is to have SignallingUndoCommand trigger notification signals before and
0045  * after one or more undo commands change some internal state; compare
0046  * AbstractAspect::exec(QUndoCommand*,const char*,const char*,QGenericArgument,QGenericArgument,QGenericArgument,QGenericArgument).
0047  * The advantage of separating out the signalling into this class is that the names and
0048  * arguments of the signals appear in the source code of the Aspect instead of its private class or
0049  * commands; this is desirable because signals are conceptually part of the public API rather than
0050  * the internal implementation (compare \ref aspect "The Aspect Framework").
0051  *
0052  * SignallingUndoCommand uses Qt's meta object system to dynamically invoke the target method, so
0053  * the class declaring the method needs to inherit from QObject and contain the Q_OBJECT macro;
0054  * just as if you wanted it to participate in signal-slot connections (though the methods to be
0055  * invoked need to be neither signals nor slots).
0056  * Method arguments are given using the macro Q_ARG(typename, const value&). Since
0057  * the variable given as "value" will likely be out of scope when undo() is called, a copy needs to
0058  * be created. This uses QMetaType, which means that (non-trivial) argument types need to be
0059  * registered using qRegisterMetaType() before giving them to a SignallingUndoCommand (in
0060  * particular, this also goes for pointers to custom data types). The situation here is analogous
0061  * to an asynchronous method invocation using QMetaMethod::invoke() with Qt::QueuedConnection.
0062  */
0063 
0064 /**
0065  * \brief Constructor.
0066  *
0067  * \arg \c text A description of the undo command (compare QUndoCommand::setText()).
0068  * \arg \c receiver The object whose methods/signals/slots should be invoked.
0069  * \arg \c redo The name of the method to be called when the command is (re-)executed; excluding the signature.
0070  * \arg \c undo Analogously to redo, the method to be called when the command is undone.
0071  * \arg <tt>val0,val1,val2,val3</tt> Arguments to the undo and redo methods; to be given using Q_ARG().
0072  *
0073  * Simple example:
0074  * \code
0075  * QUndoStack stack;
0076  * QAction action;
0077  * stack.push(new SignallingUndoCommand(i18n("enable action"), &action, "setEnabled", "setDisabled", Q_ARG(bool, true)));
0078  * \endcode
0079  */
0080 SignallingUndoCommand::SignallingUndoCommand(const QString &text, QObject *receiver, const char *redoMethod, const char *undoMethod,
0081                 QGenericArgument val0, QGenericArgument val1,
0082                 QGenericArgument val2, QGenericArgument val3)
0083     : QUndoCommand(text),
0084     m_redo(redoMethod),
0085     m_undo(undoMethod),
0086     m_receiver(receiver)
0087 {
0088     // munge arguments
0089     const char *type_names[] = { val0.name(), val1.name(), val2.name(), val3.name() };
0090     void *argument_data[] = { val0.data(), val1.data(), val2.data(), val3.data() };
0091     for (m_argument_count = 0; qstrlen(type_names[m_argument_count]) > 0; ++m_argument_count);
0092 
0093     // copy arguments (Q_ARG references will often go out of scope before redo/undo are called)
0094     m_argument_types = new int[m_argument_count];
0095     Q_CHECK_PTR(m_argument_types);
0096     m_argument_data = new void*[m_argument_count];
0097     Q_CHECK_PTR(m_argument_data);
0098     for (int i = 0; i < m_argument_count; i++) {
0099         m_argument_types[i] = QMetaType::type(type_names[i]);
0100         if (m_argument_types[i]) // type is known to QMetaType
0101             m_argument_data[i] = QMetaType::create(m_argument_types[i], argument_data[i]);
0102         else
0103             qWarning("SignallingUndoCommand: failed to copy unknown type %s"
0104                     " (needs to be registered with qRegisterMetaType())!\n", type_names[i]);
0105     }
0106 }
0107 
0108 SignallingUndoCommand::~SignallingUndoCommand() {
0109     for (int i = 0; i < m_argument_count; ++i)
0110         if (m_argument_types[i] && m_argument_data[i])
0111             QMetaType::destroy(m_argument_types[i], m_argument_data[i]);
0112     delete[] m_argument_types;
0113     delete[] m_argument_data;
0114 }
0115 
0116 QGenericArgument SignallingUndoCommand::arg(int index) {
0117     if (index >= m_argument_count)
0118         return QGenericArgument{};
0119 
0120     return QGenericArgument{QMetaType::typeName(m_argument_types[index]), m_argument_data[index]};
0121 }
0122 
0123 void SignallingUndoCommand::redo() {
0124     const QMetaObject *mo = m_receiver->metaObject();
0125     if (!mo->invokeMethod(m_receiver, m_redo, arg(0), arg(1), arg(2), arg(3)))
0126         qWarning("FAILED to invoke %s on %s\n", m_redo.constData(), mo->className());
0127 }
0128 
0129 void SignallingUndoCommand::undo() {
0130     const QMetaObject *mo = m_receiver->metaObject();
0131     if (!mo->invokeMethod(m_receiver, m_undo, arg(0), arg(1), arg(2), arg(3)))
0132         qWarning("FAILED to invoke %s on %s\n", m_undo.constData(), mo->className());
0133 }
0134