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 }