/**************************************************************************** ** ** Copyright (C) 1993-2009 NVIDIA Corporation. ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt 3D Studio. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #pragma once #ifndef QT3DSDM_TRANSACTIONS_H #define QT3DSDM_TRANSACTIONS_H #include "Qt3DSDMDataTypes.h" #include "StandardExtensions.h" #include namespace qt3dsdm { /** * Transaction is some small entity that changes data. A transaction consumer is expected to *execute * a list of these either forward on a "redo" command or backwards on an "undo" command. * * Transactions merely change data. There are two other lists on consumers for "do" *notifications * and "undo" notifications. These lists control the events sent out to the UI about what *actually * changed in the model. Note that undo/do notifications may be more clever than simple *send-signal * commands in that they may check if an object is alive or not before sending a notification *and may * decline to send a notification if the target object is not currently alive. * * Currently the undo/redo system first executes all of the transactions and then sends the *appropriate * notifications. This means that when the UI receives any notification, the model is in its *most-updated * state. */ class ITransaction { public: const char *m_File; int m_Line; ITransaction(const char *inFile, int inLine) : m_File(inFile) , m_Line(inLine) { } virtual ~ITransaction() {} virtual void Do() = 0; virtual void Undo() = 0; }; /** * Merging allows us to efficient handle situations where the users are continuously * modifying an object (or set of objects). These are referred to as live-update * scenarios. Dragging an object in the 3d view or dragging keyframes in the timeline * would be an example of live-update scenarios. */ template class IMergeableTransaction { public: virtual ~IMergeableTransaction() {} // Called when a new value has arrived and we would rather update // an existing representation rather than create a new one. virtual void Update(const TValueType &inValue) = 0; }; /** * A consumer is an object that records the transaction. This interface keeps the * base producer objects from binding to how the transaction or the notifications * are stored. */ class ITransactionConsumer { public: virtual ~ITransactionConsumer() {} virtual void OnTransaction(std::shared_ptr inTransaction) = 0; // Notifications to be sent for undo/redo These are used to // notify clients that something is different. virtual void OnDoNotification(std::function inNotification) = 0; virtual void OnUndoNotification(std::function inNotification) = 0; }; /////////////////////////////////////////////////////////////////////////////// // Implementation of helper objects and functions. /////////////////////////////////////////////////////////////////////////////// typedef std::vector> TVoidFunctionList; typedef std::shared_ptr TTransactionConsumerPtr; class ITransactionProducer { public: virtual ~ITransactionProducer() {} virtual void SetConsumer(TTransactionConsumerPtr inConsumer) = 0; }; template class CGenericTransaction : public ITransaction { Q_DISABLE_COPY(CGenericTransaction) TUndoTransaction m_UndoTransaction; TDoTransaction m_DoTransaction; public: CGenericTransaction(const char *inFile, int inLine, TDoTransaction inDo, TUndoTransaction inUndo) : ITransaction(inFile, inLine) , m_UndoTransaction(inUndo) , m_DoTransaction(inDo) { } void Do() override { m_DoTransaction(); } void Undo() override { m_UndoTransaction(); } }; template ITransaction *DoCreateGenericTransaction(const char *inFile, int inLine, TDoTransaction inDo, TUndoTransaction inUndo) { return static_cast( new CGenericTransaction(inFile, inLine, inDo, inUndo)); } #define CREATE_GENERIC_TRANSACTION(inDo, inUndo) \ DoCreateGenericTransaction(__FILE__, __LINE__, inDo, inUndo) typedef std::shared_ptr TTransactionPtr; typedef std::vector TTransactionPtrList; struct CTransactionConsumer : public ITransactionConsumer { TTransactionPtrList m_TransactionList; TVoidFunctionList m_DoNotifications; TVoidFunctionList m_UndoNotifications; void OnTransaction(TTransactionPtr inTransaction) override { m_TransactionList.push_back(inTransaction); } void OnDoNotification(std::function inNotification) override { m_DoNotifications.push_back(inNotification); } void OnUndoNotification(std::function inNotification) override { m_UndoNotifications.push_back(inNotification); } // Merge with another CTransactionConsumer virtual void Merge(const ITransactionConsumer *inConsumer) { const CTransactionConsumer *theConsumer = static_cast(inConsumer); m_TransactionList.insert(m_TransactionList.begin(), theConsumer->m_TransactionList.begin(), theConsumer->m_TransactionList.end()); } void Reset() { m_TransactionList.clear(); m_DoNotifications.clear(); m_UndoNotifications.clear(); } }; struct SIgnorantTransactionConsumer : public ITransactionConsumer { void OnTransaction(qt3dsdm::TTransactionPtr) override {} // Notifications to be sent for undo/redo These are used to // notify clients that something is different. void OnDoNotification(std::function) override {} void OnUndoNotification(std::function) override {} }; template inline void RunWithConsumer(TTransactionConsumerPtr inConsumer, TTransactionType inTransaction) { if (inConsumer) inTransaction(inConsumer); } template inline void CreateGenericTransactionWithConsumer(const char *inFile, int inLine, TTransactionConsumerPtr inConsumer, TDoTransaction inDoTransaction, TUndoTransaction inUndoTransaction) { if (inConsumer) inConsumer->OnTransaction(TTransactionPtr( DoCreateGenericTransaction(inFile, inLine, inDoTransaction, inUndoTransaction))); } template inline void DoSetConsumer(TTransactionConsumerPtr inConsumer, std::shared_ptr inTypePtr) { ITransactionProducer *theProducer = dynamic_cast(inTypePtr.get()); if (theProducer) theProducer->SetConsumer(inConsumer); } template inline void Undo(TContainer &inTransactions) { std::for_each(inTransactions.rbegin(), inTransactions.rend(), std::bind(&ITransaction::Undo, std::placeholders::_1)); } template inline void Redo(TContainer &inTransactions) { do_all(inTransactions, std::bind(&ITransaction::Do, std::placeholders::_1)); } template void Notify(std::vector &inNotifications) { do_all(inNotifications, std::bind(&TItemType::operator(), std::placeholders::_1)); } template void NotifyReverse(std::vector &inNotifications) { std::for_each(inNotifications.rbegin(), inNotifications.rend(), std::bind(&TItemType::operator(), std::placeholders::_1)); } } #endif