Merge pull request #21978 from theo-vt/document_transaction

GSOC Multi file editing
This commit is contained in:
Kacper Donat
2026-03-18 18:37:38 +01:00
committed by GitHub
253 changed files with 4247 additions and 2527 deletions
+9
View File
@@ -587,6 +587,15 @@ Document* Application::getDocument(const char *Name) const
return pos->second;
}
Document* Application::getDocumentOrActive(const char *Name) const
{
if (!Base::Tools::isNullOrEmpty(Name)) {
return getDocument(Name);
}
else {
return getActiveDocument();
}
}
const char * Application::getDocumentName(const Document* doc) const
{
+52 -27
View File
@@ -35,10 +35,12 @@
#include <set>
#include <map>
#include <string>
#include <optional>
#include <Base/Observer.h>
#include <Base/Parameter.h>
#include <Base/ProgressIndicator.h>
#include "TransactionDefs.h"
// forward declarations
using PyObject = struct _object;
@@ -203,6 +205,8 @@ public:
*/
App::Document* getDocument(const char *Name) const;
App::Document* getDocumentOrActive(const char *Name) const;
/// %Path matching modes for getDocumentByPath()
enum class PathMatchMode
{
@@ -289,9 +293,11 @@ public:
/**
* @brief Setup a pending application-wide active transaction.
*
* Call this function to setup an application-wide transaction. All current
* pending transactions of opening documents will be committed first.
* However, no new transaction is created by this call. Any subsequent
* Call this function to setup a transaction in the currently active document
* if no document is active, a global transaction is created. If the current
* active document already has a transaction setup it will either commit the
* current transaction or rename it, depending on the tmpName flag of the
* currently setup transaction. No new transaction is created by this call. Any subsequent
* changes in any current opening document will auto create a transaction
* with the given name and ID. If more than one document is changed, the
* transactions will share the same ID, and will be undo/redo together.
@@ -303,32 +309,45 @@ public:
*
* @return The new transaction ID.
*/
int setActiveTransaction(const char* name, bool persist = false);
/**
* @brief Get the current active transaction name and ID.
int setActiveTransaction(TransactionName name);
/// Return the current global transaction name and ID if such a global transaction is
/// setup (uncommon)
std::string getActiveTransaction(int *tid=nullptr) const;
int openGlobalTransaction(TransactionName name);
int getGlobalTransaction() const;
bool transactionIsActive(int tid) const;
std::string getTransactionName(int tid) const;
bool transactionTmpName(int tid) const;
Document* transactionInitiator(int tid) const;
std::optional<TransactionDescription> transactionDescription(int tid) const;
void setTransactionDescription(int tid, const TransactionDescription& desc);
void setTransactionName(int tid, const TransactionName& name);
/** Commit/abort current active transactions
*
* If there is no active transaction, an empty string is returned.
*
* @param[out] tid If not `nullptr`, the current active transaction ID is
* returned through this pointer.
* @return The current active transaction name.
*/
const char* getActiveTransaction(int* tid = nullptr) const;
/**
* @brief Commit/abort current active transactions.
*
* @param[in] abort: whether to abort or commit the transactions
* @param[in] id: by default 0 meaning that the current active transaction ID is used.
*
* Besides calling this function directly, it will be called by
* automatically if 1) any new transaction is created with a different ID,
* or 2) any transaction with the current active transaction ID is either
* committed or aborted.
* @param[in] id: by default 0 meaning that the current global transaction ID is used.
*
* Bsides calling this function directly, it will be called by automatically
* if 1) any new transaction is created with a different ID, or 2) any
* transaction with the current active transaction ID is either committed or
* aborted
* returns true if it succeeded in closing the transaction
*/
void closeActiveTransaction(bool abort=false, int id=0);
/// @}
bool closeActiveTransaction(TransactionCloseMode mode = TransactionCloseMode::Commit, int id=0);
/// Internally call closeActiveTransaction(), but it makes the call site clearer
bool commitTransaction(int tid);
bool abortTransaction(int tid);
//@}
// NOLINTBEGIN
// clang-format off
@@ -1040,10 +1059,16 @@ private:
friend class AutoTransaction;
std::string _activeTransactionName;
int _activeTransactionID{0};
int _activeTransactionGuard{0};
bool _activeTransactionTmpName{false};
std::map<int, TransactionDescription> _activeTransactionDescriptions; // Maps transaction ID to transaction name
int currentlyClosingID {0};
// This is the transaction ID for a global transaction
// Documents will take this ID if it is non-zero
// and generate their own otherwise
int _globalTransactionID { 0 };
bool _globalTransactionTmpName {false};
std::string _globalTransactionName;
Base::ProgressIndicator _progressIndicator;
+9 -8
View File
@@ -242,9 +242,7 @@ PyMethodDef ApplicationPy::Methods[] = {
METH_VARARGS,
"setActiveTransaction(name, persist=False) -- setup active transaction with the given name\n\n"
"name: the transaction name\n"
"persist(False): by default, if the calling code is inside any invocation of a command, it\n"
" will be auto closed once all commands within the current stack exists. To\n"
" disable auto closing, set persist=True\n"
"persist(False): This parameter has no effect and is kept for compatibility reasonss"
"Returns the transaction ID for the active transaction. An application-wide\n"
"active transaction causes any document changes to open a transaction with\n"
"the given name and ID."},
@@ -1201,14 +1199,14 @@ PyObject* ApplicationPy::sGetDependentObjects(PyObject* /*self*/, PyObject* args
PyObject* ApplicationPy::sSetActiveTransaction(PyObject* /*self*/, PyObject* args)
{
char* name {};
PyObject* persist = Py_False;
PyObject* persist = Py_False; // Not used
if (!PyArg_ParseTuple(args, "s|O!", &name, &PyBool_Type, &persist)) {
return nullptr;
}
PY_TRY
{
Py::Long ret(GetApplication().setActiveTransaction(name, Base::asBoolean(persist)));
Py::Long ret(GetApplication().setActiveTransaction(TransactionName {.name=name, .temporary=false}));
return Py::new_reference_to(ret);
}
PY_CATCH;
@@ -1223,8 +1221,8 @@ PyObject* ApplicationPy::sGetActiveTransaction(PyObject* /*self*/, PyObject* arg
PY_TRY
{
int id = 0;
const char* name = GetApplication().getActiveTransaction(&id);
if (!name || id <= 0) {
std::string name = GetApplication().getActiveTransaction(&id);
if (name.empty() || id <= 0) {
Py_Return;
}
Py::Tuple ret(2);
@@ -1245,7 +1243,10 @@ PyObject* ApplicationPy::sCloseActiveTransaction(PyObject* /*self*/, PyObject* a
PY_TRY
{
GetApplication().closeActiveTransaction(Base::asBoolean(abort), id);
TransactionCloseMode mode = Base::asBoolean(abort)
? TransactionCloseMode::Abort
: TransactionCloseMode::Commit;
GetApplication().closeActiveTransaction(mode, id);
Py_Return;
}
PY_CATCH;
+167 -167
View File
@@ -35,204 +35,217 @@ FC_LOG_LEVEL_INIT("App", true, true)
using namespace App;
static int _TransactionLock;
static int _TransactionClosed;
AutoTransaction::AutoTransaction(const char* name, bool tmpName)
AutoTransaction::AutoTransaction(int tid)
: tid(tid)
{
auto& app = GetApplication();
if (name && app._activeTransactionGuard >= 0) {
if (!app.getActiveTransaction() || (!tmpName && app._activeTransactionTmpName)) {
FC_LOG("auto transaction '" << name << "', " << tmpName);
tid = app.setActiveTransaction(name);
app._activeTransactionTmpName = tmpName;
}
}
// We use negative transaction guard to disable auto transaction from here
// and any stack below. This is to support user setting active transaction
// before having any existing AutoTransaction on stack, or 'persist'
// transaction that can out live AutoTransaction.
if (app._activeTransactionGuard < 0) {
--app._activeTransactionGuard;
}
else if (tid || app._activeTransactionGuard > 0) {
++app._activeTransactionGuard;
}
else if (app.getActiveTransaction()) {
FC_LOG("auto transaction disabled because of '" << app._activeTransactionName << "'");
--app._activeTransactionGuard;
}
else {
++app._activeTransactionGuard;
}
FC_TRACE("construct auto Transaction " << app._activeTransactionGuard);
}
AutoTransaction::AutoTransaction(Document* doc, const std::string& name)
: AutoTransaction(doc->openTransaction(name))
{
}
AutoTransaction::~AutoTransaction()
{
auto& app = GetApplication();
FC_TRACE("before destruct auto Transaction " << app._activeTransactionGuard);
if (app._activeTransactionGuard < 0) {
++app._activeTransactionGuard;
}
else if (!app._activeTransactionGuard) {
#ifdef FC_DEBUG
FC_ERR("Transaction guard error");
#endif
}
else if (--app._activeTransactionGuard == 0) {
try {
// We don't call close() here, because close() only closes
// transaction that we opened during construction time. However,
// when _activeTransactionGuard reaches zero here, we are supposed
// to close any transaction opened.
app.closeActiveTransaction();
}
catch (Base::Exception& e) {
e.reportException();
}
catch (...) {
}
}
FC_TRACE("destruct auto Transaction " << app._activeTransactionGuard);
close(TransactionCloseMode::Commit);
}
void AutoTransaction::close(bool abort)
void AutoTransaction::close(TransactionCloseMode mode)
{
if (tid || abort) {
GetApplication().closeActiveTransaction(abort, abort ? 0 : tid);
if (tid != NullTransaction) {
GetApplication().closeActiveTransaction(mode, tid);
tid = 0;
}
}
void AutoTransaction::setEnable(bool enable)
int Application::setActiveTransaction(TransactionName name)
{
auto& app = GetApplication();
if (!app._activeTransactionGuard) {
return;
if (name.name.empty()) {
name.name = "Command";
}
if ((enable && app._activeTransactionGuard > 0)
|| (!enable && app._activeTransactionGuard < 0)) {
return;
}
app._activeTransactionGuard = -app._activeTransactionGuard;
FC_TRACE("toggle auto Transaction " << app._activeTransactionGuard);
if (!enable && app._activeTransactionTmpName) {
bool close = true;
for (auto& v : app.DocMap) {
if (v.second->hasPendingTransaction()) {
close = false;
break;
}
}
if (close) {
app.closeActiveTransaction();
}
if (_pActiveDoc != nullptr) {
return _pActiveDoc->setActiveTransaction(name);
}
return openGlobalTransaction(name);
}
int Application::setActiveTransaction(const char* name, bool persist)
std::string Application::getActiveTransaction(int* id) const
{
if (id != nullptr) {
*id = _globalTransactionID;
}
return _globalTransactionID != 0 ? getTransactionName(_globalTransactionID) : "";
}
int Application::openGlobalTransaction(TransactionName name)
{
if (name.name.empty()) {
name.name = "Command";
}
if (!name || !name[0]) {
name = "Command";
}
this->signalBeforeOpenTransaction(name);
if (_activeTransactionGuard > 0 && getActiveTransaction()) {
if (_activeTransactionTmpName) {
FC_LOG("transaction rename to '" << name << "'");
for (auto& v : DocMap) {
v.second->renameTransaction(name, _activeTransactionID);
FC_WARN("Setting a global transaction with name='" << name.name);
if (_globalTransactionID != 0 && transactionTmpName(_globalTransactionID)) {
setTransactionName(_globalTransactionID, name);
} else {
FC_LOG("set global transaction '" << name.name << "'");
if (_globalTransactionID != 0 && !commitTransaction(_globalTransactionID)) {
FC_WARN("could not close current global transaction");
return _globalTransactionID;
}
_globalTransactionID = Transaction::getNewID();
setTransactionDescription(
_globalTransactionID,
TransactionDescription {
.initiator = nullptr,
.name = name
}
}
else {
if (persist) {
AutoTransaction::setEnable(false);
}
return 0;
}
);
}
else if (_TransactionLock) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Transaction locked, ignore new transaction '" << name << "'");
}
return 0;
return _globalTransactionID;
}
int Application::getGlobalTransaction() const
{
return _globalTransactionID;
}
bool Application::transactionIsActive(int tid) const
{
return transactionDescription(tid) != std::nullopt;
}
std::string Application::getTransactionName(int tid) const
{
auto desc = transactionDescription(tid);
return desc ? desc->name.name : "";
}
bool Application::transactionTmpName(int tid) const
{
auto desc = transactionDescription(tid);
return desc ? desc->name.temporary : false;
}
Document* Application::transactionInitiator(int tid) const
{
auto desc = transactionDescription(tid);
return desc ? desc->initiator : nullptr;
}
std::optional<TransactionDescription> Application::transactionDescription(int tid) const
{
if (tid == NullTransaction) {
return std::nullopt;
}
else {
FC_LOG("set active transaction '" << name << "'");
_activeTransactionID = 0;
auto found = _activeTransactionDescriptions.find(tid);
if (found != _activeTransactionDescriptions.end()) {
return std::optional<TransactionDescription>(found->second);
}
return std::nullopt;
}
void Application::setTransactionDescription(int tid, const TransactionDescription& desc)
{
if (tid == NullTransaction) {
return;
}
auto found = _activeTransactionDescriptions.find(tid);
bool wasPresent = (found != _activeTransactionDescriptions.end());
if (wasPresent && found->second.name.temporary) {
for (auto& v : DocMap) {
v.second->_commitTransaction();
v.second->renameTransaction(desc.name.name, tid);
}
_activeTransactionID = Transaction::getNewID();
}
_activeTransactionTmpName = false;
_activeTransactionName = name;
if (persist) {
AutoTransaction::setEnable(false);
if (!wasPresent || found->second.name.temporary) {
_activeTransactionDescriptions[tid] = desc;
FC_LOG("transaction rename to '" << desc.name.name << "'");
}
return _activeTransactionID;
}
void Application::setTransactionName(int tid, const TransactionName& name)
{
if (tid == NullTransaction || !transactionIsActive(tid)) {
return;
}
auto found = _activeTransactionDescriptions.find(tid);
if (found == _activeTransactionDescriptions.end() || found->second.name.temporary) {
_activeTransactionDescriptions[tid].name = name;
FC_LOG("transaction rename to '" << name.name << "'");
for (auto& v : DocMap) {
v.second->renameTransaction(name.name, tid);
}
}
}
const char* Application::getActiveTransaction(int* id) const
bool Application::closeActiveTransaction(TransactionCloseMode mode, int id)
{
int tid = 0;
if (Transaction::getLastID() == _activeTransactionID) {
tid = _activeTransactionID;
}
if (id) {
*id = tid;
}
return tid ? _activeTransactionName.c_str() : nullptr;
}
void Application::closeActiveTransaction(bool abort, int id)
{
if (!id) {
id = _activeTransactionID;
}
if (!id) {
return;
}
if (_activeTransactionGuard > 0 && !abort) {
FC_LOG("ignore close transaction");
return;
}
if (_TransactionLock) {
if (_TransactionClosed >= 0) {
_TransactionLock = abort ? -1 : 1;
bool abort = (mode == TransactionCloseMode::Abort);
if (id == NullTransaction) {
if (_pActiveDoc != nullptr && _pActiveDoc->getBookedTransactionID() != NullTransaction) {
id = _pActiveDoc->getBookedTransactionID();
} else {
id = _globalTransactionID;
}
FC_LOG("pending " << (abort ? "abort" : "close") << " transaction");
return;
}
if (id == NullTransaction || id == currentlyClosingID) {
return false;
}
currentlyClosingID = id;
FC_LOG("close transaction '" << _activeTransactionName << "' " << abort);
_activeTransactionID = 0;
TransactionSignaller signaller(abort, false);
for (auto& v : DocMap) {
if (v.second->getTransactionID(true) != id) {
std::vector<Document*> docsToPoke;
for (auto& docNameAndDoc : DocMap) {
if (docNameAndDoc.second->getBookedTransactionID() != id) {
continue;
}
if(docNameAndDoc.second->isTransactionLocked() || docNameAndDoc.second->transacting()) {
FC_LOG("pending " << (abort ? "abort" : "close") << " transaction");
currentlyClosingID = 0;
return false;
}
if(docNameAndDoc.second->transacting()) {
FC_LOG("pending " << (abort ? "abort" : "close") << " transaction");
currentlyClosingID = 0;
return false;
}
docsToPoke.push_back(docNameAndDoc.second);
}
FC_LOG("close transaction '" << _activeTransactionDescriptions[id].name.name << "' " << abort);
_activeTransactionDescriptions.erase(id);
if (id == _globalTransactionID) {
_globalTransactionID = 0;
}
TransactionSignaller signaller(abort, false);
for (auto& doc : docsToPoke) {
if (abort) {
v.second->_abortTransaction();
doc->_abortTransaction();
}
else {
v.second->_commitTransaction();
doc->_commitTransaction();
}
}
currentlyClosingID = 0;
return true;
}
bool Application::commitTransaction(int tid)
{
return closeActiveTransaction(TransactionCloseMode::Commit, tid);
}
bool Application::abortTransaction(int tid)
{
return closeActiveTransaction(TransactionCloseMode::Abort, tid);
}
////////////////////////////////////////////////////////////////////////
TransactionLocker::TransactionLocker(bool lock)
TransactionLocker::TransactionLocker(Document* doc, bool lock)
: active(lock)
, doc(doc)
{
if (lock) {
++_TransactionLock;
doc->lockTransaction();
}
}
@@ -267,22 +280,9 @@ void TransactionLocker::activate(bool enable)
active = enable;
if (active) {
++_TransactionLock;
doc->lockTransaction();
return;
}
if (--_TransactionLock != 0) {
return;
}
if (_TransactionClosed) {
bool abort = (_TransactionClosed < 0);
_TransactionClosed = 0;
GetApplication().closeActiveTransaction(abort);
}
}
bool TransactionLocker::isLocked()
{
return _TransactionLock > 0;
doc->unlockTransaction();
}
+22 -48
View File
@@ -26,11 +26,15 @@
#include <cstddef>
#include <FCGlobal.h>
#include <string>
#include "TransactionDefs.h"
namespace App
{
class Application;
class Document;
/**
* @brief A helper class to manage transactions (i.e. undo/redo).
@@ -45,59 +49,31 @@ public:
void* operator new(std::size_t) = delete;
public:
/**
* @brief Construct an auto transaction.
*
* @param[in] name: optional new transaction name on construction
* @param[in] tmpName: if true and a new transaction is setup, the name given is
* considered as temporary, and subsequent construction of this class (or
* calling Application::setActiveTransaction()) can override the transaction
* name.
*
* The constructor increments an internal counter
* (Application::_activeTransactionGuard). The counter prevents any new
* active transactions being setup. It also prevents to close
* (i.e. commits) the current active transaction until it reaches zero. It
* does not have any effect on aborting transactions though.
/** Constructor
*
* @param tid the ID of the transaction to manage
*
* No action is done in the constructor
*/
AutoTransaction(const char* name = nullptr, bool tmpName = false);
/**
* @brief Destruct an auto transaction.
*
* This destructor decrease an internal counter
* (Application::_activeTransactionGuard), and will commit any current
* active transaction when the counter reaches zero.
explicit AutoTransaction(int tid);
AutoTransaction(Document* doc, const std::string& name);
/** Destructor
*
* This destructor attempts to commit the transaction it manages
*/
~AutoTransaction();
/**
* @brief Close or abort the transaction.
*
* This function can be used to explicitly close (i.e. commit) the
* transaction, if the current transaction ID matches the one created inside
* the constructor. For aborting, it will abort any current transaction.
*
* @param[in] abort: if true, abort the transaction; otherwise, commit it.
* This function can be used to explicitly close (i.e. commit / abort) the
* transaction,
*/
void close(bool abort = false);
/**
* @brief Enable/Disable any AutoTransaction instance on the current stack.
*
* Once disabled, any empty temporary named transaction is closed. If there
* are non-empty or non-temporary named active transaction, it will not be
* auto closed.
*
* This function may be used in, for example, Gui::Document::setEdit() to
* allow a transaction live past any command scope.
*
* @param[in] enable: if true, enable the AutoTransaction; otherwise, disable it.
*/
static void setEnable(bool enable);
void close(TransactionCloseMode mode = TransactionCloseMode::Commit);
private:
int tid = 0;
int tid { 0 };
};
@@ -116,7 +92,7 @@ public:
*
* @param[in] lock: whether to activate the lock
*/
TransactionLocker(bool lock = true);
TransactionLocker(Document* doc, bool lock = true);
/**
* @brief Destruct a transaction locker.
@@ -142,10 +118,7 @@ public:
{
return active;
}
/// Check if transaction is being locked.
static bool isLocked();
friend class Application;
public:
@@ -154,6 +127,7 @@ public:
private:
bool active;
Document* doc;
};
} // namespace App
+199 -101
View File
@@ -35,6 +35,7 @@
#include <list>
#include <algorithm>
#include <filesystem>
#include <format>
#include <boost/algorithm/string.hpp>
#include <boost/bimap.hpp>
@@ -190,6 +191,7 @@ bool Document::undo(const int id)
mRedoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
mRedoTransactions.push_back(d->activeUndoTransaction);
d->activeUndoTransaction = nullptr;
d->bookedTransaction = 0;
mUndoMap.erase(mUndoTransactions.back()->getID());
delete mUndoTransactions.back();
@@ -242,6 +244,7 @@ bool Document::redo(const int id)
mUndoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
mUndoTransactions.push_back(d->activeUndoTransaction);
d->activeUndoTransaction = nullptr;
d->bookedTransaction = 0;
mRedoMap.erase(mRedoTransactions.back()->getID());
delete mRedoTransactions.back();
@@ -271,10 +274,10 @@ void Document::changePropertyOfObject(TransactionalObject* obj,
}
if ((d->iUndoMode != 0) && !isPerformingTransaction() && !d->activeUndoTransaction) {
if (!testStatus(Restoring) || testStatus(Importing)) {
int tid = 0;
const char* name = GetApplication().getActiveTransaction(&tid);
if (name && tid > 0) {
_openTransaction(name, tid);
if (d->bookedTransaction == NullTransaction) {
d->bookedTransaction = GetApplication().getGlobalTransaction();
} else {
_openTransaction(GetApplication().getTransactionName(d->bookedTransaction), d->bookedTransaction);
}
}
}
@@ -329,71 +332,92 @@ std::vector<std::string> Document::getAvailableRedoNames() const
return vList;
}
void Document::openTransaction(const char* name) // NOLINT
int Document::openTransaction(TransactionName name, int tid) // NOLINT
{
if (isPerformingTransaction() || d->committing) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Cannot open transaction while transacting");
}
return;
if (tid != NullTransaction && tid == d->bookedTransaction) {
return tid; // Early exit without warning
}
if (isTransactionLocked()) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Transaction locked, ignore new transaction '" << name.name << "'");
}
return 0;
}
GetApplication().setActiveTransaction(name ? name : "<empty>");
}
int Document::_openTransaction(const char* name, int id)
{
if (isPerformingTransaction() || d->committing) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Cannot open transaction while transacting");
}
return 0;
}
if (d->iUndoMode != 0) {
// Avoid recursive calls that is possible while
// clearing the redo transactions and will cause
// a double deletion of some transaction and thus
// a segmentation fault
if (d->opentransaction) {
return 0;
}
Base::FlagToggler<> flag(d->opentransaction);
if ((id != 0) && mUndoMap.find(id) != mUndoMap.end()) {
throw Base::RuntimeError("invalid transaction id");
}
if (d->activeUndoTransaction) {
_commitTransaction(true);
}
_clearRedos();
d->activeUndoTransaction = new Transaction(id);
if (!name) {
name = "<empty>";
}
d->activeUndoTransaction->Name = name;
mUndoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
id = d->activeUndoTransaction->getID();
signalOpenTransaction(*this, name);
auto& app = GetApplication();
auto activeDoc = app.getActiveDocument();
if (activeDoc && activeDoc != this && !activeDoc->hasPendingTransaction()) {
std::string aname("-> ");
aname += d->activeUndoTransaction->Name;
FC_LOG("auto transaction " << getName() << " -> " << activeDoc->getName());
activeDoc->_openTransaction(aname.c_str(), id);
}
return id;
if (name.name.empty()) {
name.name = "<empty>";
}
return 0;
return setActiveTransaction(name, tid);
}
int Document::openTransaction(std::string name, int tid)
{
return openTransaction(TransactionName {.name = name, .temporary = false}, tid);
}
void Document::renameTransaction(const char* name, const int id) const
int Document::_openTransaction(std::string name, int id)
{
if (name && d->activeUndoTransaction && d->activeUndoTransaction->getID() == id) {
if (isTransactionLocked() && id != d->bookedTransaction) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Transaction locked, ignore new transaction '" << name << "'");
}
}
if (isPerformingTransaction() || d->committing) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Cannot open transaction while transacting");
}
return 0;
}
if (d->iUndoMode == 0) {
return 0;
}
// Avoid recursive calls that is possible while
// clearing the redo transactions and will cause
// a double deletion of some transaction and thus
// a segmentation fault
if (d->opentransaction) {
return 0;
}
Base::FlagToggler<> flag(d->opentransaction);
if ((id != 0) && mUndoMap.find(id) != mUndoMap.end()) {
throw Base::RuntimeError("invalid transaction id");
}
if (d->activeUndoTransaction) {
_commitTransaction(true);
}
_clearRedos();
// When id == 0, this creates a new id
// for instance, when there is no global transaction
// from the application to stick to
d->activeUndoTransaction = new Transaction(id);
if (name.empty()) {
name = "<empty>";
}
d->activeUndoTransaction->Name = name;
mUndoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
id = d->activeUndoTransaction->getID();
signalOpenTransaction(*this, name);
Document* transactionInitiator = GetApplication().transactionInitiator(id);
if (transactionInitiator && transactionInitiator != this && !transactionInitiator->hasPendingTransaction()) {
std::string aname = std::format("-> {}", d->activeUndoTransaction->Name);
FC_LOG("auto transaction " << getName() << " -> " << transactionInitiator->getName());
transactionInitiator->_openTransaction(aname, id);
}
return id;
}
void Document::renameTransaction(const std::string& name, const int id) const
{
if (!name.empty() && d->activeUndoTransaction && d->activeUndoTransaction->getID() == id) {
if (boost::starts_with(d->activeUndoTransaction->Name, "-> ")) {
d->activeUndoTransaction->Name.resize(3);
}
@@ -403,48 +427,108 @@ void Document::renameTransaction(const char* name, const int id) const
d->activeUndoTransaction->Name += name;
}
}
int Document::setActiveTransaction(TransactionName name, int tid)
{
// Probably a group transaction situation
if (tid != NullTransaction) {
if (!GetApplication().transactionIsActive(tid)) {
FC_LOG("Could not set active transaction to inactive ID");
return NullTransaction;
}
if (d->bookedTransaction != NullTransaction && d->bookedTransaction != tid && !_commitTransaction(true)) {
FC_LOG("Could not book transaction for document");
return NullTransaction;
}
d->bookedTransaction = tid;
if (GetApplication().transactionTmpName(d->bookedTransaction)) {
GetApplication().setTransactionName(d->bookedTransaction, name);
}
return d->bookedTransaction;
}
// Rename the transaction if it had a tmp name
if (d->bookedTransaction != NullTransaction && GetApplication().transactionTmpName(d->bookedTransaction)) {
GetApplication().setTransactionName(d->bookedTransaction, name);
return d->bookedTransaction;
}
if (d->bookedTransaction != NullTransaction && !_commitTransaction(true)) {
FC_LOG("Could not book transaction for document");
return NullTransaction;
}
d->bookedTransaction = Transaction::getNewID();
GetApplication().setTransactionDescription(d->bookedTransaction, TransactionDescription {.initiator = this, .name = name});
return d->bookedTransaction;
}
void Document::lockTransaction()
{
d->TransactionLock++;
}
void Document::unlockTransaction()
{
if (d->TransactionLock > 0) {
d->TransactionLock--;
}
}
bool Document::isTransactionLocked() const
{
return d->TransactionLock > 0;
}
bool Document::transacting() const
{
return isPerformingTransaction() || d->committing;
}
void Document::_checkTransaction(DocumentObject* pcDelObj, const Property* What, int line)
{
// if the undo is active but no transaction open, open one!
if ((d->iUndoMode != 0) && !isPerformingTransaction()) {
if (!d->activeUndoTransaction) {
if (!testStatus(Restoring) || testStatus(Importing)) {
int tid = 0;
const char* name = GetApplication().getActiveTransaction(&tid);
if (name && tid > 0) {
bool ignore = false;
if (What && What->testStatus(Property::NoModify)) {
ignore = true;
}
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
if (What) {
FC_LOG((ignore ? "ignore" : "auto")
<< " transaction (" << line << ") '" << What->getFullName());
}
else {
FC_LOG((ignore ? "ignore" : "auto") << " transaction (" << line << ") '"
<< name << "' in " << getName());
}
}
if (!ignore) {
_openTransaction(name, tid);
}
return;
if (d->iUndoMode == 0 || isPerformingTransaction() || d->activeUndoTransaction) {
return;
}
if (!testStatus(Restoring) || testStatus(Importing)) {
// Priority to a transaction that has been booked
// explicitly for this document, it there are none
// get a sticky transaction from application
if (!d->bookedTransaction) {
d->bookedTransaction = GetApplication().getGlobalTransaction();
}
if (d->bookedTransaction != NullTransaction) {
std::string name = GetApplication().getTransactionName(d->bookedTransaction);
bool ignore = false;
if (What && What->testStatus(Property::NoModify)) {
ignore = true;
}
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
if (What) {
FC_LOG((ignore ? "ignore" : "auto")
<< " transaction (" << line << ") '" << What->getFullName());
}
else {
FC_LOG((ignore ? "ignore" : "auto") << " transaction (" << line << ") '"
<< name << "' in " << getName());
}
}
if (!pcDelObj) {
return;
}
// When the object is going to be deleted we have to check if it has already been added
// to the undo transactions
std::list<Transaction*>::iterator it;
for (it = mUndoTransactions.begin(); it != mUndoTransactions.end(); ++it) {
if ((*it)->hasObject(pcDelObj)) {
_openTransaction("Delete");
break;
}
if (!ignore) {
_openTransaction(name, d->bookedTransaction);
}
return;
}
}
if (!pcDelObj) {
return;
}
// When the object is going to be deleted we have to check if it has already been added
// to the undo transactions
std::list<Transaction*>::iterator it;
for (it = mUndoTransactions.begin(); it != mUndoTransactions.end(); ++it) {
if ((*it)->hasObject(pcDelObj)) {
_openTransaction("Delete");
break;
}
}
}
@@ -473,29 +557,36 @@ void Document::commitTransaction() // NOLINT
}
if (d->activeUndoTransaction) {
GetApplication().closeActiveTransaction(false, d->activeUndoTransaction->getID());
// This will iterate over all documents and ask them to
// commit their transaction if their ID matches
GetApplication().commitTransaction(d->activeUndoTransaction->getID());
} else {
d->bookedTransaction = 0; // Reset booked transaction even if it was not used
}
}
void Document::_commitTransaction(const bool notify)
bool Document::_commitTransaction(const bool notify)
{
if (isPerformingTransaction()) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Cannot commit transaction while transacting");
}
return;
return false;
}
if (d->committing) {
// for a recursive call return without printing a warning
return;
return false;
}
d->bookedTransaction = 0;
if (d->activeUndoTransaction) {
Base::FlagToggler<> flag(d->committing);
Application::TransactionSignaller signaller(false, true);
const int id = d->activeUndoTransaction->getID();
mUndoTransactions.push_back(d->activeUndoTransaction);
d->activeUndoTransaction = nullptr;
// check the stack for the limits
if (mUndoTransactions.size() > d->UndoMaxStackSize) {
mUndoMap.erase(mUndoTransactions.front()->getID());
@@ -504,11 +595,12 @@ void Document::_commitTransaction(const bool notify)
}
signalCommitTransaction(*this);
// closeActiveTransaction() may call again _commitTransaction()
// commitTransaction() may call again _commitTransaction()
if (notify) {
GetApplication().closeActiveTransaction(false, id);
GetApplication().commitTransaction(id);
}
}
return true;
}
void Document::abortTransaction() const
@@ -520,7 +612,9 @@ void Document::abortTransaction() const
return;
}
if (d->activeUndoTransaction) {
GetApplication().closeActiveTransaction(true, d->activeUndoTransaction->getID());
GetApplication().abortTransaction(d->activeUndoTransaction->getID());
} else {
d->bookedTransaction = 0; // Reset booked transaction even if it was not used
}
}
@@ -532,6 +626,7 @@ void Document::_abortTransaction()
}
}
d->bookedTransaction = 0;
if (d->activeUndoTransaction) {
Base::FlagToggler<bool> flag(d->rollback);
Application::TransactionSignaller signaller(true, true);
@@ -575,7 +670,10 @@ int Document::getTransactionID(const bool undo, unsigned pos) const
for (; pos != 0U; ++rit, --pos) {}
return (*rit)->getID();
}
int Document::getBookedTransactionID() const
{
return d->bookedTransaction;
}
bool Document::isTransactionEmpty() const
{
return !d->activeUndoTransaction;
@@ -3312,7 +3410,7 @@ void Document::_removeObject(DocumentObject* pcObject, RemoveObjectOptions optio
return;
}
TransactionLocker tlock;
TransactionLocker tlock(this);
_checkTransaction(pcObject, nullptr, __LINE__);
+18 -7
View File
@@ -35,6 +35,7 @@
#include "PropertyLinks.h"
#include "PropertyStandard.h"
#include "ExportInfo.h"
#include "TransactionDefs.h"
#include <map>
#include <vector>
@@ -865,7 +866,19 @@ public:
* setup a potential transaction that will only be created if there are
* actual changes.
*/
void openTransaction(const char* name = nullptr);
int openTransaction(TransactionName name, int tid = 0);
int openTransaction(std::string name, int tid = 0);
// If the tid != 0, it will take the transaction id if it exists
int setActiveTransaction(TransactionName name, int tid = 0);
void lockTransaction();
void unlockTransaction();
bool isTransactionLocked() const;
bool transacting() const;
int getBookedTransactionID() const;
/**
* @brief Rename the current transaction.
@@ -875,7 +888,7 @@ public:
* @param[in] name The new name of the transaction.
* @param[in] id The transaction ID to match.
*/
void renameTransaction(const char* name, int id) const;
void renameTransaction(const std::string& name, int id) const;
/**
* @brief Commit the Command transaction.
@@ -1244,7 +1257,7 @@ public:
/// Check if there is any document restoring/importing.
static bool isAnyRestoring();
/// Register a new label.
void registerLabel(const std ::string& newLabel);
/// Unregister a label.
@@ -1409,8 +1422,7 @@ protected:
* This function creates an actual transaction regardless of Application
* AutoTransaction setting.
*/
int _openTransaction(const char* name = nullptr, int id = 0);
int _openTransaction(std::string name = "", int id = 0);
/**
* @brief Commit the Command transaction.
*
@@ -1419,8 +1431,7 @@ protected:
*
* @param notify If true, notify the application to close the transaction.
*/
void _commitTransaction(bool notify = false);
bool _commitTransaction(bool notify = false);
/**
* @brief Abort the running transaction.
*
+9
View File
@@ -447,3 +447,12 @@ class Document(PropertyContainer):
sort: whether to topologically sort the return list
"""
...
def getBookedTransactionID(self) -> int:
"""
getBookedTransactionID() -> int
Returns the currently booked transaction id, which is the id of the current transaction OR the id
the next transaction will stick to if no change has occured yet
"""
...
+9
View File
@@ -1175,6 +1175,15 @@ PyObject* DocumentPy::getDependentDocuments(PyObject* args)
}
PY_CATCH;
}
PyObject* DocumentPy::getBookedTransactionID(PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
int tid = getDocumentPtr()->getBookedTransactionID();
return Py::new_reference_to(Py::Long(tid));
}
Py::Boolean DocumentPy::getRestoring() const
{
+3 -3
View File
@@ -1546,11 +1546,11 @@ void PropertyString::setValue(const char* newValue)
// OnProposedLabelChange has changed the new value to what the current value is
return;
}
if (!propChanges.empty() && !GetApplication().getActiveTransaction()) {
if (!propChanges.empty() && obj->getDocument()->getBookedTransactionID() == 0) {
commit = true;
std::ostringstream str;
str << "Change " << obj->getNameInDocument() << ".Label";
GetApplication().setActiveTransaction(str.str().c_str());
obj->getDocument()->openTransaction(str.str().c_str());
}
}
@@ -1563,7 +1563,7 @@ void PropertyString::setValue(const char* newValue)
}
if (commit) {
GetApplication().closeActiveTransaction();
obj->getDocument()->commitTransaction();
}
}
+59
View File
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2026 Théo Veilleux-Trinh <theo.veilleux.trinh@proton.me>*
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#ifndef APP_TRANSACTIONDEFS_H_
#define APP_TRANSACTIONDEFS_H_
#include <string>
namespace App {
class Document;
constexpr int NullTransaction = 0;
struct TransactionName {
// Name of the transaction as it will appear in the GUI
std::string name;
// If true, the transaction is allowed to be renamed
bool temporary { false };
};
struct TransactionDescription {
// Document on which the transaction was first created
// useful for mass transaction (e.g. delete from an assembly)
// where other documents may use the same transaction id
Document* initiator { nullptr };
TransactionName name;
};
enum class TransactionCloseMode {
Commit = 0,
Abort = 1,
};
}
#endif
+1 -1
View File
@@ -556,4 +556,4 @@ TransactionObject* TransactionFactory::createTransaction(const Base::Type& type)
Base::Console().log("Cannot create transaction object from %s\n", type.getName());
return nullptr;
}
}
+3 -1
View File
@@ -30,6 +30,7 @@
#include <Base/Factory.h>
#include <Base/Persistence.h>
#include <App/PropertyContainer.h>
#include "TransactionDefs.h"
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
@@ -61,6 +62,7 @@ class AppExport Transaction: public Base::Persistence
{
TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
/**
* @brief Construct a transaction.
@@ -71,7 +73,7 @@ public:
* transactions from different document, so that they can be undone/redone
* together.
*/
explicit Transaction(int id = 0);
explicit Transaction(int id = NullTransaction);
~Transaction() override;
+6
View File
@@ -34,6 +34,7 @@
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <optional>
#include <boost/bimap.hpp>
#include <boost/graph/adjacency_list.hpp>
@@ -93,6 +94,11 @@ struct DocumentP
int iUndoMode {0};
unsigned int UndoMemSize {0};
unsigned int UndoMaxStackSize {20};
unsigned int TransactionLock {0};
// Id and name that the next transaction will take
// as soon as there is a change to the document
int bookedTransaction { 0 };
std::string programVersion;
mutable HasherMap hashers;
std::multimap<const App::DocumentObject*, std::unique_ptr<App::DocumentObjectExecReturn>>
+68 -17
View File
@@ -227,7 +227,7 @@ struct ApplicationP
std::map<const App::Document*, Gui::Document*> documents;
/// Active document
Gui::Document* activeDocument {nullptr};
Gui::Document* editDocument {nullptr};
std::vector<Gui::Document*> editDocuments;
MacroManager* macroMngr;
PreferencePackManager* prefPackManager;
@@ -1285,6 +1285,9 @@ void Application::slotActiveDocument(const App::Document& Doc)
Py::Module("FreeCADGui").setAttr(std::string("ActiveDocument"), Py::None());
}
}
if (!d->activeDocument->workbench().empty()) {
activateWorkbench(d->activeDocument->workbench().c_str());
}
// Update the application to show the unit change
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
@@ -1491,26 +1494,65 @@ Gui::Document* Application::activeDocument() const
Gui::Document* Application::editDocument() const
{
return d->editDocument;
}
Gui::MDIView* Application::editViewOfNode(SoNode* node) const
{
return d->editDocument ? d->editDocument->getViewOfNode(node) : nullptr;
}
void Application::setEditDocument(Gui::Document* doc)
{
if (!doc) {
d->editDocument = nullptr;
if (d->editDocuments.empty()) {
return nullptr;
}
else if (doc == d->editDocument) {
return d->editDocuments[0];
}
Gui::Document* Application::editDocument(const std::function<bool(Gui::Document*)>& eval)
{
auto found = std::ranges::find_if(d->editDocuments, eval);
return found == d->editDocuments.end() ? nullptr : *found;
}
std::vector<Gui::Document*> Application::editDocuments() const
{
return d->editDocuments;
}
bool Application::isInEdit(Gui::Document* pcDocument) const
{
return std::ranges::find(d->editDocuments, pcDocument) != d->editDocuments.end();
}
void Application::unsetEditDocument(Gui::Document* pcDocument)
{
if (std::erase(d->editDocuments, pcDocument) == 0) {
return;
}
for (auto& v : d->documents) {
v.second->_resetEdit();
pcDocument->_resetEdit();
updateActions();
}
void Application::unsetEditDocumentIf(const std::function<bool(Gui::Document*)>& eval)
{
std::erase_if(d->editDocuments, [&](Gui::Document* doc) {
if (eval(doc)) {
doc->_resetEdit();
return true;
}
return false;
});
updateActions();
}
Gui::MDIView* Application::editViewOfNode(SoNode* node) const
{
for (auto editDoc : d->editDocuments) {
if (Gui::MDIView* view = editDoc->getViewOfNode(node)) {
return view;
}
}
d->editDocument = doc;
return nullptr;
}
void Application::setEditDocument(Gui::Document* pcDocument)
{
if (pcDocument == nullptr) {
return;
}
if (std::ranges::find(d->editDocuments, pcDocument) != d->editDocuments.end()) {
return;
}
d->editDocuments.push_back(pcDocument);
updateActions();
}
@@ -1531,12 +1573,18 @@ void Application::setActiveDocument(Gui::Document* pcDocument)
return;
}
}
if (d->activeDocument) {
d->activeDocument->setIsActive(false);
}
d->activeDocument = pcDocument;
std::string nameApp, nameGui;
// This adds just a line to the macro file but does not set the active document
// Macro recording of this is problematic, thus it's written out as comment.
if (pcDocument) {
pcDocument->setIsActive(true);
nameApp += "App.setActiveDocument(\"";
nameApp += pcDocument->getDocument()->getName();
nameApp += "\")\n";
@@ -1893,6 +1941,9 @@ bool Application::activateWorkbench(const char* name)
}
newWb->activated();
}
if (activeDocument()) {
activeDocument()->setWorkbench(name);
}
}
catch (Py::Exception&) {
Base::PyException e; // extract the Python error text
+17 -2
View File
@@ -181,11 +181,26 @@ public:
Gui::Document* activeDocument() const;
/// Set the active document
void setActiveDocument(Gui::Document* pcDocument);
/// Getter for the editing document
/// Getter for the editing document, will be removed soon
Gui::Document* editDocument() const;
/// Getter for the first editing document that matches a functor
Gui::Document* editDocument(const std::function<bool(Gui::Document*)>& eval);
/// Getter for all currently editing documents, all pointers are guaranteed to be non-null
std::vector<Gui::Document*> editDocuments() const;
// Returns true if the document is in edit (will make more sense once the edit document it is a
// vector)
bool isInEdit(Gui::Document* pcDocument) const;
// Reset edit if eval returns true for a document in edit
Gui::MDIView* editViewOfNode(SoNode* node) const;
/// Set editing document, which will reset editing of all other document
/// Adds a document in edit
void setEditDocument(Gui::Document* pcDocument);
// After this, isInEdit(pcDocument) returns false
void unsetEditDocument(Gui::Document* pcDocument);
void unsetEditDocumentIf(const std::function<bool(Gui::Document*)>& eval);
/** Retrieves a pointer to the Gui::Document whose App::Document has the name \a name.
* If no such document exists 0 is returned.
*/
+3
View File
@@ -490,6 +490,7 @@ SET(Gui_UIC_SRCS
InputVector.ui
Placement.ui
TaskTransform.ui
TaskCommandLink.ui
TextureMapping.ui
TaskView/TaskAppearance.ui
TaskView/TaskOrientation.ui
@@ -578,6 +579,7 @@ SET(Dialog_CPP_SRCS
TaskDlgRelocation.cpp
Dialogs/DlgCheckableMessageBox.cpp
TaskTransform.cpp
TaskCommandLink.cpp
Dialogs/DlgUndoRedo.cpp
InputVector.cpp
Placement.cpp
@@ -620,6 +622,7 @@ SET(Dialog_HPP_SRCS
Dialogs/DlgVersionMigrator.h
TaskDlgRelocation.h
TaskTransform.h
TaskCommandLink.h
Dialogs/DlgUndoRedo.h
InputVector.h
Placement.h
+27 -4
View File
@@ -21,6 +21,7 @@
***************************************************************************/
#include <limits>
#include <functional>
#include <Inventor/actions/SoGetBoundingBoxAction.h>
#include <Inventor/nodes/SoClipPlane.h>
@@ -29,6 +30,8 @@
#include <QDockWidget>
#include <QPointer>
#include <App/Application.h>
#include "Clipping.h"
#include "ui_Clipping.h"
#include "DockWindowManager.h"
@@ -51,6 +54,10 @@ public:
bool flipY {false};
bool flipZ {false};
SoTimerSensor* sensor;
App::Document* shownOn {nullptr};
QDockWidget* dockWidget {nullptr};
fastsignals::scoped_connection activeDocConnection;
Private()
{
clipX = new SoClipPlane();
@@ -99,7 +106,7 @@ public:
/* TRANSLATOR Gui::Dialog::Clipping */
Clipping::Clipping(Gui::View3DInventor* view, QWidget* parent)
Clipping::Clipping(Gui::View3DInventor* view, App::Document* showOn, QWidget* parent)
: QDialog(parent)
, d(new Private)
{
@@ -124,6 +131,7 @@ Clipping::Clipping(Gui::View3DInventor* view, QWidget* parent)
d->ui.dirZ->setRange(-max, max);
d->ui.dirZ->setSingleStep(0.1f);
d->ui.dirZ->setValue(1.0f);
d->shownOn = showOn;
d->view = view;
View3DInventorViewer* viewer = view->getViewer();
@@ -190,14 +198,15 @@ Clipping::Clipping(Gui::View3DInventor* view, QWidget* parent)
}
}
Clipping* Clipping::makeDockWidget(Gui::View3DInventor* view)
Clipping* Clipping::makeDockWidget(Gui::View3DInventor* view, App::Document* showOn)
{
// embed this dialog into a QDockWidget
auto clipping = new Clipping(view);
auto clipping = new Clipping(view, showOn);
Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance();
QDockWidget* dw = pDockMgr->addDockWindow("Clipping", clipping, Qt::LeftDockWidgetArea);
dw->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
dw->show();
clipping->d->dockWidget = dw;
return clipping;
}
@@ -205,6 +214,7 @@ Clipping* Clipping::makeDockWidget(Gui::View3DInventor* view)
/** Destroys the object and frees any allocated resources */
Clipping::~Clipping()
{
d->activeDocConnection.disconnect();
d->node->removeChild(d->clipX);
d->node->removeChild(d->clipY);
d->node->removeChild(d->clipZ);
@@ -216,6 +226,8 @@ Clipping::~Clipping()
void Clipping::setupConnections()
{
// clang-format off
d->activeDocConnection = App::GetApplication().signalActiveDocument.connect(
std::bind(&Clipping::onActiveDocument, this, std::placeholders::_1));
connect(d->ui.groupBoxX, &QGroupBox::toggled,
this, &Clipping::onGroupBoxXToggled);
connect(d->ui.groupBoxY, &QGroupBox::toggled,
@@ -259,7 +271,18 @@ void Clipping::reject()
dw->deleteLater();
}
}
void Clipping::onActiveDocument(const App::Document& doc)
{
if (!d || !d->dockWidget) {
return;
}
if (&doc == d->shownOn) {
d->dockWidget->show();
}
else {
d->dockWidget->hide();
}
}
void Clipping::onGroupBoxXToggled(bool on)
{
if (on) {
+8 -2
View File
@@ -26,6 +26,11 @@
#include <QDialog>
#include <FCGlobal.h>
namespace App
{
class Document;
}
namespace Gui
{
class View3DInventor;
@@ -40,12 +45,13 @@ class GuiExport Clipping: public QDialog
Q_OBJECT
public:
static Clipping* makeDockWidget(Gui::View3DInventor*);
Clipping(Gui::View3DInventor* view, QWidget* parent = nullptr);
static Clipping* makeDockWidget(Gui::View3DInventor*, App::Document* showOn);
Clipping(Gui::View3DInventor* view, App::Document* showOn, QWidget* parent = nullptr);
~Clipping() override;
protected:
void setupConnections();
void onActiveDocument(const App::Document& doc);
void onGroupBoxXToggled(bool);
void onGroupBoxYToggled(bool);
void onGroupBoxZToggled(bool);
+59 -24
View File
@@ -36,7 +36,7 @@
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/AutoTransaction.h>
#include <App/Transactions.h>
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/Interpreter.h>
@@ -45,6 +45,7 @@
#include "Command.h"
#include "Action.h"
#include "App/Application.h"
#include "Application.h"
#include "BitmapFactory.h"
#include "Control.h"
@@ -447,11 +448,6 @@ void Command::invoke(int i, TriggerSource trigger)
void Command::_invoke(int id, bool disablelog)
{
try {
// Because Transaction now captures ViewObject changes, auto named
// transaction is disabled here to avoid too many unnecessary transactions.
//
App::AutoTransaction committer(nullptr, true);
// set the application module type for the macro
getGuiApplication()->macroManager()->setModule(sAppModule);
@@ -463,7 +459,6 @@ void Command::_invoke(int id, bool disablelog)
// check if it really works NOW (could be a delay between click deactivation of the button)
if (isActive()) {
auto manager = getGuiApplication()->macroManager();
auto editDoc = getGuiApplication()->editDocument();
if (!logdisabler) {
activated(id);
@@ -499,12 +494,10 @@ void Command::_invoke(int id, bool disablelog)
}
getMainWindow()->updateActions();
// If this command starts an editing, let the transaction persist
if (!editDoc && getGuiApplication()->editDocument()) {
committer.setEnable(false);
}
}
// here we assume that the overriden activated() function
// commited, aborted or gave the transaction id to a dialog
currentTransactionID = App::NullTransaction; // Get ready for next invoke
}
catch (const Base::SystemExitException&) {
throw;
@@ -550,9 +543,10 @@ void Command::testActive()
if (!(eType & ForEdit)) { // special case for commands which are only in some edit modes active
if ((!Gui::Control().isAllowedAlterDocument() && eType & AlterDoc)
|| (!Gui::Control().isAllowedAlterView() && eType & Alter3DView)
|| (!Gui::Control().isAllowedAlterSelection() && eType & AlterSelection)) {
App::Document* doc = getDocument();
if ((!Gui::Control().isAllowedAlterDocument(doc) && eType & AlterDoc)
|| (!Gui::Control().isAllowedAlterView(doc) && eType & Alter3DView)
|| (!Gui::Control().isAllowedAlterSelection(doc) && eType & AlterSelection)) {
_pcAction->setEnabled(false);
return;
}
@@ -681,27 +675,68 @@ QString Command::translatedGroupName() const
* operation default is the Command name.
* @see CommitCommand(),AbortCommand()
*/
void Command::openCommand(const char* sCmdName)
int Command::openCommand(App::TransactionName name)
{
if (!sCmdName) {
sCmdName = "Command";
currentTransactionID = openActiveDocumentCommand(name);
return currentTransactionID;
}
int Command::openCommand(std::string name)
{
return openCommand(App::TransactionName {.name = name, .temporary = false});
}
int Command::openActiveDocumentCommand(App::TransactionName name, int tid)
{
if (Gui::Document* guidoc = getGuiApplication()->activeDocument()) {
return guidoc->getDocument()->setActiveTransaction(name, tid);
}
App::GetApplication().setActiveTransaction(sCmdName);
return 0;
}
int Command::openActiveDocumentCommand(std::string name, int tid)
{
return openActiveDocumentCommand(App::TransactionName {.name = name, .temporary = false}, tid);
}
void Command::rename(const std::string& name)
{
App::GetApplication().setTransactionName(
currentTransactionID,
App::TransactionName {.name = name, .temporary = false}
);
}
void Command::commitCommand()
{
App::GetApplication().closeActiveTransaction();
commitCommand(currentTransactionID);
currentTransactionID = App::NullTransaction;
}
void Command::commitCommand(int tid)
{
if (tid != App::NullTransaction) {
App::GetApplication().commitTransaction(tid);
}
}
void Command::abortCommand()
{
App::GetApplication().closeActiveTransaction(true);
abortCommand(currentTransactionID);
currentTransactionID = App::NullTransaction;
}
void Command::abortCommand(int tid)
{
if (tid != App::NullTransaction) {
App::GetApplication().abortTransaction(tid);
}
}
int Command::transactionID() const
{
return currentTransactionID;
}
void Command::resetTransactionID()
{
currentTransactionID = App::NullTransaction;
}
bool Command::hasPendingCommand()
{
return !!App::GetApplication().getActiveTransaction();
App::Document* doc = App::GetApplication().getActiveDocument();
return doc && doc->getBookedTransactionID() != App::NullTransaction;
}
bool Command::_blockCmd = false;
+18 -3
View File
@@ -473,11 +473,24 @@ public:
/** @name Helper methods for the Undo/Redo and Update handling */
//@{
/// Open a new Undo transaction on the active document
static void openCommand(const char* sName = nullptr);
int openCommand(App::TransactionName name);
int openCommand(std::string name);
static int openActiveDocumentCommand(App::TransactionName name, int tid = App::NullTransaction);
static int openActiveDocumentCommand(std::string name, int tid = App::NullTransaction);
void rename(const std::string& name);
/// Commit the Undo transaction on the active document
static void commitCommand();
void commitCommand();
static void commitCommand(int tid);
/// Abort the Undo transaction on the active document
static void abortCommand();
void abortCommand();
static void abortCommand(int tid);
int transactionID() const;
void resetTransactionID();
/// Check if an Undo transaction is open on the active document
static bool hasPendingCommand();
/// Updates the (active) document (propagate changes)
@@ -715,6 +728,8 @@ protected:
/// Indicate if the command shall log to MacroManager
bool bCanLog;
//@}
int currentTransactionID {0}; // TransactionID created in _invoke
private:
static int _busy;
bool bEnabled;
+50 -25
View File
@@ -710,6 +710,7 @@ StdCmdNew::StdCmdNew()
sStatusTip = sToolTipText;
sPixmap = "document-new";
sAccel = keySequenceToAccel(QKeySequence::New);
eType = NoTransaction;
}
void StdCmdNew::activated(int iMsg)
@@ -1446,34 +1447,50 @@ void StdCmdDelete::activated(int iMsg)
{
Q_UNUSED(iMsg);
std::set<App::Document*> docs;
int tid = 0;
try {
openCommand(QT_TRANSLATE_NOOP("Command", "Delete"));
std::set<App::Document*> docs;
std::vector<App::TransactionLocker> tlocks;
auto manageDocCommand = [&tid, &tlocks](App::Document* doc) {
// The tid will not be updated if non-zero
tid = doc->openTransaction(QT_TRANSLATE_NOOP("Command", "Delete"), tid);
tlocks.emplace_back(doc);
};
if (getGuiApplication()->sendHasMsgToFocusView(getName())) {
commitCommand();
// no command has been opened yet so we can skip this commit
// commitCommand();
return;
}
App::TransactionLocker tlock;
// Ensure that the document from which we send the command
// can undo it (e.g delete a subobject of an assembly
// from the assembly file)
manageDocCommand(getActiveGuiDocument()->getDocument());
Gui::getMainWindow()->setUpdatesEnabled(false);
auto editDoc = Application::Instance->editDocument();
ViewProviderDocumentObject* vpedit = nullptr;
if (editDoc) {
vpedit = freecad_cast<ViewProviderDocumentObject*>(editDoc->getInEdit());
}
if (vpedit && !vpedit->acceptDeletionsInEdit()) {
for (auto& sel : Selection().getSelectionEx(editDoc->getDocument()->getName())) {
if (sel.getObject() == vpedit->getObject()) {
if (!sel.getSubNames().empty()) {
vpedit->onDelete(sel.getSubNames());
docs.insert(editDoc->getDocument());
bool deletedSelectionOfEditDocument = false;
std::vector<Gui::Document*> editDocs = Application::Instance->editDocuments();
for (auto& editDoc : editDocs) {
auto vpedit = freecad_cast<ViewProviderDocumentObject*>(editDoc->getInEdit());
// In practice, no ViewProviderDocumentObject accepts deletion in edit - 2025-06-17
if (vpedit && !vpedit->acceptDeletionsInEdit()) {
for (auto& sel : Selection().getSelectionEx(editDoc->getDocument()->getName())) {
if (sel.getObject() == vpedit->getObject()) {
if (!sel.getSubNames().empty()) {
deletedSelectionOfEditDocument = true;
manageDocCommand(editDoc->getDocument());
vpedit->onDelete(sel.getSubNames());
docs.insert(editDoc->getDocument());
}
break;
}
break;
}
}
}
else {
if (!deletedSelectionOfEditDocument) {
std::set<QString> affectedLabels;
bool more = false;
auto sels = Selection().getSelectionEx();
@@ -1548,6 +1565,7 @@ void StdCmdDelete::activated(int iMsg)
auto obj = sel.getObject();
Gui::ViewProvider* vp = Application::Instance->getViewProvider(obj);
if (vp) {
manageDocCommand(obj->getDocument());
// ask the ViewProvider if it wants to do some clean up
if (vp->onDelete(sel.getSubNames())) {
docs.insert(obj->getDocument());
@@ -1581,6 +1599,8 @@ void StdCmdDelete::activated(int iMsg)
QString::fromLatin1(e.what())
);
e.reportException();
App::GetApplication().abortTransaction(tid);
tid = 0;
}
catch (...) {
QMessageBox::critical(
@@ -1588,8 +1608,11 @@ void StdCmdDelete::activated(int iMsg)
QObject::tr("Delete Failed"),
QStringLiteral("Unknown error")
);
App::GetApplication().abortTransaction(tid);
tid = 0;
}
commitCommand();
App::GetApplication().commitTransaction(tid);
Gui::getMainWindow()->setUpdatesEnabled(true);
Gui::getMainWindow()->update();
}
@@ -1635,7 +1658,8 @@ void StdCmdRefresh::activated([[maybe_unused]] int iMsg)
return;
}
App::AutoTransaction trans((eType & NoTransaction) ? nullptr : "Recompute");
App::AutoTransaction trans((eType & NoTransaction) ? 0 : openActiveDocumentCommand("Recompute"));
try {
doCommand(Doc, "App.activeDocument().recompute(None,True,True)");
}
@@ -1738,7 +1762,7 @@ void StdCmdPlacement::activated(int iMsg)
plm->clearSelection();
}
}
Gui::Control().showDialog(plm);
Gui::Control().showDialog(plm, getDocument());
}
bool StdCmdPlacement::isActive()
@@ -2133,9 +2157,10 @@ protected:
return;
}
openCommand(QT_TRANSLATE_NOOP("Command", "Paste expressions"));
int tid = App::NullTransaction;
try {
for (auto& v : exprs) {
tid = v.first->openTransaction(QT_TRANSLATE_NOOP("Command", "Paste expressions"), tid);
for (auto& v2 : v.second) {
auto& expressions = v2.second;
auto old = v2.first->getExpressions();
@@ -2152,13 +2177,13 @@ protected:
}
}
}
commitCommand();
App::GetApplication().commitTransaction(tid);
}
catch (const Base::Exception& e) {
abortCommand();
App::GetApplication().abortTransaction(tid);
QMessageBox::critical(
getMainWindow(),
QObject::tr("Failed to Paste Expressions"),
QObject::tr("Failed to paste expressions"),
QString::fromLatin1(e.what())
);
e.reportException();
+62 -44
View File
@@ -34,9 +34,11 @@
#include "Action.h"
#include "Application.h"
#include "Command.h"
#include "Control.h"
#include "Document.h"
#include "MainWindow.h"
#include "Selection.h"
#include "TaskCommandLink.h"
#include "Tree.h"
#include "ViewProviderDocumentObject.h"
#include "WaitCursor.h"
@@ -280,6 +282,51 @@ void StdCmdLinkMake::activated(int)
return;
}
auto exec = [=](std::vector<App::DocumentObject*> objs) {
doc->openTransaction(QT_TRANSLATE_NOOP("Command", "Make link"));
try {
if (objs.empty()) {
std::string name = doc->getUniqueObjectName("Link");
Command::doCommand(
Command::Doc,
"App.getDocument('%s').addObject('App::Link','%s')",
doc->getName(),
name.c_str()
);
Selection().addSelection(doc->getName(), name.c_str());
}
else {
for (auto obj : objs) {
std::string name = doc->getUniqueObjectName("Link");
Command::doCommand(
Command::Doc,
"App.getDocument('%s').addObject('App::Link','%s').setLink(App.getDocument("
"'%s'"
").%s)",
doc->getName(),
name.c_str(),
obj->getDocument()->getName(),
obj->getNameInDocument()
);
setLinkLabel(obj, doc->getName(), name.c_str());
Selection().addSelection(doc->getName(), name.c_str());
}
}
Selection().selStackPush();
doc->commitTransaction();
}
catch (const Base::Exception& e) {
doc->abortTransaction();
QMessageBox::critical(
getMainWindow(),
QObject::tr("Create link failed"),
QString::fromLatin1(e.what())
);
e.reportException();
}
};
std::set<App::DocumentObject*> objs;
for (auto& sel : Selection().getCompleteSelection()) {
if (sel.pObject && sel.pObject->isAttachedToDocument()) {
@@ -287,48 +334,13 @@ void StdCmdLinkMake::activated(int)
}
}
Selection().selStackPush();
Selection().clearCompleteSelection();
Command::openCommand(QT_TRANSLATE_NOOP("Command", "Make link"));
try {
if (objs.empty()) {
std::string name = doc->getUniqueObjectName("Link");
Command::doCommand(
Command::Doc,
"App.getDocument('%s').addObject('App::Link','%s')",
doc->getName(),
name.c_str()
);
Selection().addSelection(doc->getName(), name.c_str());
}
else {
for (auto obj : objs) {
std::string name = doc->getUniqueObjectName("Link");
Command::doCommand(
Command::Doc,
"App.getDocument('%s').addObject('App::Link','%s').setLink(App.getDocument('%s'"
").%s)",
doc->getName(),
name.c_str(),
obj->getDocument()->getName(),
obj->getNameInDocument()
);
setLinkLabel(obj, doc->getName(), name.c_str());
Selection().addSelection(doc->getName(), name.c_str());
}
}
Selection().selStackPush();
Command::commitCommand();
if (objs.empty()) {
Gui::Control().showDialog(new TaskCommandLinkDialog(exec));
}
catch (const Base::Exception& e) {
Command::abortCommand();
QMessageBox::critical(
getMainWindow(),
QObject::tr("Create link failed"),
QString::fromLatin1(e.what())
);
e.reportException();
else {
Selection().selStackPush();
Selection().clearCompleteSelection();
exec(std::vector<App::DocumentObject*>(objs.begin(), objs.end()));
}
}
@@ -476,7 +488,7 @@ static void linkConvert(bool unlink)
// now, do actual operation
const char* transactionName = unlink ? "Unlink" : "Replace with link";
Command::openCommand(transactionName);
int tid = 0;
try {
std::unordered_map<App::DocumentObject*, App::DocumentObjectT> recomputeSet;
for (auto& v : infos) {
@@ -493,6 +505,12 @@ static void linkConvert(bool unlink)
recomputeSet.emplace(parent, parent);
}
auto doc = parent->getDocument();
tid = doc->openTransaction(
App::TransactionName {.name = transactionName, .temporary = false},
tid
);
App::DocumentObject* replaceObj;
if (unlink) {
replaceObj = obj->getLinkedObject(false);
@@ -552,10 +570,10 @@ static void linkConvert(bool unlink)
recomputes.front()->getDocument()->recompute(recomputes);
}
Command::commitCommand();
App::GetApplication().commitTransaction(tid);
}
catch (const Base::Exception& e) {
Command::abortCommand();
App::GetApplication().abortTransaction(tid);
auto title = unlink ? QObject::tr("Unlink failed") : QObject::tr("Replace link failed");
QMessageBox::critical(getMainWindow(), title, QString::fromLatin1(e.what()));
e.reportException();
+2
View File
@@ -101,6 +101,8 @@ void StdCmdPart::activated(int iMsg)
PartName.c_str()
);
commitCommand();
updateActive();
}
+64 -5
View File
@@ -21,6 +21,9 @@
***************************************************************************/
#include <sstream>
#include <vector>
#include <tuple>
#include <Inventor/events/SoMouseButtonEvent.h>
#include <Inventor/nodes/SoOrthographicCamera.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
@@ -640,7 +643,34 @@ void StdCmdFreezeViews::languageChange()
// Std_ToggleClipPlane
//===========================================================================
DEF_STD_CMD_AC(StdCmdToggleClipPlane)
class StdCmdToggleClipPlane: public Gui::Command
{
public:
StdCmdToggleClipPlane();
virtual ~StdCmdToggleClipPlane()
{}
virtual const char* className() const
{
return "StdCmdToggleClipPlane";
}
protected:
virtual void activated(int iMsg);
virtual bool isActive(void);
virtual Gui::Action* createAction(void);
private:
StdCmdToggleClipPlane(const StdCmdToggleClipPlane&) = delete;
StdCmdToggleClipPlane(StdCmdToggleClipPlane&&) = delete;
StdCmdToggleClipPlane& operator=(const StdCmdToggleClipPlane&) = delete;
StdCmdToggleClipPlane& operator=(StdCmdToggleClipPlane&&) = delete;
void garbageCollect();
bool hasClipping(App::Document* doc) const;
private:
std::vector<std::pair<App::Document*, QPointer<Gui::Dialog::Clipping>>> clippings;
};
StdCmdToggleClipPlane::StdCmdToggleClipPlane()
: Command("Std_ToggleClipPlane")
@@ -663,11 +693,15 @@ Action* StdCmdToggleClipPlane::createAction()
void StdCmdToggleClipPlane::activated(int iMsg)
{
Q_UNUSED(iMsg);
static QPointer<Gui::Dialog::Clipping> clipping = nullptr;
if (!clipping) {
App::Document* doc = getActiveDocument();
if (!doc) {
return;
}
garbageCollect(); // remove dead pointers
if (!hasClipping(doc)) {
auto view = qobject_cast<View3DInventor*>(getMainWindow()->activeWindow());
if (view) {
clipping = Gui::Dialog::Clipping::makeDockWidget(view);
clippings.push_back(std::make_pair(doc, Gui::Dialog::Clipping::makeDockWidget(view, doc)));
}
}
}
@@ -678,6 +712,31 @@ bool StdCmdToggleClipPlane::isActive()
return view ? true : false;
}
void StdCmdToggleClipPlane::garbageCollect()
{
// We assume the vector to be small
std::vector<std::pair<App::Document*, QPointer<Gui::Dialog::Clipping>>> newClippings;
newClippings.reserve(clippings.size());
std::copy_if(
clippings.begin(),
clippings.end(),
std::back_inserter(newClippings),
[](const std::pair<App::Document*, QPointer<Gui::Dialog::Clipping>>& clipPair) -> bool {
return clipPair.second != nullptr;
}
);
clippings = newClippings;
}
bool StdCmdToggleClipPlane::hasClipping(App::Document* doc) const
{
return std::ranges::find(
clippings,
doc,
&std::pair<App::Document*, QPointer<Gui::Dialog::Clipping>>::first
)
!= clippings.end();
}
//===========================================================================
// StdCmdDrawStyle
//===========================================================================
@@ -3431,7 +3490,7 @@ StdCmdTextureMapping::StdCmdTextureMapping()
void StdCmdTextureMapping::activated(int iMsg)
{
Q_UNUSED(iMsg);
Gui::Control().showDialog(new Gui::Dialog::TaskTextureMapping);
Gui::Control().showDialog(new Gui::Dialog::TaskTextureMapping, getDocument());
}
bool StdCmdTextureMapping::isActive()
+110 -51
View File
@@ -26,10 +26,12 @@
#include <QDockWidget>
#include <QPointer>
#include <App/AutoTransaction.h>
#include <App/Document.h>
#include <Gui/Application.h>
#include <Gui/ComboView.h>
#include <Gui/DockWindowManager.h>
#include <Gui/MainWindow.h>
#include <Gui/Document.h>
#include "Control.h"
#include "BitmapFactory.h"
@@ -45,8 +47,7 @@ using namespace std;
ControlSingleton* ControlSingleton::_pcSingleton = nullptr;
ControlSingleton::ControlSingleton()
: ActiveDialog(nullptr)
, oldTabIndex(-1)
: oldTabIndex(-1)
{}
ControlSingleton::~ControlSingleton() = default;
@@ -145,14 +146,28 @@ void ControlSingleton::showModelView()
}
}
void ControlSingleton::showDialog(Gui::TaskView::TaskDialog* dlg)
void ControlSingleton::showDialog(Gui::TaskView::TaskDialog* dlg, App::Document* attachTo)
{
attachTo = docOrDefault(attachTo);
if (!attachTo) {
qWarning() << "ControlSingleton::showDialog: Cannot attach to nullptr document";
return;
}
Gui::TaskView::TaskView* taskView = taskPanel();
// should return the pointer to combo view
if (!taskView) {
return;
}
// only one dialog at a time, print a warning instead of raising an assert
if (ActiveDialog && ActiveDialog != dlg) {
TaskView::TaskDialog* foundDialog = taskView->dialog(attachTo);
if (!dlg || foundDialog == dlg) {
if (dlg) {
qWarning() << "ControlSingleton::showDialog: Can't show "
<< dlg->metaObject()->className()
<< " since there is already an active task dialog";
<< " since there is already an active task dialog in Document "
<< (attachTo ? attachTo->getName() : "''");
}
else {
qWarning() << "ControlSingleton::showDialog: Task dialog is null";
@@ -160,69 +175,87 @@ void ControlSingleton::showDialog(Gui::TaskView::TaskDialog* dlg)
return;
}
// Since the caller sets up a modeless task panel, it indicates intention
// for prolonged editing. So disable auto transaction in the current call
// stack.
// Do this before showing the dialog because its open() function is called
// which may open a transaction but fails when auto transaction is still active.
App::AutoTransaction::setEnable(false);
bool addedDialog = taskView->showDialog(dlg, attachTo);
Gui::TaskView::TaskView* taskView = taskPanel();
// should return the pointer to combo view
if (taskView) {
taskView->showDialog(dlg);
// make sure that the combo view is shown
auto dw = qobject_cast<QDockWidget*>(taskView->parentWidget());
if (dw) {
aboutToShowDialog(dw);
dw->setVisible(true);
dw->toggleViewAction()->setVisible(true);
dw->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
}
if (ActiveDialog == dlg) {
return; // dialog is already defined
}
ActiveDialog = dlg;
connect(dlg, &TaskView::TaskDialog::aboutToBeDestroyed, this, &ControlSingleton::closedDialog);
// make sure that the combo view is shown
if (auto dw = qobject_cast<QDockWidget*>(taskView->parentWidget())) {
aboutToShowDialog(dw);
dw->setVisible(true);
dw->toggleViewAction()->setVisible(true);
dw->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
}
if (!addedDialog) {
return; // dialog is already defined
}
connect(dlg, &TaskView::TaskDialog::aboutToBeDestroyed, this, [this, attachTo] {
closedDialog(attachTo);
});
}
Gui::TaskView::TaskDialog* ControlSingleton::activeDialog() const
Gui::TaskView::TaskDialog* ControlSingleton::activeDialog(App::Document* attachedTo) const
{
return ActiveDialog;
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
return nullptr;
}
Gui::TaskView::TaskView* taskView = taskPanel();
if (taskView) {
return taskView->dialog(attachedTo);
}
return nullptr;
}
void ControlSingleton::accept()
void ControlSingleton::accept(App::Document* attachedTo)
{
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
qWarning() << "ControlSingleton::accept: Cannot accept dialog of nullptr document";
return;
}
Gui::TaskView::TaskView* taskView = taskPanel();
if (taskView) {
taskView->accept();
taskView->accept(attachedTo);
qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
}
}
void ControlSingleton::reject()
void ControlSingleton::reject(App::Document* attachedTo)
{
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
qWarning() << "ControlSingleton::reject: Cannot reject dialog of nullptr document";
return;
}
Gui::TaskView::TaskView* taskView = taskPanel();
if (taskView) {
taskView->reject();
taskView->reject(attachedTo);
qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
}
}
void ControlSingleton::closeDialog()
void ControlSingleton::closeDialog(App::Document* attachedTo)
{
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
qWarning() << "ControlSingleton::closeDialog: Cannot close dialog of nullptr document";
return;
}
Gui::TaskView::TaskView* taskView = taskPanel();
if (taskView) {
taskView->removeDialog();
taskView->removeDialog(attachedTo);
}
}
void ControlSingleton::closedDialog()
void ControlSingleton::closedDialog(App::Document* attachedTo)
{
ActiveDialog = nullptr;
Gui::TaskView::TaskView* taskView = taskPanel();
assert(taskView);
@@ -237,31 +270,57 @@ void ControlSingleton::closedDialog()
}
}
bool ControlSingleton::isAllowedAlterDocument() const
bool ControlSingleton::isAllowedAlterDocument(App::Document* attachedTo) const
{
if (ActiveDialog) {
return ActiveDialog->isAllowedAlterDocument();
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
return true;
}
Gui::TaskView::TaskDialog* dlg = activeDialog(attachedTo);
if (dlg) {
return dlg->isAllowedAlterDocument();
}
return true;
}
bool ControlSingleton::isAllowedAlterView() const
bool ControlSingleton::isAllowedAlterView(App::Document* attachedTo) const
{
if (ActiveDialog) {
return ActiveDialog->isAllowedAlterView();
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
return true;
}
Gui::TaskView::TaskDialog* dlg = activeDialog(attachedTo);
if (dlg) {
return dlg->isAllowedAlterView();
}
return true;
}
bool ControlSingleton::isAllowedAlterSelection() const
bool ControlSingleton::isAllowedAlterSelection(App::Document* attachedTo) const
{
if (ActiveDialog) {
return ActiveDialog->isAllowedAlterSelection();
attachedTo = docOrDefault(attachedTo);
if (!attachedTo) {
return true;
}
Gui::TaskView::TaskDialog* dlg = activeDialog(attachedTo);
if (dlg) {
return dlg->isAllowedAlterSelection();
}
return true;
}
App::Document* ControlSingleton::docOrDefault(App::Document* attachedTo)
{
if (!attachedTo && Application::Instance->activeDocument()) {
attachedTo = Application::Instance->activeDocument()->getDocument();
}
return attachedTo;
}
// -------------------------------------------
ControlSingleton& ControlSingleton::instance()
+16 -11
View File
@@ -64,8 +64,9 @@ public:
*/
//@{
/// This method starts a task dialog in the task view
void showDialog(Gui::TaskView::TaskDialog* dlg);
Gui::TaskView::TaskDialog* activeDialog() const;
/// The dialog is relative to a specific document
void showDialog(Gui::TaskView::TaskDialog* dlg, App::Document* attachTo = nullptr);
Gui::TaskView::TaskDialog* activeDialog(App::Document* attachedTo = nullptr) const;
// void closeDialog();
//@}
@@ -81,28 +82,29 @@ public:
If a task dialog is open then it indicates whether this task dialog allows other commands to
modify the document while it is open. If no task dialog is open true is returned.
*/
bool isAllowedAlterDocument() const;
bool isAllowedAlterDocument(App::Document* attachedTo = nullptr) const;
/*!
If a task dialog is open then it indicates whether this task dialog allows other commands to
modify the 3d view while it is open. If no task dialog is open true is returned.
*/
bool isAllowedAlterView() const;
bool isAllowedAlterView(App::Document* attachedTo = nullptr) const;
/*!
If a task dialog is open then it indicates whether this task dialog allows other commands to
modify the selection while it is open. If no task dialog is open true is returned.
*/
bool isAllowedAlterSelection() const;
bool isAllowedAlterSelection(App::Document* attachedTo = nullptr) const;
public Q_SLOTS:
void accept();
void reject();
void closeDialog();
void accept(App::Document* attachedTo = nullptr);
void reject(App::Document* attachedTo = nullptr);
void closeDialog(App::Document* attachedTo = nullptr);
/// raises the task view panel
void showTaskView();
private Q_SLOTS:
private:
/// This get called by the TaskView when the Dialog is finished
void closedDialog();
void closedDialog(App::Document* attachedTo = nullptr);
private:
struct status
@@ -112,7 +114,7 @@ private:
std::stack<status> StatusStack;
Gui::TaskView::TaskDialog* ActiveDialog;
std::map<App::Document*, Gui::TaskView::TaskDialog*> ActiveDialogs;
int oldTabIndex;
private:
@@ -125,6 +127,9 @@ private:
void aboutToShowDialog(QDockWidget* widget);
void aboutToHideDialog(QDockWidget* widget);
// Returns attachTo if not nullptr, otherwise return the active document
static App::Document* docOrDefault(App::Document* attachedTo);
static ControlSingleton* _pcSingleton;
};
+9 -2
View File
@@ -920,7 +920,9 @@ void DlgAddProperty::valueChanged()
*/
void DlgAddProperty::openTransaction()
{
transactionID = App::GetApplication().setActiveTransaction("Add property");
transactionID = App::GetApplication().setActiveTransaction(
App::TransactionName {.name = "Add property", .temporary = false}
);
}
void DlgAddProperty::critical(const QString& title, const QString& text)
@@ -993,7 +995,12 @@ void DlgAddProperty::closeTransaction(TransactionOption option)
return;
}
App::GetApplication().closeActiveTransaction(static_cast<bool>(option), transactionID);
if (option == TransactionOption::Abort) {
App::GetApplication().abortTransaction(transactionID);
}
else {
App::GetApplication().commitTransaction(transactionID);
}
transactionID = 0;
}
+61 -27
View File
@@ -90,6 +90,7 @@ struct DocumentP
bool _isClosing;
bool _isModified;
bool _isTransacting;
bool _isActive;
bool _changeViewTouchDocument;
bool _editWantsRestore;
bool _editWantsRestorePrevious;
@@ -101,6 +102,7 @@ struct DocumentP
ViewProviderDocumentObject* _editViewProviderParent;
std::string _editSubname;
std::string _editSubElement;
std::string _workbenchName; // Name of the workbench acting on this document
Base::Matrix4D _editingTransform;
View3DInventorViewer* _editingViewer;
std::set<const App::DocumentObject*> _editObjs;
@@ -284,6 +286,8 @@ struct DocumentP
{
_editingObject = sobj;
_editMode = ModNum;
_editViewProvider = svp; // Used to resolve start editing (find the document in edit from
// within the viewprovider)
_editViewProvider = svp->startEditing(ModNum);
if (!_editViewProvider) {
_editViewProviderParent = nullptr;
@@ -313,7 +317,7 @@ struct DocumentP
void setDocumentNameOfTaskDialog(App::Document* doc)
{
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(_pcDocument);
if (dlg) {
dlg->setDocumentName(doc->getName());
}
@@ -432,6 +436,7 @@ Document::Document(App::Document* pcDocument, Application* app)
d->_isClosing = false;
d->_isModified = false;
d->_isTransacting = false;
d->_isActive = false;
d->_pcAppWnd = app;
d->_pcDocument = pcDocument;
d->_editViewProvider = nullptr;
@@ -690,7 +695,6 @@ bool Document::trySetEdit(Gui::ViewProvider* p, int ModNum, const char* subname)
d->setEditingViewerIfPossible(view3d, ModNum);
d->signalEditMode();
App::AutoTransaction::setEnable(false);
return true;
}
@@ -717,7 +721,7 @@ void Document::resetEdit()
Gui::ViewProvider* vpToRestore = d->_editViewProviderPrevious;
bool shouldRestorePrevious = d->_editWantsRestorePrevious;
Application::Instance->setEditDocument(nullptr);
Application::Instance->unsetEditDocument(this);
if (vpIsNotNull && vpHasChanged && shouldRestorePrevious) {
setEdit(vpToRestore, modeToRestore);
@@ -755,18 +759,16 @@ void Document::_resetEdit()
// The logic below is not necessary anymore, because this method is
// changed into a private one, _resetEdit(). And the exposed
// resetEdit() above calls into Application->setEditDocument(0) which
// resetEdit() above calls into Application->unsetEditDocument() which
// will prevent recursive calling.
App::GetApplication().closeActiveTransaction();
App::GetApplication().commitTransaction(getDocument()->getBookedTransactionID());
}
d->_editViewProviderParent = nullptr;
d->_editingViewer = nullptr;
d->_editObjs.clear();
d->_editingObject = nullptr;
if (Application::Instance->editDocument() == this) {
Application::Instance->setEditDocument(nullptr);
}
Application::Instance->unsetEditDocument(this);
}
ViewProvider* Document::getInEdit(
@@ -799,6 +801,10 @@ ViewProvider* Document::getInEdit(
return nullptr;
}
ViewProvider* Document::getEditViewProvider() const
{
return d->_editViewProvider;
}
void Document::setInEdit(ViewProviderDocumentObject* parentVp, const char* subname)
{
@@ -1068,12 +1074,11 @@ void Document::slotDeletedObject(const App::DocumentObject& Obj)
if (d->_editViewProvider == viewProvider || d->_editViewProviderParent == viewProvider) {
_resetEdit();
}
else if (Application::Instance->editDocument()) {
auto editDoc = Application::Instance->editDocument();
if (editDoc->d->_editViewProvider == viewProvider
|| editDoc->d->_editViewProviderParent == viewProvider) {
Application::Instance->setEditDocument(nullptr);
}
else {
Application::Instance->unsetEditDocumentIf([&viewProvider](Gui::Document* editdoc) {
return editdoc->d->_editViewProvider == viewProvider
|| editdoc->d->_editViewProviderParent == viewProvider;
});
}
handleChildren3D(viewProvider, true);
@@ -1096,15 +1101,13 @@ void Document::slotDeletedObject(const App::DocumentObject& Obj)
void Document::beforeDelete()
{
auto editDoc = Application::Instance->editDocument();
if (editDoc) {
Application::Instance->unsetEditDocumentIf([this](Gui::Document* editDoc) {
auto vp = freecad_cast<ViewProviderDocumentObject*>(editDoc->d->_editViewProvider);
auto vpp = freecad_cast<ViewProviderDocumentObject*>(editDoc->d->_editViewProviderParent);
if (editDoc == this || (vp && vp->getDocument() == this)
|| (vpp && vpp->getDocument() == this)) {
Application::Instance->setEditDocument(nullptr);
}
}
return editDoc == this || (vp && vp->getDocument() == this)
|| (vpp && vpp->getDocument() == this);
});
for (auto& v : d->_ViewProviderMap) {
v.second->beforeDelete();
}
@@ -1251,6 +1254,18 @@ void Document::slotSkipRecompute(const App::Document& doc, const std::vector<App
return;
}
App::DocumentObject* obj = nullptr;
if (Gui::Application::Instance->isInEdit(this)) {
auto vp = freecad_cast<ViewProviderDocumentObject*>(getInEdit());
if (vp) {
obj = vp->getObject();
}
}
if (objs.size() > 1 || App::GetApplication().getActiveDocument() != &doc
|| !doc.testStatus(App::Document::AllowPartialRecompute)) {
return;
}
auto editDoc = Application::Instance->editDocument();
if (editDoc) {
auto vp = freecad_cast<ViewProviderDocumentObject*>(editDoc->getInEdit());
@@ -1329,6 +1344,14 @@ bool Document::isModified() const
{
return d->_isModified;
}
void Document::setWorkbench(const std::string& name)
{
d->_workbenchName = name;
}
std::string Document::workbench() const
{
return d->_workbenchName;
}
bool Document::isAboutToClose() const
{
@@ -1399,6 +1422,17 @@ App::Document* Document::getDocument() const
{
return d->_pcDocument;
}
void Document::setIsActive(bool active)
{
d->_isActive = active;
if (d->_editViewProvider) {
d->_editViewProvider->setActive(active);
}
}
bool Document::isActive() const
{
return d->_isActive;
}
static bool checkCanonicalPath(const std::map<App::Document*, bool>& docs)
{
@@ -1568,7 +1602,7 @@ bool Document::save()
for (auto doc : docs) {
// Changed 'mustExecute' status may be triggered by saving external document
if (!dmap[doc] && doc->mustExecute()) {
App::AutoTransaction trans("Recompute");
App::AutoTransaction trans(doc, "Recompute");
Command::doCommand(
Command::Doc,
"App.getDocument(\"%s\").recompute()",
@@ -1711,7 +1745,7 @@ void Document::saveAll()
try {
// Changed 'mustExecute' status may be triggered by saving external document
if (!dmap[doc] && doc->mustExecute()) {
App::AutoTransaction trans("Recompute");
App::AutoTransaction trans(doc, "Recompute");
Command::doCommand(Command::Doc, "App.getDocument('%s').recompute()", doc->getName());
}
Command::doCommand(Command::Doc, "App.getDocument('%s').save()", doc->getName());
@@ -2476,8 +2510,8 @@ bool Document::canClose(bool checkModify, bool checkLink)
// If a task dialog is open that doesn't allow other commands to modify
// the document it must be closed by resetting the edit mode of the
// corresponding view provider.
if (!Gui::Control().isAllowedAlterDocument()) {
std::string name = Gui::Control().activeDialog()->getDocumentName();
if (!Gui::Control().isAllowedAlterDocument(getDocument())) {
std::string name = Gui::Control().activeDialog(getDocument())->getDocumentName();
if (name == this->getDocument()->getName()) {
// getInEdit() only checks if the currently active MDI view is
// a 3D view and that it is in edit mode. However, when closing a
@@ -2733,9 +2767,9 @@ Gui::MDIView* Document::getEditingViewOfViewProvider(Gui::ViewProvider* vp) cons
* operation default is the command name.
* @see CommitCommand(),AbortCommand()
*/
void Document::openCommand(const char* sName)
int Document::openCommand(const char* sName)
{
getDocument()->openTransaction(sName);
return getDocument()->openTransaction(App::TransactionName {.name = sName, .temporary = false});
}
void Document::commitCommand()
+12 -1
View File
@@ -191,12 +191,21 @@ public:
void setModified(bool);
bool isModified() const;
/// getter-setter for workbench name
void setWorkbench(const std::string& name);
std::string workbench() const;
/// Returns true if the document is about to be closed, false otherwise
bool isAboutToClose() const;
/// Getter for the App Document
App::Document* getDocument() const;
/// Notify the document when it becomes
/// the active document/stops being the active document
void setIsActive(bool active);
bool isActive() const;
/** @name methods for View handling */
//@{
/// Getter for the active view
@@ -287,6 +296,8 @@ public:
int* mode = nullptr,
std::string* subElement = nullptr
) const;
ViewProvider* getEditViewProvider() const; // Returns the _editViewProvider even if it is not
// in edit at the moment
/// set the in edit ViewProvider subname reference
void setInEdit(ViewProviderDocumentObject* parentVp, const char* subname);
/** Add or remove view provider from scene graphs of all views
@@ -300,7 +311,7 @@ public:
/** @name methods for the UNDO REDO handling */
//@{
/// Open a new Undo transaction on the document
void openCommand(const char* sName = nullptr);
int openCommand(const char* sName = nullptr);
/// Commit the Undo transaction on the document
void commitCommand();
/// Abort the Undo transaction on the document
+25
View File
@@ -187,6 +187,31 @@ class Document(Persistence):
obj : Gui.ViewProvider
"""
...
def openCommand(self, name: str) -> int:
"""
openCommand(name) -> int
Opens a named transaction for the document and returns it's
id or 0 on failure
"""
...
def commitCommand(self) -> None:
"""
commitCommand() -> None
Commits the current transaction of the document
"""
...
def abortCommand(self) -> None:
"""
abortCommand() -> None
Aborts the current transaction of the document
"""
...
ActiveObject: Any = ...
"""The active object of the document."""
+28
View File
@@ -577,6 +577,34 @@ Py::Long DocumentPy::getEditMode() const
return Py::Long(mode);
}
PyObject* DocumentPy::openCommand(PyObject* arg, PyObject* kwd)
{
const char* name = nullptr;
if (!PyArg_ParseTuple(arg, "s", &name)) {
throw Py::Exception();
}
int tid = getDocumentPtr()->openCommand(name);
return Py::new_reference_to(Py::Long(tid));
}
PyObject* DocumentPy::commitCommand(PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
getDocumentPtr()->commitCommand();
Py_Return;
}
PyObject* DocumentPy::abortCommand(PyObject* args)
{
if (!PyArg_ParseTuple(args, "")) {
return nullptr;
}
getDocumentPtr()->abortCommand();
Py_Return;
}
Py::Boolean DocumentPy::getTransacting() const
{
return {getDocumentPtr()->isPerformingTransaction()};
+9 -9
View File
@@ -75,11 +75,11 @@ void Gui::ExpressionBinding::setExpression(std::shared_ptr<Expression> expr)
lastExpression = getExpression();
bool transaction = !App::GetApplication().getActiveTransaction();
bool transaction = docObj->getDocument()->getBookedTransactionID() == 0;
if (transaction) {
std::ostringstream ss;
ss << (expr ? "Set" : "Discard") << " expression " << docObj->Label.getValue();
App::GetApplication().setActiveTransaction(ss.str().c_str());
docObj->getDocument()->openTransaction(ss.str().c_str());
}
docObj->ExpressionEngine.setValue(path, expr);
@@ -89,7 +89,7 @@ void Gui::ExpressionBinding::setExpression(std::shared_ptr<Expression> expr)
}
if (transaction) {
App::GetApplication().closeActiveTransaction();
docObj->getDocument()->commitTransaction();
}
}
@@ -214,11 +214,11 @@ bool ExpressionBinding::apply(const std::string& propName)
throw Base::RuntimeError("Document object not found.");
}
bool transaction = !App::GetApplication().getActiveTransaction();
bool transaction = docObj->getDocument()->getBookedTransactionID() == 0;
if (transaction) {
std::ostringstream ss;
ss << "Set expression " << docObj->Label.getValue();
App::GetApplication().setActiveTransaction(ss.str().c_str());
docObj->getDocument()->openTransaction(ss.str().c_str());
}
Gui::Command::doCommand(
Gui::Command::Doc,
@@ -229,7 +229,7 @@ bool ExpressionBinding::apply(const std::string& propName)
getEscapedExpressionString().c_str()
);
if (transaction) {
App::GetApplication().closeActiveTransaction();
docObj->getDocument()->commitTransaction();
}
return true;
}
@@ -242,11 +242,11 @@ bool ExpressionBinding::apply(const std::string& propName)
}
if (lastExpression) {
bool transaction = !App::GetApplication().getActiveTransaction();
bool transaction = docObj->getDocument()->getBookedTransactionID() == 0;
if (transaction) {
std::ostringstream ss;
ss << "Discard expression " << docObj->Label.getValue();
App::GetApplication().setActiveTransaction(ss.str().c_str());
docObj->getDocument()->openTransaction(ss.str().c_str());
}
Gui::Command::doCommand(
Gui::Command::Doc,
@@ -256,7 +256,7 @@ bool ExpressionBinding::apply(const std::string& propName)
path.toEscapedString().c_str()
);
if (transaction) {
App::GetApplication().closeActiveTransaction();
docObj->getDocument()->commitTransaction();
}
}
}
+3 -3
View File
@@ -1244,16 +1244,16 @@ bool OverlayTabWidget::checkAutoHide() const
}
}
bool activeDocInEdit = Application::Instance->isInEdit(Application::Instance->activeDocument());
if (autoMode == AutoMode::EditShow) {
return !Application::Instance->editDocument()
&& (!Control().taskPanel() || Control().taskPanel()->isEmpty(false));
return !activeDocInEdit && (!Control().taskPanel() || Control().taskPanel()->isEmpty(false));
}
if (autoMode == AutoMode::TaskShow) {
return (!Control().taskPanel() || Control().taskPanel()->isEmpty());
}
if (autoMode == AutoMode::EditHide && Application::Instance->editDocument()) {
if (autoMode == AutoMode::EditHide && activeDocInEdit) {
return true;
}
@@ -93,10 +93,5 @@ ActionGroup *ActionPanel::createGroup(const QPixmap &icon, const QString &title,
return group;
}
QSize ActionPanel::minimumSizeHint() const
{
return {200,150};
}
} // namespace QSint
-6
View File
@@ -89,12 +89,6 @@ public:
*/
void setScheme(ActionPanelScheme *scheme);
/**
* @brief Returns the recommended minimum size for the panel.
* @return The minimum size hint.
*/
QSize minimumSizeHint() const override;
protected:
/** @brief The color scheme used by the panel. */
ActionPanelScheme *myScheme;
File diff suppressed because it is too large Load Diff
+83 -69
View File
@@ -225,7 +225,7 @@ class GuiExport SelectionObserver
public:
/** Constructor
*
* @param attach: whether to attach this observer on construction
* @param attach: whether to attach this observer on construction
* @param resolve: sub-object resolving mode.
*/
explicit SelectionObserver(bool attach = true, ResolveMode resolve = ResolveMode::OldStyleElement);
@@ -264,6 +264,7 @@ private:
std::string filterDocName;
std::string filterObjName;
ResolveMode resolve;
const char* pDocumentScopeName {nullptr};
bool blockedSelection;
};
@@ -286,20 +287,6 @@ public:
std::string notAllowedReason;
};
/** SelectionGateFilterExternal
* The selection gate disallows any external object
*/
class GuiExport SelectionGateFilterExternal: public SelectionGate
{
public:
explicit SelectionGateFilterExternal(const char* docName, const char* objName = nullptr);
bool allow(App::Document*, App::DocumentObject*, const char*) override;
private:
std::string DocName;
std::string ObjName;
};
/** The Selection class
* The selection singleton keeps track of the selection state of
* the whole application. It gets messages from all entities which can
@@ -382,7 +369,7 @@ public:
/// of the active document is cleared.
void clearSelection(const char* pDocName = nullptr, bool clearPreSelect = true);
/// Clear the selection of all documents
void clearCompleteSelection(bool clearPreSelect = true);
void clearCompleteSelection(const char* pDocName = nullptr, bool clearPreSelect = true);
/// Check if selected
bool isSelected(
const char* pDocName,
@@ -397,7 +384,7 @@ public:
ResolveMode resolve = ResolveMode::OldStyleElement
) const;
const char* getSelectedElement(App::DocumentObject*, const char* pSubName) const;
std::string getSelectedElement(App::DocumentObject*, const char* pSubName) const;
/// set the preselected object (mostly by the 3D view)
int setPreselect(
@@ -415,10 +402,19 @@ public:
void setPreselectCoord(float x, float y, float z);
/// returns the present preselection
const SelectionChanges& getPreselection() const;
/// add a SelectionGate to control what is selectable
void addSelectionGate(Gui::SelectionGate* gate, ResolveMode resolve = ResolveMode::OldStyleElement);
/// remove the active SelectionGate
void rmvSelectionGate();
/// add a SelectionGate to control what is selectable in a document's scope, by default the
/// active document is selected
// which is usually the intended behavior
void addSelectionGate(
Gui::SelectionGate* gate,
ResolveMode resolve = ResolveMode::OldStyleElement,
const char* pDocName = nullptr
);
/// remove the document's SelectionGate, by default the active document is selected, which is
/// usually the intended behavior
void rmvSelectionGate(const char* pDocName = nullptr);
/// remove the document's SelectionGate (assumes valid pointer)
void rmvSelectionGate(App::Document* doc);
int disableCommandLog();
int enableCommandLog(bool silent = false);
@@ -495,8 +491,9 @@ public:
/** Set selection object visibility
*
* @param visible: see VisibleState
* @param pDocName: name of the document that scopes the request, defaults to active document
*/
void setVisible(VisibleState visible);
void setVisible(VisibleState visible, const char* pDocName = nullptr);
bool isClarifySelectionActive();
void setClarifySelectionActive(bool active);
@@ -580,7 +577,7 @@ public:
std::vector<SelObj> getCompleteSelection(ResolveMode resolve = ResolveMode::OldStyleElement) const;
/// Check if there is any selection
bool hasSelection() const;
bool hasSelection(const char* pDocName = nullptr) const;
/** Check if there is any selection within a given document
*
@@ -594,7 +591,7 @@ public:
* If \c resolve is false, then the match is only done with the top
* level parent object.
*/
bool hasSelection(const char* doc, ResolveMode resolve = ResolveMode::OldStyleElement) const;
bool hasSelection(const char* doc, ResolveMode resolve) const;
/** Check if there is any sub-element selection
*
@@ -611,10 +608,7 @@ public:
bool hasPreselection() const;
/// Size of selected entities for all documents
unsigned int size() const
{
return static_cast<unsigned int>(_SelList.size());
}
unsigned int size(const char* pDocName = nullptr) const;
/** @name Selection stack functions
*
@@ -623,16 +617,10 @@ public:
*/
//@{
/// Return the current selection stack size
std::size_t selStackBackSize() const
{
return _SelStackBack.size();
}
std::size_t selStackBackSize(const char* pDocName = nullptr) const;
/// Return the current forward selection stack size
std::size_t selStackForwardSize() const
{
return _SelStackForward.size();
}
std::size_t selStackForwardSize(const char* pDocName = nullptr) const;
/** Obtain selected objects from stack
*
@@ -650,28 +638,31 @@ public:
/** Go back selection history
*
* @param count: optional number of steps to go back
* @param pDocName: the name of the document to index the context, defaults to active document
*
* This function pops the selection stack, and populate the current
* selection with the content of the last pop'd entry
*/
void selStackGoBack(int count = 1);
void selStackGoBack(int count = 1, const char* pDocName = nullptr);
/** Go forward selection history
*
* @param count: optional number of steps to go back
* @param pDocName: the name of the document to index the context, defaults to active document
*
* This function pops the selection stack, and populate the current
* selection with the content of the last pop'd entry
*/
void selStackGoForward(int count = 1);
void selStackGoForward(int count = 1, const char* pDocName = nullptr);
/** Save the current selection on to the stack
*
* @param clearForward: whether to clear forward selection stack
* @param overwrite: whether to overwrite the current top entry of the
* stack instead of pushing a new entry.
* @param pDocName: the name of the document to index the context, defaults to active document
*/
void selStackPush(bool clearForward = true, bool overwrite = false);
void selStackPush(bool clearForward = true, bool overwrite = false, const char* pDocName = nullptr);
//@}
/** @name Picked list functions
@@ -682,11 +673,11 @@ public:
*/
//@{
/// Check whether picked list is enabled
bool needPickedList() const;
bool needPickedList(const char* pDocName = nullptr) const;
/// Turn on or off picked list
void enablePickedList(bool);
void enablePickedList(bool, const char* pDocName = nullptr);
/// Check if there is any selection inside picked list
bool hasPickedList() const;
bool hasPickedList(const char* pDocName = nullptr) const;
/// Return select objects inside picked list
std::vector<SelectionSingleton::SelObj> getPickedList(const char* pDocName) const;
/// Return selected object inside picked list grouped by top level parents
@@ -708,9 +699,9 @@ public:
GreedySelection
};
/// Changes the style of selection between greedy and normal.
void setSelectionStyle(SelectionStyle selStyle);
void setSelectionStyle(SelectionStyle selStyle, const char* pDocName = nullptr);
/// Get the style of selection.
SelectionStyle getSelectionStyle();
SelectionStyle getSelectionStyle(const char* pDocName = nullptr);
//@}
static SelectionSingleton& instance();
@@ -756,24 +747,20 @@ protected:
/// Observer message from the App doc
void slotDeletedObject(const App::DocumentObject&);
void slotClosedDocument(const App::Document&);
/// helper to retrieve document by name
App::Document* getDocument(const char* pDocName = nullptr) const;
void slotSelectionChanged(const SelectionChanges& msg);
SelectionChanges CurrentPreselection;
std::deque<SelectionChanges> NotificationQueue;
bool Notifying = false;
void notify(SelectionChanges&& Chng);
void notify(const SelectionChanges& Chng)
{
notify(SelectionChanges(Chng));
}
struct _SelObj
struct SelectionDescription
{
std::string DocName;
std::string FeatName;
@@ -792,34 +779,26 @@ protected:
void log(bool remove = false, bool clearPreselect = true);
std::string getSubString() const;
};
mutable std::list<_SelObj> _SelList;
mutable std::list<_SelObj> _PickedList;
bool _needPickedList {false};
using SelStackItem = std::set<App::SubObjectT>;
std::deque<SelStackItem> _SelStackBack;
std::deque<SelStackItem> _SelStackForward;
int checkSelection(
const char* pDocName,
const char* pObjectName,
const char* pSubName,
ResolveMode resolve,
_SelObj& sel,
const std::list<_SelObj>* selList = nullptr
SelectionDescription& sel,
const std::list<SelectionDescription>* selList = nullptr
) const;
std::vector<Gui::SelectionObject> getObjectList(
const char* pDocName,
Base::Type typeId,
std::list<_SelObj>& objs,
const std::list<SelectionDescription>& objs,
ResolveMode resolve,
bool single = false
) const;
static App::DocumentObject* getObjectOfType(
_SelObj& sel,
const SelectionDescription& sel,
Base::Type type,
ResolveMode resolve,
const char** subelement = nullptr
@@ -831,21 +810,56 @@ protected:
ResolveMode resolve = ResolveMode::OldStyleElement
) const;
using SelStackItem = std::set<App::SubObjectT>;
// Each document has a description context
struct SelectionInfo
{
Gui::SelectionGate* gate {nullptr};
ResolveMode resolveMode {ResolveMode::OldStyleElement};
std::list<SelectionDescription> selList;
std::list<SelectionDescription> pickedList;
bool needPickedList {false};
std::deque<SelStackItem> selStackBack;
std::deque<SelStackItem> selStackForward;
SelectionStyle selectionStyle {SelectionStyle::NormalSelection};
};
struct SelectionContext
{
SelectionInfo* info;
std::string docName;
};
struct SelectionConstContext
{
const SelectionInfo* info;
std::string docName;
};
// Returns a selection context or nullptr if the document is not found
SelectionContext getSelectionContext(const char* pDocName);
SelectionConstContext getSelectionContext(const char* pDocName) const;
static SelectionSingleton* _pcSingleton;
std::map<App::Document*, SelectionInfo> docSelectionContext;
// Preselection helpers, it's a mess, needs clarifying -theo-vt
std::string DocName;
std::string FeatName;
std::string SubName;
float hx, hy, hz;
float hx {0.0f}, hy {0.0f}, hz {0.0f};
SelectionChanges CurrentPreselection;
Gui::SelectionGate* ActiveGate;
ResolveMode gateResolve;
int logDisabled = 0;
bool logHasSelection = false;
bool clarifySelectionActive = false;
int logDisabled {0};
bool logHasSelection {false};
bool clarifySelectionActive {false};
SelectionStyle selectionStyle;
std::deque<SelectionChanges> NotificationQueue;
bool Notifying {false};
};
/**
+1 -1
View File
@@ -791,7 +791,7 @@ bool SoFCUnifiedSelection::setSelection(const std::vector<PickedInfo>& infos, bo
// Ex: Body.Pad.Face9 to Body.Pad.;g3;SKT;:H12dc,E;FAC;:H12dc:4,F;:G0;XTR;:H12dc:8,F.Face9
getFullSubElementName(subName);
const char* subSelected
= Gui::Selection().getSelectedElement(vpd->getObject(), subName.c_str());
= Gui::Selection().getSelectedElement(vpd->getObject(), subName.c_str()).c_str();
FC_TRACE(
"select " << (subSelected ? subSelected : "'null'") << ", " << objectName << ", " << subName
+135
View File
@@ -0,0 +1,135 @@
/***************************************************************************
* Copyright (c) 2026 Théo Veilleux-Trinh <theo.veilleux.trinh@proton.me>*
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "TaskCommandLink.h"
#include "ui_TaskCommandLink.h"
#include "Application.h"
#include "Document.h"
#include "ViewProvider.h"
#include <App/Application.h>
#include <App/DocumentObject.h>
#include <App/Document.h>
namespace Gui
{
TaskCommandLink::TaskCommandLink()
: ui(new Ui_TaskCommandLinkDialog())
{
proxy = new QWidget(this);
ui->setupUi(proxy);
ui->objectsList->header()->hide();
ui->objectsList->setSelectionMode(QAbstractItemView::SelectionMode::ExtendedSelection);
this->groupLayout()->addWidget(proxy);
buildObjectsList();
}
TaskCommandLink::~TaskCommandLink()
{
delete proxy;
delete ui;
}
std::vector<App::DocumentObject*> TaskCommandLink::selectedObjects()
{
auto selected = ui->objectsList->selectedItems();
std::vector<App::DocumentObject*> dst;
dst.reserve(selected.size());
for (auto sel : selected) {
dst.push_back(sel->data(0, Qt::UserRole).value<App::DocumentObject*>());
}
return dst;
}
void processObjectsHelper(std::vector<App::DocumentObject*> objs, QTreeWidgetItem* item)
{
for (auto obj : objs) {
auto objItem = new QTreeWidgetItem(item);
objItem->setText(0, obj->Label.getValue());
objItem->setData(0, Qt::UserRole, QVariant::fromValue(obj));
Gui::ViewProvider* vp = nullptr;
if (auto doc = Application::Instance->getDocument(obj->getDocument())) {
vp = doc->getViewProvider(obj);
}
if (vp) {
objItem->setIcon(0, vp->getIcon());
processObjectsHelper(vp->claimChildren(), objItem);
}
else {
objItem->setIcon(0, QIcon());
}
}
}
void TaskCommandLink::buildObjectsList()
{
ui->objectsList->clear();
auto allDocuments = App::GetApplication().getDocuments();
bool collapse = true;
std::map<QTreeWidgetItem*, App::Document*> docItemMap;
for (auto doc : allDocuments) {
auto docItem = new QTreeWidgetItem();
std::string itemName = doc->Label.getValue();
docItem->setText(0, QString::fromStdString(itemName));
docItem->setIcon(0, QIcon(QStringLiteral(":/icons/Document.svg")));
docItem->setFlags(docItem->flags() & ~Qt::ItemIsSelectable); // Can't link a whole document
docItemMap[docItem] = doc;
ui->objectsList->addTopLevelItem(docItem);
processObjectsHelper(Application::Instance->getDocument(doc)->getTreeRootObjects(), docItem);
if (collapse) {
ui->objectsList->collapseAll();
}
else {
ui->objectsList->expandToDepth(0);
}
}
ui->objectsList->selectedItems();
}
// dialog
TaskCommandLinkDialog::TaskCommandLinkDialog(
std::function<void(std::vector<App::DocumentObject*>)> executor_
)
: executor(executor_)
{
commandLink = new TaskCommandLink();
Content.push_back(commandLink);
}
void TaskCommandLinkDialog::open()
{}
bool TaskCommandLinkDialog::accept()
{
executor(commandLink->selectedObjects());
return true;
}
} // namespace Gui
+80
View File
@@ -0,0 +1,80 @@
/***************************************************************************
* Copyright (c) 2026 Théo Veilleux-Trinh <theo.veilleux.trinh@proton.me>*
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#pragma once
#include "TaskView/TaskDialog.h"
#include "TaskView/TaskView.h"
#include <QTreeWidgetItem>
#include <functional>
#include <vector>
namespace App
{
class DocumentObject;
}
namespace Gui
{
class Document;
class Ui_TaskCommandLinkDialog;
class TaskCommandLink: public Gui::TaskView::TaskBox
{
public:
TaskCommandLink();
~TaskCommandLink();
std::vector<App::DocumentObject*> selectedObjects();
private:
void buildObjectsList();
private:
Ui_TaskCommandLinkDialog* ui {nullptr};
QWidget* proxy {nullptr};
};
class TaskCommandLinkDialog: public Gui::TaskView::TaskDialog
{
Q_OBJECT
public:
TaskCommandLinkDialog(std::function<void(std::vector<App::DocumentObject*>)> executor_);
~TaskCommandLinkDialog() override = default;
QDialogButtonBox::StandardButtons getStandardButtons() const override
{
return QDialogButtonBox::Ok | QDialogButtonBox::Cancel;
}
void open() override;
bool accept() override;
private:
TaskCommandLink* commandLink {nullptr};
Gui::Document* document {nullptr};
std::function<void(std::vector<App::DocumentObject*>)> executor;
};
} // namespace Gui
+36
View File
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Gui::TaskCommandLinkDialog</class>
<widget class="QWidget" name="Gui::TaskCommandLinkDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>340</width>
<height>212</height>
</rect>
</property>
<property name="windowTitle">
<string>Insert</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTreeWidget" name="objectsList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+24 -18
View File
@@ -25,8 +25,8 @@
#include <QColorDialog>
#include <sstream>
#include <App/ElementNamingUtils.h>
#include <App/Transactions.h>
#include <App/Document.h>
#include "TaskElementColors.h"
@@ -64,6 +64,7 @@ public:
bool busy;
long onTopMode;
bool touched;
int tid {0}; // Transaction id
std::string editDoc;
std::string editObj;
@@ -77,15 +78,14 @@ public:
, vpDoc(vp->getDocument())
, editElement(element)
{
auto doc = Application::Instance->editDocument();
if (doc) {
auto editVp = doc->getInEdit(&vpParent, &editSub);
if (editVp == vp) {
auto obj = vpParent->getObject();
editDoc = obj->getDocument()->getName();
editObj = obj->getNameInDocument();
editSub = Data::noElementName(editSub.c_str());
}
if (auto editDoc = Application::Instance->editDocument([this, &vp](Gui::Document* editDoc) {
return editDoc->getInEdit(&vpParent, &editSub) == vp;
})) {
auto obj = vpParent->getObject();
this->editDoc = obj->getDocument()->getName();
this->editObj = obj->getNameInDocument();
this->editSub = Data::noElementName(editSub.c_str());
}
if (editDoc.empty()) {
vpParent = vp;
@@ -198,8 +198,8 @@ public:
std::string sub = qPrintable(item->data(Qt::UserRole + 1).value<QString>());
info.emplace(sub, Base::Color::fromValue<QColor>(col));
}
if (!App::GetApplication().getActiveTransaction()) {
App::GetApplication().setActiveTransaction("Set colors");
if (tid == App::NullTransaction) {
tid = vpDoc->openCommand(QT_TRANSLATE_NOOP("Command", "Set colors"));
}
vp->setElementColors(info);
touched = true;
@@ -209,8 +209,12 @@ public:
void reset()
{
touched = false;
App::GetApplication().closeActiveTransaction(true);
App::GetApplication().abortTransaction(tid);
Selection().clearSelection();
Application::Instance->unsetEditDocumentIf([this](Gui::Document* editdoc) {
return editdoc->getEditViewProvider() == vp;
});
}
void accept()
@@ -221,7 +225,11 @@ public:
obj->getDocument()->recompute(obj->getInListRecursive());
touched = false;
}
App::GetApplication().closeActiveTransaction();
App::GetApplication().commitTransaction(tid);
Application::Instance->unsetEditDocumentIf([this](Gui::Document* editdoc) {
return editdoc->getEditViewProvider() == vp;
});
}
void removeAll()
@@ -435,14 +443,14 @@ void ElementColors::onTopClicked(bool checked)
void ElementColors::slotDeleteDocument(const Document& Doc)
{
if (d->vpDoc == &Doc || d->editDoc == Doc.getDocument()->getName()) {
Control().closeDialog();
Control().closeDialog(Doc.getDocument());
}
}
void ElementColors::slotDeleteObject(const ViewProvider& obj)
{
if (d->vp == &obj) {
Control().closeDialog();
Control().closeDialog(d->vpDoc->getDocument());
}
}
@@ -543,14 +551,12 @@ void ElementColors::onRemoveAllClicked()
bool ElementColors::accept()
{
d->accept();
Application::Instance->setEditDocument(nullptr);
return true;
}
bool ElementColors::reject()
{
d->reset();
Application::Instance->setEditDocument(nullptr);
return true;
}
+6
View File
@@ -158,5 +158,11 @@ void TaskDialog::onUndo()
void TaskDialog::onRedo()
{}
void TaskDialog::activate()
{}
void TaskDialog::deactivate()
{}
#include "moc_TaskDialog.cpp"
+7
View File
@@ -209,6 +209,13 @@ public:
/// is called by the framework if the user press the redo button
virtual void onRedo();
/// Called by the framework when it becomes the shown dialog
/// of the stacked task panel (e.g. when it's document becomes active)
virtual void activate();
/// Called by the framework when it stops being the shown dialog
/// of the stacked task panel (e.g. when it's document stops being active)
virtual void deactivate();
void emitDestructionSignal()
{
Q_EMIT aboutToBeDestroyed();
+59 -16
View File
@@ -35,6 +35,7 @@
#include <Gui/Control.h>
#include <Gui/UiLoader.h>
#include <Gui/PythonWrapper.h>
#include <Gui/DocumentPy.h>
#include "TaskDialogPython.h"
#include "TaskView.h"
@@ -142,43 +143,67 @@ Py::Object ControlPy::repr()
Py::Object ControlPy::showDialog(const Py::Tuple& args)
{
PyObject* arg0;
if (!PyArg_ParseTuple(args.ptr(), "O", &arg0)) {
PyObject* arg0 = nullptr;
PyObject* docPy = nullptr;
if (!PyArg_ParseTuple(args.ptr(), "O|O!", &arg0, &(Gui::DocumentPy::Type), &docPy)) {
throw Py::Exception();
}
Gui::TaskView::TaskDialog* act = Gui::Control().activeDialog();
App::Document* doc = docPy
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
: nullptr;
Gui::TaskView::TaskDialog* act = Gui::Control().activeDialog(doc);
if (act) {
throw Py::RuntimeError("Active task dialog found");
}
auto dlg = new TaskDialogPython(Py::Object(arg0));
Gui::Control().showDialog(dlg);
Gui::Control().showDialog(dlg, doc);
return (Py::asObject(new TaskDialogPy(dlg)));
}
Py::Object ControlPy::activeDialog(const Py::Tuple& args)
{
if (!PyArg_ParseTuple(args.ptr(), "")) {
PyObject* docPy = nullptr;
if (!PyArg_ParseTuple(args.ptr(), "|O!", &(Gui::DocumentPy::Type), &docPy)) {
throw Py::Exception();
}
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
App::Document* doc = docPy
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
: nullptr;
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(doc);
return Py::Boolean(dlg != nullptr);
}
Py::Object ControlPy::activeTaskDialog(const Py::Tuple& args)
{
if (!PyArg_ParseTuple(args.ptr(), "")) {
PyObject* docPy = nullptr;
if (!PyArg_ParseTuple(args.ptr(), "|O!", &(Gui::DocumentPy::Type), &docPy)) {
throw Py::Exception();
}
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
App::Document* doc = docPy
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
: nullptr;
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(doc);
return (dlg ? Py::asObject(new TaskDialogPy(dlg)) : Py::None());
}
Py::Object ControlPy::closeDialog(const Py::Tuple& args)
{
if (!PyArg_ParseTuple(args.ptr(), "")) {
PyObject* docPy = nullptr;
if (!PyArg_ParseTuple(args.ptr(), "|O!", &(Gui::DocumentPy::Type), &docPy)) {
throw Py::Exception();
}
Gui::Control().closeDialog();
App::Document* doc = docPy
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
: nullptr;
Gui::Control().closeDialog(doc);
return Py::None();
}
@@ -217,28 +242,46 @@ Py::Object ControlPy::clearTaskWatcher(const Py::Tuple& args)
Py::Object ControlPy::isAllowedAlterDocument(const Py::Tuple& args)
{
if (!PyArg_ParseTuple(args.ptr(), "")) {
PyObject* docPy = nullptr;
if (!PyArg_ParseTuple(args.ptr(), "|O!", &(Gui::DocumentPy::Type), &docPy)) {
throw Py::Exception();
}
bool ok = Gui::Control().isAllowedAlterDocument();
App::Document* doc = docPy
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
: nullptr;
bool ok = Gui::Control().isAllowedAlterDocument(doc);
return Py::Boolean(ok);
}
Py::Object ControlPy::isAllowedAlterView(const Py::Tuple& args)
{
if (!PyArg_ParseTuple(args.ptr(), "")) {
PyObject* docPy = nullptr;
if (!PyArg_ParseTuple(args.ptr(), "|O!", &(Gui::DocumentPy::Type), &docPy)) {
throw Py::Exception();
}
bool ok = Gui::Control().isAllowedAlterView();
App::Document* doc = docPy
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
: nullptr;
bool ok = Gui::Control().isAllowedAlterView(doc);
return Py::Boolean(ok);
}
Py::Object ControlPy::isAllowedAlterSelection(const Py::Tuple& args)
{
if (!PyArg_ParseTuple(args.ptr(), "")) {
PyObject* docPy = nullptr;
if (!PyArg_ParseTuple(args.ptr(), "|O!", &(Gui::DocumentPy::Type), &docPy)) {
throw Py::Exception();
}
bool ok = Gui::Control().isAllowedAlterSelection();
App::Document* doc = docPy
? static_cast<Gui::DocumentPy*>(docPy)->getDocumentPtr()->getDocument()
: nullptr;
bool ok = Gui::Control().isAllowedAlterSelection(doc);
return Py::Boolean(ok);
}
+257 -190
View File
@@ -234,37 +234,7 @@ void TaskBox::actionEvent(QActionEvent* e)
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
TaskPanel::TaskPanel(QWidget* parent)
: QSint::ActionPanel(parent)
{}
TaskPanel::~TaskPanel() = default;
QSize TaskPanel::minimumSizeHint() const
{
// ActionPanel returns a size of 200x150 which leads to problems
// when there are several task groups in the panel and the first
// one is collapsed. In this case the task panel doesn't expand to
// the actually required size and all the remaining groups are
// squeezed into the available space and thus the widgets in there
// often can't be used any more.
// To fix this problem minimumSizeHint() is implemented to again
// respect the layout's minimum size.
QSize s1 = QSint::ActionPanel::minimumSizeHint();
QSize s2 = QWidget::minimumSizeHint();
return {qMax(s1.width(), s2.width()), qMax(s1.height(), s2.height())};
}
//**************************************************************************
//**************************************************************************
// TaskView
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
TaskView::TaskView(QWidget* parent)
: QWidget(parent)
, ActiveDialog(nullptr)
, ActiveCtrl(nullptr)
, hGrp(Gui::WindowParameter::getDefaultParameter()->GetGroup("General"))
{
mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
@@ -280,19 +250,55 @@ TaskView::TaskView(QWidget* parent)
dialogLayout->setSpacing(0);
mainLayout->addLayout(dialogLayout, 1);
taskPanel = new TaskPanel(scrollArea);
actionPanel = new QSint::ActionPanel(scrollArea);
QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
sizePolicy.setHorizontalStretch(0);
sizePolicy.setVerticalStretch(0);
sizePolicy.setHeightForWidth(taskPanel->sizePolicy().hasHeightForWidth());
taskPanel->setSizePolicy(sizePolicy);
taskPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
sizePolicy.setHeightForWidth(actionPanel->sizePolicy().hasHeightForWidth());
actionPanel->setSizePolicy(sizePolicy);
actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
scrollArea->setWidget(taskPanel);
scrollArea->setWidget(actionPanel);
scrollArea->setWidgetResizable(true);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setMinimumWidth(200);
dialogLayout->addWidget(scrollArea, 1);
}
TaskPanel::~TaskPanel()
{
for (QWidget* panel : contextualPanels) {
delete panel;
}
}
QSize TaskPanel::minimumSizeHint() const
{
// ActionPanel returns a size of 200x150 which leads to problems
// when there are several task groups in the panel and the first
// one is collapsed. In this case the task panel doesn't expand to
// the actually required size and all the remaining groups are
// squeezed into the available space and thus the widgets in there
// often can't be used any more.
// To fix this problem minimumSizeHint() is implemented to again
// respect the layout's minimum size.
QSize s1 = actionPanel->minimumSizeHint();
QSize s2 = QWidget::minimumSizeHint();
return {qMax(s1.width(), s2.width()), qMax(s1.height(), s2.height())};
}
//**************************************************************************
//**************************************************************************
// TaskView
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
TaskView::TaskView(QWidget* parent)
: QStackedWidget(parent)
, hGrp(Gui::WindowParameter::getDefaultParameter()->GetGroup("General"))
{
TaskWatcherPanel = new TaskPanel(this);
addWidget(TaskWatcherPanel);
Gui::Selection().Attach(this);
@@ -329,7 +335,6 @@ TaskView::TaskView(QWidget* parent)
updateWatcher();
}
TaskView::~TaskView()
{
connectApplicationActiveDocument.disconnect();
@@ -341,17 +346,22 @@ TaskView::~TaskView()
connectShowTaskWatcherSetting.disconnect();
Gui::Selection().Detach(this);
for (QWidget* panel : contextualPanels) {
delete panel;
// if well behaved, we should not have nay taskInfo at this point
for (auto& taskInfo : taskInfos) {
delete taskInfo.ActiveCtrl;
delete taskInfo.ActiveDialog;
delete taskInfo.taskPanel;
}
}
bool TaskView::isEmpty(bool includeWatcher) const
{
if (ActiveCtrl || ActiveDialog) {
std::optional<TaskInfo> active = currentTaskInfo();
if (active) {
return false;
}
// There is no active task in the document
if (includeWatcher) {
for (auto* watcher : ActiveWatcher) {
if (watcher->shouldShow()) {
@@ -397,7 +407,8 @@ bool TaskView::event(QEvent* event)
void TaskView::keyPressEvent(QKeyEvent* ke)
{
if (ActiveCtrl && ActiveDialog) {
std::optional<TaskInfo> active = currentTaskInfo();
if (active) {
if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
// get all buttons of the complete task dialog
QList<QPushButton*> list = this->findChildren<QPushButton*>();
@@ -419,9 +430,9 @@ void TaskView::keyPressEvent(QKeyEvent* ke)
}
}
}
else if (ke->key() == Qt::Key_Escape && ActiveDialog->isEscapeButtonEnabled()) {
else if (ke->key() == Qt::Key_Escape && active->ActiveDialog->isEscapeButtonEnabled()) {
// get only the buttons of the button box
QDialogButtonBox* box = ActiveCtrl->standardButtons();
QDialogButtonBox* box = active->ActiveCtrl->standardButtons();
QList<QAbstractButton*> list = box->buttons();
for (auto pb : list) {
if (box->buttonRole(pb) == QDialogButtonBox::RejectRole) {
@@ -447,7 +458,7 @@ void TaskView::keyPressEvent(QKeyEvent* ke)
auto func = new Gui::TimerFunction();
func->setAutoDelete(true);
Gui::Document* doc = Gui::Application::Instance->getDocument(
ActiveDialog->getDocumentName().c_str()
active->ActiveDialog->getDocumentName().c_str()
);
if (doc) {
func->setFunction([doc]() { doc->resetEdit(); });
@@ -474,105 +485,96 @@ void TaskView::adjustMinimumSizeHint()
QSize TaskView::minimumSizeHint() const
{
QSize ms = QWidget::minimumSizeHint();
QSize ms = currentWidget()->minimumSizeHint();
int spacing = 0;
if (QLayout* layout = taskPanel->layout()) {
if (QLayout* layout = currentWidget()->layout()) {
spacing = 2 * layout->spacing();
}
ms.setWidth(taskPanel->minimumSizeHint().width() + spacing);
ms.setWidth(ms.width() + spacing);
return ms;
}
void TaskView::slotActiveDocument(const App::Document& doc)
{
Q_UNUSED(doc);
if (!ActiveDialog) {
auto foundTaskInfo = std::ranges::find(taskInfos, &doc, &TaskInfo::Document);
if (foundTaskInfo != taskInfos.end()) {
setShownTaskInfo((foundTaskInfo - taskInfos.begin()));
}
else {
setShownTaskInfo(-1);
}
if (foundTaskInfo == taskInfos.end()) {
// at this point, active object of the active view returns None.
// which is a problem if shouldShow of a watcher rely on the presence
// of an active object (example Assembly).
QTimer::singleShot(100, this, &TaskView::updateWatcher);
}
}
void TaskView::slotInEdit(const Gui::ViewProviderDocumentObject& vp)
{
Q_UNUSED(vp);
if (!ActiveDialog) {
App::Document* doc = vp.getDocument()->getDocument();
if (std::ranges::find(taskInfos, doc, &TaskInfo::Document) == taskInfos.end()) {
updateWatcher();
}
}
void TaskView::slotDeletedDocument(const App::Document& doc)
{
if (ActiveDialog) {
if (ActiveDialog->isAutoCloseOnDeletedDocument()) {
std::string name = ActiveDialog->getDocumentName();
if (name.empty()) {
Base::Console().warning(
std::string("TaskView::slotDeletedDocument"),
"No document name set\n"
);
}
if (name == doc.getName()) {
ActiveDialog->autoClosedOnDeletedDocument();
removeDialog();
}
}
auto foundTaskInfo = std::ranges::find(taskInfos, &doc, &TaskInfo::Document);
bool hasDialog = foundTaskInfo != taskInfos.end();
if (hasDialog && foundTaskInfo->ActiveDialog->isAutoCloseOnDeletedDocument()) {
foundTaskInfo->ActiveDialog->autoClosedOnDeletedDocument();
removeDialog(foundTaskInfo);
hasDialog = false;
}
if (!ActiveDialog) {
if (!hasDialog) {
updateWatcher();
}
}
void TaskView::slotViewClosed(const Gui::MDIView* view)
{
auto foundTaskInfo = std::ranges::find_if(taskInfos, [view](const TaskInfo& info) {
return info.ActiveDialog->getAssociatedView() == view;
});
bool hasDialog = foundTaskInfo != taskInfos.end();
// It can happen that only a view is closed an not the document
if (ActiveDialog) {
if (ActiveDialog->isAutoCloseOnClosedView()) {
const Gui::MDIView* associatedView = ActiveDialog->getAssociatedView();
if (!associatedView) {
Base::Console().warning(std::string("TaskView::slotViewClosed"), "No view associated\n");
}
if (associatedView == view) {
ActiveDialog->autoClosedOnClosedView();
removeDialog();
}
}
if (hasDialog && foundTaskInfo->ActiveDialog->isAutoCloseOnClosedView()) {
foundTaskInfo->ActiveDialog->autoClosedOnClosedView();
removeDialog(foundTaskInfo);
hasDialog = false;
}
if (!ActiveDialog) {
if (!hasDialog) {
updateWatcher();
}
}
void TaskView::transactionChangeOnDocument(const App::Document& doc, bool undo)
{
if (ActiveDialog) {
std::string name = ActiveDialog->getDocumentName();
if (name == doc.getName()) {
undo ? ActiveDialog->onUndo() : ActiveDialog->onRedo();
auto foundTaskInfo = std::ranges::find(taskInfos, &doc, &TaskInfo::Document);
bool hasDialog = foundTaskInfo != taskInfos.end();
if (hasDialog) {
if (undo) {
foundTaskInfo->ActiveDialog->onUndo();
}
else {
foundTaskInfo->ActiveDialog->onRedo();
}
if (ActiveDialog->isAutoCloseOnTransactionChange()) {
if (name.empty()) {
Base::Console().warning(
std::string("TaskView::transactionChangeOnDocument"),
"No document name set\n"
);
}
if (name == doc.getName()) {
ActiveDialog->autoClosedOnTransactionChange();
removeDialog();
}
if (foundTaskInfo->ActiveDialog->isAutoCloseOnTransactionChange()) {
foundTaskInfo->ActiveDialog->autoClosedOnTransactionChange();
removeDialog(foundTaskInfo);
hasDialog = false;
}
}
if (!ActiveDialog) {
if (!hasDialog) {
updateWatcher();
}
}
@@ -600,72 +602,75 @@ void TaskView::OnChange(
|| Reason.Type == SelectionChanges::SetSelection
|| Reason.Type == SelectionChanges::RmvSelection) {
if (!ActiveDialog) {
if (!currentTaskInfo()) {
updateWatcher();
}
}
}
/// @endcond
void TaskView::showDialog(TaskDialog* dlg)
bool TaskView::showDialog(TaskDialog* dlg, App::Document* doc)
{
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
// if trying to open the same dialog twice nothing needs to be done
if (ActiveDialog == dlg) {
return;
if (foundTaskInfo != taskInfos.end() && foundTaskInfo->ActiveDialog == dlg) {
return false;
}
assert(!ActiveDialog);
assert(!ActiveCtrl);
// remove the TaskWatcher as long as the Dialog is up
removeTaskWatcher();
assert(foundTaskInfo == taskInfos.end());
TaskInfo outInfo {.Document = doc};
// first create the control element, set it up and wire it:
ActiveCtrl = new TaskEditControl(this);
ActiveCtrl->buttonBox->setStandardButtons(dlg->getStandardButtons());
TaskDialogAttorney::setButtonBox(dlg, ActiveCtrl->buttonBox);
// clang-format off
// make connection to the needed signals
connect(ActiveCtrl->buttonBox, &QDialogButtonBox::accepted,
this, &TaskView::accept);
connect(ActiveCtrl->buttonBox, &QDialogButtonBox::rejected,
this, &TaskView::reject);
connect(ActiveCtrl->buttonBox, &QDialogButtonBox::helpRequested,
this, &TaskView::helpRequested);
connect(ActiveCtrl->buttonBox, &QDialogButtonBox::clicked,
this, &TaskView::clicked);
// clang-format on
outInfo.ActiveCtrl = new TaskEditControl(this);
outInfo.ActiveCtrl->buttonBox->setStandardButtons(dlg->getStandardButtons());
TaskDialogAttorney::setButtonBox(dlg, outInfo.ActiveCtrl->buttonBox);
const std::vector<QWidget*>& cont = dlg->getDialogContent();
// give to task dialog to customize the button box
dlg->modifyStandardButtons(ActiveCtrl->buttonBox);
dlg->modifyStandardButtons(outInfo.ActiveCtrl->buttonBox);
outInfo.taskPanel = new TaskPanel(this);
if (dlg->buttonPosition() == TaskDialog::North) {
// Add button box to the top of the main layout
dialogLayout->insertWidget(0, ActiveCtrl);
outInfo.taskPanel->dialogLayout->insertWidget(0, outInfo.ActiveCtrl);
for (const auto& it : cont) {
taskPanel->addWidget(it);
outInfo.taskPanel->actionPanel->addWidget(it);
}
}
else {
for (const auto& it : cont) {
taskPanel->addWidget(it);
outInfo.taskPanel->actionPanel->addWidget(it);
}
// Add button box to the bottom of the main layout
dialogLayout->addWidget(ActiveCtrl);
outInfo.taskPanel->dialogLayout->addWidget(outInfo.ActiveCtrl);
}
taskPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
outInfo.taskPanel->actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
if (!dlg->needsFullSpace()) {
taskPanel->addStretch();
outInfo.taskPanel->actionPanel->addStretch();
}
// set as active Dialog
ActiveDialog = dlg;
outInfo.ActiveDialog = dlg;
outInfo.ActiveDialog->open();
ActiveDialog->open();
// clang-format off
// make connection to the needed signals
connect(outInfo.ActiveCtrl->buttonBox, &QDialogButtonBox::accepted,
this, [doc, this]{ accept(doc); });
connect(outInfo.ActiveCtrl->buttonBox, &QDialogButtonBox::rejected,
this, [doc, this]{ reject(doc); });
connect(outInfo.ActiveCtrl->buttonBox, &QDialogButtonBox::helpRequested,
this, [doc, this]{ helpRequested(doc); });
connect(outInfo.ActiveCtrl->buttonBox, &QDialogButtonBox::clicked,
this, [doc, this](QAbstractButton *button) { clicked(button, doc); });
// clang-format on
// This will hide whatever was shown in the taskview
taskInfos.push_back(outInfo);
addWidget(outInfo.taskPanel);
setShownTaskInfo(taskInfos.size() - 1);
saveCurrentWidth();
getMainWindow()->updateActions();
@@ -675,43 +680,51 @@ void TaskView::showDialog(TaskDialog* dlg)
Q_EMIT taskUpdate();
OverlayManager::instance()->refresh();
return true;
}
void TaskView::removeDialog()
void TaskView::removeDialog(App::Document* doc)
{
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
if (foundTaskInfo != taskInfos.end()) {
removeDialog(foundTaskInfo);
}
}
void TaskView::removeDialog(std::vector<TaskInfo>::iterator infoIt)
{
if (infoIt == taskInfos.end()) {
return;
}
getMainWindow()->updateActions();
if (ActiveCtrl) {
dialogLayout->removeWidget(ActiveCtrl);
delete ActiveCtrl;
ActiveCtrl = nullptr;
}
TaskDialog* remove = nullptr;
if (ActiveDialog) {
std::optional<TaskInfo> remove = std::nullopt;
if (infoIt->ActiveDialog) {
// See 'accept' and 'reject'
if (ActiveDialog->property("taskview_accept_or_reject").isNull()) {
const std::vector<QWidget*>& cont = ActiveDialog->getDialogContent();
if (infoIt->ActiveDialog->property("taskview_accept_or_reject").isNull()) {
const std::vector<QWidget*>& cont = infoIt->ActiveDialog->getDialogContent();
for (const auto& it : cont) {
taskPanel->removeWidget(it);
infoIt->taskPanel->actionPanel->removeWidget(it);
}
remove = ActiveDialog;
ActiveDialog = nullptr;
remove = *infoIt;
taskInfos.erase(infoIt);
removeWidget(remove->taskPanel);
}
else {
ActiveDialog->setProperty("taskview_remove_dialog", true);
infoIt->ActiveDialog->setProperty("taskview_remove_dialog", true);
}
}
taskPanel->removeStretch();
// put the watcher back in control
removeTaskWatcher();
addTaskWatcher();
if (remove) {
remove->closed();
remove->emitDestructionSignal();
delete remove;
remove->ActiveDialog->closed();
remove->ActiveDialog->emitDestructionSignal();
delete remove->ActiveCtrl;
delete remove->ActiveDialog;
delete remove->taskPanel;
}
tryRestoreWidth();
@@ -741,7 +754,7 @@ void TaskView::updateWatcher()
if (ActiveWatcher.empty()) {
auto panel = Gui::Control().taskPanel();
if (panel && panel->ActiveWatcher.size()) {
if (panel && !panel->ActiveWatcher.empty()) {
takeTaskWatcher(panel);
}
}
@@ -797,9 +810,7 @@ void TaskView::addTaskWatcher(const std::vector<TaskWatcher*>& Watcher)
}
ActiveWatcher = Watcher;
if (!ActiveCtrl && !ActiveDialog) {
addTaskWatcher();
}
addTaskWatcher();
}
void TaskView::takeTaskWatcher(TaskView* other)
@@ -823,33 +834,34 @@ void TaskView::clearTaskWatcher()
void TaskView::addTaskWatcher()
{
if (!showTaskWatcher) {
setShownTaskInfo(-1); // Switch to the empty taskwatcher panel
return;
}
// add all widgets for all watcher to the task view
for (TaskWatcher* tw : ActiveWatcher) {
std::vector<QWidget*>& cont = tw->getWatcherContent();
for (QWidget* w : cont) {
taskPanel->addWidget(w);
TaskWatcherPanel->actionPanel->addWidget(w);
}
}
if (!ActiveWatcher.empty()) {
taskPanel->addStretch();
TaskWatcherPanel->actionPanel->addStretch();
}
updateWatcher();
// Workaround to avoid a crash in Qt. See also
// https://forum.freecad.org/viewtopic.php?f=8&t=39187
//
// Notify the button box about a style change so that it can
// safely delete the style animation of its push buttons.
auto box = taskPanel->findChild<QDialogButtonBox*>();
auto box = TaskWatcherPanel->mainLayout->findChild<QDialogButtonBox*>();
if (box) {
QEvent event(QEvent::StyleChange);
QApplication::sendEvent(box, &event);
}
taskPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
TaskWatcherPanel->actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
setShownTaskInfo(-1);
}
void TaskView::saveCurrentWidth()
@@ -879,6 +891,44 @@ bool TaskView::shouldRestoreWidth() const
{
return restoreWidth;
}
std::optional<TaskInfo> TaskView::currentTaskInfo() const
{
// Index 0 is taskWatcher's panel
if (currentIndex() <= 0) {
return std::nullopt;
}
return taskInfos[currentIndex() - 1];
}
TaskDialog* TaskView::dialog(App::Document* doc)
{
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
return foundTaskInfo == taskInfos.end() ? nullptr : foundTaskInfo->ActiveDialog;
}
void TaskView::setShownTaskInfo(int index)
{
int stackedIndex = 0;
int initIndex = currentIndex();
if (index < 0 || index >= taskInfos.size()) {
updateWatcher();
stackedIndex = 0; // Show task watcher
}
else {
stackedIndex = index + 1;
}
if (stackedIndex == initIndex) {
return; // Nothing to be done
}
if (initIndex > 0) {
Gui::Selection().rmvSelectionGate();
taskInfos[initIndex - 1].ActiveDialog->deactivate();
}
if (stackedIndex > 0) {
taskInfos[stackedIndex - 1].ActiveDialog->activate();
}
setCurrentIndex(stackedIndex);
}
void TaskView::removeTaskWatcher()
{
@@ -904,92 +954,109 @@ void TaskView::removeTaskWatcher()
std::vector<QWidget*>& cont = tw->getWatcherContent();
for (QWidget* w : cont) {
w->hide();
taskPanel->removeWidget(w);
TaskWatcherPanel->actionPanel->removeWidget(w);
}
}
taskPanel->removeStretch();
TaskWatcherPanel->actionPanel->removeStretch();
}
void TaskView::accept()
void TaskView::accept(App::Document* doc)
{
if (!ActiveDialog) { // Protect against segfaults due to out-of-order deletions
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
if (foundTaskInfo == taskInfos.end()) { // Protect against segfaults due to out-of-order deletions
Base::Console().warning("ActiveDialog was null in call to TaskView::accept()\n");
return;
}
// Make sure that if 'accept' calls 'closeDialog' the deletion is postponed until
// the dialog leaves the 'accept' method
ActiveDialog->setProperty("taskview_accept_or_reject", true);
bool success = ActiveDialog->accept();
ActiveDialog->setProperty("taskview_accept_or_reject", QVariant());
if (success || ActiveDialog->property("taskview_remove_dialog").isValid()) {
removeDialog();
foundTaskInfo->ActiveDialog->setProperty("taskview_accept_or_reject", true);
bool success = foundTaskInfo->ActiveDialog->accept();
foundTaskInfo->ActiveDialog->setProperty("taskview_accept_or_reject", QVariant());
if (success || foundTaskInfo->ActiveDialog->property("taskview_remove_dialog").isValid()) {
removeDialog(doc);
}
}
void TaskView::reject()
void TaskView::reject(App::Document* doc)
{
if (!ActiveDialog) { // Protect against segfaults due to out-of-order deletions
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
if (foundTaskInfo == taskInfos.end()) { // Protect against segfaults due to out-of-order deletions
Base::Console().warning("ActiveDialog was null in call to TaskView::reject()\n");
return;
}
// Make sure that if 'reject' calls 'closeDialog' the deletion is postponed until
// the dialog leaves the 'reject' method
ActiveDialog->setProperty("taskview_accept_or_reject", true);
bool success = ActiveDialog->reject();
ActiveDialog->setProperty("taskview_accept_or_reject", QVariant());
if (success || ActiveDialog->property("taskview_remove_dialog").isValid()) {
removeDialog();
foundTaskInfo->ActiveDialog->setProperty("taskview_accept_or_reject", true);
bool success = foundTaskInfo->ActiveDialog->reject();
foundTaskInfo->ActiveDialog->setProperty("taskview_accept_or_reject", QVariant());
if (success || foundTaskInfo->ActiveDialog->property("taskview_remove_dialog").isValid()) {
removeDialog(doc);
}
}
void TaskView::helpRequested()
void TaskView::helpRequested(App::Document* doc)
{
ActiveDialog->helpRequested();
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
if (foundTaskInfo != taskInfos.end()) {
foundTaskInfo->ActiveDialog->helpRequested();
}
}
void TaskView::clicked(QAbstractButton* button)
void TaskView::clicked(QAbstractButton* button, App::Document* doc)
{
int id = ActiveCtrl->buttonBox->standardButton(button);
ActiveDialog->clicked(id);
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
if (foundTaskInfo != taskInfos.end()) {
int id = foundTaskInfo->ActiveCtrl->buttonBox->standardButton(button);
foundTaskInfo->ActiveDialog->clicked(id);
}
}
void TaskView::clearActionStyle()
{
std::optional<TaskInfo> current = currentTaskInfo();
TaskPanel* panel = current ? current->taskPanel : TaskWatcherPanel;
static_cast<QSint::ActionPanelScheme*>(QSint::ActionPanelScheme::defaultScheme())->clearActionStyle();
taskPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
panel->actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
}
void TaskView::restoreActionStyle()
{
std::optional<TaskInfo> current = currentTaskInfo();
TaskPanel* panel = current ? current->taskPanel : TaskWatcherPanel;
static_cast<QSint::ActionPanelScheme*>(QSint::ActionPanelScheme::defaultScheme())
->restoreActionStyle();
taskPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
panel->actionPanel->setScheme(QSint::ActionPanelScheme::defaultScheme());
}
void TaskView::addContextualPanel(QWidget* panel)
void TaskView::addContextualPanel(QWidget* panel, App::Document* doc)
{
if (!panel || contextualPanels.contains(panel)) {
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
if (!panel || foundTaskInfo == taskInfos.end()
|| foundTaskInfo->taskPanel->contextualPanels.contains(panel)) {
return;
}
contextualPanelsLayout->addWidget(panel);
contextualPanels.append(panel);
foundTaskInfo->taskPanel->contextualPanelsLayout->addWidget(panel);
foundTaskInfo->taskPanel->contextualPanels.append(panel);
panel->show();
triggerMinimumSizeHint();
Q_EMIT taskUpdate();
}
void TaskView::removeContextualPanel(QWidget* panel)
void TaskView::removeContextualPanel(QWidget* panel, App::Document* doc)
{
if (!panel || !contextualPanels.contains(panel)) {
auto foundTaskInfo = std::ranges::find(taskInfos, doc, &TaskInfo::Document);
if (!panel || foundTaskInfo == taskInfos.end()
|| !foundTaskInfo->taskPanel->contextualPanels.contains(panel)) {
return;
}
contextualPanelsLayout->removeWidget(panel);
contextualPanels.removeOne(panel);
foundTaskInfo->taskPanel->contextualPanelsLayout->removeWidget(panel);
foundTaskInfo->taskPanel->contextualPanels.removeOne(panel);
panel->deleteLater();
triggerMinimumSizeHint();
Q_EMIT taskUpdate();
+43 -20
View File
@@ -25,7 +25,9 @@
#pragma once
#include <vector>
#include <optional>
#include <QScrollArea>
#include <QStackedWidget>
#include <Base/Parameter.h>
#include <Gui/QSint/include/QSint>
@@ -118,7 +120,7 @@ private:
bool wasShown;
};
class GuiExport TaskPanel: public QSint::ActionPanel
class GuiExport TaskPanel: public QWidget
{
Q_OBJECT
@@ -126,6 +128,14 @@ public:
explicit TaskPanel(QWidget* parent = nullptr);
~TaskPanel() override;
QSize minimumSizeHint() const override;
public:
QVBoxLayout* mainLayout;
QScrollArea* scrollArea;
QVBoxLayout* contextualPanelsLayout;
QVBoxLayout* dialogLayout;
QList<QWidget*> contextualPanels;
QSint::ActionPanel* actionPanel;
};
/// Father class of content of a Free widget (without header and Icon), shut be an exception!
@@ -138,12 +148,20 @@ public:
~TaskWidget() override;
};
struct TaskInfo
{
TaskPanel* taskPanel {nullptr};
TaskDialog* ActiveDialog {nullptr};
TaskEditControl* ActiveCtrl {nullptr};
App::Document* Document {nullptr};
};
/** TaskView class
* handles the FreeCAD task view panel. Keeps track of the inserted content elements.
* This elements get injected mostly by the ViewProvider classes of the selected
* DocumentObjects.
*/
class GuiExport TaskView: public QWidget, public Gui::SelectionSingleton::ObserverType
class GuiExport TaskView: public QStackedWidget, public Gui::SelectionSingleton::ObserverType
{
Q_OBJECT
@@ -160,6 +178,7 @@ public:
friend class Gui::DockWnd::ComboView;
friend class Gui::ControlSingleton;
/// sets the task watcher and shows it
void addTaskWatcher(const std::vector<TaskWatcher*>& Watcher);
void clearTaskWatcher();
void takeTaskWatcher(TaskView* other);
@@ -170,8 +189,8 @@ public:
void restoreActionStyle();
/// Add a persistent panel at the top of the task view, independent of the active dialog.
void addContextualPanel(QWidget* panel);
void removeContextualPanel(QWidget* panel);
void addContextualPanel(QWidget* panel, App::Document* doc);
void removeContextualPanel(QWidget* panel, App::Document* doc);
QSize minimumSizeHint() const override;
@@ -179,14 +198,22 @@ public:
void setRestoreWidth(bool on);
bool shouldRestoreWidth() const;
std::optional<TaskInfo> currentTaskInfo() const;
TaskDialog* dialog(App::Document* doc);
// Show the task info at the index
// or taskwatcher if index = -1
void setShownTaskInfo(int index);
Q_SIGNALS:
void taskUpdate();
protected Q_SLOTS:
void accept();
void reject();
void helpRequested();
void clicked(QAbstractButton* button);
protected:
void accept(App::Document* doc);
void reject(App::Document* doc);
void helpRequested(App::Document* doc);
void clicked(QAbstractButton* button, App::Document* doc);
private:
void triggerMinimumSizeHint();
@@ -200,11 +227,6 @@ private:
void slotUndoDocument(const App::Document&);
void slotRedoDocument(const App::Document&);
void transactionChangeOnDocument(const App::Document&, bool undo);
QVBoxLayout* mainLayout;
QScrollArea* scrollArea;
QVBoxLayout* contextualPanelsLayout;
QVBoxLayout* dialogLayout;
QList<QWidget*> contextualPanels;
protected:
void keyPressEvent(QKeyEvent* event) override;
@@ -214,18 +236,19 @@ protected:
void removeTaskWatcher();
/// update the visibility of the TaskWatcher accordant to the selection
void updateWatcher();
/// used by Gui::Control to register Dialogs
void showDialog(TaskDialog* dlg);
/// used by Gui::Control to register Dialogs, returns true if the dialog was not already there
bool showDialog(TaskDialog* dlg, App::Document* doc);
// removes the running dialog after accept() or reject() from the TaskView
void removeDialog();
void removeDialog(App::Document* doc);
void removeDialog(std::vector<TaskInfo>::iterator infoIt);
void setShowTaskWatcher(bool show);
std::vector<TaskWatcher*> ActiveWatcher;
TaskPanel* TaskWatcherPanel;
QSint::ActionPanel* taskPanel;
TaskDialog* ActiveDialog;
TaskEditControl* ActiveCtrl;
// First index of the stack is reserved to the active watcher
std::vector<TaskInfo> taskInfos;
bool restoreWidth = false;
int currentWidth = 0;
ParameterGrp::handle hGrp;
+34 -28
View File
@@ -47,6 +47,7 @@
#include <Base/Writer.h>
#include <Base/Color.h>
#include <App/Application.h>
#include <App/Document.h>
#include <App/DocumentObjectGroup.h>
#include <App/AutoTransaction.h>
@@ -1351,10 +1352,10 @@ void TreeWidget::showEvent(QShowEvent* ev)
void TreeWidget::onCreateGroup()
{
QString name = tr("Group");
App::AutoTransaction trans("Create group");
if (this->contextItem->type() == DocumentType) {
auto docitem = static_cast<DocumentItem*>(this->contextItem);
App::Document* doc = docitem->document()->getDocument();
App::AutoTransaction trans(doc, "Create group");
QString cmd = QStringLiteral(
"App.getDocument(\"%1\").addObject"
"(\"App::DocumentObjectGroup\",\"Group\").Label=\"%2\""
@@ -1366,6 +1367,7 @@ void TreeWidget::onCreateGroup()
auto objitem = static_cast<DocumentObjectItem*>(this->contextItem);
App::DocumentObject* obj = objitem->object()->getObject();
App::Document* doc = obj->getDocument();
App::AutoTransaction trans(doc, "Create group");
QString cmd = QStringLiteral(
"App.getDocument(\"%1\").getObject(\"%2\")"
".newObject(\"App::DocumentObjectGroup\",\"Group\").Label=\"%3\""
@@ -1557,7 +1559,8 @@ void TreeWidget::onRecomputeObject()
if (objs.empty()) {
return;
}
App::AutoTransaction committer("Recompute object");
App::AutoTransaction committer(objs.front()->getDocument()->openTransaction("Recompute object"));
objs.front()->getDocument()->recompute(objs, true);
}
@@ -2026,7 +2029,10 @@ void TreeWidget::mouseDoubleClickEvent(QMouseEvent* event)
auto objitem = static_cast<DocumentObjectItem*>(item);
ViewProviderDocumentObject* vp = objitem->object();
objitem->getOwnerDocument()->document()->setActiveView(vp);
Gui::Document* guidoc = objitem->getOwnerDocument()->document();
App::Document* appdoc = guidoc->getDocument();
guidoc->setActiveView(vp);
auto manager = Application::Instance->macroManager();
auto lines = manager->getLines();
@@ -2035,8 +2041,7 @@ void TreeWidget::mouseDoubleClickEvent(QMouseEvent* event)
const char* commandText = vp->getTransactionText();
if (commandText) {
auto editDoc = Application::Instance->editDocument();
App::AutoTransaction committer(commandText, true);
appdoc->openTransaction(commandText);
if (!vp->doubleClicked()) {
QTreeWidget::mouseDoubleClickEvent(event);
@@ -2044,11 +2049,6 @@ void TreeWidget::mouseDoubleClickEvent(QMouseEvent* event)
else if (lines == manager->getLines()) {
manager->addLine(MacroManager::Gui, ss.str().c_str());
}
// If the double click starts an editing, let the transaction persist
if (!editDoc && Application::Instance->editDocument()) {
committer.setEnable(false);
}
}
else {
if (!vp->doubleClicked()) {
@@ -2422,23 +2422,27 @@ bool TreeWidget::dropInDocument(
infos.reserve(items.size());
bool syncPlacement = TreeParams::getSyncPlacement();
App::AutoTransaction committer(
da == Qt::LinkAction ? "Link object"
: da == Qt::CopyAction ? "Copy object"
: "Move object"
);
int tid = 0;
std::string transName = da == Qt::LinkAction ? "Link object"
: da == Qt::CopyAction ? "Copy object"
: "Move object";
// check if items can be dragged
for (auto& v : items) {
auto item = v.first;
auto obj = item->object()->getObject();
auto parentItem = item->getParentItem();
tid = obj->getDocument()->openTransaction(
transName,
tid
); // If the same document already has this transaction opened, it is ignored
if (parentItem) {
bool allParentsOK = canDragFromParents(parentItem, obj, nullptr);
if (!allParentsOK || !parentItem->object()->canDragObjects()
|| !parentItem->object()->canDragObject(obj)) {
committer.close(true);
App::GetApplication().abortTransaction(tid);
TREE_ERR(
"'" << obj->getFullName() << "' cannot be dragged out of '"
<< parentItem->object()->getObject()->getFullName() << "'"
@@ -2640,7 +2644,7 @@ bool TreeWidget::dropInDocument(
errMsg = "Unknown exception";
}
if (!errMsg.empty()) {
committer.close(true);
App::GetApplication().abortTransaction(tid);
QMessageBox::critical(
getMainWindow(),
QObject::tr("Drag & drop failed"),
@@ -2648,6 +2652,8 @@ bool TreeWidget::dropInDocument(
);
return false;
}
App::GetApplication().commitTransaction(tid);
return touched;
}
@@ -2715,14 +2721,12 @@ bool TreeWidget::dropInObject(
);
}
// Open command
App::AutoTransaction committer("Drop object");
bool syncPlacement = TreeParams::getSyncPlacement() && targetItemObj->isGroup();
bool setSelection = true;
std::vector<App::DocumentObject*> draggedObjects;
std::vector<std::pair<App::DocumentObject*, std::string>> droppedObjects;
std::vector<ItemInfo> infos;
int tid = 0;
// Only keep text names here, because you never know when doing drag
// and drop some object may delete other objects.
infos.reserve(items.size());
@@ -2731,6 +2735,7 @@ bool TreeWidget::dropInObject(
auto& info = infos.back();
auto item = v.first;
App::DocumentObject* obj = item->object()->getObject();
tid = obj->getDocument()->openTransaction("Drop object");
std::ostringstream str;
App::DocumentObject* topParent = nullptr;
@@ -2767,7 +2772,7 @@ bool TreeWidget::dropInObject(
info.parentDoc = vpp->getObject()->getDocument()->getName();
}
else {
committer.close(true);
App::GetApplication().abortTransaction(tid);
return false;
}
}
@@ -2777,13 +2782,13 @@ bool TreeWidget::dropInObject(
&& !vp->canDropObjectEx(obj, owner, info.subname.c_str(), item->mySubs)) {
if (event->possibleActions() & Qt::LinkAction) {
if (items.size() > 1) {
committer.close(true);
App::GetApplication().abortTransaction(tid);
TREE_TRACE("Cannot replace with more than one object");
return false;
}
auto ext = vp->getObject()->getExtensionByType<App::LinkBaseExtension>(true);
if ((!ext || !ext->getLinkedObjectProperty()) && !targetItemObj->getParentItem()) {
committer.close(true);
App::GetApplication().abortTransaction(tid);
TREE_TRACE("Cannot replace without parent");
return false;
}
@@ -3036,7 +3041,7 @@ bool TreeWidget::dropInObject(
errMsg = "Unknown exception";
}
if (!errMsg.empty()) {
committer.close(true);
App::GetApplication().abortTransaction(tid);
QMessageBox::critical(
getMainWindow(),
QObject::tr("Drag & drop failed"),
@@ -3044,6 +3049,7 @@ bool TreeWidget::dropInObject(
);
return false;
}
App::GetApplication().commitTransaction(tid);
return touched;
}
@@ -3715,7 +3721,7 @@ void TreeWidget::scrollItemToTop()
continue;
}
auto doc = docItem->document()->getDocument();
if (Gui::Selection().hasSelection(doc->getName())) {
if (Gui::Selection().hasSelection(doc->getName(), ResolveMode::OldStyleElement)) {
tree->currentDocItem = docItem;
docItem->selectItems(DocumentItem::SR_FORCE_EXPAND);
tree->currentDocItem = nullptr;
@@ -4345,13 +4351,13 @@ void DocumentItem::slotInEdit(const Gui::ViewProviderDocumentObject& v)
QColor color(Base::Color::fromPackedRGB<QColor>(col));
if (!getTree()->editingItem) {
auto doc = Application::Instance->editDocument();
if (!doc) {
// In which cases would this return? theo-vt
if (!Application::Instance->isInEdit(document())) {
return;
}
ViewProviderDocumentObject* parentVp = nullptr;
std::string subname;
auto vp = doc->getInEdit(&parentVp, &subname);
auto vp = document()->getInEdit(&parentVp, &subname);
if (!parentVp) {
parentVp = freecad_cast<ViewProviderDocumentObject*>(vp);
}
+4 -1
View File
@@ -191,7 +191,10 @@ void ViewProvider::setEditViewer(View3DInventorViewer*, int ModNum)
void ViewProvider::unsetEditViewer(View3DInventorViewer*)
{}
void ViewProvider::setActive(bool active)
{
Q_UNUSED(active);
}
bool ViewProvider::isUpdatesEnabled() const
{
return testStatus(UpdateData);
+2
View File
@@ -559,6 +559,8 @@ public:
virtual ViewProvider* startEditing(int ModNum = 0);
bool isEditing() const;
void finishEditing();
virtual void setActive(bool active);
/// adjust viewer settings when editing a view provider
virtual void setEditViewer(View3DInventorViewer*, int ModNum);
/// restores viewer settings when leaving editing mode
+3 -3
View File
@@ -272,11 +272,11 @@ void ViewProviderDocumentObject::setShowable(bool enable)
void ViewProviderDocumentObject::startDefaultEditMode()
{
QString text = QObject::tr("Edit %1").arg(QString::fromUtf8(getObject()->Label.getValue()));
Gui::Command::openCommand(text.toUtf8());
Gui::Document* document = this->getDocument();
if (document) {
QString text = QObject::tr("Edit %1").arg(QString::fromUtf8(getObject()->Label.getValue()));
document->openCommand(text.toUtf8()); // Command is opened here and individual dialogs have
// to close it
document->setEdit(this, ViewProvider::Default);
}
}
+6 -4
View File
@@ -152,8 +152,10 @@ bool ViewProviderDragger::forwardToLink()
ViewProviderDocumentObject* vpParent = nullptr;
std::string subname;
auto doc = Application::Instance->editDocument();
if (!doc) {
// since we don't want to edit another document, only forward if the
// current document is in edit
auto doc = getDocument();
if (!Application::Instance->isInEdit(doc)) {
return nullptr;
}
@@ -209,7 +211,7 @@ bool ViewProviderDragger::setEdit(int ModNum)
transformDragger->addFinishCallback(dragFinishCallback, this);
transformDragger->addMotionCallback(dragMotionCallback, this);
Gui::Control().showDialog(getTransformDialog());
Gui::Control().showDialog(getTransformDialog(), getDocument()->getDocument());
updateDraggerPosition();
@@ -222,7 +224,7 @@ void ViewProviderDragger::unsetEdit(int ModNum)
transformDragger.reset();
Gui::Control().closeDialog();
Gui::Control().closeDialog(getDocument()->getDocument());
}
void ViewProviderDragger::setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum)
+10 -5
View File
@@ -441,7 +441,8 @@ public:
/// Starts to drag the object
void dragObject(App::DocumentObject* obj) override
{
App::AutoTransaction committer;
// AutoTransaction does not work the way it used to, plus the called method should deal with
// transaction itself - theo-vt
switch (imp->dragObject(obj)) {
case ViewProviderFeaturePythonImp::Accepted:
case ViewProviderFeaturePythonImp::Rejected:
@@ -477,7 +478,8 @@ public:
/// If the dropped object type is accepted the object will be added as child
void dropObject(App::DocumentObject* obj) override
{
App::AutoTransaction committer;
// AutoTransaction does not work the way it used to, plus the called method should deal with
// transaction itself - theo-vt
switch (imp->dropObject(obj)) {
case ViewProviderFeaturePythonImp::Accepted:
case ViewProviderFeaturePythonImp::Rejected:
@@ -522,7 +524,8 @@ public:
const std::vector<std::string>& elements
) override
{
App::AutoTransaction committer;
// AutoTransaction does not work the way it used to, plus the called method should deal with
// transaction itself - theo-vt
std::string ret;
if (!imp->dropObjectEx(obj, owner, subname, elements, ret)) {
ret = ViewProviderT::dropObjectEx(obj, owner, subname, elements);
@@ -676,7 +679,8 @@ protected:
int replaceObject(App::DocumentObject* oldObj, App::DocumentObject* newObj) override
{
App::AutoTransaction committer;
// AutoTransaction does not work the way it used to, plus the called method should deal with
// transaction itself - theo-vt
switch (imp->replaceObject(oldObj, newObj)) {
case ViewProviderFeaturePythonImp::Accepted:
return 1;
@@ -717,7 +721,8 @@ public:
protected:
bool doubleClicked() override
{
App::AutoTransaction committer;
// AutoTransaction does not work the way it used to, plus the called method should deal with
// transaction itself - theo-vt
switch (imp->doubleClicked()) {
case ViewProviderFeaturePythonImp::Accepted:
return true;
+3 -2
View File
@@ -39,11 +39,12 @@
#include <App/Document.h>
#include <App/ImagePlane.h>
#include <Gui/Document.h>
#include <Gui/ActionFunction.h>
#include <Gui/BitmapFactory.h>
#include <Gui/Control.h>
#include <Gui/TaskView/TaskImage.h>
#include <App/ImagePlane.h>
#include "ViewProviderImagePlane.h"
@@ -184,7 +185,7 @@ void ViewProviderImagePlane::manipulateImage()
{
auto dialog = new TaskImageDialog(getObject<Image::ImagePlane>());
Gui::Control().showDialog(dialog);
Gui::Control().showDialog(dialog, getDocument()->getDocument());
}
void ViewProviderImagePlane::resizePlane(float xsize, float ysize)
+39 -19
View File
@@ -2982,6 +2982,8 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
return;
}
Gui::Document* doc = getDocument();
_setupContextMenu(ext, menu, receiver, member);
Gui::ActionFunction* func = nullptr;
@@ -3033,8 +3035,14 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
options |= App::Link::OnChangeCopyOptions::ApplyAll;
}
App::AutoTransaction guard("Setup configurable object");
int tid = 0;
auto sels = dlg.getSelections(DlgObjectSelection::SelectionOptions::InvertSort);
// Open transaction on all touched documents if there is more than one
for (const auto& sel : sels) {
tid = sel->getDocument()->openTransaction("Setup configurable object", tid);
}
for (const auto& exclude : excludes) {
auto iter = std::lower_bound(sels.begin(), sels.end(), exclude);
if (iter == sels.end() || *iter != exclude) {
@@ -3063,6 +3071,8 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
}
}
Command::updateActive();
App::GetApplication().commitTransaction(tid);
}
catch (Base::Exception& e) {
e.reportException();
@@ -3080,9 +3090,9 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
if (!func) {
func = new Gui::ActionFunction(menu);
}
func->trigger(act, [ext]() {
func->trigger(act, [ext, doc]() {
try {
App::AutoTransaction guard("Enable Link copy on change");
App::AutoTransaction guard(doc->openCommand("Enable Link copy on change"));
ext->getLinkCopyOnChangeProperty()->setValue(1);
Command::updateActive();
}
@@ -3098,9 +3108,9 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
)
);
act->setData(-1);
func->trigger(act, [ext]() {
func->trigger(act, [ext, doc]() {
try {
App::AutoTransaction guard("Enable Link tracking");
App::AutoTransaction guard(doc->openCommand("Enable Link tracking"));
ext->getLinkCopyOnChangeProperty()->setValue(3);
Command::updateActive();
}
@@ -3117,9 +3127,9 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
if (!func) {
func = new Gui::ActionFunction(menu);
}
func->trigger(act, [ext]() {
func->trigger(act, [ext, doc]() {
try {
App::AutoTransaction guard("Disable copy on change");
App::AutoTransaction guard(doc->openCommand("Disable copy on change"));
ext->getLinkCopyOnChangeProperty()->setValue((long)0);
Command::updateActive();
}
@@ -3142,9 +3152,9 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
if (!func) {
func = new Gui::ActionFunction(menu);
}
func->trigger(act, [ext]() {
func->trigger(act, [ext, doc]() {
try {
App::AutoTransaction guard("Link refresh");
App::AutoTransaction guard(doc->openCommand("Link refresh"));
ext->syncCopyOnChange();
Command::updateActive();
}
@@ -3173,9 +3183,12 @@ void ViewProviderLink::_setupContextMenu(
if (ext->getLinkedObjectProperty() && ext->_getShowElementProperty()
&& ext->_getElementCountValue() > 1) {
auto action = menu->addAction(QObject::tr("Toggle Array Elements"), [ext] {
Gui::Document* doc = getDocument();
auto action = menu->addAction(QObject::tr("Toggle Array Elements"), [ext, doc] {
try {
App::AutoTransaction guard(QT_TRANSLATE_NOOP("Command", "Toggle array elements"));
App::AutoTransaction guard(
doc->openCommand(QT_TRANSLATE_NOOP("Command", "Toggle array elements"))
);
ext->getShowElementProperty()->setValue(!ext->getShowElementValue());
Command::updateActive();
}
@@ -3283,15 +3296,18 @@ bool ViewProviderLink::initDraggingPlacement()
FC_ERR("no placement");
return false;
}
auto doc = Application::Instance->editDocument();
if (!doc) {
FC_ERR("no editing document");
// Used to check for the only document in edit
// now we specifically ask for the document to be the vp's document
// I think it makes sense but there may be cases I did not consider - theo-vt
if (!Application::Instance->isInEdit(getDocument())) {
FC_ERR("document is not in edit");
return false;
}
dragCtx = std::make_unique<DraggerContext>();
dragCtx->preTransform = doc->getEditingTransform();
dragCtx->preTransform = getDocument()->getEditingTransform();
const auto& pla = getObject()->getPlacementProperty()->getValue();
// Cancel out our own transformation from the editing transform, because
@@ -3338,7 +3354,11 @@ ViewProvider* ViewProviderLink::startEditing(int mode)
static thread_local bool _pendingTransform;
static thread_local Matrix4D _editingTransform;
auto doc = Application::Instance->editDocument();
// Used to take the document in edit when there could only be one at a time
// here we take the current document the vp's document if it is in edit
// I don't think there is a case where a this function is invoked in another document
// with the intent of modifying that other document - theo-vt
Gui::Document* doc = Application::Instance->isInEdit(getDocument()) ? getDocument() : nullptr;
if (mode == ViewProvider::Transform) {
if (_pendingTransform && doc) {
@@ -3410,9 +3430,9 @@ bool ViewProviderLink::setEdit(int ModNum)
if (!ext || !ext->getColoredElementsProperty()) {
return false;
}
TaskView::TaskDialog* dlg = Control().activeDialog();
TaskView::TaskDialog* dlg = Control().activeDialog(getDocument()->getDocument());
if (dlg) {
Control().showDialog(dlg);
Control().showDialog(dlg, getDocument()->getDocument());
return false;
}
Selection().clearSelection();
@@ -3425,7 +3445,7 @@ bool ViewProviderLink::setEdit(int ModNum)
void ViewProviderLink::setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum)
{
if (ModNum == ViewProvider::Color) {
Gui::Control().showDialog(new TaskElementColors(this));
Gui::Control().showDialog(new TaskElementColors(this), getDocument()->getDocument());
return;
}
+40 -16
View File
@@ -32,10 +32,10 @@
#include <QActionGroup>
#include <App/Application.h>
#include <App/AutoTransaction.h>
#include <App/Document.h>
#include <Base/Console.h>
#include <Base/Tools.h>
#include <Gui/Document.h>
#include "Document.h"
#include "Tree.h"
@@ -358,11 +358,6 @@ void PropertyEditor::openEditor(const QModelIndex& index)
return;
}
auto& app = App::GetApplication();
if (app.getActiveTransaction()) {
FC_LOG("editor already transacting " << app.getActiveTransaction());
return;
}
auto item = static_cast<PropertyItem*>(editingIndex.internalPointer());
auto items = item->getPropertyData();
for (auto propItem = item->parent(); items.empty() && propItem; propItem = propItem->parent()) {
@@ -407,8 +402,8 @@ void PropertyEditor::openEditor(const QModelIndex& index)
if (items.size() > 1) {
str << "...";
}
transactionID = app.setActiveTransaction(str.str().c_str());
FC_LOG("editor transaction " << app.getActiveTransaction());
transactionID = obj->getDocument()->openTransaction(str.str().c_str());
FC_LOG("editor transaction " << App::GetApplication().getActiveTransaction(&transactionID));
}
void PropertyEditor::onItemActivated(const QModelIndex& index)
@@ -462,13 +457,15 @@ void PropertyEditor::recomputeDocument(App::Document* doc)
void PropertyEditor::closeTransaction()
{
int tid = 0;
if (App::GetApplication().getActiveTransaction(&tid) && tid == transactionID) {
App::Document* doc = App::GetApplication().getActiveDocument();
if (!doc) {
return;
}
if (doc->getBookedTransactionID() == transactionID) {
if (autoupdate) {
App::Document* doc = App::GetApplication().getActiveDocument();
recomputeDocument(doc);
}
App::GetApplication().closeActiveTransaction();
doc->commitTransaction();
}
}
@@ -941,15 +938,20 @@ std::unordered_set<App::Property*> PropertyEditor::acquireSelectedProperties() c
void PropertyEditor::removeProperties(const std::unordered_set<App::Property*>& props)
{
App::AutoTransaction committer("Remove property");
int tid = 0;
for (auto prop : props) {
try {
if (App::Document* doc = propertyDocument(prop->getContainer())) {
tid = doc->openTransaction("Remove property");
}
prop->getContainer()->removeDynamicProperty(prop->getName());
}
catch (Base::Exception& e) {
App::GetApplication().abortTransaction(tid);
e.reportException();
}
}
App::GetApplication().commitTransaction(tid);
}
void PropertyEditor::contextMenuEvent(QContextMenuEvent*)
@@ -1160,9 +1162,14 @@ void PropertyEditor::contextMenuEvent(QContextMenuEvent*)
if (!container) {
return;
}
App::AutoTransaction committer("Add property");
int tid = 0;
if (App::Document* doc = propertyDocument(container)) {
tid = doc->openTransaction("Add property");
}
Gui::Dialog::DlgAddProperty dlg(Gui::getMainWindow(), container);
dlg.exec();
App::GetApplication().commitTransaction(tid);
return;
}
case MA_EditPropTooltip: {
@@ -1203,8 +1210,10 @@ void PropertyEditor::contextMenuEvent(QContextMenuEvent*)
|| prop->testStatus(App::Property::LockDynamic)) {
break;
}
App::AutoTransaction committer("Rename property");
int tid = 0;
if (App::Document* doc = propertyDocument(prop->getContainer())) {
tid = doc->openTransaction("Rename property");
}
const char* oldName = prop->getName();
QString res = QInputDialog::getText(
Gui::getMainWindow(),
@@ -1222,9 +1231,11 @@ void PropertyEditor::contextMenuEvent(QContextMenuEvent*)
prop->getContainer()->renameDynamicProperty(prop, newName.c_str());
}
catch (Base::Exception& e) {
App::GetApplication().abortTransaction(tid);
e.reportException();
break;
}
App::GetApplication().commitTransaction(tid);
break;
}
case MA_EditPropGroup: {
@@ -1324,5 +1335,18 @@ QModelIndex PropertyEditor::indexResizable(QPoint mouse_pos)
}
return QModelIndex();
}
App::Document* PropertyEditor::propertyDocument(App::PropertyContainer* cont) const
{
if (auto* doc = dynamic_cast<App::Document*>(cont)) {
return doc;
}
if (auto* docObj = dynamic_cast<App::DocumentObject*>(cont)) {
return docObj->getDocument();
}
if (auto* vp = dynamic_cast<ViewProviderDocumentObject*>(cont)) {
return vp->getDocument()->getDocument();
}
return nullptr;
}
#include "moc_PropertyEditor.cpp"
+2
View File
@@ -154,6 +154,8 @@ private:
// and return the index of that cell if found
QModelIndex indexResizable(QPoint mouse_pos);
App::Document* propertyDocument(App::PropertyContainer* cont) const;
private:
PropertyItemDelegate* delegate;
PropertyModel* propertyModel;
+6 -3
View File
@@ -61,6 +61,7 @@
#include <Gui/SpinBox.h>
#include <Gui/VectorListEditor.h>
#include <Gui/ViewProviderDocumentObject.h>
#include <Gui/Document.h>
// NOLINTBEGIN(cppcoreguidelines-pro-*,cppcoreguidelines-prefer-member-initializer)
using namespace Gui::PropertyEditor;
@@ -2650,12 +2651,14 @@ PlacementEditor::~PlacementEditor() = default;
void PlacementEditor::browse()
{
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(
Gui::Application::Instance->activeDocument()->getDocument()
);
Gui::Dialog::TaskPlacement* task {};
task = qobject_cast<Gui::Dialog::TaskPlacement*>(dlg);
if (dlg && !task) {
// there is already another task dialog which must be closed first
Gui::Control().showDialog(dlg);
Gui::Control().showDialog(dlg, Gui::Application::Instance->activeDocument()->getDocument());
return;
}
if (!task) {
@@ -2669,7 +2672,7 @@ void PlacementEditor::browse()
task->setPropertyName(propertyname);
task->setSelection(Gui::Selection().getSelectionEx());
task->bindObject();
Gui::Control().showDialog(task);
Gui::Control().showDialog(task, Gui::Application::Instance->activeDocument()->getDocument());
}
void PlacementEditor::showValue(const QVariant& d)
+2 -2
View File
@@ -68,7 +68,7 @@ class CommandCreateAssembly:
return App.ActiveDocument is not None
def Activated(self):
App.setActiveTransaction("New Assembly")
Gui.ActiveDocument.openCommand("New assembly")
activeAssembly = UtilsAssembly.activeAssembly()
Gui.addModule("UtilsAssembly")
@@ -89,7 +89,7 @@ class CommandCreateAssembly:
if not activeAssembly:
Gui.doCommandGui("Gui.ActiveDocument.setEdit(assembly)")
App.closeActiveTransaction()
Gui.ActiveDocument.commitCommand()
class ActivateAssemblyTaskPanel:
+4 -4
View File
@@ -115,7 +115,7 @@ class TaskAssemblyCreateBom(QtCore.QObject):
pref = Preferences.preferences()
if bomObj:
App.setActiveTransaction("Edit Bill Of Materials")
Gui.ActiveDocument.openCommand("Edit Bill Of Materials")
for name in bomObj.columnsNames:
if name in ColumnNames:
@@ -126,7 +126,7 @@ class TaskAssemblyCreateBom(QtCore.QObject):
self.bomObj = bomObj
else:
App.setActiveTransaction("Create Bill Of Materials")
Gui.ActiveDocument.openCommand("Create Bill Of Materials")
# Add the columns
for name in TranslatedColumnNames:
@@ -152,7 +152,7 @@ class TaskAssemblyCreateBom(QtCore.QObject):
def accept(self):
self.deactivate()
App.closeActiveTransaction()
Gui.ActiveDocument.commitCommand()
self.bomObj.recompute()
@@ -162,7 +162,7 @@ class TaskAssemblyCreateBom(QtCore.QObject):
def reject(self):
self.deactivate()
App.closeActiveTransaction(True)
Gui.ActiveDocument.abortCommand()
return True
def deactivate(self):
+2 -2
View File
@@ -455,7 +455,7 @@ class CommandToggleGrounded:
if not selection:
return
App.setActiveTransaction("Toggle grounded")
Gui.ActiveDocument("Toggle grounded")
for sel in selection:
# If you select 2 solids (bodies for example) within an assembly.
# There'll be a single sel but 2 SubElementNames.
@@ -500,7 +500,7 @@ class CommandToggleGrounded:
# Create groundedJoint.
createGroundedJoint(moving_part)
App.closeActiveTransaction()
Gui.ActiveDocument.commitCommand()
if App.GuiUp:
+4 -4
View File
@@ -821,10 +821,10 @@ class TaskAssemblyCreateSimulation(QtCore.QObject):
if simFeaturePy:
self.simFeaturePy = simFeaturePy
App.setActiveTransaction("Edit " + simFeaturePy.Label + " Simulation")
Gui.ActiveDocument.openCommand("Edit " + simFeaturePy.Label + " Simulation")
self.onMotionsChanged()
else:
App.setActiveTransaction("Create Simulation")
Gui.ActiveDocument.openCommand("Create Simulation")
self.createSimulationObject()
self.setUiInitialValues()
@@ -860,12 +860,12 @@ class TaskAssemblyCreateSimulation(QtCore.QObject):
def accept(self):
self.deactivate()
UtilsAssembly.restoreAssemblyPartsPlacements(self.assembly, self.initialPlcs)
App.closeActiveTransaction()
Gui.ActiveDocument.commitCommand()
return True
def reject(self):
self.deactivate()
App.closeActiveTransaction(True)
Gui.ActiveDocument.abortCommand()
return True
def deactivate(self):
+6 -4
View File
@@ -628,14 +628,15 @@ class TaskAssemblyCreateView(QtCore.QObject):
self.initialPlcs = UtilsAssembly.saveAssemblyPartsPlacements(self.assembly)
if viewObj:
App.setActiveTransaction("Edit Exploded View")
Gui.ActiveDocument.openCommand("Edit Exploded View")
self.viewObj = viewObj
for move in self.viewObj.Group:
move.Visibility = True
self.onMovesChanged()
else:
App.setActiveTransaction("Create Exploded View")
Gui.ActiveDocument.openCommand("Create Exploded View")
self.createExplodedViewObject()
Gui.Selection.addSelectionGate(
@@ -669,14 +670,15 @@ class TaskAssemblyCreateView(QtCore.QObject):
more = UtilsAssembly.generatePropertySettings(move)
commands = commands + more
Gui.doCommand(commands[:-1]) # Don't use the last \n
App.closeActiveTransaction()
Gui.ActiveDocument.commitCommand()
self.viewObj.purgeTouched()
return True
def reject(self):
self.deactivate()
App.closeActiveTransaction(True)
Gui.ActiveDocument.abortCommand()
App.activeDocument().recompute()
return True
+3 -3
View File
@@ -140,7 +140,7 @@ class TaskAssemblyInsertLink(QtCore.QObject):
self.buildPartList()
App.setActiveTransaction("Insert Component")
Gui.ActiveDocument.openCommand("Insert Component")
# Listen for external deletions to keep the list in sync
self.docObserver = InsertLinkObserver(self.onObjectDeleted)
@@ -184,13 +184,13 @@ class TaskAssemblyInsertLink(QtCore.QObject):
)
Gui.doCommandSkip(commands[:-1]) # Get rid of last \n
App.closeActiveTransaction()
Gui.ActiveDocument.commitCommand()
return True
def reject(self):
self.deactivated()
App.closeActiveTransaction(True)
Gui.ActiveDocument.abortCommand()
return True
def deactivated(self):
+1 -1
View File
@@ -208,7 +208,7 @@ class TaskAssemblyNewPart(JointObject.TaskAssemblyCreateJoint):
self.createPart()
App.closeActiveTransaction()
Gui.ActiveDocument.commitCommand()
return True
+1 -1
View File
@@ -66,7 +66,7 @@ class CommandSolveAssembly:
App.setActiveTransaction("Solve assembly")
assembly.recompute(True)
App.closeActiveTransaction()
Gui.ActiveDocument.commitCommand()
if App.GuiUp:
+27 -10
View File
@@ -297,9 +297,7 @@ bool ViewProviderAssembly::setEdit(int mode)
this->getObject()->getNameInDocument()
);
setDragger();
attachSelection();
setupActiveAndInEdit();
updateTaskPanel(true);
@@ -337,8 +335,7 @@ void ViewProviderAssembly::unsetEdit(int mode)
partMoving = false;
docsToMove.clear();
unsetDragger();
detachSelection();
unsetupActiveAndInEdit();
// Check if the view is still active before trying to deactivate the assembly.
auto activeView = getDocument()->getActiveView();
@@ -422,6 +419,26 @@ bool ViewProviderAssembly::isInEditMode() const
{
return asmDragger != nullptr;
}
void ViewProviderAssembly::setupActiveAndInEdit()
{
setDragger();
attachSelection();
}
void ViewProviderAssembly::unsetupActiveAndInEdit()
{
unsetDragger();
detachSelection();
}
void ViewProviderAssembly::setActive(bool active)
{
if (active) {
setupActiveAndInEdit();
}
else {
unsetupActiveAndInEdit();
}
}
App::DocumentObject* ViewProviderAssembly::getActivePart() const
{
@@ -436,7 +453,7 @@ bool ViewProviderAssembly::keyPressed(bool pressed, int key)
{
if (key == SoKeyboardEvent::ESCAPE) {
if (isInEditMode()) {
if (Gui::Control().activeDialog()) {
if (Gui::Control().activeDialog(nullptr)) {
return true;
}
@@ -1083,7 +1100,7 @@ void ViewProviderAssembly::tryInitMove(const SbVec2s& cursorPos, Gui::View3DInve
}
if (moveInCommand) {
Gui::Command::openCommand(tr("Move part").toStdString().c_str());
getDocument()->openCommand(tr("Move part").toStdString().c_str());
}
partMoving = true;
@@ -1146,7 +1163,7 @@ void ViewProviderAssembly::endMove()
}
if (moveInCommand) {
Gui::Command::commitCommand();
getDocument()->commitCommand();
}
}
@@ -1875,11 +1892,11 @@ void ViewProviderAssembly::updateTaskPanel(bool show)
if (show && !taskSolver) {
taskSolver = new TaskAssemblyMessages(this);
taskView->addContextualPanel(taskSolver);
taskView->addContextualPanel(taskSolver, this->getObject()->getDocument());
UpdateSolverInformation();
}
else if (!show && taskSolver) {
taskView->removeContextualPanel(taskSolver);
taskView->removeContextualPanel(taskSolver, this->getObject()->getDocument());
taskSolver = nullptr;
}
}
@@ -127,6 +127,10 @@ public:
void setEditViewer(Gui::View3DInventorViewer*, int ModNum) override;
bool isInEditMode() const;
void setActive(bool active) override;
void setupActiveAndInEdit();
void unsetupActiveAndInEdit();
/// Ask the view provider if it accepts object deletions while in edit
bool acceptDeletionsInEdit() override
{
@@ -137,14 +137,14 @@ void ViewProviderAssemblyLink::setupContextMenu(QMenu* menu, QObject* receiver,
func->trigger(act, [this]() {
auto* assemblyLink = dynamic_cast<Assembly::AssemblyLink*>(getObject());
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Toggle Rigid"));
getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Toggle Rigid"));
Gui::cmdAppObjectArgs(
assemblyLink,
"Rigid = %s",
assemblyLink->Rigid.getValue() ? "False" : "True"
);
Gui::Command::commitCommand();
getDocument()->commitCommand();
Gui::Selection().clearSelection();
});
+7 -7
View File
@@ -1186,7 +1186,7 @@ class ViewProviderJoint:
return None
def doubleClicked(self, vobj):
App.closeActiveTransaction(True) # Close the auto-transaction
App.ActiveDocument.abortTransaction() # Close the auto-transaction
task = Gui.Control.activeTaskDialog()
if task:
@@ -1534,7 +1534,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
self.creating = False
self.joint = jointObj
self.jointName = jointObj.Label
App.setActiveTransaction("Edit " + self.jointName + " Joint")
Gui.ActiveDocument.openCommand("Edit " + self.jointName + " Joint")
self.updateTaskboxFromJoint()
self.visibilityBackup = self.joint.Visibility
@@ -1544,9 +1544,9 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
self.creating = True
self.jointName = self.jForm.jointType.currentText().replace(" ", "")
if self.activeType == "Part":
App.setActiveTransaction("Transform")
Gui.ActiveDocument.openCommand("Transform")
else:
App.setActiveTransaction("Create " + self.jointName + " Joint")
Gui.ActiveDocument.openCommand("Create " + self.jointName + " Joint")
self.refs = []
self.presel_ref = None
@@ -1634,12 +1634,12 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
self.assembly.recompute(True)
App.closeActiveTransaction()
Gui.ActiveDocument.commitCommand()
return True
def reject(self):
self.deactivate()
App.closeActiveTransaction(True)
Gui.ActiveDocument.abortCommand()
self.assembly.recompute(True)
return True
@@ -1652,7 +1652,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
Gui.Selection.removeSelectionGate()
Gui.Selection.removeObserver(self)
Gui.Selection.setSelectionStyle(Gui.Selection.SelectionStyle.NormalSelection)
App.closeActiveTransaction(True)
App.ActiveDocument.abortTransaction()
def deactivate(self):
global activeTask
+3
View File
@@ -380,6 +380,9 @@ def getGlobalPlacement(ref, targetObj=None):
def isThereOneRootAssembly():
if Gui.activeDocument() is None:
return False
for part in Gui.activeDocument().TreeRootObjects:
if part.TypeId == "Assembly::AssemblyObject":
return True
+4 -4
View File
@@ -1192,7 +1192,7 @@ static void DefineNodesCallback(void* ud, SoEventCallback* n)
std::string str = getSelectedNodes(view);
if (!str.empty()) {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Place robot"));
int tid = Gui::Command::openActiveDocumentCommand(QT_TRANSLATE_NOOP("Command", "Place robot"));
Gui::Command::doCommand(
Gui::Command::Doc,
"App.ActiveDocument.addObject('Fem::FemSetNodesObject','NodeSet')"
@@ -1208,7 +1208,7 @@ static void DefineNodesCallback(void* ud, SoEventCallback* n)
Analysis->getNameInDocument()
);
Gui::Command::commitCommand();
Gui::Command::commitCommand(tid);
}
}
@@ -1352,7 +1352,7 @@ static void DefineElementsCallback(void* ud, SoEventCallback* n)
std::string str = getSelectedNodes(view);
if (!str.empty()) {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Place robot"));
int tid = Gui::Command::openActiveDocumentCommand(QT_TRANSLATE_NOOP("Command", "Place robot"));
Gui::Command::doCommand(
Gui::Command::Doc,
"App.ActiveDocument.addObject('Fem::FemSetElementNodesObject','ElementSet')"
@@ -1368,7 +1368,7 @@ static void DefineElementsCallback(void* ud, SoEventCallback* n)
Analysis->getNameInDocument()
);
Gui::Command::commitCommand();
Gui::Command::commitCommand(tid);
}
}
+12 -2
View File
@@ -85,6 +85,16 @@ TaskCreateNodeSet::TaskCreateNodeSet(Fem::FemSetNodesObject* pcObject, QWidget*
ui->groupBox_AngleSearch->setEnabled(false);
}
void TaskCreateNodeSet::setSelectionGate()
{
if (selectionMode == none) {
Gui::Selection().rmvSelectionGate();
}
else if (selectionMode == PickElement) {
Gui::Selection().addSelectionGate(new FemSelectionGate(FemSelectionGate::Element));
}
}
void TaskCreateNodeSet::Poly()
{
Gui::Document* doc = Gui::Application::Instance->activeDocument();
@@ -102,7 +112,7 @@ void TaskCreateNodeSet::Pick()
if (selectionMode == none) {
selectionMode = PickElement;
Gui::Selection().clearSelection();
Gui::Selection().addSelectionGate(new FemSelectionGate(FemSelectionGate::Element));
setSelectionGate();
}
}
@@ -221,7 +231,7 @@ void TaskCreateNodeSet::onSelectionChanged(const Gui::SelectionChanges& msg)
}
selectionMode = none;
Gui::Selection().rmvSelectionGate();
setSelectionGate();
MeshViewProvider->setHighlightNodes(tempSet);
}
+2
View File
@@ -58,6 +58,8 @@ public:
explicit TaskCreateNodeSet(Fem::FemSetNodesObject* pcObject, QWidget* parent = nullptr);
~TaskCreateNodeSet() override;
void setSelectionGate();
std::set<long> tempSet;
ViewProviderFemMesh* MeshViewProvider;
+16 -1
View File
@@ -21,6 +21,7 @@
* *
***************************************************************************/
#include <App/Document.h>
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Gui/Application.h>
@@ -71,10 +72,14 @@ bool TaskDlgCreateElementSet::accept()
param->MeshViewProvider->resetHighlightNodes();
FemSetElementNodesObject->Label.setValue(name->name);
Gui::Command::doCommand(Gui::Command::Gui, "Gui.activeDocument().resetEdit()");
FemSetElementNodesObject->getDocument()
->commitTransaction(); // Opened in ViewProviderDocumentObject::startDefaultEditMode()
return true;
}
catch (const Base::Exception& e) {
FemSetElementNodesObject->getDocument()
->abortTransaction(); // Opened in ViewProviderDocumentObject::startDefaultEditMode()
Base::Console().warning("TaskDlgCreateElementSet::accept(): %s\n", e.what());
}
@@ -85,7 +90,8 @@ bool TaskDlgCreateElementSet::reject()
{
FemSetElementNodesObject->execute();
param->MeshViewProvider->resetHighlightNodes();
Gui::Command::abortCommand();
FemSetElementNodesObject->getDocument()
->abortTransaction(); // Opened in ViewProviderDocumentObject::startDefaultEditMode()
Gui::Command::doCommand(Gui::Command::Gui, "Gui.activeDocument().resetEdit()");
return true;
@@ -94,4 +100,13 @@ bool TaskDlgCreateElementSet::reject()
void TaskDlgCreateElementSet::helpRequested()
{}
void TaskDlgCreateElementSet::activate()
{
param->attachSelection();
}
void TaskDlgCreateElementSet::deactivate()
{
param->detachSelection();
}
#include "moc_TaskDlgCreateElementSet.cpp"
@@ -64,6 +64,9 @@ public:
/// is called by the framework if the user press the help button
void helpRequested() override;
void activate() override;
void deactivate() override;
/// returns for Close and Help button
QDialogButtonBox::StandardButtons getStandardButtons() const override
{
+4 -1
View File
@@ -21,6 +21,7 @@
***************************************************************************/
#include <App/Document.h>
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Gui/Application.h>
@@ -73,10 +74,12 @@ bool TaskDlgCreateNodeSet::accept()
param->MeshViewProvider->resetHighlightNodes();
FemSetNodesObject->Label.setValue(name->name);
Gui::Command::doCommand(Gui::Command::Gui, "Gui.activeDocument().resetEdit()");
FemSetNodesObject->getDocument()->commitTransaction();
return true;
}
catch (const Base::Exception& e) {
FemSetNodesObject->getDocument()->abortTransaction();
Base::Console().warning("TaskDlgCreateNodeSet::accept(): %s\n", e.what());
}
@@ -90,7 +93,7 @@ bool TaskDlgCreateNodeSet::reject()
// if(doc)
// doc->resetEdit();
param->MeshViewProvider->resetHighlightNodes();
Gui::Command::abortCommand();
FemSetNodesObject->getDocument()->abortTransaction();
Gui::Command::doCommand(Gui::Command::Gui, "Gui.activeDocument().resetEdit()");
return true;
+7 -5
View File
@@ -65,9 +65,9 @@ TaskDlgMeshShapeNetgen::~TaskDlgMeshShapeNetgen() = default;
void TaskDlgMeshShapeNetgen::open()
{
// a transaction is already open at creation time of the mesh
if (!Gui::Command::hasPendingCommand()) {
if (!ViewProviderFemMeshShapeNetgen->getDocument()->hasPendingCommand()) {
QString msg = tr("Edit FEM mesh");
Gui::Command::openCommand((const char*)msg.toUtf8());
FemMeshShapeNetgenObject->getDocument()->openTransaction((const char*)msg.toUtf8());
}
}
@@ -90,6 +90,7 @@ void TaskDlgMeshShapeNetgen::clicked(int button)
bool TaskDlgMeshShapeNetgen::accept()
{
App::Document* doc = FemMeshShapeNetgenObject->getDocument();
try {
if (param->touched) {
Gui::WaitCursor wc;
@@ -112,14 +113,15 @@ bool TaskDlgMeshShapeNetgen::accept()
}
// FemSetNodesObject->Label.setValue(name->name);
App::Document* doc = FemMeshShapeNetgenObject->getDocument();
doc->commitTransaction();
Gui::cmdAppDocument(doc, "recompute()");
Gui::cmdGuiDocument(doc, "resetEdit()");
Gui::Command::commitCommand();
return true;
}
catch (const Base::Exception& e) {
doc->abortTransaction();
Base::Console().warning("TaskDlgMeshShapeNetgen::accept(): %s\n", e.what());
}
@@ -133,8 +135,8 @@ bool TaskDlgMeshShapeNetgen::reject()
// //if(doc)
// // doc->resetEdit();
// param->MeshViewProvider->resetHighlightNodes();
Gui::Command::abortCommand();
App::Document* doc = FemMeshShapeNetgenObject->getDocument();
doc->abortTransaction();
Gui::cmdGuiDocument(doc, "resetEdit()");
Gui::cmdAppDocument(doc, "recompute()");
+5 -4
View File
@@ -209,9 +209,9 @@ void TaskFemConstraint::createDeleteAction(QListWidget* parentList)
void TaskDlgFemConstraint::open()
{
if (!Gui::Command::hasPendingCommand()) {
if (!ConstraintView->getDocument()->hasPendingCommand()) {
const char* typeName = ConstraintView->getObject()->getTypeId().getName();
Gui::Command::openCommand(typeName);
ConstraintView->getDocument()->openCommand(typeName);
ConstraintView->setVisible(true);
}
}
@@ -252,9 +252,10 @@ bool TaskDlgFemConstraint::accept()
throw Base::RuntimeError(ConstraintView->getObject()->getStatusString());
}
Gui::Command::doCommand(Gui::Command::Gui, "Gui.activeDocument().resetEdit()");
Gui::Command::commitCommand();
ConstraintView->getDocument()->commitCommand();
}
catch (const Base::Exception& e) {
ConstraintView->getDocument()->abortCommand();
QMessageBox::warning(parameter, tr("Input error"), QString::fromLatin1(e.what()));
return false;
}
@@ -265,7 +266,7 @@ bool TaskDlgFemConstraint::accept()
bool TaskDlgFemConstraint::reject()
{
// roll back the changes
Gui::Command::abortCommand();
ConstraintView->getDocument()->abortCommand();
Gui::Command::doCommand(Gui::Command::Gui, "Gui.activeDocument().resetEdit()");
Gui::Command::updateActive();
+1
View File
@@ -111,6 +111,7 @@ public:
bool accept() override;
/// is called by the framework if the dialog is rejected (Cancel)
bool reject() override;
bool isAllowedAlterDocument() const override
{
return false;
@@ -110,10 +110,14 @@ bool TaskDlgFemConstraintInitialTemperature::accept()
if (!ConstraintView->getObject()->isValid()) {
throw Base::RuntimeError(ConstraintView->getObject()->getStatusString());
}
ConstraintView->getDocument()->commitCommand(); // Opened in
// ViewProviderDocumentObject::startDefaultEditMode()
Gui::Command::doCommand(Gui::Command::Gui, "Gui.activeDocument().resetEdit()");
Gui::Command::commitCommand();
}
catch (const Base::Exception& e) {
ConstraintView->getDocument()->abortCommand(); // Opened in
// ViewProviderDocumentObject::startDefaultEditMode()
QMessageBox::warning(parameter, tr("Input error"), QString::fromLatin1(e.what()));
return false;
}
+5 -3
View File
@@ -353,9 +353,9 @@ void TaskDlgPost::connectSlots()
void TaskDlgPost::open()
{
// only open a new command if none is pending (e.g. if the object was newly created)
if (!Gui::Command::hasPendingCommand()) {
if (!m_view->getDocument()->hasPendingCommand()) {
auto text = std::string("Edit ") + m_view->getObject()->Label.getValue();
Gui::Command::openCommand(text.c_str());
m_view->getDocument()->openCommand(text.c_str());
}
}
@@ -387,8 +387,10 @@ bool TaskDlgPost::accept()
}
}
}
m_view->getDocument()->commitCommand();
}
catch (const Base::Exception& e) {
m_view->getDocument()->abortCommand();
QMessageBox::warning(nullptr, tr("Input error"), QString::fromLatin1(e.what()));
return false;
}
@@ -400,7 +402,7 @@ bool TaskDlgPost::accept()
bool TaskDlgPost::reject()
{
// roll back the done things
Gui::Command::abortCommand();
m_view->getDocument()->abortCommand();
Gui::cmdGuiDocument(getDocumentName(), "resetEdit()");
return true;
@@ -495,6 +495,14 @@ class GeometryElementsSelection(QtGui.QWidget):
# but close only one SelectionObserver on leaving the task panel
self.sel_server = FemSelectionObserver(self.selectionParser, print_message)
def attachSelection(self):
if self.sel_server:
FreeCADGui.Selection.addObserver(self.sel_server)
def detachSelection(self):
if self.sel_server:
FreeCADGui.Selection.removeObserver(self.sel_server)
def selectionParser(self, selection):
if hasattr(selection[0], "Shape") and selection[1]:
FreeCAD.Console.PrintMessage(
@@ -112,6 +112,12 @@ class _TaskPanel:
self._recomputeAndRestore()
return True
def activate(self):
self._selectionWidget.attachSelection()
def deactivate(self):
self._selectionWidget.detachSelection()
def _restoreVisibility(self):
if self._mesh is not None and self._part is not None:
if self._meshVisible:
@@ -53,3 +53,11 @@ class _BaseTaskPanel:
gui_doc.Document.recompute()
return True
def activate(self):
if self._selectionWidget:
self._selectionWidget.attachSelection()
def deactivate(self):
if self._selectionWidget:
self._selectionWidget.detachSelection()
+3 -3
View File
@@ -260,7 +260,7 @@ TaskMaterial::TaskMaterial()
taskbox->groupLayout()->addWidget(widget);
Content.push_back(taskbox);
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Set Material"));
tid = Gui::Command::openActiveDocumentCommand(QT_TRANSLATE_NOOP("Command", "Set Material"));
}
TaskMaterial::~TaskMaterial() = default;
@@ -272,13 +272,13 @@ QDialogButtonBox::StandardButtons TaskMaterial::getStandardButtons() const
bool TaskMaterial::accept()
{
Gui::Command::commitCommand();
Gui::Command::commitCommand(tid);
return true;
}
bool TaskMaterial::reject()
{
Gui::Command::abortCommand();
Gui::Command::abortCommand(tid);
widget->reject();
return (widget->result() == QDialog::Rejected);
}
+2
View File
@@ -30,6 +30,7 @@
#include <vector>
#include <App/Material.h>
#include <App/TransactionDefs.h>
#include <Gui/Selection/Selection.h>
#include <Gui/TaskView/TaskDialog.h>
#include <Gui/TaskView/TaskView.h>
@@ -114,6 +115,7 @@ public:
private:
DlgMaterialImp* widget;
Gui::TaskView::TaskBox* taskbox;
int tid {App::NullTransaction};
};
} // namespace MatGui
+1 -1
View File
@@ -107,7 +107,7 @@ void QuickMeasure::tryMeasureSelection()
{
Gui::Document* doc = Gui::Application::Instance->activeDocument();
measurement->clear();
if (doc && Gui::Control().activeDialog() == nullptr) {
if (doc && Gui::Control().activeDialog(nullptr) == nullptr) {
// we (still) have a doc and are not in a tool dialog where the user needs to click on stuff
addSelectionToMeasurement();
}
+32 -19
View File
@@ -111,12 +111,7 @@ TaskMeasure::TaskMeasure()
settings.beginGroup(QLatin1String(taskMeasureSettingsGroup));
delta = settings.value(QLatin1String(taskMeasureShowDeltaSettingsName), true).toBool();
mAutoSave = settings.value(QLatin1String(taskMeasureAutoSaveSettingsName), mAutoSave).toBool();
if (settings.value(QLatin1String(taskMeasureGreedySelection), false).toBool()) {
Gui::Selection().setSelectionStyle(SelectionStyle::GreedySelection);
}
else {
Gui::Selection().setSelectionStyle(SelectionStyle::NormalSelection);
}
mGreedySelection = settings.value(QLatin1String(taskMeasureGreedySelection), false).toBool();
settings.endGroup();
showDelta = new QCheckBox();
@@ -220,7 +215,6 @@ TaskMeasure::TaskMeasure()
Content.emplace_back(taskbox);
// engage the selectionObserver
attachSelection();
if (auto* doc = App::GetApplication().getActiveDocument()) {
m_deletedConnection = doc->signalDeletedObject.connect([this](auto&& obj) {
@@ -228,8 +222,9 @@ TaskMeasure::TaskMeasure()
});
}
if (!App::GetApplication().getActiveTransaction()) {
App::GetApplication().setActiveTransaction("Add Measurement");
if (auto* doc = Gui::Application::Instance->activeDocument()) {
mTargetDoc = doc;
mTargetDoc->openCommand("Add Measurement");
}
setAutoCloseOnDeletedDocument(true);
@@ -593,8 +588,10 @@ bool TaskMeasure::apply(bool reset)
}
// Commit transaction
App::GetApplication().closeActiveTransaction();
App::GetApplication().setActiveTransaction("Add Measurement");
if (mTargetDoc) {
mTargetDoc->commitCommand();
mTargetDoc->openCommand("Add Measurement");
}
return false;
}
@@ -604,7 +601,9 @@ bool TaskMeasure::reject()
closeDialog();
// Abort transaction
App::GetApplication().closeActiveTransaction(true);
if (mTargetDoc) {
mTargetDoc->abortCommand();
}
return false;
}
@@ -624,6 +623,15 @@ void TaskMeasure::reset()
this->update();
}
void TaskMeasure::activate()
{
updateSelectionType();
qApp->installEventFilter(this);
}
void TaskMeasure::deactivate()
{
qApp->removeEventFilter(this);
}
void TaskMeasure::removeObject()
@@ -769,18 +777,23 @@ void TaskMeasure::newMeasurementBehaviourChanged(bool checked)
{
QSettings settings;
settings.beginGroup(QLatin1String(taskMeasureSettingsGroup));
if (!checked) {
Gui::Selection().setSelectionStyle(SelectionStyle::NormalSelection);
settings.setValue(QLatin1String(taskMeasureGreedySelection), false);
settings.setValue(QLatin1String(taskMeasureGreedySelection), true);
mGreedySelection = checked;
updateSelectionType();
settings.endGroup();
}
void TaskMeasure::updateSelectionType()
{
if (mGreedySelection) {
Gui::Selection().setSelectionStyle(SelectionStyle::GreedySelection);
}
else {
Gui::Selection().setSelectionStyle(SelectionStyle::GreedySelection);
settings.setValue(QLatin1String(taskMeasureGreedySelection), true);
Gui::Selection().setSelectionStyle(SelectionStyle::NormalSelection);
}
settings.endGroup();
std::list<Gui::InputHint> hints;
if (checked) {
if (mGreedySelection) {
hints = std::list<Gui::InputHint> {
{tr("%1 new measurement, %2 toggle auto-save"), {{ModifierCtrl}, {ModifierShift}}}
};
+5
View File
@@ -68,6 +68,8 @@ public:
bool reject() override;
void reset();
void closed() override;
void activate() override;
void deactivate() override;
bool hasSelection();
void clearSelection();
@@ -101,6 +103,7 @@ private:
void showDeltaChanged(int checkState);
void autoSaveChanged(bool checked);
void newMeasurementBehaviourChanged(bool checked);
void updateSelectionType();
void setModeSilent(App::MeasureType* mode);
App::MeasureType* getMeasureType();
void enableAnnotateButton(bool state);
@@ -117,6 +120,8 @@ private:
bool delta = true;
bool mAutoSave = false;
QString mLastUnitSelection = QLatin1String("-");
bool mGreedySelection = false;
Gui::Document* mTargetDoc;
};
} // namespace MeasureGui
+9 -3
View File
@@ -163,7 +163,7 @@ bool TaskDecimating::accept()
Gui::Selection().clearSelection();
Gui::WaitCursor wc;
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Mesh Decimating"));
float tolerance = float(widget->tolerance());
float reduction = float(widget->reduction());
@@ -172,6 +172,13 @@ bool TaskDecimating::accept()
if (absolute) {
targetSize = widget->targetNumberOfTriangles();
}
// Here we assume that all meshes are in the same document
// if it turns out to not be the case then the transaction can be
// opened in the loop with the tid as an argument - theo-vt
int tid = meshes[0]->getDocument()->openTransaction(
QT_TRANSLATE_NOOP("Command", "Mesh Decimating")
);
for (auto mesh : meshes) {
if (absolute) {
Gui::cmdAppObjectArgs(mesh, "decimate(%i)", targetSize);
@@ -180,8 +187,7 @@ bool TaskDecimating::accept()
Gui::cmdAppObjectArgs(mesh, "decimate(%f, %f)", tolerance, reduction);
}
}
Gui::Command::commitCommand();
App::GetApplication().commitTransaction(tid);
return true;
}
+7 -4
View File
@@ -26,6 +26,7 @@
#include <QDialogButtonBox>
#include <App/Document.h>
#include <Gui/Command.h>
#include <Gui/Selection/Selection.h>
#include <Gui/WaitCursor.h>
@@ -166,11 +167,13 @@ bool TaskSmoothing::accept()
}
Gui::WaitCursor wc;
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Mesh Smoothing"));
int tid = 0;
bool hasSelection = false;
for (auto it : meshes) {
Mesh::Feature* mesh = static_cast<Mesh::Feature*>(it);
tid = mesh->getDocument()->openTransaction(QT_TRANSLATE_NOOP("Command", "Mesh Smoothing"), tid);
std::vector<Mesh::FacetIndex> selection;
if (widget->smoothSelection()) {
// clear the selection before editing the mesh to avoid
@@ -221,12 +224,12 @@ bool TaskSmoothing::accept()
mesh->Mesh.finishEditing();
}
if (widget->smoothSelection() && !hasSelection) {
Gui::Command::abortCommand();
if (widget->smoothSelection() && !hasSelection && tid) {
App::GetApplication().abortTransaction(tid);
return false;
}
Gui::Command::commitCommand();
App::GetApplication().commitTransaction(tid);
return true;
}
+17 -17
View File
@@ -234,7 +234,7 @@ void CmdPartPrimitives::activated(int iMsg)
bool CmdPartPrimitives::isActive()
{
return (hasActiveDocument() && !Gui::Control().activeDialog());
return (hasActiveDocument() && !Gui::Control().activeDialog(getDocument()));
}
namespace PartGui
@@ -1424,7 +1424,7 @@ CmdPartBoolean::CmdPartBoolean()
void CmdPartBoolean::activated(int iMsg)
{
Q_UNUSED(iMsg);
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(getDocument());
if (!dlg) {
dlg = new PartGui::TaskBooleanOperation();
}
@@ -1433,7 +1433,7 @@ void CmdPartBoolean::activated(int iMsg)
bool CmdPartBoolean::isActive()
{
return (hasActiveDocument() && !Gui::Control().activeDialog());
return (hasActiveDocument() && !Gui::Control().activeDialog(getDocument()));
}
//===========================================================================
@@ -1461,7 +1461,7 @@ void CmdPartExtrude::activated(int iMsg)
bool CmdPartExtrude::isActive()
{
return (hasActiveDocument() && !Gui::Control().activeDialog());
return (hasActiveDocument() && !Gui::Control().activeDialog(getDocument()));
}
//===========================================================================
@@ -1490,7 +1490,7 @@ void CmdPartScale::activated(int iMsg)
bool CmdPartScale::isActive()
{
return (hasActiveDocument() && !Gui::Control().activeDialog());
return (hasActiveDocument() && !Gui::Control().activeDialog(getDocument()));
}
//===========================================================================
@@ -1577,7 +1577,7 @@ void CmdPartRevolve::activated(int iMsg)
bool CmdPartRevolve::isActive()
{
return (hasActiveDocument() && !Gui::Control().activeDialog());
return (hasActiveDocument() && !Gui::Control().activeDialog(getDocument()));
}
//===========================================================================
@@ -1605,7 +1605,7 @@ void CmdPartFillet::activated(int iMsg)
bool CmdPartFillet::isActive()
{
return (hasActiveDocument() && !Gui::Control().activeDialog());
return (hasActiveDocument() && !Gui::Control().activeDialog(getDocument()));
}
//===========================================================================
@@ -1633,7 +1633,7 @@ void CmdPartChamfer::activated(int iMsg)
bool CmdPartChamfer::isActive()
{
return (hasActiveDocument() && !Gui::Control().activeDialog());
return (hasActiveDocument() && !Gui::Control().activeDialog(getDocument()));
}
//===========================================================================
@@ -1661,7 +1661,7 @@ void CmdPartMirror::activated(int iMsg)
bool CmdPartMirror::isActive()
{
return (hasActiveDocument() && !Gui::Control().activeDialog());
return (hasActiveDocument() && !Gui::Control().activeDialog(getDocument()));
}
//===========================================================================
@@ -1684,7 +1684,7 @@ CmdPartCrossSections::CmdPartCrossSections()
void CmdPartCrossSections::activated(int iMsg)
{
Q_UNUSED(iMsg);
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(getDocument());
if (!dlg) {
std::vector<Part::TopoShape> shapes = PartGui::getShapesFromSelection();
Base::BoundBox3d bbox;
@@ -1699,7 +1699,7 @@ void CmdPartCrossSections::activated(int iMsg)
bool CmdPartCrossSections::isActive()
{
bool hasShapes = PartGui::hasShapesInSelection();
return (hasShapes && !Gui::Control().activeDialog());
return (hasShapes && !Gui::Control().activeDialog(getDocument()));
}
//===========================================================================
@@ -1729,7 +1729,7 @@ void CmdPartBuilder::activated(int iMsg)
bool CmdPartBuilder::isActive()
{
return (hasActiveDocument() && !Gui::Control().activeDialog());
return (hasActiveDocument() && !Gui::Control().activeDialog(getDocument()));
}
//===========================================================================
@@ -1758,7 +1758,7 @@ void CmdPartLoft::activated(int iMsg)
bool CmdPartLoft::isActive()
{
return (hasActiveDocument() && !Gui::Control().activeDialog());
return (hasActiveDocument() && !Gui::Control().activeDialog(getDocument()));
}
//===========================================================================
@@ -1787,7 +1787,7 @@ void CmdPartSweep::activated(int iMsg)
bool CmdPartSweep::isActive()
{
return (hasActiveDocument() && !Gui::Control().activeDialog());
return (hasActiveDocument() && !Gui::Control().activeDialog(getDocument()));
}
//===========================================================================
@@ -2347,7 +2347,7 @@ void CmdCheckGeometry::activated(int iMsg)
bool CmdCheckGeometry::isActive()
{
bool hasShapes = PartGui::hasShapesInSelection();
return (hasShapes && !Gui::Control().activeDialog());
return (hasShapes && !Gui::Control().activeDialog(getDocument()));
}
//===========================================================================
@@ -2391,7 +2391,7 @@ void CmdColorPerFace::activated(int iMsg)
bool CmdColorPerFace::isActive()
{
bool objectSelected = Gui::Selection().countObjectsOfType<Part::Feature>() == 1;
return (hasActiveDocument() && !Gui::Control().activeDialog() && objectSelected);
return (hasActiveDocument() && !Gui::Control().activeDialog(getDocument()) && objectSelected);
}
//===========================================================================
@@ -2456,7 +2456,7 @@ void CmdPartProjectionOnSurface::activated(int iMsg)
bool CmdPartProjectionOnSurface::isActive()
{
return (hasActiveDocument() && !Gui::Control().activeDialog());
return (hasActiveDocument() && !Gui::Control().activeDialog(getDocument()));
}
//===========================================================================
+3 -2
View File
@@ -247,7 +247,8 @@ CmdPartSimpleCopy::CmdPartSimpleCopy()
static void _copyShape(const char* cmdName, bool resolve, bool needElement = false, bool refine = false)
{
Gui::WaitCursor wc;
Gui::Command::openCommand(cmdName);
int tid = Gui::Command::openActiveDocumentCommand(cmdName);
for (auto& sel : Gui::Selection().getSelectionEx(
"*",
App::DocumentObject::getClassTypeId(),
@@ -300,7 +301,7 @@ static void _copyShape(const char* cmdName, bool resolve, bool needElement = fal
Gui::Command::copyVisual(newObj, "PointColor", v.second);
}
}
Gui::Command::commitCommand();
Gui::Command::commitCommand(tid);
Gui::Command::updateActive();
}
+13 -3
View File
@@ -116,6 +116,7 @@ DlgExtrusion::DlgExtrusion(QWidget* parent, Qt::WindowFlags fl)
: QDialog(parent, fl)
, ui(new Ui_DlgExtrusion)
, filter(nullptr)
, filterSelection(false)
{
ui->setupUi(this);
setupConnections();
@@ -154,6 +155,7 @@ DlgExtrusion::~DlgExtrusion()
if (filter) {
Gui::Selection().rmvSelectionGate();
filter = nullptr;
filterSelection = false;
}
// no need to delete child widgets, Qt does it all for us
@@ -222,9 +224,9 @@ void DlgExtrusion::onDirModeNormalToggled(bool on)
void DlgExtrusion::onSelectEdgeClicked()
{
if (!filter) {
filter = new EdgeSelection();
Gui::Selection().addSelectionGate(filter);
if (!filterSelection) {
filterSelection = true;
setSelectionGate();
ui->btnSelectEdge->setText(tr("Selecting…"));
// visibility automation
@@ -254,6 +256,7 @@ void DlgExtrusion::onSelectEdgeClicked()
else {
Gui::Selection().rmvSelectionGate();
filter = nullptr;
filterSelection = false;
ui->btnSelectEdge->setText(tr("Select"));
// visibility automation
@@ -894,6 +897,13 @@ void DlgExtrusion::writeParametersToFeature(App::DocumentObject& feature, App::D
ui->spinTaperAngleRev->value().getValue()
);
}
void DlgExtrusion::setSelectionGate()
{
if (filterSelection) {
filter = new EdgeSelection();
Gui::Selection().addSelectionGate(filter);
}
}
// ---------------------------------------
+3
View File
@@ -65,6 +65,8 @@ public:
void writeParametersToFeature(App::DocumentObject& feature, App::DocumentObject* base) const;
void setSelectionGate();
protected:
void findShapes();
bool canExtrude(const TopoDS_Shape&) const;
@@ -101,6 +103,7 @@ private:
std::string document, label;
class EdgeSelection;
EdgeSelection* filter;
bool filterSelection;
};
class TaskExtrusion: public Gui::TaskView::TaskDialog
+6 -2
View File
@@ -264,8 +264,7 @@ DlgFilletEdges::DlgFilletEdges(
ui->filletEndRadius->setUnit(Base::Unit::Length);
d->object = nullptr;
d->selection = new EdgeFaceSelection(d->object);
Gui::Selection().addSelectionGate(d->selection);
setSelectionGate();
d->fillet = fillet;
// NOLINTBEGIN
@@ -1126,6 +1125,11 @@ bool DlgFilletEdges::accept()
Gui::Command::copyVisual(to, "PointColor", from);
return true;
}
void DlgFilletEdges::setSelectionGate()
{
d->selection = new EdgeFaceSelection(d->object);
Gui::Selection().addSelectionGate(d->selection);
}
// ---------------------------------------
+1
View File
@@ -106,6 +106,7 @@ public:
);
~DlgFilletEdges() override;
bool accept();
void setSelectionGate();
protected:
void findShapes();
+48 -41
View File
@@ -136,8 +136,6 @@ DlgProjectionOnSurface::DlgProjectionOnSurface(QWidget* parent)
: QWidget(parent)
, ui(new Ui::DlgProjectionOnSurface)
, m_projectionObjectName(tr("Projection object"))
, filterEdge(nullptr)
, filterFace(nullptr)
{
ui->setupUi(this);
setupConnections();
@@ -298,22 +296,31 @@ void PartGui::DlgProjectionOnSurface::reject()
m_partDocument->abortTransaction();
}
}
void PartGui::DlgProjectionOnSurface::setSelectionGate()
{
if (selectionMode == SelectionMode::Face) {
Gui::Selection().addSelectionGate(new FaceSelection());
}
else if (selectionMode == SelectionMode::Edge) {
Gui::Selection().addSelectionGate(new EdgeSelection());
}
}
void PartGui::DlgProjectionOnSurface::onPushButtonAddFaceClicked()
{
if (ui->pushButtonAddFace->isChecked()) {
m_currentSelection = "add_face";
disable_ui_elements(m_guiObjectVec, ui->pushButtonAddFace);
if (!filterFace) {
filterFace = new FaceSelection();
Gui::Selection().addSelectionGate(filterFace);
if (selectionMode != SelectionMode::Face) {
selectionMode = SelectionMode::Face;
setSelectionGate();
}
}
else {
m_currentSelection = "";
enable_ui_elements(m_guiObjectVec, nullptr);
Gui::Selection().rmvSelectionGate();
filterFace = nullptr;
selectionMode = SelectionMode::None;
}
}
@@ -322,9 +329,9 @@ void PartGui::DlgProjectionOnSurface::onPushButtonAddEdgeClicked()
if (ui->pushButtonAddEdge->isChecked()) {
m_currentSelection = "add_edge";
disable_ui_elements(m_guiObjectVec, ui->pushButtonAddEdge);
if (!filterEdge) {
filterEdge = new EdgeSelection();
Gui::Selection().addSelectionGate(filterEdge);
if (selectionMode != SelectionMode::Edge) {
selectionMode = SelectionMode::Edge;
setSelectionGate();
}
ui->radioButtonEdges->setChecked(true);
onRadioButtonEdgesClicked();
@@ -333,7 +340,7 @@ void PartGui::DlgProjectionOnSurface::onPushButtonAddEdgeClicked()
m_currentSelection = "";
enable_ui_elements(m_guiObjectVec, nullptr);
Gui::Selection().rmvSelectionGate();
filterEdge = nullptr;
selectionMode = SelectionMode::None;
}
}
@@ -1118,16 +1125,16 @@ void PartGui::DlgProjectionOnSurface::onPushButtonAddProjFaceClicked()
if (ui->pushButtonAddProjFace->isChecked()) {
m_currentSelection = "add_projection_surface";
disable_ui_elements(m_guiObjectVec, ui->pushButtonAddProjFace);
if (!filterFace) {
filterFace = new FaceSelection();
Gui::Selection().addSelectionGate(filterFace);
if (selectionMode != SelectionMode::Face) {
selectionMode = SelectionMode::Face;
setSelectionGate();
}
}
else {
m_currentSelection = "";
enable_ui_elements(m_guiObjectVec, nullptr);
Gui::Selection().rmvSelectionGate();
filterFace = nullptr;
selectionMode = SelectionMode::None;
}
}
void PartGui::DlgProjectionOnSurface::onRadioButtonShowAllClicked()
@@ -1160,9 +1167,9 @@ void PartGui::DlgProjectionOnSurface::onPushButtonAddWireClicked()
if (ui->pushButtonAddWire->isChecked()) {
m_currentSelection = "add_wire";
disable_ui_elements(m_guiObjectVec, ui->pushButtonAddWire);
if (!filterEdge) {
filterEdge = new EdgeSelection();
Gui::Selection().addSelectionGate(filterEdge);
if (selectionMode != SelectionMode::Edge) {
selectionMode = SelectionMode::Edge;
setSelectionGate();
}
ui->radioButtonEdges->setChecked(true);
onRadioButtonEdgesClicked();
@@ -1171,7 +1178,7 @@ void PartGui::DlgProjectionOnSurface::onPushButtonAddWireClicked()
m_currentSelection = "";
enable_ui_elements(m_guiObjectVec, nullptr);
Gui::Selection().rmvSelectionGate();
filterEdge = nullptr;
selectionMode = SelectionMode::None;
}
}
@@ -1235,8 +1242,6 @@ void TaskProjectionOnSurface::clicked(int id)
DlgProjectOnSurface::DlgProjectOnSurface(Part::ProjectOnSurface* feature, QWidget* parent)
: QWidget(parent)
, ui(new Ui::DlgProjectionOnSurface)
, filterEdge(nullptr)
, filterFace(nullptr)
, feature(feature)
{
ui->setupUi(this);
@@ -1254,7 +1259,7 @@ DlgProjectOnSurface::DlgProjectOnSurface(Part::ProjectOnSurface* feature, QWidge
DlgProjectOnSurface::~DlgProjectOnSurface()
{
if (filterFace || filterEdge) {
if (selectionMode != SelectionMode::None) {
Gui::Selection().rmvSelectionGate();
}
}
@@ -1331,42 +1336,37 @@ void DlgProjectOnSurface::reject()
void DlgProjectOnSurface::onAddProjFaceClicked()
{
if (ui->pushButtonAddProjFace->isChecked()) {
selectionMode = SelectionMode::SupportFace;
if (!filterFace) {
filterFace = new FaceSelection();
Gui::Selection().addSelectionGate(filterFace);
if (selectionMode != SelectionMode::SupportFace) {
selectionMode = SelectionMode::SupportFace;
setSelectionGate();
}
}
else {
selectionMode = SelectionMode::None;
Gui::Selection().rmvSelectionGate();
filterFace = nullptr;
}
}
void DlgProjectOnSurface::onAddFaceClicked()
{
if (ui->pushButtonAddFace->isChecked()) {
selectionMode = SelectionMode::AddFace;
if (!filterFace) {
filterFace = new FaceSelection();
Gui::Selection().addSelectionGate(filterFace);
if (selectionMode != SelectionMode::AddFace) {
selectionMode = SelectionMode::AddFace;
setSelectionGate();
}
}
else {
selectionMode = SelectionMode::None;
Gui::Selection().rmvSelectionGate();
filterFace = nullptr;
}
}
void DlgProjectOnSurface::onAddWireClicked()
{
if (ui->pushButtonAddWire->isChecked()) {
selectionMode = SelectionMode::AddWire;
if (!filterEdge) {
filterEdge = new EdgeSelection();
Gui::Selection().addSelectionGate(filterEdge);
if (selectionMode != SelectionMode::AddWire) {
selectionMode = SelectionMode::AddWire;
setSelectionGate();
}
ui->radioButtonEdges->setChecked(true);
onEdgesClicked();
@@ -1374,25 +1374,32 @@ void DlgProjectOnSurface::onAddWireClicked()
else {
selectionMode = SelectionMode::None;
Gui::Selection().rmvSelectionGate();
filterEdge = nullptr;
}
}
void DlgProjectOnSurface::onAddEdgeClicked()
{
if (ui->pushButtonAddEdge->isChecked()) {
selectionMode = SelectionMode::AddEdge;
if (!filterEdge) {
filterEdge = new EdgeSelection();
Gui::Selection().addSelectionGate(filterEdge);
if (selectionMode != SelectionMode::AddEdge) {
selectionMode = SelectionMode::AddEdge;
setSelectionGate();
}
ui->radioButtonEdges->setChecked(true);
onEdgesClicked();
}
else {
selectionMode = SelectionMode::None;
Gui::Selection().rmvSelectionGate();
filterEdge = nullptr;
}
}
void DlgProjectOnSurface::setSelectionGate()
{
if (selectionMode == SelectionMode::SupportFace || selectionMode == SelectionMode::AddFace) {
Gui::Selection().addSelectionGate(new FaceSelection());
}
else if (selectionMode == SelectionMode::AddEdge || selectionMode == SelectionMode::AddWire) {
Gui::Selection().addSelectionGate(new EdgeSelection());
}
}

Some files were not shown because too many files have changed in this diff Show More