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