diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 2036136457..2bb61f208d 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -288,11 +288,12 @@ Application::Application(bool GUIenabled) { //App::GetApplication().Attach(this); if (GUIenabled) { - App::GetApplication().signalNewDocument.connect(boost::bind(&Gui::Application::slotNewDocument, this, _1)); + App::GetApplication().signalNewDocument.connect(boost::bind(&Gui::Application::slotNewDocument, this, _1, _2)); App::GetApplication().signalDeleteDocument.connect(boost::bind(&Gui::Application::slotDeleteDocument, this, _1)); App::GetApplication().signalRenameDocument.connect(boost::bind(&Gui::Application::slotRenameDocument, this, _1)); App::GetApplication().signalActiveDocument.connect(boost::bind(&Gui::Application::slotActiveDocument, this, _1)); App::GetApplication().signalRelabelDocument.connect(boost::bind(&Gui::Application::slotRelabelDocument, this, _1)); + App::GetApplication().signalShowHidden.connect(boost::bind(&Gui::Application::slotShowHidden, this, _1)); // install the last active language @@ -669,7 +670,7 @@ void Application::createStandardOperations() Gui::CreateTestCommands(); } -void Application::slotNewDocument(const App::Document& Doc) +void Application::slotNewDocument(const App::Document& Doc, bool isMainDoc) { #ifdef FC_DEBUG std::map::const_iterator it = d->documents.find(&Doc); @@ -687,12 +688,13 @@ void Application::slotNewDocument(const App::Document& Doc) pDoc->signalInEdit.connect(boost::bind(&Gui::Application::slotInEdit, this, _1)); pDoc->signalResetEdit.connect(boost::bind(&Gui::Application::slotResetEdit, this, _1)); - signalNewDocument(*pDoc); - pDoc->createView(View3DInventor::getClassTypeId()); + signalNewDocument(*pDoc, isMainDoc); + if(isMainDoc) + pDoc->createView(View3DInventor::getClassTypeId()); // FIXME: Do we really need this further? Calling processEvents() mixes up order of execution in an // unpredicatable way. At least it seems that with Qt5 we don't need this any more. #if QT_VERSION < 0x050000 - qApp->processEvents(); // make sure to show the window stuff on the right place + // qApp->processEvents(); // make sure to show the window stuff on the right place #endif } @@ -704,11 +706,15 @@ void Application::slotDeleteDocument(const App::Document& Doc) return; } - // We must clear the selection here to notify all observers - Gui::Selection().clearSelection(doc->second->getDocument()->getName()); + // We must clear the selection here to notify all observers. + // And because of possible cross document link, better clear all selection + // to be safe + Gui::Selection().clearCompleteSelection(); doc->second->signalDeleteDocument(*doc->second); signalDeleteDocument(*doc->second); + doc->second->beforeDelete(); + // If the active document gets destructed we must set it to 0. If there are further existing documents then the // view that becomes active sets the active document again. So, we needn't worry about this. if (d->activeDocument == doc->second) @@ -740,6 +746,16 @@ void Application::slotRenameDocument(const App::Document& Doc) signalRenameDocument(*doc->second); } +void Application::slotShowHidden(const App::Document& Doc) +{ + std::map::iterator doc = d->documents.find(&Doc); +#ifdef FC_DEBUG + assert(doc!=d->documents.end()); +#endif + + signalShowHidden(*doc->second); +} + void Application::slotActiveDocument(const App::Document& Doc) { std::map::iterator doc = d->documents.find(&Doc); @@ -753,6 +769,12 @@ void Application::slotActiveDocument(const App::Document& Doc) Base::PyGILStateLocker lock; Py::Object active(d->activeDocument->getPyObject(), true); Py::Module("FreeCADGui").setAttr(std::string("ActiveDocument"),active); + + auto view = getMainWindow()->activeWindow(); + if(!view || view->getAppDocument()!=&Doc) { + Gui::MDIView* view = d->activeDocument->getActiveView(); + getMainWindow()->setActiveWindow(view); + } } else { Base::PyGILStateLocker lock; @@ -760,6 +782,7 @@ void Application::slotActiveDocument(const App::Document& Doc) } } signalActiveDocument(*doc->second); + getMainWindow()->updateActions(); } } @@ -776,6 +799,7 @@ void Application::slotDeletedObject(const ViewProvider& vp) void Application::slotChangedObject(const ViewProvider& vp, const App::Property& prop) { this->signalChangedObject(vp,prop); + getMainWindow()->updateActions(true); } void Application::slotRelabelObject(const ViewProvider& vp) @@ -786,6 +810,7 @@ void Application::slotRelabelObject(const ViewProvider& vp) void Application::slotActivatedObject(const ViewProvider& vp) { this->signalActivatedObject(vp); + getMainWindow()->updateActions(); } void Application::slotInEdit(const Gui::ViewProviderDocumentObject& vp) @@ -804,6 +829,19 @@ void Application::onLastWindowClosed(Gui::Document* pcDoc) try { // Call the closing mechanism from Python. This also checks whether pcDoc is the last open document. Command::doCommand(Command::Doc, "App.closeDocument(\"%s\")", pcDoc->getDocument()->getName()); + if (!d->activeDocument && d->documents.size()) { + for(auto &v : d->documents) { + Gui::MDIView* view = v.second->getActiveView(); + if(view) { + setActiveDocument(v.second); + getMainWindow()->setActiveWindow(view); + return; + } + } + auto gdoc = d->documents.begin()->second; + setActiveDocument(gdoc); + activateView(View3DInventor::getClassTypeId(),true); + } } catch (const Base::Exception& e) { e.ReportException(); @@ -828,6 +866,31 @@ bool Application::sendHasMsgToActiveView(const char* pMsg) return pView ? pView->onHasMsg(pMsg) : false; } +/// send Messages to the active view +bool Application::sendMsgToFocusView(const char* pMsg, const char** ppReturn) +{ + MDIView* pView = getMainWindow()->activeWindow(); + if(!pView) + return false; + for(auto focus=qApp->focusWidget();focus;focus=focus->parentWidget()) { + if(focus == pView) + return pView->onMsg(pMsg,ppReturn); + } + return false; +} + +bool Application::sendHasMsgToFocusView(const char* pMsg) +{ + MDIView* pView = getMainWindow()->activeWindow(); + if(!pView) + return false; + for(auto focus=qApp->focusWidget();focus;focus=focus->parentWidget()) { + if(focus == pView) + return pView->onHasMsg(pMsg); + } + return false; +} + Gui::MDIView* Application::activeView(void) const { if (activeDocument()) @@ -1040,24 +1103,9 @@ void Application::updateActive(void) void Application::tryClose(QCloseEvent * e) { - if (d->documents.size() == 0) { - e->accept(); - } - else { - // ask all documents if closable - std::map::iterator It; - for (It = d->documents.begin();It!=d->documents.end();++It) { - // a document may have several views attached, so ask it directly -#if 0 - MDIView* active = It->second->getActiveView(); - e->setAccepted(active->canClose()); -#else - e->setAccepted(It->second->canClose()); -#endif - if (!e->isAccepted()) - return; - } - } + e->setAccepted(getMainWindow()->closeAllDocuments(false)); + if(!e->isAccepted()) + return; // ask all passive views if closable for (std::list::iterator It = d->passive.begin();It!=d->passive.end();++It) { @@ -1079,14 +1127,7 @@ void Application::tryClose(QCloseEvent * e) itp = d->passive.begin(); } - // remove all documents - size_t cnt = d->documents.size(); - while (d->documents.size() > 0 && cnt > 0) { - // destroys also the Gui document - It = d->documents.begin(); - App::GetApplication().closeDocument(It->second->getDocument()->getName()); - --cnt; // avoid infinite loop - } + App::GetApplication().closeAllDocuments(); } } @@ -1680,6 +1721,14 @@ void Application::runApplication(void) // A new QApplication Base::Console().Log("Init: Creating Gui::Application and QApplication\n"); + +#if defined(QTWEBENGINE) && defined(Q_OS_LINUX) + // Avoid warning of 'Qt WebEngine seems to be initialized from a plugin...' + // QTWEBENGINE is defined in src/Gui/CMakeLists.txt, currently only enabled + // when build with Conda. + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); +#endif + // if application not yet created by the splasher int argc = App::Application::GetARGC(); GUISingleApplication mainApp(argc, App::Application::GetARGV()); @@ -2144,3 +2193,61 @@ void Application::checkForPreviousCrashes() dlg.exec(); } } + +App::Document *Application::reopen(App::Document *doc) { + if(!doc) return 0; + std::string name = doc->FileName.getValue(); + std::set untouchedDocs; + for(auto &v : d->documents) { + if(!v.second->isModified() && !v.second->getDocument()->isTouched()) + untouchedDocs.insert(v.second); + } + + WaitCursor wc; + wc.setIgnoreEvents(WaitCursor::NoEvents); + + if(doc->testStatus(App::Document::PartialDoc) + || doc->testStatus(App::Document::PartialRestore)) + { + App::GetApplication().openDocument(name.c_str()); + } else { + std::vector docs; + for(auto d : doc->getDependentDocuments(true)) { + if(d->testStatus(App::Document::PartialDoc) + || d->testStatus(App::Document::PartialRestore) ) + docs.push_back(d->FileName.getValue()); + } + for(auto &file : docs) + App::GetApplication().openDocument(file.c_str(),false); + } + + doc = 0; + for(auto &v : d->documents) { + if(name == v.first->FileName.getValue()) + doc = const_cast(v.first); + if(untouchedDocs.count(v.second)) { + if(!v.second->isModified()) continue; + bool reset = true; + for(auto obj : v.second->getDocument()->getObjects()) { + if(!obj->isTouched()) + continue; + std::vector props; + obj->getPropertyList(props); + for(auto prop : props){ + auto link = dynamic_cast(prop); + if(link && link->checkRestore()) { + reset = false; + break; + } + } + if(!reset) + break; + } + if(reset) { + v.second->getDocument()->purgeTouched(); + v.second->setModified(false); + } + } + } + return doc; +} diff --git a/src/Gui/Application.h b/src/Gui/Application.h index e3a5a74a6f..be1664ea06 100644 --- a/src/Gui/Application.h +++ b/src/Gui/Application.h @@ -33,6 +33,7 @@ #include class QCloseEvent; +class SoNode; namespace Gui{ class BaseView; @@ -65,6 +66,8 @@ public: void importFrom(const char* FileName, const char* DocName, const char* Module); /// Export objects from the document DocName to a single file void exportTo(const char* FileName, const char* DocName, const char* Module); + /// Reload a partial opened document + App::Document *reopen(App::Document *doc); //@} @@ -74,6 +77,10 @@ public: bool sendMsgToActiveView(const char* pMsg, const char** ppReturn=0); /// send Messages test to the active view bool sendHasMsgToActiveView(const char* pMsg); + /// send Messages to the focused view + bool sendMsgToFocusView(const char* pMsg, const char** ppReturn=0); + /// send Messages test to the focused view + bool sendHasMsgToFocusView(const char* pMsg); /// Attach a view (get called by the FCView constructor) void attachView(Gui::BaseView* pcView); /// Detach a view (get called by the FCView destructor) @@ -89,7 +96,7 @@ public: /** @name Signals of the Application */ //@{ /// signal on new Document - boost::signals2::signal signalNewDocument; + boost::signals2::signal signalNewDocument; /// signal on deleted Document boost::signals2::signal signalDeleteDocument; /// signal on relabeling Document @@ -114,6 +121,8 @@ public: boost::signals2::signal signalAddWorkbench; /// signal on removed workbench boost::signals2::signal signalRemoveWorkbench; + /// signal on show hidden items + boost::signals2::signal signalShowHidden; /// signal on activating view boost::signals2::signal signalActivateView; /// signal on entering in edit mode @@ -126,11 +135,12 @@ public: //@{ protected: /// Observer message from the Application - void slotNewDocument(const App::Document&); + void slotNewDocument(const App::Document&,bool); void slotDeleteDocument(const App::Document&); void slotRelabelDocument(const App::Document&); void slotRenameDocument(const App::Document&); void slotActiveDocument(const App::Document&); + void slotShowHidden(const App::Document&); void slotNewObject(const ViewProvider&); void slotDeletedObject(const ViewProvider&); void slotChangedObject(const ViewProvider&, const App::Property& Prop); @@ -223,8 +233,10 @@ public: static PyObject* sAddLangPath (PyObject *self,PyObject *args); // adds a path to a qm file static PyObject* sAddIconPath (PyObject *self,PyObject *args); // adds a path to an icon file static PyObject* sAddIcon (PyObject *self,PyObject *args); // adds an icon to the cache + static PyObject* sGetIcon (PyObject *self,PyObject *args); // get an icon from the cache static PyObject* sSendActiveView (PyObject *self,PyObject *args); + static PyObject* sSendFocusView (PyObject *self,PyObject *args); static PyObject* sGetMainWindow (PyObject *self,PyObject *args); static PyObject* sUpdateGui (PyObject *self,PyObject *args); @@ -238,6 +250,7 @@ public: static PyObject* sRunCommand (PyObject *self,PyObject *args); static PyObject* sAddCommand (PyObject *self,PyObject *args); static PyObject* sListCommands (PyObject *self,PyObject *args); + static PyObject* sIsCommandActive (PyObject *self,PyObject *args); static PyObject* sHide (PyObject *self,PyObject *args); // deprecated static PyObject* sShow (PyObject *self,PyObject *args); // deprecated @@ -247,6 +260,7 @@ public: static PyObject* sOpen (PyObject *self,PyObject *args); // open Python scripts static PyObject* sInsert (PyObject *self,PyObject *args); // open Python scripts static PyObject* sExport (PyObject *self,PyObject *args); + static PyObject* sReload (PyObject *self,PyObject *args); static PyObject* sCoinRemoveAllChildren (PyObject *self,PyObject *args); diff --git a/src/Gui/ApplicationPy.cpp b/src/Gui/ApplicationPy.cpp index 2c269ed7c8..6f0f1729aa 100644 --- a/src/Gui/ApplicationPy.cpp +++ b/src/Gui/ApplicationPy.cpp @@ -101,6 +101,9 @@ PyMethodDef Application::Methods[] = { {"addIcon", (PyCFunction) Application::sAddIcon, METH_VARARGS, "addIcon(string, string or list) -> None\n\n" "Add an icon as file name or in XPM format to the system"}, + {"getIcon", (PyCFunction) Application::sGetIcon, METH_VARARGS, + "getIcon(string -> QIcon\n\n" + "Get an icon in the system"}, {"getMainWindow", (PyCFunction) Application::sGetMainWindow, METH_VARARGS, "getMainWindow() -> QMainWindow\n\n" "Return the main window instance"}, @@ -132,11 +135,16 @@ PyMethodDef Application::Methods[] = { {"runCommand", (PyCFunction) Application::sRunCommand, METH_VARARGS, "runCommand(string) -> None\n\n" "Run command with name"}, + {"isCommandActive", (PyCFunction) Application::sIsCommandActive, METH_VARARGS, + "isCommandActive(string) -> Bool\n\n" + "Test if a command is active"}, {"listCommands", (PyCFunction) Application::sListCommands, METH_VARARGS, "listCommands() -> list of strings\n\n" "Returns a list of all commands known to FreeCAD."}, {"SendMsgToActiveView", (PyCFunction) Application::sSendActiveView, METH_VARARGS, "deprecated -- use class View"}, + {"sendMsgToFocusView", (PyCFunction) Application::sSendFocusView, METH_VARARGS, + "send message to the focused view"}, {"hide", (PyCFunction) Application::sHide, METH_VARARGS, "deprecated"}, {"show", (PyCFunction) Application::sShow, METH_VARARGS, @@ -160,8 +168,8 @@ PyMethodDef Application::Methods[] = { "setActiveDocument(string or App.Document) -> None\n\n" "Activate the specified document"}, {"activeView", (PyCFunction)Application::sActiveView, METH_VARARGS, - "activeView() -> object or None\n\n" - "Return the active view of the active document or None if no one exists"}, + "activeView(typename=None) -> object or None\n\n" + "Return the active view of the active document or None if no one exists" }, {"activateView", (PyCFunction)Application::sActivateView, METH_VARARGS, "activateView(type)\n\n" "Activate a view of the given type of the active document"}, @@ -200,6 +208,10 @@ PyMethodDef Application::Methods[] = { "removeDocumentObserver() -> None\n\n" "Remove an added document observer."}, + {"reload", (PyCFunction) Application::sReload, METH_VARARGS, + "reload(name) -> doc\n\n" + "Reload a partial opened document"}, + {"coinRemoveAllChildren", (PyCFunction) Application::sCoinRemoveAllChildren, METH_VARARGS, "Remove all children from a group node"}, @@ -236,16 +248,37 @@ PyObject* Gui::Application::sActiveDocument(PyObject * /*self*/, PyObject *args) PyObject* Gui::Application::sActiveView(PyObject * /*self*/, PyObject *args) { - if (!PyArg_ParseTuple(args, "")) + const char *typeName=0; + if (!PyArg_ParseTuple(args, "|s", &typeName)) return NULL; - Gui::MDIView* mdiView = Instance->activeView(); - if (mdiView) { - // already incremented in getPyObject(). - return mdiView->getPyObject(); - } + PY_TRY { + Base::Type type; + if(typeName) { + type = Base::Type::fromName(typeName); + if(type.isBad()) { + PyErr_Format(PyExc_TypeError, "Invalid type '%s'", typeName); + return 0; + } + } - Py_Return; + Gui::MDIView* mdiView = Instance->activeView(); + if (mdiView && (type.isBad() || mdiView->isDerivedFrom(type))) { + auto res = Py::asObject(mdiView->getPyObject()); + if(!res.isNone() || !type.isBad()) + return Py::new_reference_to(res); + } + + if(type.isBad()) + type = Gui::View3DInventor::getClassTypeId(); + Instance->activateView(type, true); + mdiView = Instance->activeView(); + if (mdiView) + return mdiView->getPyObject(); + + Py_Return; + + } PY_CATCH } PyObject* Gui::Application::sActivateView(PyObject * /*self*/, PyObject *args) @@ -624,6 +657,28 @@ PyObject* Application::sSendActiveView(PyObject * /*self*/, PyObject *args) return Py_None; } +PyObject* Application::sSendFocusView(PyObject * /*self*/, PyObject *args) +{ + char *psCommandStr; + PyObject *suppress=Py_False; + if (!PyArg_ParseTuple(args, "s|O!",&psCommandStr,&PyBool_Type,&suppress)) + return NULL; + + const char* ppReturn=0; + if (!Instance->sendMsgToFocusView(psCommandStr,&ppReturn)) { + if (!PyObject_IsTrue(suppress)) + Base::Console().Warning("Unknown view command: %s\n",psCommandStr); + } + + // Print the return value to the output + if (ppReturn) { + return Py_BuildValue("s",ppReturn); + } + + Py_INCREF(Py_None); + return Py_None; +} + PyObject* Application::sGetMainWindow(PyObject * /*self*/, PyObject *args) { if (!PyArg_ParseTuple(args, "")) @@ -1042,6 +1097,21 @@ PyObject* Application::sAddIcon(PyObject * /*self*/, PyObject *args) return Py_None; } +PyObject* Application::sGetIcon(PyObject * /*self*/, PyObject *args) +{ + char *iconName; + if (!PyArg_ParseTuple(args, "s", &iconName)) + return NULL; + + PythonWrapper wrap; + wrap.loadGuiModule(); + wrap.loadWidgetsModule(); + auto pixmap = BitmapFactory().pixmap(iconName); + if(!pixmap.isNull()) + return Py::new_reference_to(wrap.fromQIcon(new QIcon(pixmap))); + Py_Return; +} + PyObject* Application::sAddCommand(PyObject * /*self*/, PyObject *args) { char* pName; @@ -1079,7 +1149,11 @@ PyObject* Application::sAddCommand(PyObject * /*self*/, PyObject *args) group = what[1]; } else { - group = module; + boost::regex rx("/Ext/freecad/(\\w+)/"); + if (boost::regex_search(file, what, rx)) + group = what[1]; + else + group = module; } } catch (Py::Exception& e) { @@ -1131,6 +1205,9 @@ PyObject* Application::sRunCommand(PyObject * /*self*/, PyObject *args) if (!PyArg_ParseTuple(args, "s|i", &pName, &item)) return NULL; + Gui::Command::LogDisabler d1; + Gui::SelectionLogDisabler d2; + Command* cmd = Application::Instance->commandManager().getCommandByName(pName); if (cmd) { cmd->invoke(item); @@ -1143,6 +1220,23 @@ PyObject* Application::sRunCommand(PyObject * /*self*/, PyObject *args) } } +PyObject* Application::sIsCommandActive(PyObject * /*self*/, PyObject *args) +{ + char* pName; + if (!PyArg_ParseTuple(args, "s", &pName)) + return NULL; + + Command* cmd = Application::Instance->commandManager().getCommandByName(pName); + if (!cmd) { + PyErr_Format(Base::BaseExceptionFreeCADError, "No such command '%s'", pName); + return 0; + } + PY_TRY { + return Py::new_reference_to(Py::Boolean(cmd->isActive())); + }PY_CATCH; +} + + PyObject* Application::sListCommands(PyObject * /*self*/, PyObject *args) { if (!PyArg_ParseTuple(args, "")) @@ -1168,6 +1262,10 @@ PyObject* Application::sDoCommand(PyObject * /*self*/, PyObject *args) if (!PyArg_ParseTuple(args, "s", &sCmd)) return NULL; + Gui::Command::LogDisabler d1; + Gui::SelectionLogDisabler d2; + + Gui::Command::printPyCaller(); Gui::Application::Instance->macroManager()->addLine(MacroManager::App, sCmd); PyObject *module, *dict; @@ -1189,6 +1287,10 @@ PyObject* Application::sDoCommandGui(PyObject * /*self*/, PyObject *args) if (!PyArg_ParseTuple(args, "s", &sCmd)) return NULL; + Gui::Command::LogDisabler d1; + Gui::SelectionLogDisabler d2; + + Gui::Command::printPyCaller(); Gui::Application::Instance->macroManager()->addLine(MacroManager::Gui, sCmd); PyObject *module, *dict; @@ -1325,6 +1427,19 @@ PyObject* Application::sGetMarkerIndex(PyObject * /*self*/, PyObject *args) }PY_CATCH; } +PyObject* Application::sReload(PyObject * /*self*/, PyObject *args) +{ + const char *name; + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + PY_TRY { + auto doc = Application::Instance->reopen(App::GetApplication().getDocument(name)); + if(doc) + return doc->getPyObject(); + }PY_CATCH; + Py_Return; +} PyObject* Application::sAddDocObserver(PyObject * /*self*/, PyObject *args) { diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 54dab95773..abcc6fc225 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -77,6 +77,11 @@ else(MSVC) endif(MSVC) if (BUILD_QT5) + + if (Qt5WebEngineWidgets_FOUND AND BUILD_WITH_CONDA) + add_definitions(-DQTWEBENGINE) + endif() + include_directories( ${Qt5Core_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} @@ -356,6 +361,7 @@ set(Gui_MOC_HDRS TaskView/TaskView.h DAGView/DAGView.h DAGView/DAGModel.h + DlgObjectSelection.h ${FreeCADGui_SDK_MOC_HDRS} ) @@ -428,6 +434,7 @@ SET(Gui_UIC_SRCS TextureMapping.ui TaskView/TaskAppearance.ui TaskView/TaskSelectLinkProperty.ui + DlgObjectSelection.ui ) SET(Gui_RES_SRCS @@ -500,6 +507,7 @@ SET(Dialog_CPP_SRCS DownloadItem.cpp DownloadManager.cpp DocumentRecovery.cpp + DlgObjectSelection.cpp ) SET(Dialog_HPP_SRCS @@ -533,6 +541,7 @@ SET(Dialog_HPP_SRCS DownloadItem.h DownloadManager.h DocumentRecovery.h + DlgObjectSelection.h ) SET(Dialog_SRCS @@ -568,6 +577,7 @@ SET(Dialog_SRCS Placement.ui SceneInspector.ui TextureMapping.ui + DlgObjectSelection.ui ) SOURCE_GROUP("Dialog" FILES ${Dialog_SRCS}) diff --git a/src/Gui/CommandDoc.cpp b/src/Gui/CommandDoc.cpp index 423963fd1d..f62b8dbfd5 100644 --- a/src/Gui/CommandDoc.cpp +++ b/src/Gui/CommandDoc.cpp @@ -37,11 +37,15 @@ #endif #include +#include +#include + #include #include #include #include #include +#include #include #include #include @@ -69,6 +73,9 @@ #include "MergeDocuments.h" #include "NavigationStyle.h" #include "GraphvizView.h" +#include "DlgObjectSelection.h" + +FC_LOG_LEVEL_INIT("Command", false); using namespace Gui; @@ -90,6 +97,7 @@ StdCmdOpen::StdCmdOpen() sStatusTip = QT_TR_NOOP("Open a document or import files"); sPixmap = "document-open"; sAccel = keySequenceToAccel(QKeySequence::Open); + eType = NoTransaction; } void StdCmdOpen::activated(int iMsg) @@ -538,6 +546,33 @@ bool StdCmdSaveCopy::isActive(void) return ( getActiveGuiDocument() ? true : false ); } +//=========================================================================== +// Std_SaveAll +//=========================================================================== +DEF_STD_CMD_A(StdCmdSaveAll); + +StdCmdSaveAll::StdCmdSaveAll() + :Command("Std_SaveAll") +{ + sGroup = QT_TR_NOOP("File"); + sMenuText = QT_TR_NOOP("Save All"); + sToolTipText = QT_TR_NOOP("Save all opened document"); + sWhatsThis = "Std_SaveAll"; + sStatusTip = QT_TR_NOOP("Save all opened document"); +} + +void StdCmdSaveAll::activated(int iMsg) +{ + Q_UNUSED(iMsg); + Gui::Document::saveAll(); +} + +bool StdCmdSaveAll::isActive(void) +{ + return ( getActiveGuiDocument() ? true : false ); +} + + //=========================================================================== // Std_Revert //=========================================================================== @@ -552,6 +587,7 @@ StdCmdRevert::StdCmdRevert() sWhatsThis = "Std_Revert"; sStatusTip = QT_TR_NOOP("Reverts to the saved version of this file"); //sPixmap = "document-revert"; + eType = NoTransaction; } void StdCmdRevert::activated(int iMsg) @@ -742,6 +778,7 @@ StdCmdQuit::StdCmdQuit() sPixmap = "application-exit"; #endif sAccel = "Alt+F4"; + eType = NoTransaction; } void StdCmdQuit::activated(int iMsg) @@ -767,7 +804,7 @@ StdCmdUndo::StdCmdUndo() sStatusTip = QT_TR_NOOP("Undo exactly one action"); sPixmap = "edit-undo"; sAccel = keySequenceToAccel(QKeySequence::Undo); - eType = ForEdit; + eType = ForEdit|NoTransaction; } void StdCmdUndo::activated(int iMsg) @@ -811,7 +848,7 @@ StdCmdRedo::StdCmdRedo() sStatusTip = QT_TR_NOOP("Redoes a previously undone action"); sPixmap = "edit-redo"; sAccel = keySequenceToAccel(QKeySequence::Redo); - eType = ForEdit; + eType = ForEdit|NoTransaction; } void StdCmdRedo::activated(int iMsg) @@ -887,7 +924,7 @@ StdCmdCopy::StdCmdCopy() void StdCmdCopy::activated(int iMsg) { Q_UNUSED(iMsg); - bool done = getGuiApplication()->sendMsgToActiveView("Copy"); + bool done = getGuiApplication()->sendMsgToFocusView("Copy"); if (!done) { QMimeData * mimeData = getMainWindow()->createMimeDataFromSelection(); QClipboard* cb = QApplication::clipboard(); @@ -897,7 +934,7 @@ void StdCmdCopy::activated(int iMsg) bool StdCmdCopy::isActive(void) { - if (getGuiApplication()->sendHasMsgToActiveView("Copy")) + if (getGuiApplication()->sendHasMsgToFocusView("Copy")) return true; return Selection().hasSelection(); } @@ -922,7 +959,7 @@ StdCmdPaste::StdCmdPaste() void StdCmdPaste::activated(int iMsg) { Q_UNUSED(iMsg); - bool done = getGuiApplication()->sendMsgToActiveView("Paste"); + bool done = getGuiApplication()->sendMsgToFocusView("Paste"); if (!done) { QClipboard* cb = QApplication::clipboard(); const QMimeData* mimeData = cb->mimeData(); @@ -935,7 +972,7 @@ void StdCmdPaste::activated(int iMsg) bool StdCmdPaste::isActive(void) { - if (getGuiApplication()->sendHasMsgToActiveView("Paste")) + if (getGuiApplication()->sendHasMsgToFocusView("Paste")) return true; QClipboard* cb = QApplication::clipboard(); const QMimeData* mime = cb->mimeData(); @@ -959,38 +996,34 @@ StdCmdDuplicateSelection::StdCmdDuplicateSelection() void StdCmdDuplicateSelection::activated(int iMsg) { Q_UNUSED(iMsg); - std::vector sel = Selection().getCompleteSelection(); - std::set unique_objs; - std::map< App::Document*, std::vector > objs; - for (std::vector::iterator it = sel.begin(); it != sel.end(); ++it) { - if (it->pObject && it->pObject->getDocument()) { - if (unique_objs.insert(it->pObject).second) - objs[it->pObject->getDocument()].push_back(it->pObject); - } + std::vector sel; + std::set objSet; + for(auto &s : Selection().getCompleteSelection()) { + if(s.pObject && s.pObject->getNameInDocument() && objSet.insert(s.pObject).second) + sel.push_back(s.pObject); } - - if (objs.empty()) + if(sel.empty()) return; + bool hasXLink = false; Base::FileInfo fi(App::Application::getTempFileName()); { - std::vector sel; // selected - std::vector all; // object sub-graph - for (std::map< App::Document*, std::vector >::iterator it = objs.begin(); it != objs.end(); ++it) { - std::vector dep = it->first->getDependencyList(it->second); - sel.insert(sel.end(), it->second.begin(), it->second.end()); - all.insert(all.end(), dep.begin(), dep.end()); - } - + auto all = App::Document::getDependencyList(sel); if (all.size() > sel.size()) { - int ret = QMessageBox::question(getMainWindow(), - qApp->translate("Std_DuplicateSelection","Object dependencies"), - qApp->translate("Std_DuplicateSelection","The selected objects have a dependency to unselected objects.\n" - "Do you want to duplicate them, too?"), - QMessageBox::Yes,QMessageBox::No); - if (ret == QMessageBox::Yes) { - sel = all; - } + DlgObjectSelection dlg(sel,getMainWindow()); + if(dlg.exec()!=QDialog::Accepted) + return; + sel = dlg.getSelections(); + if(sel.empty()) + return; + } + std::vector unsaved; + hasXLink = App::PropertyXLink::hasXLink(sel,&unsaved); + if(unsaved.size()) { + QMessageBox::critical(getMainWindow(), QObject::tr("Unsaved document"), + QObject::tr("The exported object contains external link. Please save the document" + "at least once before exporting.")); + return; } // save stuff to file @@ -1002,13 +1035,26 @@ void StdCmdDuplicateSelection::activated(int iMsg) } App::Document* doc = App::GetApplication().getActiveDocument(); if (doc) { - doc->openTransaction("Duplicate"); - // restore objects from file and add to active document - Base::ifstream str(fi, std::ios::in | std::ios::binary); - MergeDocuments mimeView(doc); - mimeView.importObjects(str); - str.close(); - doc->commitTransaction(); + bool proceed = true; + if(hasXLink && !doc->isSaved()) { + int ret = QMessageBox::question(getMainWindow(), + qApp->translate("Std_DuplicateSelection","Object dependencies"), + qApp->translate("Std_DuplicateSelection", + "To link to external objects, the document must be saved at least once.\n" + "Do you want to save the document now?"), + QMessageBox::Yes,QMessageBox::No); + if(ret == QMessageBox::Yes) + proceed = Application::Instance->getDocument(doc)->saveAs(); + } + if(proceed) { + doc->openTransaction("Duplicate"); + // restore objects from file and add to active document + Base::ifstream str(fi, std::ios::in | std::ios::binary); + MergeDocuments mimeView(doc); + mimeView.importObjects(str); + str.close(); + doc->commitTransaction(); + } } fi.deleteFile(); } @@ -1076,137 +1122,147 @@ void StdCmdDelete::activated(int iMsg) { Q_UNUSED(iMsg); - // go through all documents - const SelectionSingleton& rSel = Selection(); - const std::vector docs = App::GetApplication().getDocuments(); - for (std::vector::const_iterator it = docs.begin(); it != docs.end(); ++it) { - Gui::Document* pGuiDoc = Gui::Application::Instance->getDocument(*it); - std::vector sel = rSel.getSelectionEx((*it)->getName()); - if (!sel.empty()) { - bool autoDeletion = true; - - // if an object is in edit mode handle only this object even if unselected (#0001838) - Gui::ViewProvider* vpedit = pGuiDoc->getInEdit(); - if (vpedit) { - // check if the edited view provider is selected - for (std::vector::iterator ft = sel.begin(); ft != sel.end(); ++ft) { - Gui::ViewProvider* vp = pGuiDoc->getViewProvider(ft->getObject()); - if (vp == vpedit) { - if (!ft->getSubNames().empty()) { - // handle the view provider - Gui::getMainWindow()->setUpdatesEnabled(false); - - try { - (*it)->openTransaction("Delete"); - vpedit->onDelete(ft->getSubNames()); - (*it)->commitTransaction(); - } - catch (const Base::Exception& e) { - (*it)->abortTransaction(); - e.ReportException(); - } - - Gui::getMainWindow()->setUpdatesEnabled(true); - Gui::getMainWindow()->update(); - } - break; + std::set docs; + try { + openCommand("Delete"); + if (getGuiApplication()->sendHasMsgToFocusView(getName())) { + commitCommand(); + return; + } + Gui::getMainWindow()->setUpdatesEnabled(false); + auto editDoc = Application::Instance->editDocument(); + ViewProviderDocumentObject *vpedit = 0; + if(editDoc) + vpedit = dynamic_cast(editDoc->getInEdit()); + if(vpedit) { + 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()); } + break; } } - else { - // check if we can delete the object - linked objects - std::set affectedLabels; - for (std::vector::iterator ft = sel.begin(); ft != sel.end(); ++ft) { - App::DocumentObject* obj = ft->getObject(); - std::vector links = obj->getInList(); - if (!links.empty()) { - // check if the referenced objects are groups or are selected too - for (std::vector::iterator lt = links.begin(); lt != links.end(); ++lt) { - if (!rSel.isSelected(*lt)) { - ViewProvider* vp = pGuiDoc->getViewProvider(*lt); - if (!vp->canDelete(obj)) { - autoDeletion = false; - affectedLabels.insert(QString::fromUtf8((*lt)->Label.getValue())); - } + } else { + std::set affectedLabels; + bool more = false; + auto sels = Selection().getSelectionEx(); + bool autoDeletion = true; + for(auto &sel : sels) { + auto obj = sel.getObject(); + for(auto parent : obj->getInList()) { + if(!Selection().isSelected(parent)) { + ViewProvider* vp = Application::Instance->getViewProvider(parent); + if (vp && !vp->canDelete(obj)) { + autoDeletion = false; + QString label; + if(parent->getDocument() != obj->getDocument()) + label = QLatin1String(parent->getFullName().c_str()); + else + label = QLatin1String(parent->getNameInDocument()); + if(parent->Label.getStrValue() != parent->getNameInDocument()) + label += QString::fromLatin1(" (%1)").arg( + QString::fromUtf8(parent->Label.getValue())); + affectedLabels.insert(label); + if(affectedLabels.size()>=10) { + more = true; + break; } } } } + if(more) + break; + } - //check for inactive objects in selection (Mantis #3477) - std::set inactiveLabels; - App::Application& app = App::GetApplication(); - App::Document* actDoc = app.getActiveDocument(); - for (std::vector::iterator ft = sel.begin(); ft != sel.end(); ++ft) { - App::DocumentObject* obj = ft->getObject(); - App::Document* objDoc = obj->getDocument(); - if (actDoc != objDoc) { - inactiveLabels.insert(QString::fromUtf8(obj->Label.getValue())); - autoDeletion = false; - } + // The check below is not needed because we now only get selection + // from the active document +#if 0 + //check for inactive objects in selection Mantis #3477 + std::set inactiveLabels; + App::Application& app = App::GetApplication(); + App::Document* actDoc = app.getActiveDocument(); + for (std::vector::iterator ft = sels.begin(); ft != sels.end(); ++ft) { + App::DocumentObject* obj = ft->getObject(); + App::Document* objDoc = obj->getDocument(); + if (actDoc != objDoc) { + inactiveLabels.insert(QString::fromUtf8(obj->Label.getValue())); + autoDeletion = false; } + } +#endif - if (!autoDeletion) { //can't just delete, need to ask - QString bodyMessage; - QTextStream bodyMessageStream(&bodyMessage); - - //message for linked items + if (!autoDeletion) { + QString bodyMessage; + QTextStream bodyMessageStream(&bodyMessage); + bodyMessageStream << qApp->translate("Std_Delete", + "The following referencing objects might break.\n\n" + "Are you sure you want to continue?\n"); + for (const auto ¤tLabel : affectedLabels) + bodyMessageStream << '\n' << currentLabel; + if(more) + bodyMessageStream << "\n..."; +#if 0 + //message for inactive items + if (!inactiveLabels.empty()) { if (!affectedLabels.empty()) { - bodyMessageStream << qApp->translate("Std_Delete", - "These items are linked to items selected for deletion and might break.") << "\n\n"; - for (const auto ¤tLabel : affectedLabels) - bodyMessageStream << currentLabel << '\n'; + bodyMessageStream << "\n"; } - - //message for inactive items - if (!inactiveLabels.empty()) { - if (!affectedLabels.empty()) { - bodyMessageStream << "\n"; - } - std::string thisDoc = pGuiDoc->getDocument()->getName(); - bodyMessageStream << qApp->translate("Std_Delete", - "These items are selected for deletion, but are not in the active document.") << "\n\n"; - for (const auto ¤tLabel : inactiveLabels) - bodyMessageStream << currentLabel << " / " << Base::Tools::fromStdString(thisDoc) << '\n'; - } - bodyMessageStream << "\n\n" << qApp->translate("Std_Delete", - "Are you sure you want to continue?"); - - int ret = QMessageBox::question(Gui::getMainWindow(), - qApp->translate("Std_Delete", "Delete Selection Issues"), bodyMessage, - QMessageBox::Yes, QMessageBox::No); - if (ret == QMessageBox::Yes) - autoDeletion = true; + std::string thisDoc = pGuiDoc->getDocument()->getName(); + bodyMessageStream << qApp->translate("Std_Delete", + "These items are selected for deletion, but are not in the active document. \n\n"); + for (const auto ¤tLabel : inactiveLabels) + bodyMessageStream << currentLabel << " / " << Base::Tools::fromStdString(thisDoc) << '\n'; } +#endif - if (autoDeletion) { - Gui::getMainWindow()->setUpdatesEnabled(false); - try { - (*it)->openTransaction("Delete"); - for (std::vector::iterator ft = sel.begin(); ft != sel.end(); ++ft) { - Gui::ViewProvider* vp = pGuiDoc->getViewProvider(ft->getObject()); - if (vp) { - // ask the ViewProvider if it wants to do some clean up - if (vp->onDelete(ft->getSubNames())) { - doCommand(Doc,"App.getDocument(\"%s\").removeObject(\"%s\")" - ,(*it)->getName(), ft->getFeatName()); - } - } + int ret = QMessageBox::warning(Gui::getMainWindow(), + qApp->translate("Std_Delete", "Object dependencies"), bodyMessage, + QMessageBox::Yes, QMessageBox::No); + if (ret == QMessageBox::Yes) + autoDeletion = true; + } + if (autoDeletion) { + for(auto &sel : sels) { + auto obj = sel.getObject(); + Gui::ViewProvider* vp = Application::Instance->getViewProvider(obj); + if (vp) { + // ask the ViewProvider if it wants to do some clean up + if (vp->onDelete(sel.getSubNames())) { + FCMD_OBJ_DOC_CMD(obj,"removeObject('" << obj->getNameInDocument() << "')"); + docs.insert(obj->getDocument()); } - (*it)->commitTransaction(); } - catch (const Base::Exception& e) { - (*it)->abortTransaction(); - e.ReportException(); - } - - Gui::getMainWindow()->setUpdatesEnabled(true); - Gui::getMainWindow()->update(); } } } - doCommand(Doc,"App.getDocument(\"%s\").recompute()", (*it)->getName()); + if(docs.size()) { + const auto &outList = App::PropertyXLink::getDocumentOutList(); + for(auto it=docs.begin();it!=docs.end();++it) { + auto itd = outList.find(*it); + if(itd!=outList.end()) { + for(auto doc : itd->second) { + if(doc != *it) + docs.erase(doc); + } + } + } + for(auto doc : docs) { + FCMD_DOC_CMD(doc,"recompute()"); + } + } + } catch (const Base::Exception& e) { + QMessageBox::critical(getMainWindow(), QObject::tr("Delete failed"), + QString::fromLatin1(e.what())); + e.ReportException(); + } catch (...) { + QMessageBox::critical(getMainWindow(), QObject::tr("Delete failed"), + QString::fromLatin1("Unknown error")); } + commitCommand(); + Gui::getMainWindow()->setUpdatesEnabled(true); + Gui::getMainWindow()->update(); } bool StdCmdDelete::isActive(void) @@ -1230,24 +1286,32 @@ StdCmdRefresh::StdCmdRefresh() sPixmap = "view-refresh"; sAccel = keySequenceToAccel(QKeySequence::Refresh); eType = AlterDoc | Alter3DView | AlterSelection | ForEdit; + bCanLog = false; } void StdCmdRefresh::activated(int iMsg) { Q_UNUSED(iMsg); if (getActiveGuiDocument()) { - //Note: Don't add the recompute to undo/redo because it complicates - //testing the changes of properties. - //openCommand("Refresh active document"); - this->getDocument()->setStatus(App::Document::SkipRecompute, false); - doCommand(Doc,"App.activeDocument().recompute()"); - //commitCommand(); + App::AutoTransaction trans("Recompute"); + try { + doCommand(Doc,"App.activeDocument().recompute(None,True,True)"); + } catch(Base::Exception &e) { + int ret = QMessageBox::warning(getMainWindow(), QObject::tr("Dependency error"), + QObject::tr("The document contains dependency cycles.\n" + "Please check the Report View for more details.\n\n" + "Do you still want to proceed?"), + QMessageBox::Yes, QMessageBox::No); + if(ret == QMessageBox::No) + return; + doCommand(Doc,"App.activeDocument().recompute(None,True)"); + } } } bool StdCmdRefresh::isActive(void) { - return this->getDocument() && this->getDocument()->isTouched(); + return this->getDocument() && this->getDocument()->mustExecute(); } //=========================================================================== @@ -1453,6 +1517,220 @@ bool StdCmdEdit::isActive(void) return (Selection().getCompleteSelection().size() > 0) || (Gui::Control().activeDialog() != 0); } +//====================================================================== +// StdCmdExpression +//=========================================================================== +class StdCmdExpression : public Gui::Command +{ +public: + StdCmdExpression() : Command("Std_Expressions") + { + sGroup = QT_TR_NOOP("Edit"); + sMenuText = QT_TR_NOOP("Expression actions"); + sToolTipText = QT_TR_NOOP("Expression actions"); + sWhatsThis = "Std_Expressions"; + sStatusTip = QT_TR_NOOP("Expression actions"); + eType = ForEdit; + } + + virtual const char* className() const {return "StdCmdExpression";} +protected: + + virtual void activated(int iMsg) { + std::map > objs; + switch(iMsg) { + case 0: + for(auto &sel : Selection().getCompleteSelection()) + objs[sel.pObject->getDocument()].insert(sel.pObject); + break; + case 1: + if(App::GetApplication().getActiveDocument()) { + auto doc = App::GetApplication().getActiveDocument(); + auto array = doc->getObjects(); + auto &set = objs[doc]; + set.insert(array.begin(),array.end()); + } + break; + case 2: + for(auto doc : App::GetApplication().getDocuments()) { + auto &set = objs[doc]; + auto array = doc->getObjects(); + set.insert(array.begin(),array.end()); + } + break; + case 3: + pasteExpressions(); + break; + } + copyExpressions(objs); + } + + virtual Gui::Action * createAction(void) { + ActionGroup* pcAction = new ActionGroup(this, getMainWindow()); + pcAction->setDropDownMenu(true); + applyCommandData(this->className(), pcAction); + + pcActionCopySel = pcAction->addAction(QObject::tr("Copy selected")); + pcActionCopyActive = pcAction->addAction(QObject::tr("Copy active document")); + pcActionCopyAll = pcAction->addAction(QObject::tr("Copy all documents")); + pcActionPaste = pcAction->addAction(QObject::tr("Paste")); + + return pcAction; + } + + void copyExpressions(const std::map > &objs) { + std::ostringstream ss; + std::vector props; + for(auto &v : objs) { + for(auto obj : v.second) { + props.clear(); + obj->getPropertyList(props); + for(auto prop : props) { + auto p = dynamic_cast(prop); + if(!p) continue; + for(auto &v : p->getExpressions()) { + ss << "##@@ " << v.first.toString() << ' ' + << obj->getFullName() << '.' << p->getName() + << " (" << obj->Label.getValue() << ')' << std::endl; + ss << "##@@"; + if(v.second->comment.size()) { + if(v.second->comment[0] == '&' + || v.second->comment.find('\n') != std::string::npos + || v.second->comment.find('\r') != std::string::npos) + { + std::string comment = v.second->comment; + boost::replace_all(comment,"&","&"); + boost::replace_all(comment,"\n"," "); + boost::replace_all(comment,"\r"," "); + ss << '&' << comment; + }else + ss << v.second->comment; + } + ss << std::endl << v.second->toString(true) << std::endl << std::endl; + } + } + } + } + QApplication::clipboard()->setText(QString::fromUtf8(ss.str().c_str())); + } + + void pasteExpressions() { + std::map > > exprs; + + bool failed = false; + std::string txt = QApplication::clipboard()->text().toUtf8().constData(); + const char *tstart = txt.c_str(); + const char *tend = tstart + txt.size(); + + static boost::regex rule("^##@@ ([^ ]+) (\\w+)#(\\w+)\\.(\\w+) [^\n]+\n##@@([^\n]*)\n"); + boost::cmatch m; + if(!boost::regex_search(tstart,m,rule)) { + FC_WARN("No expression header found"); + return; + } + boost::cmatch m2; + bool found = true; + for(;found;m=m2) { + found = boost::regex_search(m[0].second,tend,m2,rule); + + auto pathName = m.str(1); + auto docName = m.str(2); + auto objName = m.str(3); + auto propName = m.str(4); + auto comment = m.str(5); + + App::Document *doc = App::GetApplication().getDocument(docName.c_str()); + if(!doc) { + FC_WARN("Cannot find document '" << docName << "'"); + continue; + } + + auto obj = doc->getObject(objName.c_str()); + if(!obj) { + FC_WARN("Cannot find object '" << docName << '#' << objName << "'"); + continue; + } + + auto prop = dynamic_cast( + obj->getPropertyByName(propName.c_str())); + if(!prop) { + FC_WARN("Invalid property '" << docName << '#' << objName << '.' << propName << "'"); + continue; + } + + size_t len = (found?m2[0].first:tend) - m[0].second; + try { + App::ExpressionPtr expr(App::Expression::parse(obj,std::string(m[0].second,len))); + if(expr && comment.size()) { + if(comment[0] == '&') { + expr->comment = comment.c_str()+1; + boost::replace_all(expr->comment,"&","&"); + boost::replace_all(expr->comment," ","\n"); + boost::replace_all(expr->comment," ","\r"); + } else + expr->comment = comment; + } + exprs[doc][prop][App::ObjectIdentifier::parse(obj,pathName)] = std::move(expr); + } catch(Base::Exception &e) { + FC_ERR(e.what() << std::endl << m[0].str()); + failed = true; + } + } + if(failed) { + QMessageBox::critical(getMainWindow(), QObject::tr("Expression error"), + QObject::tr("Failed to parse some of the expressions.\n" + "Please check the Report View for more details.")); + return; + } + + openCommand("Paste expressions"); + try { + for(auto &v : exprs) { + for(auto &v2 : v.second) { + auto &expressions = v2.second; + auto old = v2.first->getExpressions(); + for(auto it=expressions.begin(),itNext=it;it!=expressions.end();it=itNext) { + ++itNext; + auto iter = old.find(it->first); + if(iter != old.end() && it->second->isSame(*iter->second)) + expressions.erase(it); + } + if(expressions.size()) + v2.first->setExpressions(std::move(expressions)); + } + } + commitCommand(); + } catch (const Base::Exception& e) { + abortCommand(); + QMessageBox::critical(getMainWindow(), QObject::tr("Failed to paste expressions"), + QString::fromLatin1(e.what())); + e.ReportException(); + } + } + + bool isActive() { + if(!App::GetApplication().getActiveDocument()) { + pcActionCopyAll->setEnabled(false); + pcActionCopySel->setEnabled(false); + pcActionCopyActive->setEnabled(false); + pcActionPaste->setEnabled(false); + return true; + } + pcActionCopyActive->setEnabled(true); + pcActionCopyAll->setEnabled(true); + pcActionCopySel->setEnabled(Selection().hasSelection()); + + pcActionPaste->setEnabled( + QApplication::clipboard()->text().startsWith(QLatin1String("##@@ "))); + return true; + } + + QAction *pcActionCopyAll; + QAction *pcActionCopySel; + QAction *pcActionCopyActive; + QAction *pcActionPaste; +}; namespace Gui { @@ -1470,6 +1748,7 @@ void CreateDocCommands(void) rcCmdMgr.addCommand(new StdCmdSave()); rcCmdMgr.addCommand(new StdCmdSaveAs()); rcCmdMgr.addCommand(new StdCmdSaveCopy()); + rcCmdMgr.addCommand(new StdCmdSaveAll()); rcCmdMgr.addCommand(new StdCmdRevert()); rcCmdMgr.addCommand(new StdCmdProjectInfo()); rcCmdMgr.addCommand(new StdCmdProjectUtil()); @@ -1491,6 +1770,7 @@ void CreateDocCommands(void) rcCmdMgr.addCommand(new StdCmdTransformManip()); rcCmdMgr.addCommand(new StdCmdAlignment()); rcCmdMgr.addCommand(new StdCmdEdit()); + rcCmdMgr.addCommand(new StdCmdExpression()); } } // namespace Gui diff --git a/src/Gui/CommandWindow.cpp b/src/Gui/CommandWindow.cpp index 794f2167c9..232cc92e26 100644 --- a/src/Gui/CommandWindow.cpp +++ b/src/Gui/CommandWindow.cpp @@ -169,18 +169,18 @@ StdCmdCloseAllWindows::StdCmdCloseAllWindows() sToolTipText = QT_TR_NOOP("Close all windows"); sWhatsThis = "Std_CloseAllWindows"; sStatusTip = QT_TR_NOOP("Close all windows"); - eType = 0; + eType = NoTransaction; } void StdCmdCloseAllWindows::activated(int iMsg) { Q_UNUSED(iMsg); - getMainWindow()->closeAllWindows(); + getMainWindow()->closeAllDocuments(); } bool StdCmdCloseAllWindows::isActive(void) { - return !(getMainWindow()->windows().isEmpty()); + return !(getMainWindow()->windows().isEmpty()) || App::GetApplication().getDocuments().size(); } //=========================================================================== diff --git a/src/Gui/DlgObjectSelection.cpp b/src/Gui/DlgObjectSelection.cpp new file mode 100644 index 0000000000..0e53c64809 --- /dev/null +++ b/src/Gui/DlgObjectSelection.cpp @@ -0,0 +1,375 @@ +/**************************************************************************** + * Copyright (c) 2018 Zheng, Lei (realthunder) * + * * + * 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 "PreCompiled.h" +#ifndef _PreComp_ +# include +#endif + +#include +#include +#include +#include +#include "DlgObjectSelection.h" +#include "Application.h" +#include "ViewProviderDocumentObject.h" +#include "ui_DlgObjectSelection.h" + +FC_LOG_LEVEL_INIT("Gui",true,true); + +using namespace Gui; + +/* TRANSLATOR Gui::DlgObjectSelection */ + +DlgObjectSelection::DlgObjectSelection( + const std::vector &objs, QWidget* parent, Qt::WindowFlags fl) + : QDialog(parent, fl), ui(new Ui_DlgObjectSelection) +{ + ui->setupUi(this); + + // make sure to show a horizontal scrollbar if needed +#if QT_VERSION >= 0x050000 + ui->depList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + ui->depList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + ui->depList->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + ui->depList->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); + ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); +#else + ui->depList->header()->setResizeMode(0, QHeaderView::ResizeToContents); + ui->depList->header()->setResizeMode(1, QHeaderView::ResizeToContents); + ui->depList->header()->setResizeMode(2, QHeaderView::ResizeToContents); + ui->depList->header()->setResizeMode(3, QHeaderView::ResizeToContents); + ui->treeWidget->header()->setResizeMode(0, QHeaderView::ResizeToContents); +#endif + ui->depList->header()->setStretchLastSection(false); + ui->depList->headerItem()->setText(0, tr("Dependency")); + ui->depList->headerItem()->setText(1, tr("Document")); + ui->depList->headerItem()->setText(2, tr("Name")); + ui->depList->headerItem()->setText(3, tr("State")); + + ui->treeWidget->headerItem()->setText(0, tr("Hierarchy")); + ui->treeWidget->header()->setStretchLastSection(false); + + for(auto obj : App::Document::getDependencyList(objs)) { + auto &info = objMap[obj]; + info.depItem = new QTreeWidgetItem(ui->depList); + auto vp = Gui::Application::Instance->getViewProvider(obj); + if(vp) info.depItem->setIcon(0, vp->getIcon()); + info.depItem->setIcon(0, vp->getIcon()); + info.depItem->setText(0, QString::fromUtf8((obj)->Label.getValue())); + info.depItem->setText(1, QString::fromUtf8(obj->getDocument()->getName())); + info.depItem->setText(2, QString::fromLatin1(obj->getNameInDocument())); + info.depItem->setText(3, tr("Selected")); + info.depItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsUserCheckable|Qt::ItemIsEnabled); + info.depItem->setCheckState(0,Qt::Checked); + } + for(auto obj : objs) { + auto &info = objMap[obj]; + info.items.push_back(createItem(obj,0)); + info.items.back()->setCheckState(0,Qt::Checked); + } + + for(auto &v : objMap) { + for(auto obj : v.first->getOutListRecursive()) { + if(obj == v.first) + continue; + auto it = objMap.find(obj); + if(it == objMap.end()) + continue; + v.second.outList[obj] = &it->second; + } + for(auto obj : v.first->getInListRecursive()) { + if(obj == v.first) + continue; + auto it = objMap.find(obj); + if(it == objMap.end()) + continue; + v.second.inList[obj] = &it->second; + } + } + + connect(ui->treeWidget, SIGNAL(itemExpanded(QTreeWidgetItem*)), + this, SLOT(onItemExpanded(QTreeWidgetItem*))); + connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)), + this, SLOT(onItemChanged(QTreeWidgetItem*,int))); + connect(ui->depList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), + this, SLOT(onItemChanged(QTreeWidgetItem*,int))); + connect(ui->treeWidget, SIGNAL(itemSelectionChanged()), + this, SLOT(onItemSelectionChanged())); + connect(ui->depList, SIGNAL(itemSelectionChanged()), + this, SLOT(onDepSelectionChanged())); + connect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject())); +} + +/** + * Destroys the object and frees any allocated resources + */ +DlgObjectSelection::~DlgObjectSelection() +{ + // no need to delete child widgets, Qt does it all for us + delete ui; +} + +QTreeWidgetItem *DlgObjectSelection::createItem(App::DocumentObject *obj, QTreeWidgetItem *parent) { + QTreeWidgetItem* item; + if(parent) + item = new QTreeWidgetItem(parent); + else + item = new QTreeWidgetItem(ui->treeWidget); + auto vp = Gui::Application::Instance->getViewProvider(obj); + if(vp) item->setIcon(0, vp->getIcon()); + item->setText(0, QString::fromUtf8((obj)->Label.getValue())); + item->setData(0, Qt::UserRole, QByteArray(obj->getDocument()->getName())); + item->setData(0, Qt::UserRole+1, QByteArray(obj->getNameInDocument())); + item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsUserCheckable|Qt::ItemIsEnabled); + std::set outSet; + for(auto o : obj->getOutList()) { + if(objMap.count(o)) + outSet.insert(o); + } + if(outSet.empty()) + return item; + item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); + if(!parent) { + bool populate = false; + for(auto o : outSet) { + if(objMap[o].items.empty()) { + populate = true; + break; + } + } + if(!populate) + return item; + for(auto o : outSet) { + auto &info = objMap[o]; + info.items.push_back(createItem(o,item)); + info.items.back()->setCheckState(0,info.checkState); + } + } + return item; +} + +class SignalBlocker { +public: + SignalBlocker(QTreeWidget *treeWidget) + :treeWidget(treeWidget) + { + treeWidget->blockSignals(true); + } + ~SignalBlocker() { + treeWidget->blockSignals(false); + } + QTreeWidget *treeWidget; +}; + +App::DocumentObject *DlgObjectSelection::objFromItem(QTreeWidgetItem *item) { + std::string name; + std::string docName; + if(item->treeWidget() == ui->treeWidget) { + docName = item->data(0,Qt::UserRole).toByteArray().constData(); + name = item->data(0,Qt::UserRole+1).toByteArray().constData(); + }else{ + docName = qPrintable(item->text(1)); + name = qPrintable(item->text(2)); + } + auto doc = App::GetApplication().getDocument(docName.c_str()); + if(!doc) return 0; + return doc->getObject(name.c_str()); +} + +void DlgObjectSelection::onItemExpanded(QTreeWidgetItem * item) { + if(item->childCount()) + return; + auto obj = objFromItem(item); + if(!obj) + return; + SignalBlocker blocker(ui->treeWidget); + std::set outSet; + for(auto o : obj->getOutList()) { + if(!objMap.count(obj) || !outSet.insert(o).second) + continue; + auto &info = objMap[o]; + info.items.push_back(createItem(o,item)); + info.items.back()->setCheckState(0,info.checkState); + } +} + +void DlgObjectSelection::onItemChanged(QTreeWidgetItem * item, int column) { + if(column) return; + auto obj = objFromItem(item); + if(!obj) return; + auto state = item->checkState(0); + auto it = objMap.find(obj); + if(it == objMap.end() || state == it->second.checkState) + return; + SignalBlocker blocker(ui->treeWidget); + SignalBlocker blocker2(ui->depList); + auto &info = it->second; + info.checkState = state; + + if(item == info.depItem) { + for(auto item : info.items) + item->setCheckState(0,state); + }else{ + info.depItem->setCheckState(0,state); + info.depItem->setText(3,state==Qt::Checked?tr("Selected"):QString()); + } + + if(state == Qt::Unchecked) { + for(auto &v : info.outList) { + if(info.inList.count(v.first)) { + // This indicates a dependency loop. The check here is so that + // object selection still works despite of the loop + continue; + } + if(v.second->checkState == Qt::Unchecked) + continue; + v.second->checkState = Qt::Unchecked; + v.second->depItem->setText(3,QString()); + v.second->depItem->setCheckState(0,Qt::Unchecked); + for(auto item : v.second->items) + item->setCheckState(0,Qt::Unchecked); + } + for(auto &v : info.inList) { + if(v.second->checkState != Qt::Checked) + continue; + v.second->checkState = Qt::PartiallyChecked; + v.second->depItem->setText(3,tr("Partial")); + v.second->depItem->setCheckState(0,Qt::PartiallyChecked); + for(auto item : v.second->items) + item->setCheckState(0,Qt::PartiallyChecked); + } + return; + } else if(state == Qt::Checked) { + for(auto &v : info.outList) { + if(info.inList.count(v.first)) { + // This indicates a dependency loop. The check here is so that + // object selection still works despite of the loop + continue; + } + if(v.second->checkState == Qt::Checked) + continue; + v.second->checkState = Qt::Checked; + v.second->depItem->setText(3,tr("Selected")); + v.second->depItem->setCheckState(0,Qt::Checked); + for(auto item : v.second->items) + item->setCheckState(0,Qt::Checked); + } + bool touched; + do { + touched = false; + for(auto &v : info.inList) { + if(v.second->checkState != Qt::PartiallyChecked) + continue; + bool partial = false; + for(auto &vv : v.second->outList) { + if(vv.second->checkState != Qt::Checked) { + partial = true; + break; + } + } + if(partial) + continue; + touched = true; + v.second->checkState = Qt::Checked; + v.second->depItem->setText(3,tr("Selected")); + v.second->depItem->setCheckState(0,Qt::Checked); + for(auto item : v.second->items) + item->setCheckState(0,Qt::Checked); + } + }while(touched); + } +} + +std::vector DlgObjectSelection::getSelections() const { + std::vector res; + for(auto &v : objMap) { + if(v.second.checkState != Qt::Unchecked) + res.push_back(v.first); + } + return res; +} + +void DlgObjectSelection::onItemSelectionChanged() { + SignalBlocker block2(ui->treeWidget); + SignalBlocker block(ui->depList); + QTreeWidgetItem *scroll=0; + for(auto &v : objMap) { + auto &info = v.second; + auto it = sels.find(v.first); + auto selected = it==sels.end(); + for(auto item : info.items) { + if(selected == item->isSelected()) { + for(auto item : info.items) + item->setSelected(selected); + scroll = info.depItem; + info.depItem->setSelected(selected); + scroll = info.depItem; + if(!selected) + sels.erase(it); + else + sels.insert(v.first); + break; + } + } + } + if(scroll) + ui->depList->scrollToItem(scroll); +} + +void DlgObjectSelection::onDepSelectionChanged() { + SignalBlocker block2(ui->treeWidget); + SignalBlocker block(ui->depList); + QTreeWidgetItem *scroll=0; + for(auto &v : objMap) { + auto &info = v.second; + auto it = sels.find(v.first); + auto selected = it==sels.end(); + if(info.depItem->isSelected()==selected) { + for(auto item : info.items) { + scroll = item; + item->setSelected(selected); + } + if(!selected) + sels.erase(it); + else { + sels.insert(v.first); + for(auto item : info.items) { + for(auto parent=item->parent();parent;parent=parent->parent()) + parent->setExpanded(true); + } + } + } + } + if(scroll) + ui->treeWidget->scrollToItem(scroll); +} + +void DlgObjectSelection::accept() { + QDialog::accept(); +} + +void DlgObjectSelection::reject() { + QDialog::reject(); +} + +#include "moc_DlgObjectSelection.cpp" diff --git a/src/Gui/DlgObjectSelection.h b/src/Gui/DlgObjectSelection.h new file mode 100644 index 0000000000..b8c22f3ff0 --- /dev/null +++ b/src/Gui/DlgObjectSelection.h @@ -0,0 +1,70 @@ +/**************************************************************************** + * Copyright (c) 2018 Zheng, Lei (realthunder) * + * * + * 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 GUI_DLGOBJECTSELECTION_H +#define GUI_DLGOBJECTSELECTION_H + +#include + +namespace Gui { + +class Ui_DlgObjectSelection; +class GuiExport DlgObjectSelection : public QDialog +{ + Q_OBJECT + +public: + DlgObjectSelection(const std::vector &objs, + QWidget* parent = 0, Qt::WindowFlags fl = 0); + ~DlgObjectSelection(); + + std::vector getSelections() const; + void accept(); + void reject(); + +private Q_SLOTS: + void onItemExpanded(QTreeWidgetItem * item); + void onItemChanged(QTreeWidgetItem * item, int); + void onItemSelectionChanged(); + void onDepSelectionChanged(); + +private: + QTreeWidgetItem *createItem(App::DocumentObject *obj, QTreeWidgetItem *parent); + App::DocumentObject *objFromItem(QTreeWidgetItem *item); + +private: + struct Info { + std::map inList; + std::map outList; + std::vector items; + QTreeWidgetItem *depItem = 0; + Qt::CheckState checkState = Qt::Checked; + }; + std::map objMap; + Ui_DlgObjectSelection* ui; + std::set sels; +}; + +} // namespace Gui + + +#endif // GUI_DLGOBJECTSELECTION_H + diff --git a/src/Gui/DlgObjectSelection.ui b/src/Gui/DlgObjectSelection.ui new file mode 100644 index 0000000000..e5aa99f8a1 --- /dev/null +++ b/src/Gui/DlgObjectSelection.ui @@ -0,0 +1,156 @@ + + + Gui::DlgObjectSelection + + + + 0 + 0 + 621 + 383 + + + + Object selection + + + true + + + true + + + + + + + 0 + 0 + + + + The selected objects contain other dependencies. Please select which objects to export. All dependencies are auto selected by default. + + + true + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + 0 + 0 + + + + + 0 + 0 + + + + QAbstractItemView::ExtendedSelection + + + true + + + true + + + false + + + 1 + + + true + + + false + + + false + + + false + + + + 1 + + + + + + + 1 + 0 + + + + QAbstractItemView::ExtendedSelection + + + false + + + true + + + 3 + + + true + + + + 1 + + + + + 2 + + + + + 3 + + + + + + + + + + 0 + 1 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/src/Gui/DlgSettingsDocument.ui b/src/Gui/DlgSettingsDocument.ui index feb9db40ce..5d0a2e34e9 100644 --- a/src/Gui/DlgSettingsDocument.ui +++ b/src/Gui/DlgSettingsDocument.ui @@ -407,6 +407,19 @@ automatically run a file recovery when it is started. + + + + Disable partial loading of external linked objects + + + NoPartialLoading + + + Document + + + diff --git a/src/Gui/DlgSettingsDocumentImp.cpp b/src/Gui/DlgSettingsDocumentImp.cpp index 55ba77947a..93103e0e9e 100644 --- a/src/Gui/DlgSettingsDocumentImp.cpp +++ b/src/Gui/DlgSettingsDocumentImp.cpp @@ -73,6 +73,7 @@ void DlgSettingsDocumentImp::saveSettings() prefSaveBackupFiles->onSave(); prefCountBackupFiles->onSave(); prefDuplicateLabel->onSave(); + prefPartialLoading->onSave(); prefLicenseType->onSave(); prefLicenseUrl->onSave(); prefAuthor->onSave(); @@ -102,6 +103,7 @@ void DlgSettingsDocumentImp::loadSettings() prefSaveBackupFiles->onRestore(); prefCountBackupFiles->onRestore(); prefDuplicateLabel->onRestore(); + prefPartialLoading->onRestore(); prefLicenseType->onRestore(); prefLicenseUrl->onRestore(); prefAuthor->onRestore(); diff --git a/src/Gui/Document.cpp b/src/Gui/Document.cpp index 9d5f4da168..adb2ac70d4 100644 --- a/src/Gui/Document.cpp +++ b/src/Gui/Document.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include "Application.h" #include "MainWindow.h" @@ -66,6 +67,8 @@ #include "Selection.h" #include "WaitCursor.h" #include "Thumbnail.h" +#include "ViewProviderLink.h" + FC_LOG_LEVEL_INIT("Gui",true,true) using namespace Gui; @@ -80,7 +83,7 @@ struct DocumentP int _iDocId; bool _isClosing; bool _isModified; - ViewProvider* _editViewProvider; + bool _isTransacting; int _editMode; ViewProvider* _editViewProvider; ViewProviderDocumentObject* _editViewProviderParent; @@ -95,6 +98,7 @@ struct DocumentP /// List of all registered views std::list passiveViews; std::map _ViewProviderMap; + std::map _CoinMap; std::map _ViewProviderMapAnnotation; typedef boost::signals2::connection Connection; @@ -107,12 +111,20 @@ struct DocumentP Connection connectRestDocument; Connection connectStartLoadDocument; Connection connectFinishLoadDocument; + Connection connectShowHidden; + Connection connectFinishRestoreObject; Connection connectExportObjects; Connection connectImportObjects; + Connection connectFinishImportObjects; Connection connectUndoDocument; Connection connectRedoDocument; + Connection connectRecomputed; + Connection connectSkipRecompute; Connection connectTransactionAppend; Connection connectTransactionRemove; + Connection connectTouchedObject; + Connection connectChangePropertyEditor; + typedef boost::signals2::shared_connection_block ConnectionBlock; ConnectionBlock connectActObjectBlocker; }; @@ -133,6 +145,7 @@ Document::Document(App::Document* pcDocument,Application * app) d->_iDocId = (++_iDocCount); d->_isClosing = false; d->_isModified = false; + d->_isTransacting = false; d->_pcAppWnd = app; d->_pcDocument = pcDocument; d->_editViewProvider = 0; @@ -160,16 +173,30 @@ Document::Document(App::Document* pcDocument,Application * app) (boost::bind(&Gui::Document::slotStartRestoreDocument, this, _1)); d->connectFinishLoadDocument = App::GetApplication().signalFinishRestoreDocument.connect (boost::bind(&Gui::Document::slotFinishRestoreDocument, this, _1)); + d->connectShowHidden = App::GetApplication().signalShowHidden.connect + (boost::bind(&Gui::Document::slotShowHidden, this, _1)); + d->connectChangePropertyEditor = pcDocument->signalChangePropertyEditor.connect + (boost::bind(&Gui::Document::slotChangePropertyEditor, this, _1, _2)); + d->connectFinishRestoreObject = pcDocument->signalFinishRestoreObject.connect + (boost::bind(&Gui::Document::slotFinishRestoreObject, this, _1)); d->connectExportObjects = pcDocument->signalExportViewObjects.connect (boost::bind(&Gui::Document::exportObjects, this, _1, _2)); d->connectImportObjects = pcDocument->signalImportViewObjects.connect (boost::bind(&Gui::Document::importObjects, this, _1, _2, _3)); - + d->connectFinishImportObjects = pcDocument->signalFinishImportObjects.connect + (boost::bind(&Gui::Document::slotFinishImportObjects, this, _1)); + d->connectUndoDocument = pcDocument->signalUndo.connect (boost::bind(&Gui::Document::slotUndoDocument, this, _1)); d->connectRedoDocument = pcDocument->signalRedo.connect (boost::bind(&Gui::Document::slotRedoDocument, this, _1)); + d->connectRecomputed = pcDocument->signalRecomputed.connect + (boost::bind(&Gui::Document::slotRecomputed, this, _1)); + d->connectSkipRecompute = pcDocument->signalSkipRecompute.connect + (boost::bind(&Gui::Document::slotSkipRecompute, this, _1, _2)); + d->connectTouchedObject = pcDocument->signalTouchedObject.connect + (boost::bind(&Gui::Document::slotTouchedObject, this, _1)); d->connectTransactionAppend = pcDocument->signalTransactionAppend.connect (boost::bind(&Gui::Document::slotTransactionAppend, this, _1, _2)); @@ -201,12 +228,19 @@ Document::~Document() d->connectRestDocument.disconnect(); d->connectStartLoadDocument.disconnect(); d->connectFinishLoadDocument.disconnect(); + d->connectShowHidden.disconnect(); + d->connectFinishRestoreObject.disconnect(); d->connectExportObjects.disconnect(); d->connectImportObjects.disconnect(); + d->connectFinishImportObjects.disconnect(); d->connectUndoDocument.disconnect(); d->connectRedoDocument.disconnect(); + d->connectRecomputed.disconnect(); + d->connectSkipRecompute.disconnect(); d->connectTransactionAppend.disconnect(); d->connectTransactionRemove.disconnect(); + d->connectTouchedObject.disconnect(); + d->connectChangePropertyEditor.disconnect(); // e.g. if document gets closed from within a Python command d->_isClosing = true; @@ -308,6 +342,7 @@ bool Document::setEdit(Gui::ViewProvider* p, int ModNum, const char *subname) FC_ERR("cannot edit object '" << obj->getNameInDocument() << "': not found in document " << "'" << getDocument()->getName() << "'"); return false; + } d->_editingTransform = Base::Matrix4D(); // Geo feature group now handles subname like link group. So no need of the @@ -581,7 +616,7 @@ void Document::slotNewObject(const App::DocumentObject& Obj) ViewProviderDocumentObject* pcProvider = static_cast(getViewProvider(&Obj)); if (!pcProvider) { //Base::Console().Log("Document::slotNewObject() called\n"); - std::string cName = Obj.getViewProviderName(); + std::string cName = Obj.getViewProviderNameStored(); if (cName.empty()) { // handle document object with no view provider specified Base::Console().Log("%s has no view provider specified\n", Obj.getTypeId().getName()); @@ -595,6 +630,7 @@ void Document::slotNewObject(const App::DocumentObject& Obj) assert(base->getTypeId().isDerivedFrom(Gui::ViewProviderDocumentObject::getClassTypeId())); pcProvider = static_cast(base); d->_ViewProviderMap[&Obj] = pcProvider; + d->_CoinMap[pcProvider->getRoot()] = pcProvider; try { // if successfully created set the right name and calculate the view @@ -604,19 +640,25 @@ void Document::slotNewObject(const App::DocumentObject& Obj) pcProvider->setActiveMode(); } catch(const Base::MemoryException& e){ - Base::Console().Error("Memory exception in '%s' thrown: %s\n",Obj.getNameInDocument(),e.what()); + FC_ERR("Memory exception in " << Obj.getFullName() << " thrown: " << e.what()); } catch(Base::Exception &e){ e.ReportException(); } #ifndef FC_DEBUG catch(...){ - Base::Console().Error("App::Document::_RecomputeFeature(): Unknown exception in Feature \"%s\" thrown\n",Obj.getNameInDocument()); + FC_ERR("Unknown exception in Feature " << Obj.getFullName() << " thrown"); } #endif } else { - Base::Console().Warning("Gui::Document::slotNewObject() no view provider for the object %s found\n",cName.c_str()); + FC_WARN("no view provider for the object " << cName << " found"); + } + }else{ + try { + pcProvider->reattach(const_cast(&Obj)); + } catch(Base::Exception &e){ + e.ReportException(); } } @@ -656,6 +698,9 @@ void Document::slotDeletedObject(const App::DocumentObject& Obj) editDoc->d->_editViewProviderParent==viewProvider) Application::Instance->setEditDocument(0); } + + handleChildren3D(viewProvider,true); + #if 0 // With this we can show child objects again if this method was called by undo viewProvider->onDelete(std::vector()); #endif @@ -664,11 +709,8 @@ void Document::slotDeletedObject(const App::DocumentObject& Obj) // go through the views for (vIt = d->baseViews.begin();vIt != d->baseViews.end();++vIt) { View3DInventor *activeView = dynamic_cast(*vIt); - if (activeView) { - if (d->_editViewProvider == viewProvider) - resetEdit(); + if (activeView) activeView->getViewer()->removeViewProvider(viewProvider); - } } // removing from tree @@ -703,16 +745,16 @@ void Document::slotChangedObject(const App::DocumentObject& Obj, const App::Prop viewProvider->update(&Prop); } catch(const Base::MemoryException& e) { - Base::Console().Error("Memory exception in '%s' thrown: %s\n",Obj.getNameInDocument(),e.what()); + FC_ERR("Memory exception in " << Obj.getFullName() << " thrown: " << e.what()); } catch(Base::Exception& e){ e.ReportException(); } catch(const std::exception& e){ - Base::Console().Error("C++ exception in '%s' thrown: %s\n",Obj.getNameInDocument(),e.what()); + FC_ERR("C++ exception in " << Obj.getFullName() << " thrown " << e.what()); } catch (...) { - Base::Console().Error("Cannot update representation for '%s'.\n", Obj.getNameInDocument()); + FC_ERR("Cannot update representation for " << Obj.getFullName()); } handleChildren3D(viewProvider); @@ -722,7 +764,10 @@ void Document::slotChangedObject(const App::DocumentObject& Obj, const App::Prop } // a property of an object has changed - setModified(true); + if(!Prop.testStatus(App::Property::NoModify) && !isModified()) { + FC_LOG(Prop.getFullName() << " modified"); + setModified(true); + } } void Document::slotRelabelObject(const App::DocumentObject& Obj) @@ -748,14 +793,9 @@ void Document::slotTransactionRemove(const App::DocumentObject& obj, App::Transa if (it != d->_ViewProviderMap.end()) { ViewProvider* viewProvider = it->second; - // Issue #0003540: - // When deleting a view provider that claims the Inventor nodes - // of its children then we must update the 3d viewers to re-add - // the root nodes if needed. - bool rebuild = false; - SoGroup* childGroup = viewProvider->getChildRoot(); - if (childGroup && childGroup->getNumChildren() > 0) - rebuild = true; + auto itC = d->_CoinMap.find(viewProvider->getRoot()); + if(itC != d->_CoinMap.end()) + d->_CoinMap.erase(itC); d->_ViewProviderMap.erase(&obj); // transaction being a nullptr indicates that undo/redo is off and the object @@ -764,9 +804,6 @@ void Document::slotTransactionRemove(const App::DocumentObject& obj, App::Transa transaction->addObjectNew(viewProvider); else delete viewProvider; - - if (rebuild) - rebuildRootNodes(); } } @@ -784,6 +821,7 @@ void Document::slotUndoDocument(const App::Document& doc) return; signalUndoDocument(*this); + getMainWindow()->updateActions(); } void Document::slotRedoDocument(const App::Document& doc) @@ -792,6 +830,52 @@ void Document::slotRedoDocument(const App::Document& doc) return; signalRedoDocument(*this); + getMainWindow()->updateActions(); +} + +void Document::slotRecomputed(const App::Document& doc) +{ + if (d->_pcDocument != &doc) + return; + getMainWindow()->updateActions(); + TreeWidget::updateStatus(); +} + +// This function is called when some asks to recompute a document that is marked +// as 'SkipRecompute'. We'll check if we are the current document, and if either +// not given an explicit recomputing object list, or the given single object is +// the eidting object or the active object. If the conditions are met, we'll +// force recompute only that object and all its dependent objects. +void Document::slotSkipRecompute(const App::Document& doc, const std::vector &objs) +{ + if (d->_pcDocument != &doc) + return; + if(objs.size()>1 || + App::GetApplication().getActiveDocument()!=&doc || + !doc.testStatus(App::Document::AllowPartialRecompute)) + return; + App::DocumentObject *obj = 0; + auto editDoc = Application::Instance->editDocument(); + if(editDoc) { + auto vp = dynamic_cast(editDoc->getInEdit()); + if(vp) + obj = vp->getObject(); + } + if(!obj) + obj = doc.getActiveObject(); + if(!obj || !obj->getNameInDocument() || (objs.size() && objs.front()!=obj)) + return; + obj->recomputeFeature(true); +} + +void Document::slotTouchedObject(const App::DocumentObject &Obj) +{ + getMainWindow()->updateActions(true); + TreeWidget::updateStatus(true); + if(!isModified()) { + FC_LOG(Obj.getFullName() << " touched"); + setModified(true); + } } void Document::addViewProvider(Gui::ViewProviderDocumentObject* vp) @@ -804,10 +888,13 @@ void Document::addViewProvider(Gui::ViewProviderDocumentObject* vp) assert(d->_ViewProviderMap.find(vp->getObject()) == d->_ViewProviderMap.end()); vp->setStatus(Detach, false); d->_ViewProviderMap[vp->getObject()] = vp; + d->_CoinMap[vp->getRoot()] = vp; } void Document::setModified(bool b) { + if(d->_isModified == b) + return; d->_isModified = b; std::list mdis = getMDIViews(); @@ -822,24 +909,57 @@ bool Document::isModified() const } -ViewProvider* Document::getViewProviderByPathFromTail(SoPath * path) const +ViewProviderDocumentObject* Document::getViewProviderByPathFromTail(SoPath * path) const { // Get the lowest root node in the pick path! for (int i = 0; i < path->getLength(); i++) { SoNode *node = path->getNodeFromTail(i); if (node->isOfType(SoSeparator::getClassTypeId())) { - std::map::const_iterator it; - for(it = d->_ViewProviderMap.begin();it!= d->_ViewProviderMap.end();++it) { - if (node == it->second->getRoot()) - return it->second; - } - } + auto it = d->_CoinMap.find(static_cast(node)); + if(it!=d->_CoinMap.end()) + return it->second; + } } return 0; } +ViewProviderDocumentObject* Document::getViewProviderByPathFromHead(SoPath * path) const +{ + for (int i = 0; i < path->getLength(); i++) { + SoNode *node = path->getNode(i); + if (node->isOfType(SoSeparator::getClassTypeId())) { + auto it = d->_CoinMap.find(static_cast(node)); + if(it!=d->_CoinMap.end()) + return it->second; + } + } + return 0; +} + +ViewProviderDocumentObject *Document::getViewProvider(SoNode *node) const { + if(!node || !node->isOfType(SoSeparator::getClassTypeId())) + return 0; + auto it = d->_CoinMap.find(static_cast(node)); + if(it!=d->_CoinMap.end()) + return it->second; + return 0; +} + +std::vector > Document::getViewProvidersByPath(SoPath * path) const +{ + std::vector > ret; + for (int i = 0; i < path->getLength(); i++) { + SoNode *node = path->getNodeFromTail(i); + if (node->isOfType(SoSeparator::getClassTypeId())) { + auto it = d->_CoinMap.find(static_cast(node)); + if(it!=d->_CoinMap.end()) + ret.push_back(std::make_pair(it->second,i)); + } + } + return ret; +} App::Document* Document::getDocument(void) const { @@ -851,10 +971,41 @@ bool Document::save(void) { if (d->_pcDocument->isSaved()) { try { + std::vector > docs; + try { + for(auto doc : getDocument()->getDependentDocuments()) { + auto gdoc = Application::Instance->getDocument(doc); + if(gdoc && (gdoc==this || gdoc->isModified())) + docs.emplace_back(doc,doc->mustExecute()); + } + }catch(const Base::RuntimeError &e) { + FC_ERR(e.what()); + docs.emplace_back(getDocument(),getDocument()->mustExecute()); + } + if(docs.size()>1) { + int ret = QMessageBox::question(getMainWindow(), + QObject::tr("Save dependent files"), + QObject::tr("The file contain external depencencies. " + "Do you want to save the dependent files, too?"), + QMessageBox::Yes,QMessageBox::No); + if (ret != QMessageBox::Yes) { + docs.clear(); + docs.emplace_back(getDocument(),getDocument()->mustExecute()); + } + } Gui::WaitCursor wc; - Command::doCommand(Command::Doc,"App.getDocument(\"%s\").save()" - ,d->_pcDocument->getName()); - setModified(false); + // save all documents + for(auto v : docs) { + auto doc = v.first; + // Changed 'mustExecute' status may be triggered by saving external document + if(!v.second && doc->mustExecute()) { + App::AutoTransaction trans("Recompute"); + Command::doCommand(Command::Doc,"App.getDocument(\"%s\").recompute()",doc->getName()); + } + Command::doCommand(Command::Doc,"App.getDocument(\"%s\").save()",doc->getName()); + auto gdoc = Application::Instance->getDocument(doc); + if(gdoc) gdoc->setModified(false); + } } catch (const Base::Exception& e) { QMessageBox::critical(getMainWindow(), QObject::tr("Saving document failed"), @@ -874,7 +1025,8 @@ bool Document::saveAs(void) QString exe = qApp->applicationName(); QString fn = FileDialog::getSaveFileName(getMainWindow(), QObject::tr("Save %1 Document").arg(exe), - QString(), QString::fromLatin1("%1 %2 (*.FCStd)").arg(exe, QObject::tr("Document"))); + QString::fromUtf8(getDocument()->FileName.getValue()), + QString::fromLatin1("%1 %2 (*.FCStd)").arg(exe).arg(QObject::tr("Document"))); if (!fn.isEmpty()) { QFileInfo fi; fi.setFile(fn); @@ -902,6 +1054,52 @@ bool Document::saveAs(void) } } +void Document::saveAll() { + std::vector docs; + try { + docs = App::Document::getDependentDocuments(App::GetApplication().getDocuments(),true); + }catch(Base::Exception &e) { + e.ReportException(); + int ret = QMessageBox::critical(getMainWindow(), QObject::tr("Failed to save document"), + QObject::tr("Documents contains cyclic dependices. Do you still want to save them?"), + QMessageBox::Yes,QMessageBox::No); + if(ret!=QMessageBox::Yes) + return; + docs = App::GetApplication().getDocuments(); + } + std::map dmap; + for(auto doc : docs) + dmap[doc] = doc->mustExecute(); + for(auto doc : docs) { + if(doc->testStatus(App::Document::PartialDoc)) + continue; + auto gdoc = Application::Instance->getDocument(doc); + if(!gdoc) + continue; + if(!doc->isSaved()) { + if(!gdoc->saveAs()) + break; + } + Gui::WaitCursor wc; + + try { + // Changed 'mustExecute' status may be triggered by saving external document + if(!dmap[doc] && doc->mustExecute()) { + App::AutoTransaction trans("Recompute"); + Command::doCommand(Command::Doc,"App.getDocument('%s').recompute()",doc->getName()); + } + Command::doCommand(Command::Doc,"App.getDocument('%s').save()",doc->getName()); + gdoc->setModified(false); + } catch (const Base::Exception& e) { + QMessageBox::critical(getMainWindow(), + QObject::tr("Failed to save document") + + QString::fromLatin1(": %1").arg(QString::fromUtf8(doc->getName())), + QString::fromLatin1(e.what())); + break; + } + } +} + /// Save a copy of the document under a new file name bool Document::saveCopy(void) { @@ -909,7 +1107,8 @@ bool Document::saveCopy(void) QString exe = qApp->applicationName(); QString fn = FileDialog::getSaveFileName(getMainWindow(), QObject::tr("Save %1 Document").arg(exe), - QString(), QObject::tr("%1 document (*.FCStd)").arg(exe)); + QString::fromUtf8(getDocument()->FileName.getValue()), + QObject::tr("%1 document (*.FCStd)").arg(exe)); if (!fn.isEmpty()) { QFileInfo fi; fi.setFile(fn); @@ -977,6 +1176,7 @@ void Document::Restore(Base::XMLReader &reader) std::map::iterator it; for (it = d->_ViewProviderMap.begin(); it != d->_ViewProviderMap.end(); ++it) { it->second->startRestoring(); + it->second->setStatus(Gui::isRestoring,true); } } @@ -993,6 +1193,16 @@ void Document::RestoreDocFile(Base::Reader &reader) long scheme = xmlReader.getAttributeAsInteger("SchemaVersion"); xmlReader.DocumentSchema = scheme; + bool hasExpansion = xmlReader.hasAttribute("HasExpansion"); + if(hasExpansion) { + auto tree = TreeWidget::instance(); + if(tree) { + auto docItem = tree->getDocumentItem(this); + if(docItem) + docItem->Restore(xmlReader); + } + } + // At this stage all the document objects and their associated view providers exist. // Now we must restore the properties of the view providers only. // @@ -1005,7 +1215,7 @@ void Document::RestoreDocFile(Base::Reader &reader) xmlReader.readElement("ViewProvider"); std::string name = xmlReader.getAttribute("name"); bool expanded = false; - if (xmlReader.hasAttribute("expanded")) { + if (!hasExpansion && xmlReader.hasAttribute("expanded")) { const char* attr = xmlReader.getAttribute("expanded"); if (strcmp(attr,"1") == 0) { expanded = true; @@ -1016,7 +1226,7 @@ void Document::RestoreDocFile(Base::Reader &reader) pObj->Restore(xmlReader); if (pObj && expanded) { Gui::ViewProviderDocumentObject* vp = static_cast(pObj); - this->signalExpandObject(*vp, Gui::ExpandItem); + this->signalExpandObject(*vp, Gui::ExpandItem,0,0); } xmlReader.readEndElement("ViewProvider"); } @@ -1025,15 +1235,15 @@ void Document::RestoreDocFile(Base::Reader &reader) // read camera settings xmlReader.readElement("Camera"); const char* ppReturn = xmlReader.getAttribute("settings"); - std::string sMsg = "SetCamera "; - sMsg += ppReturn; - if (strcmp(ppReturn, "") != 0) { // non-empty attribute + cameraSettings.clear(); + if(ppReturn && ppReturn[0]) { + saveCameraSettings(ppReturn); try { const char** pReturnIgnore=0; std::list mdi = getMDIViews(); for (std::list::iterator it = mdi.begin(); it != mdi.end(); ++it) { if ((*it)->onHasMsg("SetCamera")) - (*it)->onMsg(sMsg.c_str(), pReturnIgnore); + (*it)->onMsg(cameraSettings.c_str(), pReturnIgnore); } } catch (const Base::Exception& e) { @@ -1060,6 +1270,16 @@ void Document::slotStartRestoreDocument(const App::Document& doc) d->connectActObjectBlocker.block(); } +void Document::slotFinishRestoreObject(const App::DocumentObject &obj) { + auto vpd = dynamic_cast(getViewProvider(&obj)); + if(vpd) { + vpd->setStatus(Gui::isRestoring,false); + vpd->finishRestoring(); + if(!vpd->canAddToSceneGraph()) + toggleInSceneGraph(vpd); + } +} + void Document::slotFinishRestoreDocument(const App::Document& doc) { if (d->_pcDocument != &doc) @@ -1072,16 +1292,19 @@ void Document::slotFinishRestoreDocument(const App::Document& doc) signalActivatedObject(*(static_cast(viewProvider))); } } - // some post-processing of view providers - std::map::iterator it; - for (it = d->_ViewProviderMap.begin(); it != d->_ViewProviderMap.end(); ++it) { - it->second->finishRestoring(); - } // reset modified flag setModified(false); } +void Document::slotShowHidden(const App::Document& doc) +{ + if (d->_pcDocument != &doc) + return; + + Application::Instance->signalShowHidden(*this); +} + /** * Saves the properties of the view providers. */ @@ -1092,12 +1315,26 @@ void Document::SaveDocFile (Base::Writer &writer) const << " FreeCAD Document, see http://www.freecadweb.org for more information..." << std::endl << "-->" << std::endl; - writer.Stream() << "" << std::endl; + writer.Stream() << "getDocumentItem(this); + if(docItem) { + hasExpansion = true; + writer.Stream() << " HasExpansion=\"1\">" << std::endl; + docItem->Save(writer); + } + } + if(!hasExpansion) + writer.Stream() << ">" << std::endl; std::map::const_iterator it; // writing the view provider names itself - writer.incInd(); // indentation for 'ViewProviderData Count' writer.Stream() << writer.ind() << "_ViewProviderMap.size() <<"\">" << std::endl; @@ -1175,7 +1412,7 @@ void Document::exportObjects(const std::vector& obj, Base: const App::DocumentObject* doc = jt->first; ViewProvider* obj = jt->second; writer.Stream() << writer.ind() << "getNameInDocument() << "\" " + << doc->getExportName() << "\" " << "expanded=\"" << (doc->testStatus(App::Expand) ? 1:0) << "\""; if (obj->hasExtensions()) writer.Stream() << " Extensions=\"True\""; @@ -1229,11 +1466,13 @@ void Document::importObjects(const std::vector& obj, Base: } } Gui::ViewProvider* pObj = this->getViewProviderByName(name.c_str()); - if (pObj) + if (pObj) { + pObj->setStatus(Gui::isRestoring,true); + auto vpd = Base::freecad_dynamic_cast(pObj); + if(vpd) vpd->startRestoring(); pObj->Restore(xmlReader); - if (pObj && expanded) { - Gui::ViewProviderDocumentObject* vp = static_cast(pObj); - this->signalExpandObject(*vp, Gui::ExpandItem); + if (expanded && vpd) + this->signalExpandObject(*vpd, Gui::ExpandItem,0,0); } xmlReader.readEndElement("ViewProvider"); if (it == obj.end()) @@ -1249,6 +1488,20 @@ void Document::importObjects(const std::vector& obj, Base: xmlReader.readFiles(static_cast(reader.getStream())); } +void Document::slotFinishImportObjects(const std::vector &objs) { + (void)objs; + // finishRestoring() is now trigged by signalFinishRestoreObject + // + // for(auto obj : objs) { + // auto vp = getViewProvider(obj); + // if(!vp) continue; + // vp->setStatus(Gui::isRestoring,false); + // auto vpd = dynamic_cast(vp); + // if(vpd) vpd->finishRestoring(); + // } +} + + void Document::addRootObjectsToGroup(const std::vector& obj, App::DocumentObjectGroup* grp) { std::map rootMap; @@ -1276,18 +1529,23 @@ void Document::addRootObjectsToGroup(const std::vector& ob } } -void Document::createView(const Base::Type& typeId) +MDIView *Document::createView(const Base::Type& typeId) { if (!typeId.isDerivedFrom(MDIView::getClassTypeId())) - return; + return 0; std::list theViews = this->getMDIViewsOfType(typeId); if (typeId == View3DInventor::getClassTypeId()) { + QtGLWidget* shareWidget = 0; // VBO rendering doesn't work correctly when we don't share the OpenGL widgets if (!theViews.empty()) { View3DInventor* firstView = static_cast(theViews.front()); shareWidget = qobject_cast(firstView->getViewer()->getGLWidget()); + + const char *ppReturn = 0; + firstView->onMsg("GetCamera",&ppReturn); + saveCameraSettings(ppReturn); } View3DInventor* view3D = new View3DInventor(this, getMainWindow(), shareWidget); @@ -1325,8 +1583,15 @@ void Document::createView(const Base::Type& typeId) view3D->setWindowModified(this->isModified()); view3D->setWindowIcon(QApplication::windowIcon()); view3D->resize(400, 300); + + if(cameraSettings.size()) { + const char *ppReturn = 0; + view3D->onMsg(cameraSettings.c_str(),&ppReturn); + } getMainWindow()->addWindow(view3D); + return view3D; } + return 0; } Gui::MDIView* Document::cloneView(Gui::MDIView* oldview) @@ -1356,6 +1621,15 @@ Gui::MDIView* Document::cloneView(Gui::MDIView* oldview) return 0; } +const std::string &Document::getCameraSettings() const { + return cameraSettings; +} + +void Document::saveCameraSettings(const char *settings) { + if(settings && settings[0]) + cameraSettings = std::string("SetCamera ") + settings; +} + void Document::attachView(Gui::BaseView* pcView, bool bPassiv) { if (!bPassiv) @@ -1385,9 +1659,12 @@ void Document::detachView(Gui::BaseView* pcView, bool bPassiv) it = d->passiveViews.begin(); } - // is already closing the document - if (d->_isClosing == false) + // is already closing the document, and is not linked by other documents + if (d->_isClosing == false && + App::PropertyXLink::getDocumentInList(getDocument()).empty()) + { d->_pcAppWnd->onLastWindowClosed(this); + } } } } @@ -1437,7 +1714,7 @@ bool Document::isLastView(void) * This method checks if the document can be closed. It checks on * the save state of the document and is able to abort the closing. */ -bool Document::canClose () +bool Document::canClose (bool checkModify, bool checkLink) { if (d->_isClosing) return true; @@ -1460,45 +1737,16 @@ bool Document::canClose () // } //} + if (checkLink && App::PropertyXLink::getDocumentInList(getDocument()).size()) + return true; + bool ok = true; - if (isModified()) { - QMessageBox box(getActiveView()); - box.setIcon(QMessageBox::Question); - box.setWindowTitle(QObject::tr("Unsaved document")); - box.setText(QObject::tr("Do you want to save your changes to document '%1' before closing?") - .arg(QString::fromUtf8(getDocument()->Label.getValue()))); - box.setInformativeText(QObject::tr("If you don't save, your changes will be lost.")); - box.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel | QMessageBox::Save); - box.setDefaultButton(QMessageBox::Save); - box.setEscapeButton(QMessageBox::Cancel); - - // add shortcuts - QAbstractButton* saveBtn = box.button(QMessageBox::Save); - if (saveBtn->shortcut().isEmpty()) { - QString text = saveBtn->text(); - text.prepend(QLatin1Char('&')); - saveBtn->setShortcut(QKeySequence::mnemonic(text)); - } - - QAbstractButton* discardBtn = box.button(QMessageBox::Discard); - if (discardBtn->shortcut().isEmpty()) { - QString text = discardBtn->text(); - text.prepend(QLatin1Char('&')); - discardBtn->setShortcut(QKeySequence::mnemonic(text)); - } - - switch (box.exec()) - { - case QMessageBox::Save: + if (checkModify && isModified() && !getDocument()->testStatus(App::Document::PartialDoc)) { + int res = getMainWindow()->confirmSave(getDocument()->Label.getValue(),getActiveView()); + if(res>0) ok = save(); - break; - case QMessageBox::Discard: - ok = true; - break; - case QMessageBox::Cancel: - ok = false; - break; - } + else + ok = res<0; } if (ok) { @@ -1602,11 +1850,61 @@ MDIView* Document::getActiveView(void) const } } - // the active view is not part of this document, just use the last view - if (!ok && !mdis.empty()) - active = mdis.back(); + if (ok) + return active; - return active; + // the active view is not part of this document, just use the last view + const auto &windows = Gui::getMainWindow()->windows(); + for(auto rit=mdis.rbegin();rit!=mdis.rend();++rit) { + // Some view is removed from window list for some reason, e.g. TechDraw + // hidden page has view but not in the list. By right, the view will + // self delete, but not the case for TechDraw, especially during + // document restore. + if(windows.contains(*rit) || (*rit)->isDerivedFrom(View3DInventor::getClassTypeId())) + return *rit; + } + return 0; +} + +MDIView *Document::setActiveView(ViewProviderDocumentObject *vp, Base::Type typeId) { + MDIView *view = 0; + if(!vp) + view = getActiveView(); + else{ + view = vp->getMDIView(); + if(!view) { + auto obj = vp->getObject(); + if(!obj) + view = getActiveView(); + else { + auto linked = obj->getLinkedObject(true); + if(linked!=obj) { + auto vpLinked = dynamic_cast( + Application::Instance->getViewProvider(linked)); + if(vpLinked) + view = vpLinked->getMDIView(); + } + if(!view && typeId.isBad()) + typeId = View3DInventor::getClassTypeId(); + } + } + } + if(!view || (!typeId.isBad() && !view->isDerivedFrom(typeId))) { + view = 0; + for (auto *v : d->baseViews) { + if(v->isDerivedFrom(MDIView::getClassTypeId()) && + (typeId.isBad() || v->isDerivedFrom(typeId))) + { + view = static_cast(v); + break; + } + } + } + if(!view && !typeId.isBad()) + view = createView(typeId); + if(view) + getMainWindow()->setActiveWindow(view); + return view; } /** @@ -1713,20 +2011,98 @@ std::vector Document::getRedoVector(void) const return getDocument()->getAvailableRedoNames(); } +bool Document::checkTransactionID(bool undo, int iSteps) { + if(!iSteps) + return false; + + std::vector ids; + for (int i=0;igetTransactionID(undo,i); + if(!id) break; + ids.push_back(id); + } + std::set prompts; + std::map dmap; + for(auto doc : App::GetApplication().getDocuments()) { + if(doc == getDocument()) + continue; + for(auto id : ids) { + int steps = undo?doc->getAvailableUndos(id):doc->getAvailableRedos(id); + if(!steps) continue; + int ¤tSteps = dmap[doc]; + if(currentSteps+1 != steps) + prompts.insert(doc); + if(currentSteps < steps) + currentSteps = steps; + } + } + if(prompts.size()) { + std::ostringstream str; + int i=0; + for(auto doc : prompts) { + if(i++==5) { + str << "...\n"; + break; + } + str << " " << doc->getName() << "\n"; + } + int ret = QMessageBox::warning(getMainWindow(), + undo?QObject::tr("Undo"):QObject::tr("Redo"), + QString::fromLatin1("%1,\n%2%3") + .arg(QObject::tr( + "There are grouped transactions in the following documents with " + "other preceding transactions")) + .arg(QString::fromUtf8(str.str().c_str())) + .arg(QObject::tr("Choose 'Yes' to roll back all preceeding transactions.\n" + "Choose 'No' to roll back in the active document only.\n" + "Choose 'Abort' to abort")), + QMessageBox::Yes|QMessageBox::No|QMessageBox::Abort, QMessageBox::Yes); + if(ret == QMessageBox::Abort) + return false; + if(ret == QMessageBox::No) + return true; + } + for(auto &v : dmap) { + for(int i=0;iundo(); + else + v.first->redo(); + } + } + return true; +} + +bool Document::isPerformingTransaction() const { + return d->_isTransacting; +} + /// Will UNDO one or more steps void Document::undo(int iSteps) { + Base::FlagToggler<> flag(d->_isTransacting); + + if(!checkTransactionID(true,iSteps)) + return; + for (int i=0;iundo(); } + App::GetApplication().signalUndo(); } /// Will REDO one or more steps void Document::redo(int iSteps) { + Base::FlagToggler<> flag(d->_isTransacting); + + if(!checkTransactionID(false,iSteps)) + return; + for (int i=0;iredo(); } + App::GetApplication().signalRedo(); } PyObject* Document::getPyObject(void) @@ -1735,71 +2111,91 @@ PyObject* Document::getPyObject(void) return _pcDocPy; } -void Document::handleChildren3D(ViewProvider* viewProvider) +void Document::handleChildren3D(ViewProvider* viewProvider, bool deleting) { // check for children - bool rebuild = false; if (viewProvider && viewProvider->getChildRoot()) { std::vector children = viewProvider->claimChildren3D(); SoGroup* childGroup = viewProvider->getChildRoot(); // size not the same -> build up the list new - if (childGroup->getNumChildren() != static_cast(children.size())) { + if (deleting || childGroup->getNumChildren() != static_cast(children.size())) { + + std::set oldChildren; + for(int i=0,count=childGroup->getNumChildren();i_CoinMap.find(static_cast(childGroup->getChild(i))); + if(it == d->_CoinMap.end()) continue; + oldChildren.insert(it->second); + } - rebuild = true; Gui::coinRemoveAllChildren(childGroup); - for (std::vector::iterator it=children.begin();it!=children.end();++it) { - ViewProvider* ChildViewProvider = getViewProvider(*it); - if (ChildViewProvider) { - SoSeparator* childRootNode = ChildViewProvider->getRoot(); - childGroup->addChild(childRootNode); + if(!deleting) { + for (std::vector::iterator it=children.begin();it!=children.end();++it) { + ViewProvider* ChildViewProvider = getViewProvider(*it); + if (ChildViewProvider) { + auto itOld = oldChildren.find(static_cast(ChildViewProvider)); + if(itOld!=oldChildren.end()) oldChildren.erase(itOld); - // cycling to all views of the document to remove the viewprovider from the viewer itself - for (std::list::iterator vIt = d->baseViews.begin();vIt != d->baseViews.end();++vIt) { - View3DInventor *activeView = dynamic_cast(*vIt); - if (activeView && activeView->getViewer()->hasViewProvider(ChildViewProvider)) { + SoSeparator* childRootNode = ChildViewProvider->getRoot(); + childGroup->addChild(childRootNode); - // @Note hasViewProvider() - // remove the viewprovider serves the purpose of detaching the inventor nodes from the - // top level root in the viewer. However, if some of the children were grouped beneath the object - // earlier they are not anymore part of the toplevel inventor node. we need to check for that. - if (d->_editViewProvider == ChildViewProvider) - resetEdit(); - activeView->getViewer()->removeViewProvider(ChildViewProvider); + // cycling to all views of the document to remove the viewprovider from the viewer itself + for (std::list::iterator vIt = d->baseViews.begin();vIt != d->baseViews.end();++vIt) { + View3DInventor *activeView = dynamic_cast(*vIt); + if (activeView && activeView->getViewer()->hasViewProvider(ChildViewProvider)) { + // @Note hasViewProvider() + // remove the viewprovider serves the purpose of detaching the inventor nodes from the + // top level root in the viewer. However, if some of the children were grouped beneath the object + // earlier they are not anymore part of the toplevel inventor node. we need to check for that. + activeView->getViewer()->removeViewProvider(ChildViewProvider); + } } } } } - } - } - - //find all unclaimed viewproviders and add them back to the document (this happens if a - //viewprovider has been claimed before, but the object dropped it. - if (rebuild) { - rebuildRootNodes(); - } -} -void Document::rebuildRootNodes() -{ - auto vpmap = d->_ViewProviderMap; - for (auto& pair : d->_ViewProviderMap) { - auto claimed = pair.second->claimChildren3D(); - for (auto obj : claimed) { - auto it = vpmap.find(obj); - if (it != vpmap.end()) - vpmap.erase(it); - } - } + // add the remaining old children back to toplevel invertor node + for(auto vpd : oldChildren) { + auto obj = vpd->getObject(); + if(!obj || !obj->getNameInDocument()) + continue; - for (auto& pair : vpmap) { - // cycling to all views of the document to add the viewprovider to the viewer itself - for (std::list::iterator vIt = d->baseViews.begin();vIt != d->baseViews.end();++vIt) { - View3DInventor *activeView = dynamic_cast(*vIt); - if (activeView && !activeView->getViewer()->hasViewProvider(pair.second)) { - activeView->getViewer()->addViewProvider(pair.second); + for (BaseView* view : d->baseViews) { + View3DInventor *activeView = dynamic_cast(view); + if (activeView && !activeView->getViewer()->hasViewProvider(vpd)) + activeView->getViewer()->addViewProvider(vpd); + } } } + } +} + +void Document::toggleInSceneGraph(ViewProvider *vp) { + for (auto view : d->baseViews) { + View3DInventor *activeView = dynamic_cast(view); + if (!activeView) + continue; + auto root = vp->getRoot(); + if(!root) + continue; + auto scenegraph = dynamic_cast( + activeView->getViewer()->getSceneGraph()); + if(!scenegraph) + continue; + int idx = scenegraph->findChild(root); + if(idx<0) { + if(vp->canAddToSceneGraph()) + scenegraph->addChild(root); + }else if(!vp->canAddToSceneGraph()) + scenegraph->removeChild(idx); } } + +void Document::slotChangePropertyEditor(const App::Document &doc, const App::Property &Prop) { + if(getDocument() == &doc) { + FC_LOG(Prop.getFullName() << " editor changed"); + setModified(true); + } +} + diff --git a/src/Gui/Document.h b/src/Gui/Document.h index c51711d043..f2dbd8e834 100644 --- a/src/Gui/Document.h +++ b/src/Gui/Document.h @@ -84,6 +84,13 @@ protected: void slotFinishRestoreDocument(const App::Document&); void slotUndoDocument(const App::Document&); void slotRedoDocument(const App::Document&); + void slotShowHidden(const App::Document&); + void slotFinishImportObjects(const std::vector &); + void slotFinishRestoreObject(const App::DocumentObject &obj); + void slotRecomputed(const App::Document&); + void slotSkipRecompute(const App::Document &doc, const std::vector &objs); + void slotTouchedObject(const App::DocumentObject &); + void slotChangePropertyEditor(const App::Document&, const App::Property &); //@} void addViewProvider(Gui::ViewProviderDocumentObject*); @@ -108,12 +115,18 @@ public: /// signal on leaving edit mode mutable boost::signals2::signal signalResetEdit; /// signal on changed Object, the 2nd argument is the highlite mode to use - mutable boost::signals2::signal signalHighlightObject; + mutable boost::signals2::signal signalHighlightObject; /// signal on changed Object, the 2nd argument is the highlite mode to use mutable boost::signals2::signal signalExpandObject; + const Gui::TreeItemMode&, + App::DocumentObject *parent, + const char *subname)> signalExpandObject; + /// signal on changed ShowInTree property in view provider + mutable boost::signals2::signal signalShowItem; /// signal on scrolling to an object mutable boost::signals2::signal signalScrollToObject; /// signal on undo Document @@ -133,6 +146,8 @@ public: bool saveAs(void); /// Save a copy of the document under a new file name bool saveCopy(void); + /// Save all open document + static void saveAll(); /// This method is used to save properties or very small amounts of data to an XML document. virtual void Save (Base::Writer &writer) const; /// This method is used to restore properties from an XML document. @@ -164,7 +179,7 @@ public: Gui::MDIView* getViewOfViewProvider(Gui::ViewProvider*) const; Gui::MDIView* getViewOfNode(SoNode*) const; /// Create a new view - void createView(const Base::Type& typeId); + MDIView *createView(const Base::Type& typeId); /// Create a clone of the given view Gui::MDIView* cloneView(Gui::MDIView*); /** send messages to the active view @@ -184,7 +199,11 @@ public: /// Detach a view (get called by the MDIView destructor) void detachView(Gui::BaseView* pcView, bool bPassiv=false); /// helper for selection - ViewProvider* getViewProviderByPathFromTail(SoPath * path) const; + ViewProviderDocumentObject* getViewProviderByPathFromTail(SoPath * path) const; + /// helper for selection + ViewProviderDocumentObject* getViewProviderByPathFromHead(SoPath * path) const; + /// Get all view providers along the path and the corresponding node index in the path + std::vector > getViewProvidersByPath(SoPath * path) const; /// call update on all attached views void onUpdate(void); /// call relabel to all attached views @@ -195,10 +214,13 @@ public: std::list getMDIViewsOfType(const Base::Type& typeId) const; //@} + MDIView *setActiveView(ViewProviderDocumentObject *vp=0, Base::Type typeId = Base::Type()); + /** @name View provider handling */ //@{ /// Get the view provider for that object ViewProvider* getViewProvider(const App::DocumentObject *) const; + ViewProviderDocumentObject *getViewProvider(SoNode *node) const; /// set an annotation view provider void setAnnotationViewProvider(const char* name, ViewProvider *pcProvider); /// get an annotation view provider @@ -228,6 +250,12 @@ public: std::string *subname=0, int *mode=0, std::string *subElement=0) const; /// 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 + * + * It calls ViewProvider::canAddToSceneGraph() to decide whether to add the + * view provider or remove it + */ + void toggleInSceneGraph(ViewProvider *vp); //@} /** @name methods for the UNDO REDO handling */ @@ -248,10 +276,16 @@ public: void undo(int iSteps); /// Will REDO one or more steps void redo(int iSteps) ; + /** Check if the document is performing undo/redo transaction + * + * Unlike App::Document::isPerformingTransaction(), Gui::Document will + * report transacting when triggering grouped undo/redo in other documents + */ + bool isPerformingTransaction() const; //@} /// handles the application close event - bool canClose(); + bool canClose(bool checkModify=true, bool checkLink=false); bool isLastView(void); /// called by Application before being deleted @@ -259,18 +293,25 @@ public: virtual PyObject *getPyObject(void); + const std::string &getCameraSettings() const; + void saveCameraSettings(const char *); + protected: // pointer to the python class Gui::DocumentPy *_pcDocPy; private: //handles the scene graph nodes to correctly group child and parents - void handleChildren3D(ViewProvider* viewProvider); - void rebuildRootNodes(); + void handleChildren3D(ViewProvider* viewProvider, bool deleting=false); + + /// Check other documents for the same transaction ID + bool checkTransactionID(bool undo, int iSteps); struct DocumentP* d; static int _iDocCount; + std::string cameraSettings; + /** @name attributes for the UNDO REDO facility */ //@{ diff --git a/src/Gui/DocumentPy.xml b/src/Gui/DocumentPy.xml index 461e5df80b..db7c950ffe 100644 --- a/src/Gui/DocumentPy.xml +++ b/src/Gui/DocumentPy.xml @@ -31,7 +31,7 @@ - setEdit([String:Name|ViewProvider|DocumentObject]|,mod) + setEdit([String:Name|ViewProvider|DocumentObject]|,mod,subname=None) Set the given object in edit mode. @@ -101,6 +101,15 @@ scrollToTreeItem(ViewObject) - scroll the tree view to the item of a view object + + + +toggleInSceneGraph(ViewObject) + +Add or remove view object from scene graph of all views depending on its canAddToSceneGraph() + + + The active object of the document @@ -113,11 +122,35 @@ + + + The editing transformation matrix + + + + + + A tuple(obj,subname,subElement,editMode) of editing object reference, or None if no object is in edit + + + + + + Current edit mode. Only meaningful when there is a current object in edit + + + The related App document to this Gui document + + + + Indicate whether the document is undoing/redoing + + diff --git a/src/Gui/DocumentPyImp.cpp b/src/Gui/DocumentPyImp.cpp index f316b144ac..860e6c18c0 100644 --- a/src/Gui/DocumentPyImp.cpp +++ b/src/Gui/DocumentPyImp.cpp @@ -31,6 +31,7 @@ #include +#include "Application.h" #include "Document.h" #include "MergeDocuments.h" #include "ViewProviderExtern.h" @@ -41,6 +42,7 @@ #include #include "Tree.h" #include "ViewProviderDocumentObject.h" +#include "ViewProviderDocumentObjectPy.h" #include "ViewProviderPy.h" #include "ViewProviderDocumentObjectPy.h" @@ -103,38 +105,42 @@ PyObject* DocumentPy::setEdit(PyObject *args) { char *psFeatStr; int mod = 0; + char *subname = 0; + ViewProvider *vp = 0; + App::DocumentObject *obj = 0; // by name - if (PyArg_ParseTuple(args, "s|i;Name of the object to edit has to be given!", &psFeatStr,&mod)) { - App::DocumentObject * obj = getDocumentPtr()->getDocument()->getObject(psFeatStr); + if (PyArg_ParseTuple(args, "s|is;Name of the object to edit has to be given!", &psFeatStr,&mod,&subname)) { + obj = getDocumentPtr()->getDocument()->getObject(psFeatStr); if (!obj) { PyErr_Format(Base::BaseExceptionFreeCADError, "No such object found in document: '%s'", psFeatStr); return 0; } + }else{ + PyErr_Clear(); + PyObject *pyObj; + if(!PyArg_ParseTuple(args, "O|is", &pyObj,&mod,&subname)) + return 0; - bool ok = getDocumentPtr()->setEdit(getDocumentPtr()->getViewProvider(obj),mod); - return PyBool_FromLong(ok ? 1 : 0); + if(PyObject_TypeCheck(pyObj,&App::DocumentObjectPy::Type)) + obj = static_cast(pyObj)->getDocumentObjectPtr(); + else if(PyObject_TypeCheck(pyObj,&ViewProviderPy::Type)) + vp = static_cast(pyObj)->getViewProviderPtr(); + else { + PyErr_SetString(PyExc_TypeError,"Expect the first argument to be string|DocObject|ViewObject"); + return 0; + } } - // by document object - PyErr_Clear(); - PyObject *docObj; - if (PyArg_ParseTuple(args, "O!|i", &(App::DocumentObjectPy::Type), &docObj,&mod)) { - App::DocumentObject * obj = static_cast(docObj)->getDocumentObjectPtr(); - bool ok = getDocumentPtr()->setEdit(getDocumentPtr()->getViewProvider(obj),mod); - return PyBool_FromLong(ok ? 1 : 0); + if(!vp) { + if(!obj || !obj->getNameInDocument() || !(vp=Application::Instance->getViewProvider(obj))) { + PyErr_SetString(PyExc_ValueError,"Invalid document object"); + return 0; + } } - // by view provider - PyErr_Clear(); - if (PyArg_ParseTuple(args, "O!|i", &(Gui::ViewProviderPy::Type), &docObj,&mod)) { - Gui::ViewProvider * view = static_cast(docObj)->getViewProviderPtr(); - bool ok = getDocumentPtr()->setEdit(view,mod); - return PyBool_FromLong(ok ? 1 : 0); - } - - PyErr_SetString(PyExc_TypeError, "Either string, document object or view provider expected."); - return 0; + bool ok = getDocumentPtr()->setEdit(vp,mod,subname); + return PyBool_FromLong(ok ? 1 : 0); } PyObject* DocumentPy::getInEdit(PyObject *args) @@ -289,22 +295,32 @@ PyObject* DocumentPy::mergeProject(PyObject *args) PyObject* DocumentPy::toggleTreeItem(PyObject *args) { PyObject *object=0; + const char *subname=0; int mod = 0; - if (PyArg_ParseTuple(args,"O!|i",&(App::DocumentObjectPy::Type), &object,&mod)) { + if (PyArg_ParseTuple(args,"O!|is",&(App::DocumentObjectPy::Type), &object,&mod,&subname)) { App::DocumentObject* Object = static_cast(object)->getDocumentObjectPtr(); // Should be set! assert(Object); + App::DocumentObject *parent = 0; + if(subname) { + auto sobj = Object->getSubObject(subname); + if(!sobj) + throw Py::RuntimeError("Sub-object not found"); + parent = Object; + Object = sobj; + } + // get the gui document of the Assembly Item //ActiveAppDoc = Item->getDocument(); //ActiveGuiDoc = Gui::Application::Instance->getDocument(getDocumentPtr()); Gui::ViewProviderDocumentObject* ActiveVp = dynamic_cast (getDocumentPtr()->getViewProvider(Object)); assert(ActiveVp); switch(mod) { - case 0: getDocumentPtr()->signalExpandObject(*ActiveVp,Gui::ToggleItem); break; - case 1: getDocumentPtr()->signalExpandObject(*ActiveVp,Gui::CollapseItem); break; - case 2: getDocumentPtr()->signalExpandObject(*ActiveVp,Gui::ExpandItem); break; - case 3: getDocumentPtr()->signalExpandObject(*ActiveVp,Gui::ExpandPath); break; + case 0: getDocumentPtr()->signalExpandObject(*ActiveVp,Gui::ToggleItem,parent,subname); break; + case 1: getDocumentPtr()->signalExpandObject(*ActiveVp,Gui::CollapseItem,parent,subname); break; + case 2: getDocumentPtr()->signalExpandObject(*ActiveVp,Gui::ExpandItem,parent,subname); break; + case 3: getDocumentPtr()->signalExpandObject(*ActiveVp,Gui::ExpandPath,parent,subname); break; default: break; } } @@ -324,6 +340,16 @@ PyObject* DocumentPy::scrollToTreeItem(PyObject *args) Py_Return; } +PyObject* DocumentPy::toggleInSceneGraph(PyObject *args) { + PyObject *view; + if (!PyArg_ParseTuple(args,"O!",&(Gui::ViewProviderPy::Type), &view)) + return 0; + + Gui::ViewProvider* vp = static_cast(view)->getViewProviderPtr(); + getDocumentPtr()->toggleInSceneGraph(vp); + Py_Return; +} + Py::Object DocumentPy::getActiveObject(void) const { App::DocumentObject *object = getDocumentPtr()->getDocument()->getActiveObject(); @@ -367,6 +393,49 @@ Py::Object DocumentPy::getDocument(void) const } } +Py::Object DocumentPy::getEditingTransform(void) const { + return Py::Object(new Base::MatrixPy(new Base::Matrix4D( + getDocumentPtr()->getEditingTransform()))); +} + +void DocumentPy::setEditingTransform(Py::Object arg) { + if(!PyObject_TypeCheck(arg.ptr(),&Base::MatrixPy::Type)) + throw Py::TypeError("Expecting type of matrix"); + getDocumentPtr()->setEditingTransform( + *static_cast(arg.ptr())->getMatrixPtr()); +} + +Py::Object DocumentPy::getInEditInfo(void) const { + ViewProviderDocumentObject *vp = 0; + std::string subname,subelement; + int mode = 0; + getDocumentPtr()->getInEdit(&vp,&subname,&mode,&subelement); + if(!vp || !vp->getObject() || !vp->getObject()->getNameInDocument()) + return Py::None(); + return Py::TupleN(Py::Object(vp->getObject()->getPyObject(),true), + Py::String(subname),Py::String(subelement),Py::Int(mode)); +} + +void DocumentPy::setInEditInfo(Py::Object arg) { + PyObject *pyobj = 0; + const char *subname = 0; + if (!PyArg_ParseTuple(arg.ptr(), "O!s", + &Gui::ViewProviderDocumentObjectPy::Type, &pyobj,&subname)) + throw Py::Exception(); + getDocumentPtr()->setInEdit(static_cast( + pyobj)->getViewProviderDocumentObjectPtr(),subname); +} + +Py::Int DocumentPy::getEditMode(void) const { + int mode = -1; + getDocumentPtr()->getInEdit(0,0,&mode); + return Py::Int(mode); +} + +Py::Boolean DocumentPy::getTransacting() const { + return Py::Boolean(getDocumentPtr()->isPerformingTransaction()); +} + Py::Boolean DocumentPy::getModified(void) const { return Py::Boolean(getDocumentPtr()->isModified()); diff --git a/src/Gui/InventorAll.h b/src/Gui/InventorAll.h index 24bad50923..0fee123edc 100644 --- a/src/Gui/InventorAll.h +++ b/src/Gui/InventorAll.h @@ -66,6 +66,7 @@ #include #include #include +#include #include #include #include @@ -76,6 +77,7 @@ #include #include #include +#include #include #include #include @@ -114,6 +116,7 @@ #include #include #include +#include #include #include #include diff --git a/src/Gui/MDIView.cpp b/src/Gui/MDIView.cpp index 6d8fbcc5fd..b6e1486365 100644 --- a/src/Gui/MDIView.cpp +++ b/src/Gui/MDIView.cpp @@ -49,6 +49,7 @@ TYPESYSTEM_SOURCE_ABSTRACT(Gui::MDIView,Gui::BaseView); MDIView::MDIView(Gui::Document* pcDocument,QWidget* parent, Qt::WindowFlags wflags) : QMainWindow(parent, wflags), BaseView(pcDocument),currentMode(Child), wstate(Qt::WindowNoState) + , ActiveObjects(pcDocument) { setAttribute(Qt::WA_DeleteOnClose); @@ -167,7 +168,7 @@ bool MDIView::canClose(void) { if (!bIsPassive && getGuiDocument() && getGuiDocument()->isLastView()) { this->setFocus(); // raises the view to front - return (getGuiDocument()->canClose()); + return (getGuiDocument()->canClose(true,true)); } return true; @@ -284,7 +285,7 @@ void MDIView::setCurrentViewMode(ViewMode mode) { if (this->currentMode == Child) { if (qobject_cast(this->parentWidget())) - getMainWindow()->removeWindow(this); + getMainWindow()->removeWindow(this,false); setWindowFlags(windowFlags() | Qt::Window); setParent(0, Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint); @@ -314,7 +315,7 @@ void MDIView::setCurrentViewMode(ViewMode mode) { if (this->currentMode == Child) { if (qobject_cast(this->parentWidget())) - getMainWindow()->removeWindow(this); + getMainWindow()->removeWindow(this,false); setWindowFlags(windowFlags() | Qt::Window); setParent(0, Qt::Window); showFullScreen(); diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index e22247bf78..cbb9619c90 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -25,6 +25,7 @@ #ifndef _PreComp_ # include # include +# include # include # include # include @@ -114,6 +115,9 @@ #include "SpaceballEvent.h" #include "View3DInventor.h" #include "View3DInventorViewer.h" +#include "DlgObjectSelection.h" + +FC_LOG_LEVEL_INIT("MainWindow",false,true,true); #if defined(Q_OS_WIN32) #define slots @@ -130,12 +134,43 @@ MainWindow* MainWindow::instance = 0L; namespace Gui { +/** + * The CustomMessageEvent class is used to send messages as events in the methods + * Error(), Warning() and Message() of the StatusBarObserver class to the main window + * to display them on the status bar instead of printing them directly to the status bar. + * + * This makes the usage of StatusBarObserver thread-safe. + * @author Werner Mayer + */ +class CustomMessageEvent : public QEvent +{ +public: + enum Type {None, Err, Wrn, Pane, Msg, Log, Tmp}; + CustomMessageEvent(Type t, const QString& s, int timeout=0) + : QEvent(QEvent::User), _type(t), msg(s), _timeout(timeout) + { } + ~CustomMessageEvent() + { } + Type type() const + { return _type; } + const QString& message() const + { return msg; } + int timeout() const + { return _timeout; } +private: + Type _type; + QString msg; + int _timeout; +}; + +// ------------------------------------- // Pimpl class struct MainWindowP { QLabel* sizeLabel; QLabel* actionLabel; QTimer* actionTimer; + QTimer* statusTimer; QTimer* activityTimer; QTimer* visibleTimer; QMdiArea* mdiArea; @@ -146,6 +181,8 @@ struct MainWindowP bool whatsthis; QString whatstext; Assistant* assistant; + int currentStatusType = 100; + int actionUpdateDelay = 0; QMap > urlHandler; }; @@ -278,6 +315,9 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) d->mdiArea->setBackground(QBrush(QColor(160,160,160))); setCentralWidget(d->mdiArea); + statusBar()->setObjectName(QString::fromLatin1("statusBar")); + connect(statusBar(), SIGNAL(messageChanged(const QString &)), this, SLOT(statusMessageChanged())); + // labels and progressbar d->status = new StatusBarObserver(); d->actionLabel = new QLabel(statusBar()); @@ -294,12 +334,17 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) d->actionTimer->setObjectName(QString::fromLatin1("actionTimer")); connect(d->actionTimer, SIGNAL(timeout()), d->actionLabel, SLOT(clear())); + // clear status type + d->statusTimer = new QTimer( this ); + d->statusTimer->setObjectName(QString::fromLatin1("statusTimer")); + connect(d->statusTimer, SIGNAL(timeout()), this, SLOT(clearStatus())); + // update gui timer d->activityTimer = new QTimer(this); d->activityTimer->setObjectName(QString::fromLatin1("activityTimer")); - connect(d->activityTimer, SIGNAL(timeout()),this, SLOT(updateActions())); + connect(d->activityTimer, SIGNAL(timeout()),this, SLOT(_updateActions())); d->activityTimer->setSingleShot(false); - d->activityTimer->start(300); + d->activityTimer->start(150); // show main window timer d->visibleTimer = new QTimer(this); @@ -335,12 +380,15 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) } #endif - // Tree view if (hiddenDockWindows.find("Std_TreeView") == std::string::npos) { //work through parameter. ParameterGrp::handle group = App::GetApplication().GetUserParameter(). - GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("TreeView"); + GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("TreeView"); bool enabled = group->GetBool("Enabled", true); + if(enabled != group->GetBool("Enabled", false)) { + enabled = App::GetApplication().GetUserParameter().GetGroup("BaseApp") + ->GetGroup("MainWindow")->GetGroup("DockWindows")->GetBool("Std_TreeView",false); + } group->SetBool("Enabled", enabled); //ensure entry exists. if (enabled) { TreeDockWidget* tree = new TreeDockWidget(0, this); @@ -355,8 +403,12 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) if (hiddenDockWindows.find("Std_PropertyView") == std::string::npos) { //work through parameter. ParameterGrp::handle group = App::GetApplication().GetUserParameter(). - GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("PropertyView"); + GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("PropertyView"); bool enabled = group->GetBool("Enabled", true); + if(enabled != group->GetBool("Enabled", false)) { + enabled = App::GetApplication().GetUserParameter().GetGroup("BaseApp") + ->GetGroup("MainWindow")->GetGroup("DockWindows")->GetBool("Std_PropertyView",false); + } group->SetBool("Enabled", enabled); //ensure entry exists. if (enabled) { PropertyDockView* pcPropView = new PropertyDockView(0, this); @@ -422,6 +474,8 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) pDockMgr->registerDockWindow("Std_PythonView", pcPython); } + //TODO: Add external object support for DAGView +#if 0 //Dag View. if (hiddenDockWindows.find("Std_DAGView") == std::string::npos) { //work through parameter. @@ -445,6 +499,7 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) pDockMgr->registerDockWindow("Std_DAGView", dagDockWindow); } } +#endif #if 0 //defined(Q_OS_WIN32) this portion of code is not able to run with a vanilla Qtlib build on Windows. // The MainWindowTabBar is used to show tabbed dock windows with icons @@ -528,9 +583,98 @@ void MainWindow::closeActiveWindow () d->mdiArea->closeActiveSubWindow(); } -void MainWindow::closeAllWindows () +int MainWindow::confirmSave(const char *docName, QWidget *parent, bool addCheckbox) { + QMessageBox box(parent?parent:this); + box.setIcon(QMessageBox::Question); + box.setWindowTitle(QObject::tr("Unsaved document")); + if(docName) + box.setText(QObject::tr("Do you want to save your changes to document '%1' before closing?") + .arg(QString::fromUtf8(docName))); + else + box.setText(QObject::tr("Do you want to save your changes to document before closing?")); + + box.setInformativeText(QObject::tr("If you don't save, your changes will be lost.")); + box.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel | QMessageBox::Save); + box.setDefaultButton(QMessageBox::Save); + box.setEscapeButton(QMessageBox::Cancel); + + QCheckBox checkBox(QObject::tr("Apply answer to all")); + ParameterGrp::handle hGrp; + if(addCheckbox) { + hGrp = App::GetApplication().GetUserParameter(). + GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("General"); + checkBox.setChecked(hGrp->GetBool("ConfirmAll",false)); + checkBox.blockSignals(true); + box.addButton(&checkBox, QMessageBox::ResetRole); + } + + // add shortcuts + QAbstractButton* saveBtn = box.button(QMessageBox::Save); + if (saveBtn->shortcut().isEmpty()) { + QString text = saveBtn->text(); + text.prepend(QLatin1Char('&')); + saveBtn->setShortcut(QKeySequence::mnemonic(text)); + } + + QAbstractButton* discardBtn = box.button(QMessageBox::Discard); + if (discardBtn->shortcut().isEmpty()) { + QString text = discardBtn->text(); + text.prepend(QLatin1Char('&')); + discardBtn->setShortcut(QKeySequence::mnemonic(text)); + } + + int res = 0; + switch (box.exec()) + { + case QMessageBox::Save: + res = checkBox.isChecked()?2:1; + break; + case QMessageBox::Discard: + res = checkBox.isChecked()?-2:-1; + break; + } + if(addCheckbox && res) + hGrp->SetBool("ConfirmAll",checkBox.isChecked()); + return res; +} + +bool MainWindow::closeAllDocuments (bool close) { - d->mdiArea->closeAllSubWindows(); + auto docs = App::GetApplication().getDocuments(); + try { + docs = App::Document::getDependentDocuments(docs,true); + }catch(Base::Exception &e) { + e.ReportException(); + } + bool checkModify = true; + bool saveAll = false; + for(auto doc : docs) { + auto gdoc = Application::Instance->getDocument(doc); + if(!gdoc) + continue; + if(!gdoc->canClose(false)) + return false; + if(!gdoc->isModified() || doc->testStatus(App::Document::PartialDoc)) + continue; + bool save = saveAll; + if(!save && checkModify) { + int res = confirmSave(doc->Label.getStrValue().c_str(),this,docs.size()>1); + if(res==0) + return false; + if(res>0) { + save = true; + if(res==2) + saveAll = true; + } else if(res==-2) + checkModify = false; + } + if(save && !gdoc->save()) + return false; + } + if(close) + App::GetApplication().closeAllDocuments(); + // d->mdiArea->closeAllSubWindows(); + return true; } void MainWindow::activateNextWindow () @@ -547,6 +691,7 @@ void MainWindow::activateWorkbench(const QString& name) { // emit this signal workbenchActivated(name); + updateActions(true); } void MainWindow::whatsThis() @@ -640,6 +785,10 @@ bool MainWindow::event(QEvent *e) qApp->sendEvent(viewWidget, &anotherEvent); } return true; + }else if(e->type() == QEvent::StatusTip) { + // make sure warning and error message don't get blocked by tooltips + if(std::abs(d->currentStatusType) <= CustomMessageEvent::Wrn) + return true; } return QMainWindow::event(e); } @@ -728,24 +877,27 @@ void MainWindow::addWindow(MDIView* view) { // make workspace parent of view bool isempty = d->mdiArea->subWindowList().isEmpty(); - QMdiSubWindow* child = new QMdiSubWindow(d->mdiArea->viewport()); - child->setAttribute(Qt::WA_DeleteOnClose); - child->setWidget(view); - child->setWindowIcon(view->windowIcon()); - QMenu* menu = child->systemMenu(); + QMdiSubWindow* child = qobject_cast(view->parentWidget()); + if(!child) { + child = new QMdiSubWindow(d->mdiArea->viewport()); + child->setAttribute(Qt::WA_DeleteOnClose); + child->setWidget(view); + child->setWindowIcon(view->windowIcon()); + QMenu* menu = child->systemMenu(); - // See StdCmdCloseActiveWindow (#0002631) - QList acts = menu->actions(); - for (QList::iterator it = acts.begin(); it != acts.end(); ++it) { - if ((*it)->shortcut() == QKeySequence(QKeySequence::Close)) { - (*it)->setShortcuts(QList()); - break; + // See StdCmdCloseActiveWindow (#0002631) + QList acts = menu->actions(); + for (QList::iterator it = acts.begin(); it != acts.end(); ++it) { + if ((*it)->shortcut() == QKeySequence(QKeySequence::Close)) { + (*it)->setShortcuts(QList()); + break; + } } - } - QAction* action = menu->addAction(tr("Close All")); - connect(action, SIGNAL(triggered()), d->mdiArea, SLOT(closeAllSubWindows())); - d->mdiArea->addSubWindow(child); + QAction* action = menu->addAction(tr("Close All")); + connect(action, SIGNAL(triggered()), d->mdiArea, SLOT(closeAllSubWindows())); + d->mdiArea->addSubWindow(child); + } connect(view, SIGNAL(message(const QString&, int)), this, SLOT(showMessage(const QString&, int))); @@ -768,13 +920,13 @@ void MainWindow::addWindow(MDIView* view) * If you want to avoid that the Gui::MDIView instance gets destructed too you * must reparent it afterwards, e.g. set parent to NULL. */ -void MainWindow::removeWindow(Gui::MDIView* view) +void MainWindow::removeWindow(Gui::MDIView* view, bool close) { // free all connections disconnect(view, SIGNAL(message(const QString&, int)), - this, SLOT(showMessage(const QString&, int ))); + this, SLOT(showMessage(const QString&, int ))); disconnect(this, SIGNAL(windowStateChanged(MDIView*)), - view, SLOT(windowStateChanged(MDIView*))); + view, SLOT(windowStateChanged(MDIView*))); view->removeEventFilter(this); // check if the focus widget is a child of the view @@ -791,18 +943,33 @@ void MainWindow::removeWindow(Gui::MDIView* view) } QWidget* parent = view->parentWidget(); + // The call of 'd->mdiArea->removeSubWindow(parent)' causes the QMdiSubWindow // to lose its parent and thus the notification in QMdiSubWindow::closeEvent // of other mdi windows to get maximized if this window is maximized will fail. // However, we must let it here otherwise deleting MDI child views directly can // cause other problems. - d->mdiArea->removeSubWindow(parent); - parent->deleteLater(); + // + // The above mentioned problem can be fixed by setParent(0) which triggers a + // ChildRemoved event being handled properly inside QMidArea::viewportEvent() + // + auto subwindow = qobject_cast(parent); + if(subwindow && d->mdiArea->subWindowList().contains(subwindow)) { + subwindow->setParent(0); + + assert(!d->mdiArea->subWindowList().contains(subwindow)); + // d->mdiArea->removeSubWindow(parent); + } + + if(close) + parent->deleteLater(); + updateActions(); } void MainWindow::tabChanged(MDIView* view) { Q_UNUSED(view); + updateActions(); } void MainWindow::tabCloseRequested(int index) @@ -817,6 +984,7 @@ void MainWindow::tabCloseRequested(int index) QMdiSubWindow *subWindow = d->mdiArea->subWindowList().at(index); Q_ASSERT(subWindow); subWindow->close(); + updateActions(); } void MainWindow::onSetActiveSubWindow(QWidget *window) @@ -824,13 +992,19 @@ void MainWindow::onSetActiveSubWindow(QWidget *window) if (!window) return; d->mdiArea->setActiveSubWindow(qobject_cast(window)); + updateActions(); } void MainWindow::setActiveWindow(MDIView* view) { + if(!view || d->activeView == view) + return; + if(!windows().contains(view->parentWidget())) + addWindow(view); onSetActiveSubWindow(view->parentWidget()); d->activeView = view; Application::Instance->viewActivated(view); + updateActions(); } void MainWindow::onWindowActivated(QMdiSubWindow* w) @@ -854,6 +1028,7 @@ void MainWindow::onWindowActivated(QMdiSubWindow* w) // set active the appropriate window (it needs not to be part of mdiIds, e.g. directly after creation) d->activeView = view; Application::Instance->viewActivated(view); + updateActions(); } void MainWindow::onWindowsMenuAboutToShow() @@ -948,19 +1123,6 @@ QList MainWindow::windows(QMdiArea::WindowOrder order) const return mdis; } -// set text to the pane -void MainWindow::setPaneText(int i, QString text) -{ - if (i==1) { - d->actionLabel->setText(text); - d->actionTimer->setSingleShot(true); - d->actionTimer->start(5000); - } - else if (i==2) { - d->sizeLabel->setText(text); - } -} - MDIView* MainWindow::activeWindow(void) const { // each activated window notifies this main window when it is activated @@ -1117,11 +1279,27 @@ void MainWindow::appendRecentFile(const QString& filename) } } -void MainWindow::updateActions() +void MainWindow::updateActions(bool delay) { + //make it safe to call before the main window is actually created + if(!this) + return; + if(!d->activityTimer->isActive()) + d->activityTimer->start(150); + else if(delay) { + if(!d->actionUpdateDelay) + d->actionUpdateDelay=1; + }else + d->actionUpdateDelay=-1; +} + +void MainWindow::_updateActions() { - if (isVisible()) { + if (isVisible() && d->actionUpdateDelay<=0) { + FC_LOG("update actions"); + d->activityTimer->stop(); Application::Instance->commandManager().testActive(); } + d->actionUpdateDelay = 0; } void MainWindow::switchToTopLevelMode() @@ -1370,54 +1548,39 @@ void MainWindow::dragEnterEvent (QDragEnterEvent * e) } } +static QLatin1String _MimeDocObj("application/x-documentobject"); +static QLatin1String _MimeDocObjX("application/x-documentobject-x"); +static QLatin1String _MimeDocObjFile("application/x-documentobject-file"); +static QLatin1String _MimeDocObjXFile("application/x-documentobject-x-file"); + QMimeData * MainWindow::createMimeDataFromSelection () const { - std::vector selobj = Selection().getCompleteSelection(); - std::set unique_objs; - std::map< App::Document*, std::vector > objs; - for (std::vector::iterator it = selobj.begin(); it != selobj.end(); ++it) { - if (it->pObject && it->pObject->getDocument()) { - if (unique_objs.insert(it->pObject).second) - objs[it->pObject->getDocument()].push_back(it->pObject); - } + std::vector sel; + std::set objSet; + for(auto &s : Selection().getCompleteSelection()) { + if(s.pObject && s.pObject->getNameInDocument() && objSet.insert(s.pObject).second) + sel.push_back(s.pObject); } - - if (objs.empty()) + if(sel.empty()) return 0; - std::vector sel; // selected - std::vector all; // object sub-graph - for (std::map< App::Document*, std::vector >::iterator it = objs.begin(); it != objs.end(); ++it) { - std::vector dep = it->first->getDependencyList(it->second); - sel.insert(sel.end(), it->second.begin(), it->second.end()); - all.insert(all.end(), dep.begin(), dep.end()); + auto all = App::Document::getDependencyList(sel); + if (all.size() > sel.size()) { + DlgObjectSelection dlg(sel,getMainWindow()); + if(dlg.exec()!=QDialog::Accepted) + return 0; + sel = dlg.getSelections(); + if(sel.empty()) + return 0; } - if (all.size() > sel.size()) { - //check if selection are only geofeaturegroup objects, for them it is intuitive and wanted to copy the - //dependencies - bool hasGroup = false, hasNormal = false; - for(auto obj : sel) { - if(obj->hasExtension(App::GroupExtension::getExtensionClassTypeId())) - hasGroup = true; - else - hasNormal = true; - } - if(hasGroup && !hasNormal) { - sel = all; - } - else { - //if there are normal objects selected it may be possible that some dependencies are - //from them, and not only from groups. so ask the user what to do. - int ret = QMessageBox::question(getMainWindow(), - tr("Object dependencies"), - tr("The selected objects have a dependency to unselected objects.\n" - "Do you want to copy them, too?"), - QMessageBox::Yes,QMessageBox::No); - if (ret == QMessageBox::Yes) { - sel = all; - } - } + std::vector unsaved; + bool hasXLink = App::PropertyXLink::hasXLink(sel,&unsaved); + if(unsaved.size()) { + QMessageBox::critical(getMainWindow(), tr("Unsaved document"), + tr("The exported object contains external link. Please save the document" + "at least once before exporting.")); + return 0; } unsigned int memsize=1000; // ~ for the meta-information @@ -1437,7 +1600,7 @@ QMimeData * MainWindow::createMimeDataFromSelection () const WaitCursor wc; QString mime; if (use_buffer) { - mime = QLatin1String("application/x-documentobject"); + mime = hasXLink?_MimeDocObjX:_MimeDocObj; Base::ByteArrayOStreambuf buf(res); std::ostream str(&buf); // need this instance to call MergeDocuments::Save() @@ -1446,7 +1609,7 @@ QMimeData * MainWindow::createMimeDataFromSelection () const doc->exportObjects(sel, str); } else { - mime = QLatin1String("application/x-documentobject-file"); + mime = hasXLink?_MimeDocObjXFile:_MimeDocObjFile; static Base::FileInfo fi(App::Application::getTempFileName()); Base::ofstream str(fi, std::ios::out | std::ios::binary); // need this instance to call MergeDocuments::Save() @@ -1471,18 +1634,46 @@ bool MainWindow::canInsertFromMimeData (const QMimeData * source) const if (!source) return false; return source->hasUrls() || - source->hasFormat(QLatin1String("application/x-documentobject")) || - source->hasFormat(QLatin1String("application/x-documentobject-file")); + source->hasFormat(_MimeDocObj) || source->hasFormat(_MimeDocObjX) || + source->hasFormat(_MimeDocObjFile) || source->hasFormat(_MimeDocObjXFile); } void MainWindow::insertFromMimeData (const QMimeData * mimeData) { if (!mimeData) return; - if (mimeData->hasFormat(QLatin1String("application/x-documentobject"))) { - QByteArray res = mimeData->data(QLatin1String("application/x-documentobject")); - App::Document* doc = App::GetApplication().getActiveDocument(); - if (!doc) doc = App::GetApplication().newDocument(); + bool fromDoc = false; + bool hasXLink = false; + QString format; + if(mimeData->hasFormat(_MimeDocObj)) + format = _MimeDocObj; + else if(mimeData->hasFormat(_MimeDocObjX)) { + format = _MimeDocObjX; + hasXLink = true; + }else if(mimeData->hasFormat(_MimeDocObjFile)) + fromDoc = true; + else if(mimeData->hasFormat(_MimeDocObjXFile)) { + fromDoc = true; + hasXLink = true; + }else { + if (mimeData->hasUrls()) + loadUrls(App::GetApplication().getActiveDocument(), mimeData->urls()); + return; + } + + App::Document* doc = App::GetApplication().getActiveDocument(); + if(!doc) doc = App::GetApplication().newDocument(); + + if(hasXLink && !doc->isSaved()) { + int ret = QMessageBox::question(getMainWindow(), tr("Unsaved document"), + tr("To link to external objects, the document must be saved at least once.\n" + "Do you want to save the document now?"), + QMessageBox::Yes,QMessageBox::No); + if(ret != QMessageBox::Yes || !Application::Instance->getDocument(doc)->saveAs()) + return; + } + if(!fromDoc) { + QByteArray res = mimeData->data(format); doc->openTransaction("Paste"); Base::ByteArrayIStreambuf buf(res); @@ -1498,10 +1689,8 @@ void MainWindow::insertFromMimeData (const QMimeData * mimeData) } doc->commitTransaction(); } - else if (mimeData->hasFormat(QLatin1String("application/x-documentobject-file"))) { - QByteArray res = mimeData->data(QLatin1String("application/x-documentobject-file")); - App::Document* doc = App::GetApplication().getActiveDocument(); - if (!doc) doc = App::GetApplication().newDocument(); + else { + QByteArray res = mimeData->data(format); doc->openTransaction("Paste"); Base::FileInfo fi((const char*)res); @@ -1517,10 +1706,6 @@ void MainWindow::insertFromMimeData (const QMimeData * mimeData) } doc->commitTransaction(); } - else if (mimeData->hasUrls()) { - // load the files into the active document if there is one, otherwise let create one - loadUrls(App::GetApplication().getActiveDocument(), mimeData->urls()); - } } void MainWindow::setUrlHandler(const QString &scheme, Gui::UrlHandler* handler) @@ -1630,51 +1815,82 @@ void MainWindow::changeEvent(QEvent *e) } } -void MainWindow::showMessage (const QString& message, int timeout) -{ - QFontMetrics fm(statusBar()->font()); - QString msg = fm.elidedText(message, Qt::ElideMiddle, this->d->actionLabel->width()); -#if QT_VERSION <= 0x040600 - this->statusBar()->showMessage(msg, timeout); -#else - //#0000665: There is a crash under Ubuntu 12.04 (Qt 4.8.1) - QMetaObject::invokeMethod(statusBar(), "showMessage", - Qt::QueuedConnection, - QGenericReturnArgument(), - Q_ARG(QString,msg), - Q_ARG(int, timeout)); -#endif +void MainWindow::clearStatus() { + d->currentStatusType = 100; + statusBar()->setStyleSheet(QString::fromLatin1("#statusBar{}")); } -// ------------------------------------------------------------- +void MainWindow::statusMessageChanged() { + if(d->currentStatusType<0) + d->currentStatusType = -d->currentStatusType; + else { + // here probably means the status bar message is changed by QMainWindow + // internals, e.g. for displaying tooltip and stuff. Set reset what + // we've changed. + d->statusTimer->stop(); + clearStatus(); + } +} -namespace Gui { +void MainWindow::showMessage(const QString& message, int timeout) { + if(QApplication::instance()->thread() != QThread::currentThread()) { + QApplication::postEvent(this, new CustomMessageEvent(CustomMessageEvent::Tmp,message,timeout)); + return; + } + d->actionLabel->setText(message.simplified()); + if(timeout) { + d->actionTimer->setSingleShot(true); + d->actionTimer->start(timeout); + }else + d->actionTimer->stop(); +} -/** - * The CustomMessageEvent class is used to send messages as events in the methods - * Error(), Warning() and Message() of the StatusBarObserver class to the main window - * to display them on the status bar instead of printing them directly to the status bar. - * - * This makes the usage of StatusBarObserver thread-safe. - * @author Werner Mayer - */ -class CustomMessageEvent : public QEvent +void MainWindow::showStatus(int type, const QString& message) { -public: - enum Type {Msg, Wrn, Err, Log}; - CustomMessageEvent(Type t, const QString& s) - : QEvent(QEvent::User), _type(t), msg(s) - { } - ~CustomMessageEvent() - { } - Type type() const - { return _type; } - const QString& message() const - { return msg; } -private: - Type _type; - QString msg; -}; + if(QApplication::instance()->thread() != QThread::currentThread()) { + QApplication::postEvent(this, + new CustomMessageEvent((CustomMessageEvent::Type)type,message)); + return; + } + + if(d->currentStatusType < type) + return; + + d->statusTimer->setSingleShot(true); + // TODO: hardcode? + int timeout = 5000; + d->statusTimer->start(timeout); + + QFontMetrics fm(statusBar()->font()); + QString msg = fm.elidedText(message, Qt::ElideMiddle, this->d->actionLabel->width()); + switch(type) { + case CustomMessageEvent::Err: + statusBar()->setStyleSheet(d->status->err); + break; + case CustomMessageEvent::Wrn: + statusBar()->setStyleSheet(d->status->wrn); + break; + case CustomMessageEvent::Pane: + statusBar()->setStyleSheet(QString::fromLatin1("#statusBar{}")); + break; + default: + statusBar()->setStyleSheet(d->status->msg); + break; + } + d->currentStatusType = -type; + statusBar()->showMessage(msg.simplified(), timeout); +} + + +// set text to the pane +void MainWindow::setPaneText(int i, QString text) +{ + if (i==1) { + showStatus(CustomMessageEvent::Pane, text); + } + else if (i==2) { + d->sizeLabel->setText(text); + } } void MainWindow::customEvent(QEvent* e) @@ -1682,7 +1898,8 @@ void MainWindow::customEvent(QEvent* e) if (e->type() == QEvent::User) { Gui::CustomMessageEvent* ce = static_cast(e); QString msg = ce->message(); - if (ce->type() == CustomMessageEvent::Log) { + switch(ce->type()) { + case CustomMessageEvent::Log: { if (msg.startsWith(QLatin1String("#Inventor V2.1 ascii "))) { Gui::Document *d = Application::Instance->activeDocument(); if (d) { @@ -1696,11 +1913,12 @@ void MainWindow::customEvent(QEvent* e) } } } - } - else { - d->actionLabel->setText(msg); - d->actionTimer->setSingleShot(true); - d->actionTimer->start(5000); + break; + } case CustomMessageEvent::Tmp: { + showMessage(msg, ce->timeout()); + break; + } default: + showStatus(ce->type(),msg); } } else if (e->type() == ActionStyleEvent::EventType) { @@ -1723,9 +1941,9 @@ void MainWindow::customEvent(QEvent* e) StatusBarObserver::StatusBarObserver() : WindowParameter("OutputWindow") { - msg = QString::fromLatin1("#000000"); // black - wrn = QString::fromLatin1("#ffaa00"); // orange - err = QString::fromLatin1("#ff0000"); // red + msg = QString::fromLatin1("#statusBar{color: #000000}"); // black + wrn = QString::fromLatin1("#statusBar{color: #ffaa00}"); // orange + err = QString::fromLatin1("#statusBar{color: #ff0000}"); // red Base::Console().AttachObserver(this); getWindowParameter()->Attach(this); getWindowParameter()->NotifyAll(); @@ -1740,17 +1958,18 @@ StatusBarObserver::~StatusBarObserver() void StatusBarObserver::OnChange(Base::Subject &rCaller, const char * sReason) { ParameterGrp& rclGrp = ((ParameterGrp&)rCaller); + auto format = QString::fromLatin1("#statusBar{color: %1}"); if (strcmp(sReason, "colorText") == 0) { unsigned long col = rclGrp.GetUnsigned( sReason ); - this->msg = QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff).name(); + this->msg = format.arg(QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff).name()); } else if (strcmp(sReason, "colorWarning") == 0) { unsigned long col = rclGrp.GetUnsigned( sReason ); - this->wrn = QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff).name(); + this->wrn = format.arg(QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff).name()); } else if (strcmp(sReason, "colorError") == 0) { unsigned long col = rclGrp.GetUnsigned( sReason ); - this->err = QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff).name(); + this->err = format.arg(QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff).name()); } } @@ -1760,8 +1979,7 @@ void StatusBarObserver::OnChange(Base::Subject &rCaller, const char void StatusBarObserver::Message(const char * m) { // Send the event to the main window to allow thread-safety. Qt will delete it when done. - QString txt = QString::fromLatin1("%2").arg(this->msg, QString::fromUtf8(m)); - CustomMessageEvent* ev = new CustomMessageEvent(CustomMessageEvent::Msg, txt); + CustomMessageEvent* ev = new CustomMessageEvent(CustomMessageEvent::Msg, QString::fromUtf8(m)); QApplication::postEvent(getMainWindow(), ev); } @@ -1771,8 +1989,7 @@ void StatusBarObserver::Message(const char * m) void StatusBarObserver::Warning(const char *m) { // Send the event to the main window to allow thread-safety. Qt will delete it when done. - QString txt = QString::fromLatin1("%2").arg(this->wrn, QString::fromUtf8(m)); - CustomMessageEvent* ev = new CustomMessageEvent(CustomMessageEvent::Wrn, txt); + CustomMessageEvent* ev = new CustomMessageEvent(CustomMessageEvent::Wrn, QString::fromUtf8(m)); QApplication::postEvent(getMainWindow(), ev); } @@ -1782,8 +1999,7 @@ void StatusBarObserver::Warning(const char *m) void StatusBarObserver::Error (const char *m) { // Send the event to the main window to allow thread-safety. Qt will delete it when done. - QString txt = QString::fromLatin1("%2").arg(this->err, QString::fromUtf8(m)); - CustomMessageEvent* ev = new CustomMessageEvent(CustomMessageEvent::Err, txt); + CustomMessageEvent* ev = new CustomMessageEvent(CustomMessageEvent::Err, QString::fromUtf8(m)); QApplication::postEvent(getMainWindow(), ev); } diff --git a/src/Gui/MainWindow.h b/src/Gui/MainWindow.h index cc8e00b35c..d7f8d6338f 100644 --- a/src/Gui/MainWindow.h +++ b/src/Gui/MainWindow.h @@ -97,7 +97,7 @@ public: * Removes an MDI window from the main window's workspace and its associated tab without * deleting the widget. If the main windows does not have such a window nothing happens. */ - void removeWindow(MDIView* view); + void removeWindow(MDIView* view, bool close=true); /** * Returns a list of all MDI windows in the worpspace. */ @@ -178,6 +178,8 @@ public: void unsetUrlHandler(const QString &scheme); //@} + void updateActions(bool delay = false); + public Q_SLOTS: /** * Sets text to the pane in the status bar. @@ -200,11 +202,12 @@ public Q_SLOTS: */ void closeActiveWindow (); /** - * Closes all child windows. - * The windows are closed in random order. The operation stops - * if a window does not accept the close event. + * Closes all document window. */ - void closeAllWindows (); + bool closeAllDocuments (bool close=true); + /** Pop up a message box asking for saving document + */ + int confirmSave(const char *docName, QWidget *parent=0, bool addCheckBox=false); /** * Activates the next window in the child window chain. */ @@ -223,6 +226,9 @@ public Q_SLOTS: void whatsThis(); void switchToTopLevelMode(); void switchToDockedMode(); + + void statusMessageChanged(); + void showMessage (const QString & message, int timeout = 0); protected: @@ -250,6 +256,8 @@ protected: */ void changeEvent(QEvent *e); + void showStatus(int type, const QString & message); + private Q_SLOTS: /** * \internal @@ -278,7 +286,7 @@ private Q_SLOTS: /** * This method gets frequently activated and test the commands if they are still active. */ - void updateActions(); + void _updateActions(); /** * \internal */ @@ -291,6 +299,10 @@ private Q_SLOTS: * \internal */ void processMessages(const QList &); + /** + * \internal + */ + void clearStatus(); Q_SIGNALS: void timeEvent(); @@ -339,6 +351,7 @@ public: /// name of the observer const char *Name(void){return "StatusBar";} + friend class MainWindow; private: QString msg, wrn, err; }; diff --git a/src/Gui/PreCompiled.h b/src/Gui/PreCompiled.h index 536e538d67..686f4c2526 100644 --- a/src/Gui/PreCompiled.h +++ b/src/Gui/PreCompiled.h @@ -73,6 +73,8 @@ #include #include #include +#include +#include // Boost #include diff --git a/src/Gui/ProgressBar.cpp b/src/Gui/ProgressBar.cpp index f1246013c9..856830f0e7 100644 --- a/src/Gui/ProgressBar.cpp +++ b/src/Gui/ProgressBar.cpp @@ -48,6 +48,7 @@ struct SequencerPrivate WaitCursor* waitCursor; QTime measureTime; QTime progressTime; + QTime checkAbortTime; QString text; bool guiThread; }; @@ -133,6 +134,7 @@ void Sequencer::startStep() d->guiThread = false; d->bar->setRange(0, (int)nTotalSteps); d->progressTime.start(); + d->checkAbortTime.start(); d->measureTime.start(); QMetaObject::invokeMethod(d->bar, "aboutToShow", Qt::QueuedConnection); } @@ -140,6 +142,7 @@ void Sequencer::startStep() d->guiThread = true; d->bar->setRange(0, (int)nTotalSteps); d->progressTime.start(); + d->checkAbortTime.start(); d->measureTime.start(); d->waitCursor = new Gui::WaitCursor; d->bar->enterControlEvents(); @@ -147,6 +150,30 @@ void Sequencer::startStep() } } +void Sequencer::checkAbort() { + if(d->bar->thread() != QThread::currentThread()) + return; + if (!wasCanceled()) { + if(d->checkAbortTime.elapsed() < 500) + return; + d->checkAbortTime.restart(); + qApp->processEvents(); + return; + } + // restore cursor + pause(); + bool ok = d->bar->canAbort(); + // continue and show up wait cursor if needed + resume(); + + // force to abort the operation + if ( ok ) { + abort(); + } else { + rejectCancel(); + } +} + void Sequencer::nextStep(bool canAbort) { QThread *currentThread = QThread::currentThread(); @@ -246,7 +273,7 @@ void Sequencer::showRemainingTime() QString status = QString::fromLatin1("%1\t[%2]").arg(txt, remain); if (thr != currentThread) { - QMetaObject::invokeMethod(getMainWindow()->statusBar(), "showMessage", + QMetaObject::invokeMethod(getMainWindow(), "showMessage", Qt::/*Blocking*/QueuedConnection, QGenericReturnArgument(), Q_ARG(QString,status)); @@ -265,7 +292,7 @@ void Sequencer::resetData() if (thr != currentThread) { QMetaObject::invokeMethod(d->bar, "reset", Qt::QueuedConnection); QMetaObject::invokeMethod(d->bar, "aboutToHide", Qt::QueuedConnection); - QMetaObject::invokeMethod(getMainWindow()->statusBar(), "showMessage", + QMetaObject::invokeMethod(getMainWindow(), "showMessage", Qt::/*Blocking*/QueuedConnection, QGenericReturnArgument(), Q_ARG(QString,QString())); @@ -295,7 +322,7 @@ void Sequencer::abort() { //resets resetData(); - Base::AbortException exc("Aborting..."); + Base::AbortException exc("User aborted"); throw exc; } @@ -307,7 +334,7 @@ void Sequencer::setText (const char* pszTxt) // print message to the statusbar d->text = pszTxt ? QString::fromUtf8(pszTxt) : QLatin1String(""); if (thr != currentThread) { - QMetaObject::invokeMethod(getMainWindow()->statusBar(), "showMessage", + QMetaObject::invokeMethod(getMainWindow(), "showMessage", Qt::/*Blocking*/QueuedConnection, QGenericReturnArgument(), Q_ARG(QString,d->text)); diff --git a/src/Gui/ProgressBar.h b/src/Gui/ProgressBar.h index 58eca20a0b..1910a9a04d 100644 --- a/src/Gui/ProgressBar.h +++ b/src/Gui/ProgressBar.h @@ -107,6 +107,8 @@ public: /** Returns an instance of the progress bar. It creates one if needed. */ QProgressBar* getProgressBar(QWidget* parent=0); + virtual void checkAbort() override; + protected: /** Construction */ Sequencer (); diff --git a/src/Gui/ProgressDialog.cpp b/src/Gui/ProgressDialog.cpp index 72f204a4f7..323445ef15 100644 --- a/src/Gui/ProgressDialog.cpp +++ b/src/Gui/ProgressDialog.cpp @@ -36,6 +36,7 @@ struct SequencerDialogPrivate ProgressDialog* dlg; QTime measureTime; QTime progressTime; + QTime checkAbortTime; QString text; bool guiThread; }; @@ -91,6 +92,7 @@ void SequencerDialog::startStep() d->dlg->setModal(false); if (nTotalSteps == 0) { d->progressTime.start(); + d->checkAbortTime.start(); } d->measureTime.start(); @@ -103,6 +105,7 @@ void SequencerDialog::startStep() d->dlg->setModal(true); if (nTotalSteps == 0) { d->progressTime.start(); + d->checkAbortTime.start(); } d->measureTime.start(); @@ -111,6 +114,30 @@ void SequencerDialog::startStep() } } +void SequencerDialog::checkAbort() { + if(d->dlg->thread() != QThread::currentThread()) + return; + if (!wasCanceled()) { + if(d->checkAbortTime.elapsed() < 500) + return; + d->checkAbortTime.restart(); + qApp->processEvents(); + return; + } + // restore cursor + pause(); + bool ok = d->dlg->canAbort(); + // continue and show up wait cursor if needed + resume(); + + // force to abort the operation + if ( ok ) { + abort(); + } else { + rejectCancel(); + } +} + void SequencerDialog::nextStep(bool canAbort) { QThread *currentThread = QThread::currentThread(); @@ -239,7 +266,7 @@ void SequencerDialog::abort() { //resets resetData(); - Base::AbortException exc("Aborting..."); + Base::AbortException exc("User aborted"); throw exc; } diff --git a/src/Gui/ProgressDialog.h b/src/Gui/ProgressDialog.h index 4ee25d55c0..6680ec97b3 100644 --- a/src/Gui/ProgressDialog.h +++ b/src/Gui/ProgressDialog.h @@ -43,6 +43,8 @@ public: void resume(); bool isBlocking() const; + virtual void checkAbort() override; + protected: /** Construction */ SequencerDialog (); diff --git a/src/Gui/ReportView.cpp b/src/Gui/ReportView.cpp index b298403597..b4c97b6aa9 100644 --- a/src/Gui/ReportView.cpp +++ b/src/Gui/ReportView.cpp @@ -353,6 +353,12 @@ ReportOutput::ReportOutput(QWidget* parent) _prefs->Attach(this); _prefs->Notify("FontSize"); +#ifdef FC_DEBUG + messageSize = _prefs->GetInt("LogMessageSize",0); +#else + messageSize = _prefs->GetInt("LogMessageSize",2048); +#endif + // scroll to bottom at startup to make sure that last appended text is visible ensureCursorVisible(); } @@ -399,11 +405,13 @@ void ReportOutput::Error (const char * s) void ReportOutput::Log (const char * s) { QString msg = QString::fromUtf8(s); - if (msg.length() < 1000){ - // Send the event to itself to allow thread-safety. Qt will delete it when done. - CustomReportEvent* ev = new CustomReportEvent(ReportHighlighter::LogText, msg); - QApplication::postEvent(this, ev); + if(messageSize>0 && msg.size()>messageSize) { + msg.truncate(messageSize); + msg += QString::fromLatin1("...\n"); } + // Send the event to itself to allow thread-safety. Qt will delete it when done. + CustomReportEvent* ev = new CustomReportEvent(ReportHighlighter::LogText, msg); + QApplication::postEvent(this, ev); } void ReportOutput::customEvent ( QEvent* ev ) @@ -626,6 +634,12 @@ void ReportOutput::OnChange(Base::Subject &rCaller, const char * sR bool checked = rclGrp.GetBool(sReason, true); if (checked != d->redirected_stderr) onToggleRedirectPythonStderr(); + }else if(strcmp(sReason, "LogMessageSize") == 0) { +#ifdef FC_DEBUG + messageSize = rclGrp.GetInt(sReason,0); +#else + messageSize = rclGrp.GetInt(sReason,2048); +#endif } } diff --git a/src/Gui/ReportView.h b/src/Gui/ReportView.h index e943ea4fdb..7d5083e5cb 100644 --- a/src/Gui/ReportView.h +++ b/src/Gui/ReportView.h @@ -188,6 +188,7 @@ private: Data* d; bool gotoEnd; ReportHighlighter* reportHl; /**< Syntax highlighter */ + int messageSize; ParameterGrp::handle _prefs; }; diff --git a/src/Gui/View3DInventor.cpp b/src/Gui/View3DInventor.cpp index 5872137ec4..d927b6c784 100644 --- a/src/Gui/View3DInventor.cpp +++ b/src/Gui/View3DInventor.cpp @@ -195,6 +195,11 @@ View3DInventor::View3DInventor(Gui::Document* pcDocument, QWidget* parent, View3DInventor::~View3DInventor() { + if(_pcDocument) { + SoCamera * Cam = _viewer->getSoRenderManager()->getCamera(); + if (Cam) + _pcDocument->saveCameraSettings(SoFCDB::writeNodesToString(Cam).c_str()); + } hGrp->Detach(this); //If we destroy this viewer by calling 'delete' directly the focus proxy widget which is defined