From bd2f5191c9d3d3adfaf63bdd55b77cc5359d9264 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 11 Jul 2019 10:00:57 +0800 Subject: [PATCH] Gui: Application/Document/MainWindow changes following App namespace Application: * signalNewDocument, check the extra argument, isMainDoc, the decide whether to create view of the new document. This is so that external linked document can be opened in background without crowding the tab list. * slotDeleteDocument, calls Document::beforeDelete() * slotActiveDocument, creates view if none, because external document is now opened without view. * onLastWindowClosed(), switch to next active document, and creates view if none. * send(Has)MsgToFocusView(), new API to send message to the active view in focus. This is to solve the ambiguity of things like pressing delete key, copy, paste handling when the active new is not in focus. For example, when spread sheet view is active, delete/copy/paste handling should be different when the focus on the spread sheet view or focus on tree view. * tryClose(), delegate to MainWindow for close confirmation * reopen(), new API to reload a partial document in full Document/DocumentP: * _CoinMap, new internal map for quick access view provider from its root node. * slotNewObject, modified to support view provider override from App::FeaturePython, through new API DocumentObject::getViewProviderNameOverride(). * slotDeletedObject/slotTransactionRemove, improve handling of geo group children rebuild * slotSkipRecompute, add special handling of document with skip recompute. Some command cannot work when skip recompute is active. For example, sketcher command will check if recompute is successful on many commands, and will undo if not. New 'PartialCompute' flag is added to allow recompute only the editing object and all its dependencies if 'SkipRecompute' is active. * slotTouchedObject, new signal handling of manually touched object. * setModified(), do nothing if already modified. This is a critical performance improvement, because marking tab window as modified turns out to be a relatively expensive operation, and will cause massive slow down if calling it on every property change. * getViewProviderByPathFromHead/getViewProvidersByPath(), new APIs to obtain view provider(s) from coin SoPath. * save/saveAll/saveCopy, modified to support external document saving. * Save/RestoreDocFile(), save and restore tree item recursive expansion status. * slotFinishRestoreObject(), handle new signal signalFinishRestoreObject(), unifies postprocessing in restore and import operation. * createView/setActiveView(), add support of delayed view creations * canClose(), delegate to MainWindows to ask for confirmation * undo/redo(), support grouped transaction undo/redo. Transactions may be grouped by the same transaction ID if they are triggered by a single operation but involves objects from multiple documents. * toggleInSceneGraph(), new API to add or remove root node from or to scenegraph without deleting the view object. MainWindow: * Update command status using a single shot timer instead of periodical one. * Improve message display is status bar. Give error and warning message higher priority (using QStatusBar::showMessage()) than normal status message (using actionLabel), reversed from original implementation. * confirmSave(), new API to check for modification, and ask user to save the document before closing. The confirmation dialog allows user to apply the answer to all document for convenience. * saveAll(), new API to save all document with correct ordering in case of external linking. * createMimeDataFromSelection/insertFromMimeData(), support copy paste object with external linking. A new dialog DlgObjectSelection is used to let user select exactly which object to export. CommandDoc/CommandWindow: * Related changes to object delete, document import, export, and save. --- src/Gui/Application.cpp | 173 ++++++-- src/Gui/Application.h | 18 +- src/Gui/ApplicationPy.cpp | 135 +++++- src/Gui/CMakeLists.txt | 10 + src/Gui/CommandDoc.cpp | 594 ++++++++++++++++++------- src/Gui/CommandWindow.cpp | 6 +- src/Gui/DlgObjectSelection.cpp | 375 ++++++++++++++++ src/Gui/DlgObjectSelection.h | 70 +++ src/Gui/DlgObjectSelection.ui | 156 +++++++ src/Gui/DlgSettingsDocument.ui | 13 + src/Gui/DlgSettingsDocumentImp.cpp | 2 + src/Gui/Document.cpp | 690 +++++++++++++++++++++++------ src/Gui/Document.h | 59 ++- src/Gui/DocumentPy.xml | 35 +- src/Gui/DocumentPyImp.cpp | 121 +++-- src/Gui/InventorAll.h | 3 + src/Gui/MDIView.cpp | 7 +- src/Gui/MainWindow.cpp | 530 +++++++++++++++------- src/Gui/MainWindow.h | 25 +- src/Gui/PreCompiled.h | 2 + src/Gui/ProgressBar.cpp | 35 +- src/Gui/ProgressBar.h | 2 + src/Gui/ProgressDialog.cpp | 29 +- src/Gui/ProgressDialog.h | 2 + src/Gui/ReportView.cpp | 22 +- src/Gui/ReportView.h | 1 + src/Gui/View3DInventor.cpp | 5 + 27 files changed, 2557 insertions(+), 563 deletions(-) create mode 100644 src/Gui/DlgObjectSelection.cpp create mode 100644 src/Gui/DlgObjectSelection.h create mode 100644 src/Gui/DlgObjectSelection.ui 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