File indexing completed on 2024-05-12 03:47:46

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