Merge pull request #21978 from theo-vt/document_transaction
GSOC Multi file editing
This commit is contained in:
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
...
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -101,6 +101,8 @@ void StdCmdPart::activated(int iMsg)
|
||||
PartName.c_str()
|
||||
);
|
||||
|
||||
commitCommand();
|
||||
|
||||
updateActive();
|
||||
}
|
||||
|
||||
|
||||
+64
-5
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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()};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+477
-355
File diff suppressed because it is too large
Load Diff
@@ -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};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -158,5 +158,11 @@ void TaskDialog::onUndo()
|
||||
void TaskDialog::onRedo()
|
||||
{}
|
||||
|
||||
void TaskDialog::activate()
|
||||
{}
|
||||
|
||||
void TaskDialog::deactivate()
|
||||
{}
|
||||
|
||||
|
||||
#include "moc_TaskDialog.cpp"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -208,7 +208,7 @@ class TaskAssemblyNewPart(JointObject.TaskAssemblyCreateJoint):
|
||||
|
||||
self.createPart()
|
||||
|
||||
App.closeActiveTransaction()
|
||||
Gui.ActiveDocument.commitCommand()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class CommandSolveAssembly:
|
||||
|
||||
App.setActiveTransaction("Solve assembly")
|
||||
assembly.recompute(True)
|
||||
App.closeActiveTransaction()
|
||||
Gui.ActiveDocument.commitCommand()
|
||||
|
||||
|
||||
if App.GuiUp:
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ public:
|
||||
explicit TaskCreateNodeSet(Fem::FemSetNodesObject* pcObject, QWidget* parent = nullptr);
|
||||
~TaskCreateNodeSet() override;
|
||||
|
||||
void setSelectionGate();
|
||||
|
||||
std::set<long> tempSet;
|
||||
ViewProviderFemMesh* MeshViewProvider;
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()");
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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}}}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
// ---------------------------------------
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ public:
|
||||
);
|
||||
~DlgFilletEdges() override;
|
||||
bool accept();
|
||||
void setSelectionGate();
|
||||
|
||||
protected:
|
||||
void findShapes();
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user