2583 lines
90 KiB
C++
2583 lines
90 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2004 Jürgen Riegel <juergen.riegel@web.de> *
|
|
* *
|
|
* 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 <boost/interprocess/sync/file_lock.hpp>
|
|
#include <Inventor/errors/SoDebugError.h>
|
|
#include <Inventor/errors/SoError.h>
|
|
#include <QCloseEvent>
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QLocale>
|
|
#include <QMessageBox>
|
|
#include <QMessageLogContext>
|
|
#include <QRegularExpression>
|
|
#include <QRegularExpressionMatch>
|
|
#include <QStatusBar>
|
|
#include <QStyle>
|
|
#include <QTextStream>
|
|
#include <QTimer>
|
|
#include <QWindow>
|
|
#endif
|
|
|
|
#include <QLoggingCategory>
|
|
#include <fmt/format.h>
|
|
|
|
#include <App/Document.h>
|
|
#include <App/DocumentObjectPy.h>
|
|
#include <Base/Console.h>
|
|
#include <Base/Interpreter.h>
|
|
#include <Base/Exception.h>
|
|
#include <Base/FileInfo.h>
|
|
#include <Base/Parameter.h>
|
|
#include <Base/Stream.h>
|
|
#include <Base/Tools.h>
|
|
|
|
#include <Base/UnitsApi.h>
|
|
|
|
#include <Language/Translator.h>
|
|
#include <Quarter/Quarter.h>
|
|
|
|
#include "Application.h"
|
|
#include "AxisOriginPy.h"
|
|
#include "BitmapFactory.h"
|
|
#include "Command.h"
|
|
#include "CommandActionPy.h"
|
|
#include "CommandPy.h"
|
|
#include "Control.h"
|
|
#include "PreferencePages/DlgSettingsCacheDirectory.h"
|
|
#include "DocumentPy.h"
|
|
#include "DocumentRecovery.h"
|
|
#include "EditorView.h"
|
|
#include "ExpressionBindingPy.h"
|
|
#include "FileDialog.h"
|
|
#include "GuiApplication.h"
|
|
#include "GuiInitScript.h"
|
|
#include "LinkViewPy.h"
|
|
#include "MainWindow.h"
|
|
#include "Macro.h"
|
|
#include "PreferencePackManager.h"
|
|
#include "PythonConsolePy.h"
|
|
#include "PythonDebugger.h"
|
|
#include "MainWindowPy.h"
|
|
#include "MDIViewPy.h"
|
|
#include "Placement.h"
|
|
#include "SoFCDB.h"
|
|
#include "Selection.h"
|
|
#include "SelectionFilterPy.h"
|
|
#include "SoQtOffscreenRendererPy.h"
|
|
#include "SplitView3DInventor.h"
|
|
#include "StartupProcess.h"
|
|
#include "TaskView/TaskView.h"
|
|
#include "TaskView/TaskDialogPython.h"
|
|
#include "TransactionObject.h"
|
|
#include "TextDocumentEditorView.h"
|
|
#include "UiLoader.h"
|
|
#include "View3DPy.h"
|
|
#include "View3DViewerPy.h"
|
|
#include "View3DInventor.h"
|
|
#include "ViewProviderAnnotation.h"
|
|
#include "ViewProviderDocumentObject.h"
|
|
#include "ViewProviderDocumentObjectGroup.h"
|
|
#include "ViewProviderDragger.h"
|
|
#include "ViewProviderExtension.h"
|
|
#include "ViewProviderExtern.h"
|
|
#include "ViewProviderFeature.h"
|
|
#include "ViewProviderGeoFeatureGroup.h"
|
|
#include "ViewProviderGeometryObject.h"
|
|
#include "ViewProviderGeometryObjectPy.h"
|
|
#include "ViewProviderGroupExtension.h"
|
|
#include "ViewProviderSuppressibleExtension.h"
|
|
#include "ViewProviderImagePlane.h"
|
|
#include "ViewProviderInventorObject.h"
|
|
#include "ViewProviderLine.h"
|
|
#include "ViewProviderLink.h"
|
|
#include "ViewProviderLinkPy.h"
|
|
#include "ViewProviderMaterialObject.h"
|
|
#include "ViewProviderOrigin.h"
|
|
#include "ViewProviderOriginFeature.h"
|
|
#include "ViewProviderOriginGroup.h"
|
|
#include "ViewProviderPlacement.h"
|
|
#include "ViewProviderPlane.h"
|
|
#include "ViewProviderPart.h"
|
|
#include "ViewProviderFeaturePython.h"
|
|
#include "ViewProviderTextDocument.h"
|
|
#include "ViewProviderTextureExtension.h"
|
|
#include "ViewProviderVRMLObject.h"
|
|
#include "ViewProviderVarSet.h"
|
|
#include "WaitCursor.h"
|
|
#include "Workbench.h"
|
|
#include "WorkbenchManager.h"
|
|
#include "WorkbenchManipulator.h"
|
|
#include "WidgetFactory.h"
|
|
#include "3Dconnexion/navlib/NavlibInterface.h"
|
|
|
|
|
|
using namespace Gui;
|
|
using namespace Gui::DockWnd;
|
|
using namespace std;
|
|
namespace sp = std::placeholders;
|
|
|
|
|
|
Application* Application::Instance = nullptr;
|
|
|
|
namespace Gui
|
|
{
|
|
|
|
class ViewProviderMap
|
|
{
|
|
std::unordered_map<const App::DocumentObject*, ViewProvider*> map;
|
|
|
|
public:
|
|
void newObject(const ViewProvider& vp)
|
|
{
|
|
auto vpd =
|
|
Base::freecad_dynamic_cast<ViewProviderDocumentObject>(const_cast<ViewProvider*>(&vp));
|
|
if (vpd && vpd->getObject()) {
|
|
map[vpd->getObject()] = vpd;
|
|
}
|
|
}
|
|
void deleteObject(const ViewProvider& vp)
|
|
{
|
|
auto vpd =
|
|
Base::freecad_dynamic_cast<ViewProviderDocumentObject>(const_cast<ViewProvider*>(&vp));
|
|
if (vpd && vpd->getObject()) {
|
|
map.erase(vpd->getObject());
|
|
}
|
|
}
|
|
void deleteDocument(const App::Document& doc)
|
|
{
|
|
for (auto obj : doc.getObjects()) {
|
|
map.erase(obj);
|
|
}
|
|
}
|
|
Gui::ViewProvider* getViewProvider(const App::DocumentObject* obj) const
|
|
{
|
|
auto it = map.find(obj);
|
|
if (it == map.end()) {
|
|
return nullptr;
|
|
}
|
|
return it->second;
|
|
}
|
|
};
|
|
|
|
// Pimpl class
|
|
struct ApplicationP
|
|
{
|
|
explicit ApplicationP(bool GUIenabled)
|
|
{
|
|
// create the macro manager
|
|
if (GUIenabled) {
|
|
macroMngr = new MacroManager();
|
|
}
|
|
else {
|
|
macroMngr = nullptr;
|
|
}
|
|
|
|
// Create the Theme Manager
|
|
prefPackManager = new PreferencePackManager();
|
|
}
|
|
|
|
~ApplicationP()
|
|
{
|
|
delete macroMngr;
|
|
delete prefPackManager;
|
|
}
|
|
|
|
/// list of all handled documents
|
|
std::map<const App::Document*, Gui::Document*> documents;
|
|
/// Active document
|
|
Gui::Document* activeDocument {nullptr};
|
|
Gui::Document* editDocument {nullptr};
|
|
MacroManager* macroMngr;
|
|
PreferencePackManager* prefPackManager;
|
|
/// List of all registered views
|
|
std::list<Gui::BaseView*> passive;
|
|
bool isClosing {false};
|
|
bool startingUp {true};
|
|
/// Handles all commands
|
|
CommandManager commandManager;
|
|
ViewProviderMap viewproviderMap;
|
|
std::bitset<32> StatusBits;
|
|
};
|
|
|
|
static PyObject* FreeCADGui_subgraphFromObject(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
PyObject* o;
|
|
if (!PyArg_ParseTuple(args, "O!", &(App::DocumentObjectPy::Type), &o)) {
|
|
return nullptr;
|
|
}
|
|
App::DocumentObject* obj = static_cast<App::DocumentObjectPy*>(o)->getDocumentObjectPtr();
|
|
std::string vp = obj->getViewProviderName();
|
|
SoNode* node = nullptr;
|
|
try {
|
|
auto base =
|
|
static_cast<Base::BaseClass*>(Base::Type::createInstanceByName(vp.c_str(), true));
|
|
if (base && base->isDerivedFrom<Gui::ViewProviderDocumentObject>()) {
|
|
std::unique_ptr<Gui::ViewProviderDocumentObject> vp(
|
|
static_cast<Gui::ViewProviderDocumentObject*>(base));
|
|
std::map<std::string, App::Property*> Map;
|
|
obj->getPropertyMap(Map);
|
|
vp->attach(obj);
|
|
|
|
// this is needed to initialize Python-based view providers
|
|
App::Property* pyproxy = vp->getPropertyByName("Proxy");
|
|
if (pyproxy && pyproxy->is<App::PropertyPythonObject>()) {
|
|
static_cast<App::PropertyPythonObject*>(pyproxy)->setValue(Py::Long(1));
|
|
}
|
|
|
|
for (const auto& it : Map) {
|
|
vp->updateData(it.second);
|
|
}
|
|
|
|
std::vector<std::string> modes = vp->getDisplayModes();
|
|
if (!modes.empty()) {
|
|
vp->setDisplayMode(modes.front().c_str());
|
|
}
|
|
node = vp->getRoot()->copy();
|
|
node->ref();
|
|
std::string prefix = "So";
|
|
std::string type = node->getTypeId().getName().getString();
|
|
// doesn't start with the prefix 'So'
|
|
if (type.rfind("So", 0) != 0) {
|
|
type = prefix + type;
|
|
}
|
|
else if (type == "SoFCSelectionRoot") {
|
|
type = "SoSeparator";
|
|
}
|
|
|
|
type += " *";
|
|
PyObject* proxy = nullptr;
|
|
proxy = Base::Interpreter().createSWIGPointerObj("pivy.coin",
|
|
type.c_str(),
|
|
static_cast<void*>(node),
|
|
1);
|
|
return Py::new_reference_to(Py::Object(proxy, true));
|
|
}
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
if (node) {
|
|
node->unref();
|
|
}
|
|
PyErr_SetString(PyExc_RuntimeError, e.what());
|
|
return nullptr;
|
|
}
|
|
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
static PyObject* FreeCADGui_exportSubgraph(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
const char* format = "VRML";
|
|
PyObject* proxy;
|
|
PyObject* output;
|
|
if (!PyArg_ParseTuple(args, "OO|s", &proxy, &output, &format)) {
|
|
return nullptr;
|
|
}
|
|
|
|
void* ptr = nullptr;
|
|
try {
|
|
Base::Interpreter().convertSWIGPointerObj("pivy.coin", "SoNode *", proxy, &ptr, 0);
|
|
auto node = static_cast<SoNode*>(ptr);
|
|
if (node) {
|
|
std::string formatStr(format);
|
|
std::string buffer;
|
|
|
|
if (formatStr == "VRML") {
|
|
SoFCDB::writeToVRML(node, buffer);
|
|
}
|
|
else if (formatStr == "IV") {
|
|
buffer = SoFCDB::writeNodesToString(node);
|
|
}
|
|
else {
|
|
throw Base::ValueError("Unsupported format");
|
|
}
|
|
|
|
Base::PyStreambuf buf(output);
|
|
std::ostream str(nullptr);
|
|
str.rdbuf(&buf);
|
|
str << buffer;
|
|
}
|
|
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
PyErr_SetString(PyExc_RuntimeError, e.what());
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
static PyObject* FreeCADGui_getSoDBVersion(PyObject* /*self*/, PyObject* args)
|
|
{
|
|
if (!PyArg_ParseTuple(args, "")) {
|
|
return nullptr;
|
|
}
|
|
return PyUnicode_FromString(SoDB::getVersion());
|
|
}
|
|
|
|
struct PyMethodDef FreeCADGui_methods[] = {
|
|
{"subgraphFromObject",
|
|
FreeCADGui_subgraphFromObject,
|
|
METH_VARARGS,
|
|
"subgraphFromObject(object) -> Node\n\n"
|
|
"Return the Inventor subgraph to an object"},
|
|
{"exportSubgraph",
|
|
FreeCADGui_exportSubgraph,
|
|
METH_VARARGS,
|
|
"exportSubgraph(Node, File or Buffer, [Format='VRML']) -> None\n\n"
|
|
"Exports the sub-graph in the requested format"
|
|
"The format string can be VRML or IV"},
|
|
{"getSoDBVersion",
|
|
FreeCADGui_getSoDBVersion,
|
|
METH_VARARGS,
|
|
"getSoDBVersion() -> String\n\n"
|
|
"Return a text string containing the name\n"
|
|
"of the Coin library and version information"},
|
|
{nullptr, nullptr, 0, nullptr} /* sentinel */
|
|
};
|
|
|
|
} // namespace Gui
|
|
|
|
// clang-format off
|
|
Application::Application(bool GUIenabled)
|
|
{
|
|
// App::GetApplication().Attach(this);
|
|
if (GUIenabled) {
|
|
// NOLINTBEGIN
|
|
App::GetApplication().signalNewDocument.connect(
|
|
std::bind(&Gui::Application::slotNewDocument, this, sp::_1, sp::_2));
|
|
App::GetApplication().signalDeleteDocument.connect(
|
|
std::bind(&Gui::Application::slotDeleteDocument, this, sp::_1));
|
|
App::GetApplication().signalRenameDocument.connect(
|
|
std::bind(&Gui::Application::slotRenameDocument, this, sp::_1));
|
|
App::GetApplication().signalActiveDocument.connect(
|
|
std::bind(&Gui::Application::slotActiveDocument, this, sp::_1));
|
|
App::GetApplication().signalRelabelDocument.connect(
|
|
std::bind(&Gui::Application::slotRelabelDocument, this, sp::_1));
|
|
App::GetApplication().signalShowHidden.connect(
|
|
std::bind(&Gui::Application::slotShowHidden, this, sp::_1));
|
|
// NOLINTEND
|
|
// install the last active language
|
|
ParameterGrp::handle hPGrp = App::GetApplication().GetUserParameter().GetGroup("BaseApp");
|
|
hPGrp = hPGrp->GetGroup("Preferences")->GetGroup("General");
|
|
QString lang = QLocale::languageToString(QLocale().language());
|
|
Translator::instance()->activateLanguage(
|
|
hPGrp->GetASCII("Language", (const char*)lang.toLatin1()).c_str());
|
|
GetWidgetFactorySupplier();
|
|
|
|
// Coin3d disabled VBO support for all Intel drivers but in the meantime they have improved
|
|
// so we can try to override the workaround by setting COIN_VBO
|
|
ParameterGrp::handle hViewGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/View");
|
|
if (hViewGrp->GetBool("UseVBO", false)) {
|
|
(void)coin_setenv("COIN_VBO", "0", true);
|
|
}
|
|
|
|
// Check for the symbols for group separator and decimal point. They must be different
|
|
// otherwise Qt doesn't work properly.
|
|
#if defined(Q_OS_WIN32)
|
|
if (QLocale().groupSeparator() == QLocale().decimalPoint()) {
|
|
QMessageBox::critical(
|
|
0,
|
|
QLatin1String("Invalid system settings"),
|
|
QLatin1String(
|
|
"Your system uses the same symbol for decimal point and group separator.\n\n"
|
|
"This causes serious problems and makes the application fail to work "
|
|
"properly.\n"
|
|
"Go to the system configuration panel of the OS and fix this issue, please."));
|
|
throw Base::RuntimeError("Invalid system settings");
|
|
}
|
|
#endif
|
|
|
|
// setting up Python binding
|
|
Base::PyGILStateLocker lock;
|
|
|
|
PyDoc_STRVAR(
|
|
FreeCADGui_doc,
|
|
"The functions in the FreeCADGui module allow working with GUI documents,\n"
|
|
"view providers, views, workbenches and much more.\n\n"
|
|
"The FreeCADGui instance provides a list of references of GUI documents which\n"
|
|
"can be addressed by a string. These documents contain the view providers for\n"
|
|
"objects in the associated App document. An App and GUI document can be\n"
|
|
"accessed with the same name.\n\n"
|
|
"The FreeCADGui module also provides a set of functions to work with so called\n"
|
|
"workbenches.");
|
|
|
|
// if this returns a valid pointer then the 'FreeCADGui' Python module was loaded,
|
|
// otherwise the executable was launched
|
|
PyObject* modules = PyImport_GetModuleDict();
|
|
PyObject* module = PyDict_GetItemString(modules, "FreeCADGui");
|
|
if (!module) {
|
|
static struct PyModuleDef FreeCADGuiModuleDef = {PyModuleDef_HEAD_INIT,
|
|
"FreeCADGui",
|
|
FreeCADGui_doc,
|
|
-1,
|
|
Application::Methods,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr};
|
|
module = PyModule_Create(&FreeCADGuiModuleDef);
|
|
|
|
PyDict_SetItemString(modules, "FreeCADGui", module);
|
|
}
|
|
else {
|
|
// extend the method list
|
|
PyModule_AddFunctions(module, Application::Methods);
|
|
}
|
|
Py::Module(module).setAttr(std::string("ActiveDocument"), Py::None());
|
|
Py::Module(module).setAttr(std::string("HasQtBug_129596"),
|
|
#ifdef HAS_QTBUG_129596
|
|
Py::True()
|
|
#else
|
|
Py::False()
|
|
#endif
|
|
);
|
|
|
|
UiLoaderPy::init_type();
|
|
Base::Interpreter().addType(UiLoaderPy::type_object(), module, "UiLoader");
|
|
PyResource::init_type();
|
|
|
|
Gui::Dialog::TaskPlacementPy::init_type();
|
|
Base::Interpreter().addType(Gui::Dialog::TaskPlacementPy::type_object(),
|
|
module, "TaskPlacement");
|
|
|
|
// PySide additions
|
|
PyModule_AddObject(module, "PySideUic", Base::Interpreter().addModule(new PySideUicModule));
|
|
|
|
ExpressionBindingPy::init_type();
|
|
Base::Interpreter().addType(ExpressionBindingPy::type_object(),
|
|
module,
|
|
"ExpressionBinding");
|
|
|
|
// insert Selection module
|
|
static struct PyModuleDef SelectionModuleDef = {PyModuleDef_HEAD_INIT,
|
|
"Selection",
|
|
"Selection module",
|
|
-1,
|
|
SelectionSingleton::Methods,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr};
|
|
PyObject* pSelectionModule = PyModule_Create(&SelectionModuleDef);
|
|
Py_INCREF(pSelectionModule);
|
|
PyModule_AddObject(module, "Selection", pSelectionModule);
|
|
|
|
SelectionFilterPy::init_type();
|
|
Base::Interpreter().addType(SelectionFilterPy::type_object(), pSelectionModule, "Filter");
|
|
|
|
Gui::TaskView::ControlPy::init_type();
|
|
Py::Module(module).setAttr(std::string("Control"),
|
|
Py::Object(Gui::TaskView::ControlPy::getInstance(), true));
|
|
Gui::TaskView::TaskDialogPy::init_type();
|
|
|
|
CommandActionPy::init_type();
|
|
Base::Interpreter().addType(CommandActionPy::type_object(), module, "CommandAction");
|
|
|
|
Base::Interpreter().addType(&LinkViewPy::Type, module, "LinkView");
|
|
Base::Interpreter().addType(&AxisOriginPy::Type, module, "AxisOrigin");
|
|
Base::Interpreter().addType(&CommandPy::Type, module, "Command");
|
|
Base::Interpreter().addType(&DocumentPy::Type, module, "Document");
|
|
Base::Interpreter().addType(&ViewProviderPy::Type, module, "ViewProvider");
|
|
Base::Interpreter().addType(&ViewProviderDocumentObjectPy::Type,
|
|
module,
|
|
"ViewProviderDocumentObject");
|
|
Base::Interpreter().addType(&ViewProviderGeometryObjectPy::Type,
|
|
module,
|
|
"ViewProviderGeometryObject");
|
|
Base::Interpreter().addType(&ViewProviderLinkPy::Type, module, "ViewProviderLink");
|
|
}
|
|
|
|
Base::PyGILStateLocker lock;
|
|
PyObject* module = PyImport_AddModule("FreeCADGui");
|
|
PyMethodDef* meth = FreeCADGui_methods;
|
|
PyObject* dict = PyModule_GetDict(module);
|
|
for (; meth->ml_name != nullptr; meth++) {
|
|
PyObject* descr;
|
|
descr = PyCFunction_NewEx(meth, nullptr, nullptr);
|
|
if (!descr) {
|
|
break;
|
|
}
|
|
if (PyDict_SetItemString(dict, meth->ml_name, descr) != 0) {
|
|
break;
|
|
}
|
|
Py_DECREF(descr);
|
|
}
|
|
|
|
SoQtOffscreenRendererPy::init_type();
|
|
Base::Interpreter().addType(SoQtOffscreenRendererPy::type_object(),
|
|
module,
|
|
"SoQtOffscreenRenderer");
|
|
|
|
App::Application::Config()["COIN_VERSION"] = COIN_VERSION;
|
|
|
|
// clang-format off
|
|
// Python console binding
|
|
PythonDebugModule ::init_module();
|
|
PythonStdout ::init_type();
|
|
PythonStderr ::init_type();
|
|
OutputStdout ::init_type();
|
|
OutputStderr ::init_type();
|
|
PythonStdin ::init_type();
|
|
MainWindowPy ::init_type();
|
|
MDIViewPy ::init_type();
|
|
View3DInventorPy ::init_type();
|
|
View3DInventorViewerPy ::init_type();
|
|
AbstractSplitViewPy ::init_type();
|
|
// clang-format on
|
|
|
|
d = new ApplicationP(GUIenabled);
|
|
|
|
// global access
|
|
Instance = this;
|
|
|
|
// instantiate the workbench dictionary
|
|
_pcWorkbenchDictionary = PyDict_New();
|
|
|
|
#ifdef USE_3DCONNEXION_NAVLIB
|
|
// Instantiate the 3Dconnexion controller
|
|
pNavlibInterface = new NavlibInterface();
|
|
#endif
|
|
|
|
if (GUIenabled) {
|
|
createStandardOperations();
|
|
MacroCommand::load();
|
|
}
|
|
}
|
|
// clang-format on
|
|
|
|
Application::~Application()
|
|
{
|
|
Base::Console().Log("Destruct Gui::Application\n");
|
|
#ifdef USE_3DCONNEXION_NAVLIB
|
|
delete pNavlibInterface;
|
|
#endif
|
|
WorkbenchManager::destruct();
|
|
WorkbenchManipulator::removeAll();
|
|
SelectionSingleton::destruct();
|
|
Translator::destruct();
|
|
WidgetFactorySupplier::destruct();
|
|
BitmapFactoryInst::destruct();
|
|
|
|
Base::PyGILStateLocker lock;
|
|
Py_DECREF(_pcWorkbenchDictionary);
|
|
|
|
// save macros
|
|
try {
|
|
MacroCommand::save();
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
std::cerr << "Saving macros failed: " << e.what() << std::endl;
|
|
}
|
|
|
|
delete d;
|
|
Instance = nullptr;
|
|
}
|
|
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
// creating std commands
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
|
|
void Application::open(const char* FileName, const char* Module)
|
|
{
|
|
WaitCursor wc;
|
|
wc.setIgnoreEvents(WaitCursor::NoEvents);
|
|
Base::FileInfo File(FileName);
|
|
string te = File.extension();
|
|
string unicodepath = Base::Tools::escapedUnicodeFromUtf8(File.filePath().c_str());
|
|
unicodepath = Base::Tools::escapeEncodeFilename(unicodepath);
|
|
|
|
// if the active document is empty and not modified, close it
|
|
// in case of an automatically created empty document at startup
|
|
App::Document* act = App::GetApplication().getActiveDocument();
|
|
Gui::Document* gui = this->getDocument(act);
|
|
if (act && act->countObjects() == 0 && gui && !gui->isModified()) {
|
|
Command::doCommand(Command::App, "App.closeDocument('%s')", act->getName());
|
|
qApp->processEvents(); // an update is needed otherwise the new view isn't shown
|
|
}
|
|
|
|
if (Module) {
|
|
try {
|
|
if (File.hasExtension("FCStd")) {
|
|
bool handled = false;
|
|
std::string filepath = File.filePath();
|
|
for (auto& v : d->documents) {
|
|
auto doc = v.second->getDocument();
|
|
std::string fi = Base::FileInfo(doc->FileName.getValue()).filePath();
|
|
if (filepath == fi) {
|
|
handled = true;
|
|
Command::doCommand(Command::App, "FreeCADGui.reload('%s')", doc->getName());
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!handled) {
|
|
Command::doCommand(Command::App,
|
|
"FreeCAD.openDocument('%s')",
|
|
unicodepath.c_str());
|
|
Gui::Application::checkForRecomputes();
|
|
}
|
|
}
|
|
else {
|
|
std::string code = fmt::format("from freecad import module_io\n"
|
|
"module_io.OpenInsertObject(\"{}\", \"{}\", \"{}\")\n",
|
|
Module, unicodepath, "open");
|
|
Gui::Command::runCommand(Gui::Command::App, code.c_str());
|
|
|
|
// ViewFit
|
|
if (sendHasMsgToActiveView("ViewFit")) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/View");
|
|
if (hGrp->GetBool("AutoFitToView", true)) {
|
|
Command::doCommand(Command::Gui, "Gui.SendMsgToActiveView(\"ViewFit\")");
|
|
}
|
|
}
|
|
}
|
|
|
|
// the original file name is required
|
|
QString filename = QString::fromUtf8(File.filePath().c_str());
|
|
getMainWindow()->appendRecentFile(filename);
|
|
FileDialog::setWorkingDirectory(filename);
|
|
}
|
|
catch (const Base::PyException& e) {
|
|
// Usually thrown if the file is invalid somehow
|
|
e.ReportException();
|
|
}
|
|
}
|
|
else {
|
|
wc.restoreCursor();
|
|
QMessageBox::warning(
|
|
getMainWindow(),
|
|
QObject::tr("Unknown filetype"),
|
|
QObject::tr("Cannot open unknown filetype: %1").arg(QLatin1String(te.c_str())));
|
|
wc.setWaitCursor();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Application::importFrom(const char* FileName, const char* DocName, const char* Module)
|
|
{
|
|
WaitCursor wc;
|
|
wc.setIgnoreEvents(WaitCursor::NoEvents);
|
|
Base::FileInfo File(FileName);
|
|
std::string te = File.extension();
|
|
string unicodepath = File.filePath().c_str();
|
|
unicodepath = Base::Tools::escapeEncodeFilename(unicodepath);
|
|
|
|
if (Module) {
|
|
try {
|
|
// issue module loading
|
|
Command::doCommand(Command::App, "import %s", Module);
|
|
|
|
// load the file with the module
|
|
if (File.hasExtension("FCStd")) {
|
|
Command::doCommand(Command::App, "%s.open(u\"%s\")", Module, unicodepath.c_str());
|
|
if (activeDocument()) {
|
|
activeDocument()->setModified(false);
|
|
}
|
|
}
|
|
else {
|
|
// Open transaction when importing a file
|
|
Gui::Document* doc = DocName ? getDocument(DocName) : activeDocument();
|
|
bool pendingCommand = false;
|
|
if (doc) {
|
|
pendingCommand = doc->hasPendingCommand();
|
|
if (!pendingCommand) {
|
|
doc->openCommand(QT_TRANSLATE_NOOP("Command", "Import"));
|
|
}
|
|
}
|
|
|
|
std::string code = fmt::format("from freecad import module_io\n"
|
|
"module_io.OpenInsertObject(\"{}\", \"{}\", \"{}\", \"{}\")\n",
|
|
Module, unicodepath, "insert", DocName);
|
|
Gui::Command::runCommand(Gui::Command::App, code.c_str());
|
|
|
|
// Commit the transaction
|
|
if (doc && !pendingCommand) {
|
|
doc->commitCommand();
|
|
}
|
|
|
|
// It's possible that before importing a file the document with the
|
|
// given name doesn't exist or there is no active document.
|
|
// The import function then may create a new document.
|
|
if (!doc) {
|
|
doc = activeDocument();
|
|
}
|
|
|
|
if (doc) {
|
|
doc->setModified(true);
|
|
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/View");
|
|
if (hGrp->GetBool("AutoFitToView", true)) {
|
|
MDIView* view = doc->getActiveView();
|
|
if (view) {
|
|
const char* ret = nullptr;
|
|
if (view->onMsg("ViewFit", &ret)) {
|
|
updateActions(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// the original file name is required
|
|
QString filename = QString::fromUtf8(File.filePath().c_str());
|
|
auto parameterGroup = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/General");
|
|
bool addToRecent = parameterGroup->GetBool("RecentIncludesImported", true);
|
|
parameterGroup->SetBool("RecentIncludesImported",
|
|
addToRecent); // Make sure it gets added to the parameter list
|
|
if (addToRecent) {
|
|
getMainWindow()->appendRecentFile(filename);
|
|
}
|
|
FileDialog::setWorkingDirectory(filename);
|
|
}
|
|
catch (const Base::PyException& e) {
|
|
// Usually thrown if the file is invalid somehow
|
|
e.ReportException();
|
|
}
|
|
}
|
|
else {
|
|
wc.restoreCursor();
|
|
QMessageBox::warning(
|
|
getMainWindow(),
|
|
QObject::tr("Unknown filetype"),
|
|
QObject::tr("Cannot open unknown filetype: %1").arg(QLatin1String(te.c_str())));
|
|
wc.setWaitCursor();
|
|
}
|
|
}
|
|
|
|
void Application::exportTo(const char* FileName, const char* DocName, const char* Module)
|
|
{
|
|
WaitCursor wc;
|
|
wc.setIgnoreEvents(WaitCursor::NoEvents);
|
|
Base::FileInfo File(FileName);
|
|
std::string te = File.extension();
|
|
string unicodepath = Base::Tools::escapedUnicodeFromUtf8(File.filePath().c_str());
|
|
unicodepath = Base::Tools::escapeEncodeFilename(unicodepath);
|
|
|
|
if (Module) {
|
|
try {
|
|
std::vector<App::DocumentObject*> sel =
|
|
Gui::Selection().getObjectsOfType(App::DocumentObject::getClassTypeId(), DocName);
|
|
if (sel.empty()) {
|
|
App::Document* doc = App::GetApplication().getDocument(DocName);
|
|
sel = doc->getObjectsOfType(App::DocumentObject::getClassTypeId());
|
|
}
|
|
|
|
std::stringstream str;
|
|
std::set<App::DocumentObject*> unique_objs;
|
|
str << "__objs__ = []\n";
|
|
for (auto it : sel) {
|
|
if (unique_objs.insert(it).second) {
|
|
str << "__objs__.append(FreeCAD.getDocument(\"" << DocName << "\").getObject(\""
|
|
<< it->getNameInDocument() << "\"))\n";
|
|
}
|
|
}
|
|
|
|
// check for additional export options
|
|
str << "import " << Module << '\n';
|
|
str << "if hasattr(" << Module << ", \"exportOptions\"):\n"
|
|
<< " options = " << Module << ".exportOptions(u\"" << unicodepath << "\")\n"
|
|
<< " " << Module << ".export(__objs__, u\"" << unicodepath << "\", options)\n"
|
|
<< "else:\n"
|
|
<< " " << Module << ".export(__objs__, u\"" << unicodepath << "\")\n";
|
|
|
|
std::string code = str.str();
|
|
// the original file name is required
|
|
Gui::Command::runCommand(Gui::Command::App, code.c_str());
|
|
|
|
auto parameterGroup = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/General");
|
|
bool addToRecent = parameterGroup->GetBool("RecentIncludesExported", false);
|
|
parameterGroup->SetBool("RecentIncludesExported",
|
|
addToRecent); // Make sure it gets added to the parameter list
|
|
if (addToRecent) {
|
|
// search for a module that is able to open the exported file because otherwise
|
|
// it doesn't need to be added to the recent files list (#0002047)
|
|
std::map<std::string, std::string> importMap =
|
|
App::GetApplication().getImportFilters(te.c_str());
|
|
if (!importMap.empty()) {
|
|
getMainWindow()->appendRecentFile(QString::fromUtf8(File.filePath().c_str()));
|
|
}
|
|
}
|
|
// allow exporters to pass _objs__ to submodules before deleting it
|
|
Gui::Command::runCommand(Gui::Command::App, "del __objs__");
|
|
}
|
|
catch (const Base::PyException& e) {
|
|
// Usually thrown if the file is invalid somehow
|
|
e.ReportException();
|
|
wc.restoreCursor();
|
|
QMessageBox::critical(getMainWindow(),
|
|
QObject::tr("Export failed"),
|
|
QString::fromUtf8(e.what()));
|
|
wc.setWaitCursor();
|
|
}
|
|
}
|
|
else {
|
|
wc.restoreCursor();
|
|
QMessageBox::warning(
|
|
getMainWindow(),
|
|
QObject::tr("Unknown filetype"),
|
|
QObject::tr("Cannot save to unknown filetype: %1").arg(QLatin1String(te.c_str())));
|
|
wc.setWaitCursor();
|
|
}
|
|
}
|
|
|
|
void Application::createStandardOperations()
|
|
{
|
|
// register the application Standard commands from CommandStd.cpp
|
|
Gui::CreateStdCommands();
|
|
Gui::CreateDocCommands();
|
|
Gui::CreateFeatCommands();
|
|
Gui::CreateMacroCommands();
|
|
Gui::CreateViewStdCommands();
|
|
Gui::CreateWindowStdCommands();
|
|
Gui::CreateStructureCommands();
|
|
Gui::CreateTestCommands();
|
|
Gui::CreateLinkCommands();
|
|
}
|
|
|
|
void Application::slotNewDocument(const App::Document& Doc, bool isMainDoc)
|
|
{
|
|
#ifdef FC_DEBUG
|
|
assert(d->documents.find(&Doc) == d->documents.end());
|
|
#endif
|
|
auto pDoc = new Gui::Document(const_cast<App::Document*>(&Doc), this);
|
|
d->documents[&Doc] = pDoc;
|
|
|
|
// NOLINTBEGIN
|
|
// connect the signals to the application for the new document
|
|
pDoc->signalNewObject.connect(std::bind(&Gui::Application::slotNewObject, this, sp::_1));
|
|
pDoc->signalDeletedObject.connect(
|
|
std::bind(&Gui::Application::slotDeletedObject, this, sp::_1));
|
|
pDoc->signalChangedObject.connect(
|
|
std::bind(&Gui::Application::slotChangedObject, this, sp::_1, sp::_2));
|
|
pDoc->signalRelabelObject.connect(
|
|
std::bind(&Gui::Application::slotRelabelObject, this, sp::_1));
|
|
pDoc->signalActivatedObject.connect(
|
|
std::bind(&Gui::Application::slotActivatedObject, this, sp::_1));
|
|
pDoc->signalInEdit.connect(std::bind(&Gui::Application::slotInEdit, this, sp::_1));
|
|
pDoc->signalResetEdit.connect(std::bind(&Gui::Application::slotResetEdit, this, sp::_1));
|
|
// NOLINTEND
|
|
|
|
signalNewDocument(*pDoc, isMainDoc);
|
|
if (isMainDoc) {
|
|
pDoc->createView(View3DInventor::getClassTypeId());
|
|
}
|
|
}
|
|
|
|
void Application::slotDeleteDocument(const App::Document& Doc)
|
|
{
|
|
std::map<const App::Document*, Gui::Document*>::iterator doc = d->documents.find(&Doc);
|
|
if (doc == d->documents.end()) {
|
|
Base::Console().Log("GUI document '%s' already deleted\n", Doc.getName());
|
|
return;
|
|
}
|
|
|
|
// Inside beforeDelete() a view provider may finish editing mode
|
|
// and therefore can alter the selection.
|
|
doc->second->beforeDelete();
|
|
|
|
// 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);
|
|
|
|
// 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) {
|
|
setActiveDocument(nullptr);
|
|
}
|
|
|
|
d->viewproviderMap.deleteDocument(Doc);
|
|
|
|
// For exception-safety use a smart pointer
|
|
unique_ptr<Document> delDoc(doc->second);
|
|
d->documents.erase(doc);
|
|
}
|
|
|
|
void Application::slotRelabelDocument(const App::Document& Doc)
|
|
{
|
|
std::map<const App::Document*, Gui::Document*>::iterator doc = d->documents.find(&Doc);
|
|
#ifdef FC_DEBUG
|
|
assert(doc != d->documents.end());
|
|
#endif
|
|
|
|
signalRelabelDocument(*doc->second);
|
|
doc->second->onRelabel();
|
|
}
|
|
|
|
void Application::slotRenameDocument(const App::Document& Doc)
|
|
{
|
|
std::map<const App::Document*, Gui::Document*>::iterator doc = d->documents.find(&Doc);
|
|
#ifdef FC_DEBUG
|
|
assert(doc != d->documents.end());
|
|
#endif
|
|
|
|
signalRenameDocument(*doc->second);
|
|
}
|
|
|
|
void Application::slotShowHidden(const App::Document& Doc)
|
|
{
|
|
std::map<const App::Document*, Gui::Document*>::iterator doc = d->documents.find(&Doc);
|
|
#ifdef FC_DEBUG
|
|
assert(doc != d->documents.end());
|
|
#endif
|
|
|
|
signalShowHidden(*doc->second);
|
|
}
|
|
|
|
void Application::checkForRecomputes() {
|
|
std::vector<App::Document *> docs;
|
|
for (auto doc: App::GetApplication().getDocuments()) {
|
|
if (doc->testStatus(App::Document::RecomputeOnRestore)) {
|
|
docs.push_back(doc);
|
|
doc->setStatus(App::Document::RecomputeOnRestore, false);
|
|
}
|
|
}
|
|
// Certain tests want to use very old .FCStd files. We should not prompt during those tests, so this
|
|
// allows them to 'FreeCAD.ConfigSet("SuppressRecomputeRequiredDialog", "True")`
|
|
const std::map<std::string, std::string>& Map = App::Application::Config();
|
|
auto value = Map.find("SuppressRecomputeRequiredDialog");
|
|
bool skip = value != Map.end() && ! value->second.empty(); // Any non empty string is true.
|
|
if (docs.empty() || skip )
|
|
return;
|
|
WaitCursor wc;
|
|
wc.restoreCursor();
|
|
auto res = QMessageBox::warning(getMainWindow(), QObject::tr("Recomputation required"),
|
|
QObject::tr("Some document(s) require recomputation for migration purposes. "
|
|
"It is highly recommended to perform a recomputation before "
|
|
"any modification to avoid compatibility problems.\n\n"
|
|
"Do you want to recompute now?"),
|
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
|
if (res != QMessageBox::Yes)
|
|
return;
|
|
bool hasError = false;
|
|
for (auto doc: App::Document::getDependentDocuments(docs, true)) {
|
|
try {
|
|
doc->recompute({}, false, &hasError);
|
|
} catch (Base::Exception &e) {
|
|
e.ReportException();
|
|
hasError = true;
|
|
}
|
|
}
|
|
if (hasError)
|
|
QMessageBox::critical(getMainWindow(), QObject::tr("Recompute error"),
|
|
QObject::tr("Failed to recompute some document(s).\n"
|
|
"Please check report view for more details."));
|
|
}
|
|
|
|
void Application::slotActiveDocument(const App::Document& Doc)
|
|
{
|
|
std::map<const App::Document*, Gui::Document*>::iterator doc = d->documents.find(&Doc);
|
|
// this can happen when closing a document with two views opened
|
|
if (doc != d->documents.end()) {
|
|
// this can happen when calling App.setActiveDocument directly from Python
|
|
// because no MDI view will be activated
|
|
if (d->activeDocument != doc->second) {
|
|
d->activeDocument = doc->second;
|
|
if (d->activeDocument) {
|
|
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;
|
|
Py::Module("FreeCADGui").setAttr(std::string("ActiveDocument"), Py::None());
|
|
}
|
|
}
|
|
|
|
// Update the application to show the unit change
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Units");
|
|
if (!hGrp->GetBool("IgnoreProjectSchema")) {
|
|
int userSchema = Doc.UnitSystem.getValue();
|
|
Base::UnitsApi::setSchema(static_cast<Base::UnitSystem>(userSchema));
|
|
getMainWindow()->setUserSchema(userSchema);
|
|
Application::Instance->onUpdate();
|
|
}
|
|
else { // set up Unit system default
|
|
Base::UnitsApi::setSchema((Base::UnitSystem)hGrp->GetInt("UserSchema", 0));
|
|
Base::UnitsApi::setDecimals(hGrp->GetInt("Decimals", Base::UnitsApi::getDecimals()));
|
|
}
|
|
signalActiveDocument(*doc->second);
|
|
updateActions();
|
|
}
|
|
}
|
|
|
|
void Application::slotNewObject(const ViewProvider& vp)
|
|
{
|
|
d->viewproviderMap.newObject(vp);
|
|
this->signalNewObject(vp);
|
|
}
|
|
|
|
void Application::slotDeletedObject(const ViewProvider& vp)
|
|
{
|
|
this->signalDeletedObject(vp);
|
|
d->viewproviderMap.deleteObject(vp);
|
|
}
|
|
|
|
void Application::slotChangedObject(const ViewProvider& vp, const App::Property& prop)
|
|
{
|
|
this->signalChangedObject(vp, prop);
|
|
updateActions(true);
|
|
}
|
|
|
|
void Application::slotRelabelObject(const ViewProvider& vp)
|
|
{
|
|
this->signalRelabelObject(vp);
|
|
}
|
|
|
|
void Application::slotActivatedObject(const ViewProvider& vp)
|
|
{
|
|
this->signalActivatedObject(vp);
|
|
updateActions();
|
|
}
|
|
|
|
void Application::slotInEdit(const Gui::ViewProviderDocumentObject& vp)
|
|
{
|
|
this->signalInEdit(vp);
|
|
}
|
|
|
|
void Application::slotResetEdit(const Gui::ViewProviderDocumentObject& vp)
|
|
{
|
|
this->signalResetEdit(vp);
|
|
}
|
|
|
|
void Application::onLastWindowClosed(Gui::Document* pcDoc)
|
|
{
|
|
try {
|
|
if (!d->isClosing && pcDoc) {
|
|
// 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.empty()) {
|
|
Document* gdoc = nullptr;
|
|
for (auto& v : d->documents) {
|
|
if (v.second->getDocument()->testStatus(App::Document::TempDoc)) {
|
|
continue;
|
|
}
|
|
else if (!gdoc) {
|
|
gdoc = v.second;
|
|
}
|
|
|
|
Gui::MDIView* view = v.second->getActiveView();
|
|
if (view) {
|
|
setActiveDocument(v.second);
|
|
getMainWindow()->setActiveWindow(view);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (gdoc) {
|
|
setActiveDocument(gdoc);
|
|
activateView(View3DInventor::getClassTypeId(), true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
e.ReportException();
|
|
}
|
|
catch (const Py::Exception&) {
|
|
Base::PyException e;
|
|
e.ReportException();
|
|
}
|
|
catch (const std::exception& e) {
|
|
Base::Console().Error(
|
|
"Unhandled std::exception caught in Application::onLastWindowClosed.\n"
|
|
"The error message is: %s\n",
|
|
e.what());
|
|
}
|
|
catch (...) {
|
|
Base::Console().Error(
|
|
"Unhandled unknown exception caught in Application::onLastWindowClosed.\n");
|
|
}
|
|
}
|
|
|
|
/// send Messages to the active view
|
|
bool Application::sendMsgToActiveView(const char* pMsg, const char** ppReturn)
|
|
{
|
|
MDIView* pView = getMainWindow()->activeWindow();
|
|
bool res = pView ? pView->onMsg(pMsg, ppReturn) : false;
|
|
updateActions(true);
|
|
return res;
|
|
}
|
|
|
|
bool Application::sendHasMsgToActiveView(const char* pMsg)
|
|
{
|
|
MDIView* pView = getMainWindow()->activeWindow();
|
|
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) {
|
|
bool res = pView->onMsg(pMsg, ppReturn);
|
|
updateActions(true);
|
|
return res;
|
|
}
|
|
}
|
|
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() const
|
|
{
|
|
if (activeDocument()) {
|
|
return activeDocument()->getActiveView();
|
|
}
|
|
else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Application::activateView
|
|
* Activates a view of the given type of the active document.
|
|
* If a view of this type doesn't exist and \a create is true
|
|
* a new view of this type will be created.
|
|
* @param type
|
|
* @param create
|
|
*/
|
|
void Application::activateView(const Base::Type& type, bool create)
|
|
{
|
|
Document* doc = activeDocument();
|
|
if (doc) {
|
|
MDIView* mdiView = doc->getActiveView();
|
|
if (mdiView && mdiView->isDerivedFrom(type)) {
|
|
return;
|
|
}
|
|
std::list<MDIView*> mdiViews = doc->getMDIViewsOfType(type);
|
|
if (!mdiViews.empty()) {
|
|
doc->setActiveWindow(mdiViews.back());
|
|
}
|
|
else if (create) {
|
|
doc->createView(type);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Getter for the active view
|
|
Gui::Document* Application::activeDocument() const
|
|
{
|
|
return d->activeDocument;
|
|
}
|
|
|
|
Gui::Document* Application::editDocument() const
|
|
{
|
|
return d->editDocument;
|
|
}
|
|
|
|
Gui::MDIView* Application::editViewOfNode(SoNode* node) const
|
|
{
|
|
return d->editDocument ? d->editDocument->getViewOfNode(node) : nullptr;
|
|
}
|
|
|
|
void Application::setEditDocument(Gui::Document* doc)
|
|
{
|
|
if (doc == d->editDocument) {
|
|
return;
|
|
}
|
|
if (!doc) {
|
|
d->editDocument = nullptr;
|
|
}
|
|
for (auto& v : d->documents) {
|
|
v.second->_resetEdit();
|
|
}
|
|
d->editDocument = doc;
|
|
updateActions();
|
|
}
|
|
|
|
void Application::setActiveDocument(Gui::Document* pcDocument)
|
|
{
|
|
if (d->activeDocument == pcDocument) {
|
|
return; // nothing needs to be done
|
|
}
|
|
|
|
updateActions();
|
|
|
|
if (pcDocument) {
|
|
// This happens if a document with more than one view is about being
|
|
// closed and a second view is activated. The document is still not
|
|
// removed from the map.
|
|
App::Document* doc = pcDocument->getDocument();
|
|
if (d->documents.find(doc) == d->documents.end()) {
|
|
return;
|
|
}
|
|
}
|
|
d->activeDocument = pcDocument;
|
|
std::string nameApp, nameGui;
|
|
|
|
// This adds just a line to the macro file but does not set the active document
|
|
// Macro recording of this is problematic, thus it's written out as comment.
|
|
if (pcDocument) {
|
|
nameApp += "App.setActiveDocument(\"";
|
|
nameApp += pcDocument->getDocument()->getName();
|
|
nameApp += "\")\n";
|
|
nameApp += "App.ActiveDocument=App.getDocument(\"";
|
|
nameApp += pcDocument->getDocument()->getName();
|
|
nameApp += "\")";
|
|
macroManager()->addLine(MacroManager::Cmt, nameApp.c_str());
|
|
nameGui += "Gui.ActiveDocument=Gui.getDocument(\"";
|
|
nameGui += pcDocument->getDocument()->getName();
|
|
nameGui += "\")";
|
|
macroManager()->addLine(MacroManager::Cmt, nameGui.c_str());
|
|
}
|
|
else {
|
|
nameApp += "App.setActiveDocument(\"\")\n";
|
|
nameApp += "App.ActiveDocument=None";
|
|
macroManager()->addLine(MacroManager::Cmt, nameApp.c_str());
|
|
nameGui += "Gui.ActiveDocument=None";
|
|
macroManager()->addLine(MacroManager::Cmt, nameGui.c_str());
|
|
}
|
|
|
|
// Sets the currently active document
|
|
try {
|
|
Base::Interpreter().runString(nameApp.c_str());
|
|
Base::Interpreter().runString(nameGui.c_str());
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
Base::Console().Warning(e.what());
|
|
return;
|
|
}
|
|
|
|
#ifdef FC_DEBUG
|
|
// May be useful for error detection
|
|
if (d->activeDocument) {
|
|
App::Document* doc = d->activeDocument->getDocument();
|
|
Base::Console().Log("Active document is %s (at %p)\n",
|
|
doc->getName(),
|
|
static_cast<void*>(doc));
|
|
}
|
|
else {
|
|
Base::Console().Log("No active document\n");
|
|
}
|
|
#endif
|
|
|
|
// notify all views attached to the application (not views belong to a special document)
|
|
for (list<Gui::BaseView*>::iterator It = d->passive.begin(); It != d->passive.end(); ++It) {
|
|
(*It)->setDocument(pcDocument);
|
|
}
|
|
}
|
|
|
|
Gui::Document* Application::getDocument(const char* name) const
|
|
{
|
|
App::Document* pDoc = App::GetApplication().getDocument(name);
|
|
std::map<const App::Document*, Gui::Document*>::const_iterator it = d->documents.find(pDoc);
|
|
if (it != d->documents.end()) {
|
|
return it->second;
|
|
}
|
|
else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
Gui::Document* Application::getDocument(const App::Document* pDoc) const
|
|
{
|
|
std::map<const App::Document*, Gui::Document*>::const_iterator it = d->documents.find(pDoc);
|
|
if (it != d->documents.end()) {
|
|
return it->second;
|
|
}
|
|
else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void Application::showViewProvider(const App::DocumentObject* obj)
|
|
{
|
|
ViewProvider* vp = getViewProvider(obj);
|
|
if (vp) {
|
|
vp->show();
|
|
}
|
|
}
|
|
|
|
void Application::hideViewProvider(const App::DocumentObject* obj)
|
|
{
|
|
ViewProvider* vp = getViewProvider(obj);
|
|
if (vp) {
|
|
vp->hide();
|
|
}
|
|
}
|
|
|
|
Gui::ViewProvider* Application::getViewProvider(const App::DocumentObject* obj) const
|
|
{
|
|
return d->viewproviderMap.getViewProvider(obj);
|
|
}
|
|
|
|
void Application::attachView(Gui::BaseView* pcView)
|
|
{
|
|
d->passive.push_back(pcView);
|
|
}
|
|
|
|
void Application::detachView(Gui::BaseView* pcView)
|
|
{
|
|
d->passive.remove(pcView);
|
|
}
|
|
|
|
void Application::onUpdate()
|
|
{
|
|
// update all documents
|
|
std::map<const App::Document*, Gui::Document*>::iterator It;
|
|
for (It = d->documents.begin(); It != d->documents.end(); ++It) {
|
|
It->second->onUpdate();
|
|
}
|
|
// update all the independent views
|
|
for (std::list<Gui::BaseView*>::iterator It2 = d->passive.begin(); It2 != d->passive.end();
|
|
++It2) {
|
|
(*It2)->onUpdate();
|
|
}
|
|
}
|
|
|
|
/// Gets called if a view gets activated, this manages the whole activation scheme
|
|
void Application::viewActivated(MDIView* pcView)
|
|
{
|
|
#ifdef FC_DEBUG
|
|
// May be useful for error detection
|
|
Base::Console().Log("Active view is %s (at %p)\n",
|
|
(const char*)pcView->windowTitle().toUtf8(),
|
|
static_cast<void*>(pcView));
|
|
#endif
|
|
|
|
signalActivateView(pcView);
|
|
getMainWindow()->setWindowTitle(pcView->buildWindowTitle());
|
|
if (auto document = pcView->getGuiDocument()) {
|
|
getMainWindow()->setWindowModified(document->isModified());
|
|
}
|
|
|
|
// Set the new active document which is taken of the activated view. If, however,
|
|
// this view is passive we let the currently active document unchanged as we would
|
|
// have no document active which is causing a lot of trouble.
|
|
if (!pcView->isPassive()) {
|
|
setActiveDocument(pcView->getGuiDocument());
|
|
}
|
|
}
|
|
|
|
/// Gets called if a view gets closed
|
|
void Application::viewClosed(MDIView* pcView)
|
|
{
|
|
signalCloseView(pcView);
|
|
}
|
|
|
|
void Application::updateActive()
|
|
{
|
|
activeDocument()->onUpdate();
|
|
}
|
|
|
|
void Application::updateActions(bool delay)
|
|
{
|
|
getMainWindow()->updateActions(delay);
|
|
}
|
|
|
|
void Application::tryClose(QCloseEvent* e)
|
|
{
|
|
e->setAccepted(getMainWindow()->closeAllDocuments(false));
|
|
if (!e->isAccepted()) {
|
|
return;
|
|
}
|
|
|
|
// ask all passive views if closable
|
|
for (std::list<Gui::BaseView*>::iterator It = d->passive.begin(); It != d->passive.end();
|
|
++It) {
|
|
e->setAccepted((*It)->canClose());
|
|
if (!e->isAccepted()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (e->isAccepted()) {
|
|
d->isClosing = true;
|
|
|
|
std::map<const App::Document*, Gui::Document*>::iterator It;
|
|
|
|
// detach the passive views
|
|
// SetActiveDocument(0);
|
|
std::list<Gui::BaseView*>::iterator itp = d->passive.begin();
|
|
while (itp != d->passive.end()) {
|
|
(*itp)->onClose();
|
|
itp = d->passive.begin();
|
|
}
|
|
|
|
App::GetApplication().closeAllDocuments();
|
|
}
|
|
}
|
|
|
|
int Application::getUserEditMode(const std::string& mode) const
|
|
{
|
|
if (mode.empty()) {
|
|
return userEditMode;
|
|
}
|
|
for (auto const& uem : userEditModes) {
|
|
if (uem.second.first == mode) {
|
|
return uem.first;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
std::pair<std::string, std::string> Application::getUserEditModeUIStrings(int mode) const
|
|
{
|
|
if (mode == -1) {
|
|
return userEditModes.at(userEditMode);
|
|
}
|
|
if (userEditModes.find(mode) != userEditModes.end()) {
|
|
return userEditModes.at(mode);
|
|
}
|
|
return std::make_pair(std::string(), std::string());
|
|
}
|
|
|
|
bool Application::setUserEditMode(int mode)
|
|
{
|
|
if (userEditModes.find(mode) != userEditModes.end() && userEditMode != mode) {
|
|
userEditMode = mode;
|
|
this->signalUserEditModeChanged(userEditMode);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Application::setUserEditMode(const std::string& mode)
|
|
{
|
|
for (auto const& uem : userEditModes) {
|
|
if (uem.second.first == mode) {
|
|
return setUserEditMode(uem.first);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Activate the matching workbench to the registered workbench handler with name \a name.
|
|
* The handler must be an instance of a class written in Python.
|
|
* Normally, if a handler gets activated a workbench with the same name gets created unless it
|
|
* already exists.
|
|
*
|
|
* The old workbench gets deactivated before. If the workbench to the handler is already
|
|
* active or if the switch fails false is returned.
|
|
*/
|
|
bool Application::activateWorkbench(const char* name)
|
|
{
|
|
bool ok = false;
|
|
WaitCursor wc;
|
|
Workbench* oldWb = WorkbenchManager::instance()->active();
|
|
if (oldWb && oldWb->name() == name) {
|
|
return false; // already active
|
|
}
|
|
|
|
Base::PyGILStateLocker lock;
|
|
// we check for the currently active workbench and call its 'Deactivated'
|
|
// method, if available
|
|
PyObject* pcOldWorkbench = nullptr;
|
|
if (oldWb) {
|
|
pcOldWorkbench = PyDict_GetItemString(_pcWorkbenchDictionary, oldWb->name().c_str());
|
|
}
|
|
|
|
// get the python workbench object from the dictionary
|
|
PyObject* pcWorkbench = nullptr;
|
|
pcWorkbench = PyDict_GetItemString(_pcWorkbenchDictionary, name);
|
|
// test if the workbench exists
|
|
if (!pcWorkbench) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
std::string type;
|
|
Py::Object handler(pcWorkbench);
|
|
if (!handler.hasAttr(std::string("__Workbench__"))) {
|
|
// call its GetClassName method if possible
|
|
Py::Callable method(handler.getAttr(std::string("GetClassName")));
|
|
Py::Tuple args;
|
|
Py::String result(method.apply(args));
|
|
type = result.as_std_string("ascii");
|
|
if (Base::Type::fromName(type.c_str())
|
|
.isDerivedFrom(Gui::PythonBaseWorkbench::getClassTypeId())) {
|
|
Workbench* wb = WorkbenchManager::instance()->createWorkbench(name, type);
|
|
if (!wb) {
|
|
throw Py::RuntimeError("Failed to instantiate workbench of type " + type);
|
|
}
|
|
handler.setAttr(std::string("__Workbench__"), Py::Object(wb->getPyObject(), true));
|
|
}
|
|
|
|
// import the matching module first
|
|
Py::Callable activate(handler.getAttr(std::string("Initialize")));
|
|
activate.apply(args);
|
|
|
|
// Dependent on the implementation of a workbench handler the type
|
|
// can be defined after the call of Initialize()
|
|
if (type.empty()) {
|
|
Py::String result(method.apply(args));
|
|
type = result.as_std_string("ascii");
|
|
}
|
|
}
|
|
|
|
// does the Python workbench handler have changed the workbench?
|
|
Workbench* curWb = WorkbenchManager::instance()->active();
|
|
if (curWb && curWb->name() == name) {
|
|
ok = true; // already active
|
|
}
|
|
// now try to create and activate the matching workbench object
|
|
else if (WorkbenchManager::instance()->activate(name, type)) {
|
|
getMainWindow()->activateWorkbench(QString::fromLatin1(name));
|
|
this->signalActivateWorkbench(name);
|
|
ok = true;
|
|
}
|
|
|
|
// if we still not have this member then it must be built-in C++ workbench
|
|
// which could be created after loading the appropriate module
|
|
if (!handler.hasAttr(std::string("__Workbench__"))) {
|
|
Workbench* wb = WorkbenchManager::instance()->getWorkbench(name);
|
|
if (wb) {
|
|
handler.setAttr(std::string("__Workbench__"), Py::Object(wb->getPyObject(), true));
|
|
}
|
|
}
|
|
|
|
// If the method Deactivate is available we call it
|
|
if (pcOldWorkbench) {
|
|
Py::Object handler(pcOldWorkbench);
|
|
if (handler.hasAttr(std::string("Deactivated"))) {
|
|
Py::Object method(handler.getAttr(std::string("Deactivated")));
|
|
if (method.isCallable()) {
|
|
Py::Tuple args;
|
|
Py::Callable activate(method);
|
|
activate.apply(args);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (oldWb) {
|
|
oldWb->deactivated();
|
|
}
|
|
|
|
// If the method Activate is available we call it
|
|
if (handler.hasAttr(std::string("Activated"))) {
|
|
Py::Object method(handler.getAttr(std::string("Activated")));
|
|
if (method.isCallable()) {
|
|
Py::Tuple args;
|
|
Py::Callable activate(method);
|
|
activate.apply(args);
|
|
}
|
|
}
|
|
|
|
// now get the newly activated workbench
|
|
Workbench* newWb = WorkbenchManager::instance()->active();
|
|
if (newWb) {
|
|
if (!Instance->d->startingUp) {
|
|
std::string nameWb = newWb->name();
|
|
App::GetApplication()
|
|
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")
|
|
->SetASCII("LastModule", nameWb.c_str());
|
|
}
|
|
newWb->activated();
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException e; // extract the Python error text
|
|
QString msg = QString::fromUtf8(e.what());
|
|
QRegularExpression rx;
|
|
// ignore '<type 'exceptions.ImportError'>' prefixes
|
|
rx.setPattern(QLatin1String("^\\s*<type 'exceptions.ImportError'>:\\s*"));
|
|
auto match = rx.match(msg);
|
|
while (match.hasMatch()) {
|
|
msg = msg.mid(match.capturedLength());
|
|
match = rx.match(msg);
|
|
}
|
|
|
|
Base::Console().Error("%s\n", (const char*)msg.toUtf8());
|
|
if (!d->startingUp) {
|
|
Base::Console().Error("%s\n", e.getStackTrace().c_str());
|
|
}
|
|
else {
|
|
Base::Console().Log("%s\n", e.getStackTrace().c_str());
|
|
}
|
|
|
|
if (!d->startingUp) {
|
|
wc.restoreCursor();
|
|
QMessageBox::critical(getMainWindow(),
|
|
QObject::tr("Workbench failure"),
|
|
QObject::tr("%1").arg(msg));
|
|
wc.setWaitCursor();
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
QPixmap Application::workbenchIcon(const QString& wb) const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
// get the python workbench object from the dictionary
|
|
PyObject* pcWorkbench = PyDict_GetItemString(_pcWorkbenchDictionary, wb.toLatin1());
|
|
// test if the workbench exists
|
|
if (pcWorkbench) {
|
|
// make a unique icon name
|
|
std::stringstream str;
|
|
str << static_cast<const void*>(pcWorkbench) << std::ends;
|
|
std::string iconName = str.str();
|
|
QPixmap icon;
|
|
if (BitmapFactory().findPixmapInCache(iconName.c_str(), icon)) {
|
|
return icon;
|
|
}
|
|
|
|
// get its Icon member if possible
|
|
try {
|
|
Py::Object handler(pcWorkbench);
|
|
if (handler.hasAttr(std::string("Icon"))) {
|
|
Py::Object member = handler.getAttr(std::string("Icon"));
|
|
Py::String data(member);
|
|
std::string content = data.as_std_string("utf-8");
|
|
|
|
// test if in XPM format
|
|
QByteArray ary;
|
|
int strlen = (int)content.size();
|
|
ary.resize(strlen);
|
|
for (int j = 0; j < strlen; j++) {
|
|
ary[j] = content[j];
|
|
}
|
|
if (ary.indexOf("/* XPM */") > 0) {
|
|
// Make sure to remove crap around the XPM data
|
|
QList<QByteArray> lines = ary.split('\n');
|
|
QByteArray buffer;
|
|
buffer.reserve(ary.size() + lines.size());
|
|
for (QList<QByteArray>::iterator it = lines.begin(); it != lines.end(); ++it) {
|
|
QByteArray trim = it->trimmed();
|
|
if (!trim.isEmpty()) {
|
|
buffer.append(trim);
|
|
buffer.append('\n');
|
|
}
|
|
}
|
|
icon.loadFromData(buffer, "XPM");
|
|
}
|
|
else {
|
|
// is it a file name...
|
|
QString file = QString::fromUtf8(content.c_str());
|
|
icon.load(file);
|
|
if (icon.isNull()) {
|
|
// ... or the name of another icon?
|
|
icon = BitmapFactory().pixmap(file.toUtf8());
|
|
}
|
|
}
|
|
|
|
if (!icon.isNull()) {
|
|
BitmapFactory().addPixmapToCache(iconName.c_str(), icon);
|
|
}
|
|
|
|
return icon;
|
|
}
|
|
}
|
|
catch (Py::Exception& e) {
|
|
e.clear();
|
|
}
|
|
}
|
|
|
|
QIcon icon = QApplication::windowIcon();
|
|
if (!icon.isNull()) {
|
|
QList<QSize> s = icon.availableSizes();
|
|
if (!s.isEmpty()) {
|
|
return icon.pixmap(s[0]);
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
QString Application::workbenchToolTip(const QString& wb) const
|
|
{
|
|
// get the python workbench object from the dictionary
|
|
Base::PyGILStateLocker lock;
|
|
PyObject* pcWorkbench = PyDict_GetItemString(_pcWorkbenchDictionary, wb.toLatin1());
|
|
// test if the workbench exists
|
|
if (pcWorkbench) {
|
|
// get its ToolTip member if possible
|
|
try {
|
|
Py::Object handler(pcWorkbench);
|
|
Py::Object member = handler.getAttr(std::string("ToolTip"));
|
|
if (member.isString()) {
|
|
Py::String tip(member);
|
|
return QString::fromUtf8(tip.as_std_string("utf-8").c_str());
|
|
}
|
|
}
|
|
catch (Py::Exception& e) {
|
|
e.clear();
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
QString Application::workbenchMenuText(const QString& wb) const
|
|
{
|
|
// get the python workbench object from the dictionary
|
|
Base::PyGILStateLocker lock;
|
|
PyObject* pcWorkbench = PyDict_GetItemString(_pcWorkbenchDictionary, wb.toLatin1());
|
|
// test if the workbench exists
|
|
if (pcWorkbench) {
|
|
// get its ToolTip member if possible
|
|
Base::PyGILStateLocker locker;
|
|
try {
|
|
Py::Object handler(pcWorkbench);
|
|
Py::Object member = handler.getAttr(std::string("MenuText"));
|
|
if (member.isString()) {
|
|
Py::String tip(member);
|
|
return QString::fromUtf8(tip.as_std_string("utf-8").c_str());
|
|
}
|
|
}
|
|
catch (Py::Exception& e) {
|
|
e.clear();
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
QStringList Application::workbenches() const
|
|
{
|
|
// If neither 'HiddenWorkbench' nor 'ExtraWorkbench' is set then all workbenches are returned.
|
|
const std::map<std::string, std::string>& config = App::Application::Config();
|
|
auto ht = config.find("HiddenWorkbench");
|
|
auto et = config.find("ExtraWorkbench");
|
|
auto st = config.find("StartWorkbench");
|
|
const char* start = (st != config.end() ? st->second.c_str() : "<none>");
|
|
QStringList hidden, extra;
|
|
if (ht != config.end()) {
|
|
QString items = QString::fromLatin1(ht->second.c_str());
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
|
hidden = items.split(QLatin1Char(';'), Qt::SkipEmptyParts);
|
|
#else
|
|
hidden = items.split(QLatin1Char(';'), QString::SkipEmptyParts);
|
|
#endif
|
|
if (hidden.isEmpty()) {
|
|
hidden.push_back(QLatin1String(""));
|
|
}
|
|
}
|
|
if (et != config.end()) {
|
|
QString items = QString::fromLatin1(et->second.c_str());
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
|
extra = items.split(QLatin1Char(';'), Qt::SkipEmptyParts);
|
|
#else
|
|
extra = items.split(QLatin1Char(';'), QString::SkipEmptyParts);
|
|
#endif
|
|
if (extra.isEmpty()) {
|
|
extra.push_back(QLatin1String(""));
|
|
}
|
|
}
|
|
|
|
PyObject *key, *value;
|
|
Py_ssize_t pos = 0;
|
|
QStringList wb;
|
|
// insert all items
|
|
while (PyDict_Next(_pcWorkbenchDictionary, &pos, &key, &value)) {
|
|
/* do something interesting with the values... */
|
|
const char* wbName = PyUnicode_AsUTF8(key);
|
|
// add only allowed workbenches
|
|
bool ok = true;
|
|
if (!extra.isEmpty() && ok) {
|
|
ok = (extra.indexOf(QString::fromLatin1(wbName)) != -1);
|
|
}
|
|
if (!hidden.isEmpty() && ok) {
|
|
ok = (hidden.indexOf(QString::fromLatin1(wbName)) == -1);
|
|
}
|
|
|
|
// okay the item is visible
|
|
if (ok) {
|
|
wb.push_back(QString::fromLatin1(wbName));
|
|
}
|
|
// also allow start workbench in case it is hidden
|
|
else if (strcmp(wbName, start) == 0) {
|
|
wb.push_back(QString::fromLatin1(wbName));
|
|
}
|
|
}
|
|
|
|
return wb;
|
|
}
|
|
|
|
void Application::setupContextMenu(const char* recipient, MenuItem* items) const
|
|
{
|
|
Workbench* actWb = WorkbenchManager::instance()->active();
|
|
if (actWb) {
|
|
// when populating the context-menu of a Python workbench invoke the method
|
|
// 'ContextMenu' of the handler object
|
|
if (actWb->isDerivedFrom<PythonWorkbench>()) {
|
|
static_cast<PythonWorkbench*>(actWb)->clearContextMenu();
|
|
Base::PyGILStateLocker lock;
|
|
PyObject* pWorkbench = nullptr;
|
|
pWorkbench = PyDict_GetItemString(_pcWorkbenchDictionary, actWb->name().c_str());
|
|
|
|
try {
|
|
// call its GetClassName method if possible
|
|
Py::Object handler(pWorkbench);
|
|
Py::Callable method(handler.getAttr(std::string("ContextMenu")));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, Py::String(recipient));
|
|
method.apply(args);
|
|
}
|
|
catch (Py::Exception& e) {
|
|
Py::Object o = Py::type(e);
|
|
e.clear();
|
|
if (o.isString()) {
|
|
Py::String s(o);
|
|
std::clog << "Application::setupContextMenu: " << s.as_std_string("utf-8")
|
|
<< std::endl;
|
|
}
|
|
}
|
|
}
|
|
actWb->createContextMenu(recipient, items);
|
|
}
|
|
}
|
|
|
|
bool Application::isClosing()
|
|
{
|
|
return d->isClosing;
|
|
}
|
|
|
|
MacroManager* Application::macroManager()
|
|
{
|
|
return d->macroMngr;
|
|
}
|
|
|
|
CommandManager& Application::commandManager()
|
|
{
|
|
return d->commandManager;
|
|
}
|
|
|
|
Gui::PreferencePackManager* Application::prefPackManager()
|
|
{
|
|
return d->prefPackManager;
|
|
}
|
|
|
|
|
|
//**************************************************************************
|
|
// Init, Destruct and singleton
|
|
|
|
namespace
|
|
{
|
|
void setCategoryFilterRules()
|
|
{
|
|
QString filter;
|
|
QTextStream stream(&filter);
|
|
stream << "qt.qpa.xcb.warning=false\n";
|
|
stream << "qt.qpa.mime.warning=false\n";
|
|
stream << "qt.qpa.wayland.warning=false\n";
|
|
stream << "qt.qpa.wayland.*.warning=false\n";
|
|
stream << "qt.svg.warning=false\n";
|
|
stream << "qt.xkb.compose.warning=false\n";
|
|
stream << "kf.*.warning=false\n";
|
|
stream.flush();
|
|
QLoggingCategory::setFilterRules(filter);
|
|
}
|
|
} // namespace
|
|
|
|
using _qt_msg_handler_old = void (*)(QtMsgType, const QMessageLogContext&, const QString&);
|
|
_qt_msg_handler_old old_qtmsg_handler = nullptr;
|
|
|
|
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg)
|
|
{
|
|
QByteArray output;
|
|
if (context.category && strcmp(context.category, "default") != 0) {
|
|
output.append('(');
|
|
output.append(context.category);
|
|
output.append(')');
|
|
output.append(' ');
|
|
}
|
|
|
|
output.append(msg.toUtf8());
|
|
|
|
switch (type) {
|
|
case QtInfoMsg:
|
|
case QtDebugMsg:
|
|
#ifdef FC_DEBUG
|
|
Base::Console().Message("%s\n", output.constData());
|
|
#else
|
|
// do not stress user with Qt internals but write to log file if enabled
|
|
Base::Console().Log("%s\n", output.constData());
|
|
#endif
|
|
break;
|
|
case QtWarningMsg:
|
|
Base::Console().Warning("%s\n", output.constData());
|
|
break;
|
|
case QtCriticalMsg:
|
|
Base::Console().Error("%s\n", output.constData());
|
|
break;
|
|
case QtFatalMsg:
|
|
Base::Console().Error("%s\n", output.constData());
|
|
abort(); // deliberately core dump
|
|
}
|
|
#ifdef FC_OS_WIN32
|
|
if (old_qtmsg_handler) {
|
|
(*old_qtmsg_handler)(type, context, msg);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef FC_DEBUG // redirect Coin messages to FreeCAD
|
|
void messageHandlerCoin(const SoError* error, void* /*userdata*/)
|
|
{
|
|
if (error && error->getTypeId() == SoDebugError::getClassTypeId()) {
|
|
const SoDebugError* dbg = static_cast<const SoDebugError*>(error);
|
|
const char* msg = error->getDebugString().getString();
|
|
switch (dbg->getSeverity()) {
|
|
case SoDebugError::INFO:
|
|
Base::Console().Message("%s\n", msg);
|
|
break;
|
|
case SoDebugError::WARNING:
|
|
Base::Console().Warning("%s\n", msg);
|
|
break;
|
|
default: // error
|
|
Base::Console().Error("%s\n", msg);
|
|
break;
|
|
}
|
|
#ifdef FC_OS_WIN32
|
|
if (old_qtmsg_handler) {
|
|
(*old_qtmsg_handler)(QtDebugMsg, QMessageLogContext(), QString::fromLatin1(msg));
|
|
}
|
|
#endif
|
|
}
|
|
else if (error) {
|
|
const char* msg = error->getDebugString().getString();
|
|
Base::Console().Log(msg);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
// To fix bug #0000345 move Q_INIT_RESOURCE() outside initApplication()
|
|
static void init_resources()
|
|
{
|
|
// init resources
|
|
Q_INIT_RESOURCE(resource);
|
|
Q_INIT_RESOURCE(translation);
|
|
Q_INIT_RESOURCE(FreeCAD_translation);
|
|
}
|
|
|
|
void Application::initApplication()
|
|
{
|
|
static bool init = false;
|
|
if (init) {
|
|
Base::Console().Error("Tried to run Gui::Application::initApplication() twice!\n");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
initTypes();
|
|
new Base::ScriptProducer("FreeCADGuiInit", FreeCADGuiInit);
|
|
init_resources();
|
|
setCategoryFilterRules();
|
|
old_qtmsg_handler = qInstallMessageHandler(messageHandler);
|
|
init = true;
|
|
}
|
|
catch (...) {
|
|
// force to flush the log
|
|
App::Application::destructObserver();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void Application::initTypes()
|
|
{
|
|
// clang-format off
|
|
// views
|
|
Gui::BaseView ::init();
|
|
Gui::MDIView ::init();
|
|
Gui::View3DInventor ::init();
|
|
Gui::AbstractSplitView ::init();
|
|
Gui::SplitView3DInventor ::init();
|
|
Gui::TextDocumentEditorView ::init();
|
|
Gui::EditorView ::init();
|
|
Gui::PythonEditorView ::init();
|
|
// View Provider
|
|
Gui::ViewProvider ::init();
|
|
Gui::ViewProviderExtension ::init();
|
|
Gui::ViewProviderExtensionPython ::init();
|
|
Gui::ViewProviderGroupExtension ::init();
|
|
Gui::ViewProviderGroupExtensionPython ::init();
|
|
Gui::ViewProviderGeoFeatureGroupExtension ::init();
|
|
Gui::ViewProviderGeoFeatureGroupExtensionPython::init();
|
|
Gui::ViewProviderOriginGroupExtension ::init();
|
|
Gui::ViewProviderOriginGroupExtensionPython ::init();
|
|
Gui::ViewProviderSuppressibleExtension ::init();
|
|
Gui::ViewProviderSuppressibleExtensionPython::init();
|
|
Gui::ViewProviderExtern ::init();
|
|
Gui::ViewProviderDocumentObject ::init();
|
|
Gui::ViewProviderFeature ::init();
|
|
Gui::ViewProviderDocumentObjectGroup ::init();
|
|
Gui::ViewProviderDocumentObjectGroupPython ::init();
|
|
Gui::ViewProviderDragger ::init();
|
|
Gui::ViewProviderGeometryObject ::init();
|
|
Gui::ViewProviderImagePlane ::init();
|
|
Gui::ViewProviderInventorObject ::init();
|
|
Gui::ViewProviderVRMLObject ::init();
|
|
Gui::ViewProviderAnnotation ::init();
|
|
Gui::ViewProviderAnnotationLabel ::init();
|
|
Gui::ViewProviderFeaturePython ::init();
|
|
Gui::ViewProviderGeometryPython ::init();
|
|
Gui::ViewProviderPlacement ::init();
|
|
Gui::ViewProviderPlacementPython ::init();
|
|
Gui::ViewProviderOriginFeature ::init();
|
|
Gui::ViewProviderPlane ::init();
|
|
Gui::ViewProviderLine ::init();
|
|
Gui::ViewProviderGeoFeatureGroup ::init();
|
|
Gui::ViewProviderGeoFeatureGroupPython ::init();
|
|
Gui::ViewProviderOriginGroup ::init();
|
|
Gui::ViewProviderPart ::init();
|
|
Gui::ViewProviderOrigin ::init();
|
|
Gui::ViewProviderMaterialObject ::init();
|
|
Gui::ViewProviderMaterialObjectPython ::init();
|
|
Gui::ViewProviderTextDocument ::init();
|
|
Gui::ViewProviderTextureExtension ::init();
|
|
Gui::ViewProviderFaceTexture ::init();
|
|
Gui::ViewProviderLinkObserver ::init();
|
|
Gui::LinkView ::init();
|
|
Gui::ViewProviderLink ::init();
|
|
Gui::ViewProviderLinkPython ::init();
|
|
Gui::ViewProviderVarSet ::init();
|
|
Gui::AxisOrigin ::init();
|
|
|
|
// Workbench
|
|
Gui::Workbench ::init();
|
|
Gui::StdWorkbench ::init();
|
|
Gui::BlankWorkbench ::init();
|
|
Gui::NoneWorkbench ::init();
|
|
Gui::TestWorkbench ::init();
|
|
Gui::PythonBaseWorkbench ::init();
|
|
Gui::PythonBlankWorkbench ::init();
|
|
Gui::PythonWorkbench ::init();
|
|
|
|
// register transaction type
|
|
new App::TransactionProducer<TransactionViewProvider>
|
|
(ViewProviderDocumentObject::getClassTypeId());
|
|
// clang-format on
|
|
}
|
|
|
|
void Application::initOpenInventor()
|
|
{
|
|
// init the Inventor subsystem
|
|
SoDB::init();
|
|
SIM::Coin3D::Quarter::Quarter::init();
|
|
SoFCDB::init();
|
|
}
|
|
|
|
void Application::runInitGuiScript()
|
|
{
|
|
Base::Interpreter().runString(Base::ScriptFactory().ProduceScript("FreeCADGuiInit"));
|
|
}
|
|
|
|
namespace
|
|
{
|
|
bool onlySingleInstance(GUISingleApplication& mainApp)
|
|
{
|
|
const std::map<std::string, std::string>& cfg = App::Application::Config();
|
|
auto it = cfg.find("SingleInstance");
|
|
if (it != cfg.end() && mainApp.isRunning()) {
|
|
// send the file names to be opened to the server application so that this
|
|
// opens them
|
|
QDir cwd = QDir::current();
|
|
std::list<std::string> files = App::Application::getCmdLineFiles();
|
|
for (const auto& file : files) {
|
|
QString fn = QString::fromUtf8(file.c_str(), static_cast<int>(file.size()));
|
|
QFileInfo fi(fn);
|
|
// if path name is relative make it absolute because the running instance
|
|
// cannot determine the full path when trying to load the file
|
|
if (fi.isRelative()) {
|
|
fn = cwd.absoluteFilePath(fn);
|
|
fn = QDir::cleanPath(fn);
|
|
}
|
|
|
|
fn.prepend(QLatin1String("OpenFile:"));
|
|
if (!mainApp.sendMessage(fn)) {
|
|
qWarning("Failed to send OpenFile message to server");
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void setAppNameAndIcon()
|
|
{
|
|
const std::map<std::string, std::string>& cfg = App::Application::Config();
|
|
|
|
// set application icon and window title
|
|
auto it = cfg.find("Application");
|
|
if (it != cfg.end()) {
|
|
QApplication::setApplicationName(QString::fromUtf8(it->second.c_str()));
|
|
}
|
|
else {
|
|
QApplication::setApplicationName(
|
|
QString::fromStdString(App::Application::getExecutableName()));
|
|
}
|
|
#ifndef Q_OS_MACOS
|
|
QApplication::setWindowIcon(
|
|
Gui::BitmapFactory().pixmap(App::Application::Config()["AppIcon"].c_str()));
|
|
#endif
|
|
}
|
|
|
|
void tryRunEventLoop(GUISingleApplication& mainApp)
|
|
{
|
|
std::stringstream out;
|
|
out << App::Application::getUserCachePath()
|
|
<< App::Application::getExecutableName()
|
|
<< "_"
|
|
<< QCoreApplication::applicationPid()
|
|
<< ".lock";
|
|
|
|
// open a lock file with the PID
|
|
Base::FileInfo fi(out.str());
|
|
Base::ofstream lock(fi);
|
|
|
|
// In case the file_lock cannot be created start FreeCAD without IPC support.
|
|
#if !defined(FC_OS_WIN32) || (BOOST_VERSION < 107600)
|
|
std::string filename = out.str();
|
|
#else
|
|
std::wstring filename = fi.toStdWString();
|
|
#endif
|
|
try {
|
|
boost::interprocess::file_lock flock(filename.c_str());
|
|
if (flock.try_lock()) {
|
|
Base::Console().Log("Init: Executing event loop...\n");
|
|
QApplication::exec();
|
|
|
|
// Qt can't handle exceptions thrown from event handlers, so we need
|
|
// to manually rethrow SystemExitExceptions.
|
|
if (mainApp.caughtException) {
|
|
throw Base::SystemExitException(*mainApp.caughtException.get());
|
|
}
|
|
|
|
// close the lock file, in case of a crash we can see the existing lock file
|
|
// on the next restart and try to repair the documents, if needed.
|
|
flock.unlock();
|
|
lock.close();
|
|
fi.deleteFile();
|
|
}
|
|
else {
|
|
Base::Console().Warning("Failed to create a file lock for the IPC.\n"
|
|
"The application will be terminated\n");
|
|
}
|
|
}
|
|
catch (const boost::interprocess::interprocess_exception& e) {
|
|
QString msg = QString::fromLocal8Bit(e.what());
|
|
Base::Console().Warning("Failed to create a file lock for the IPC: %s\n",
|
|
msg.toUtf8().constData());
|
|
}
|
|
}
|
|
|
|
void runEventLoop(GUISingleApplication& mainApp)
|
|
{
|
|
try {
|
|
tryRunEventLoop(mainApp);
|
|
}
|
|
catch (const Base::SystemExitException&) {
|
|
Base::Console().Message("System exit\n");
|
|
throw;
|
|
}
|
|
catch (const std::exception& e) {
|
|
// catching nasty stuff coming out of the event loop
|
|
Base::Console().Error("Event loop left through unhandled exception: %s\n", e.what());
|
|
App::Application::destructObserver();
|
|
throw;
|
|
}
|
|
catch (...) {
|
|
// catching nasty stuff coming out of the event loop
|
|
Base::Console().Error("Event loop left through unknown unhandled exception\n");
|
|
App::Application::destructObserver();
|
|
throw;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
void Application::runApplication()
|
|
{
|
|
StartupProcess::setupApplication();
|
|
|
|
// A new QApplication
|
|
Base::Console().Log("Init: Creating Gui::Application and QApplication\n");
|
|
|
|
// if application not yet created by the splasher
|
|
int argc = App::Application::GetARGC();
|
|
GUISingleApplication mainApp(argc, App::Application::GetARGV());
|
|
// https://forum.freecad.org/viewtopic.php?f=3&t=15540
|
|
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus, false);
|
|
|
|
// Make sure that we use '.' as decimal point. See also
|
|
// http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=559846
|
|
// and issue #0002891
|
|
// http://doc.qt.io/qt-5/qcoreapplication.html#locale-settings
|
|
setlocale(LC_NUMERIC, "C");
|
|
|
|
// check if a single or multiple instances can run
|
|
if (onlySingleInstance(mainApp)) {
|
|
return;
|
|
}
|
|
|
|
setAppNameAndIcon();
|
|
|
|
StartupProcess process;
|
|
process.execute();
|
|
|
|
Application app(true);
|
|
MainWindow mw;
|
|
mw.setProperty("QuitOnClosed", true);
|
|
|
|
#ifdef FC_DEBUG // redirect Coin messages to FreeCAD
|
|
SoDebugError::setHandlerCallback(messageHandlerCoin, 0);
|
|
#endif
|
|
|
|
StartupPostProcess postProcess(&mw, app, &mainApp);
|
|
postProcess.execute();
|
|
|
|
Instance->d->startingUp = false;
|
|
|
|
// gets called once we start the event loop
|
|
QTimer::singleShot(0, &mw, SLOT(delayedStartup()));
|
|
|
|
// run the Application event loop
|
|
Base::Console().Log("Init: Entering event loop\n");
|
|
|
|
// boot phase reference point
|
|
// https://forum.freecad.org/viewtopic.php?f=10&t=21665
|
|
Gui::getMainWindow()->setProperty("eventLoop", true);
|
|
|
|
#ifdef USE_3DCONNEXION_NAVLIB
|
|
Instance->pNavlibInterface->enableNavigation();
|
|
#endif
|
|
|
|
runEventLoop(mainApp);
|
|
|
|
Base::Console().Log("Finish: Event loop left\n");
|
|
}
|
|
|
|
bool Application::testStatus(Status pos) const
|
|
{
|
|
return d->StatusBits.test((size_t)pos);
|
|
}
|
|
|
|
void Application::setStatus(Status pos, bool on)
|
|
{
|
|
d->StatusBits.set((size_t)pos, on);
|
|
}
|
|
|
|
void Application::setStyleSheet(const QString& qssFile, bool tiledBackground)
|
|
{
|
|
Gui::MainWindow* mw = getMainWindow();
|
|
auto mdi = mw->findChild<QMdiArea*>();
|
|
mdi->setProperty("showImage", tiledBackground);
|
|
|
|
// Qt's style sheet doesn't support it to define the link color of a QLabel
|
|
// or in the property editor when an expression is set because therefore the
|
|
// link color of the application's palette is used.
|
|
// A workaround is to set a user-defined property to e.g. a QLabel and then
|
|
// define it in the .qss file.
|
|
//
|
|
// Example:
|
|
// QLabel label;
|
|
// label.setProperty("haslink", QByteArray("true"));
|
|
// label.show();
|
|
// QColor link = label.palette().color(QPalette::Text);
|
|
//
|
|
// The .qss file must define it with:
|
|
// QLabel[haslink="true"] {
|
|
// color: #rrggbb;
|
|
// }
|
|
//
|
|
// See
|
|
// https://stackoverflow.com/questions/5497799/how-do-i-customise-the-appearance-of-links-in-qlabels-using-style-sheets
|
|
// and https://forum.freecad.org/viewtopic.php?f=34&t=50744
|
|
static bool init = true;
|
|
if (init) {
|
|
init = false;
|
|
mw->setProperty("fc_originalLinkCoor", qApp->palette().color(QPalette::Link));
|
|
}
|
|
else {
|
|
QPalette newPal(qApp->palette());
|
|
newPal.setColor(QPalette::Link, mw->property("fc_originalLinkCoor").value<QColor>());
|
|
qApp->setPalette(newPal);
|
|
}
|
|
|
|
mw->setProperty("fc_currentStyleSheet", qssFile);
|
|
mw->setProperty("fc_tiledBackground", tiledBackground);
|
|
|
|
if (!qssFile.isEmpty()) {
|
|
// Search for stylesheet in user-defined search paths.
|
|
// For qss they are set-up in runApplication() with the prefix "qss"
|
|
QString prefix(QLatin1String("qss:"));
|
|
|
|
QFile f;
|
|
if (QFile::exists(qssFile)) {
|
|
f.setFileName(qssFile);
|
|
}
|
|
else if (QFile::exists(prefix + qssFile)) {
|
|
f.setFileName(prefix + qssFile);
|
|
}
|
|
|
|
if (!f.fileName().isEmpty() && f.open(QFile::ReadOnly | QFile::Text)) {
|
|
mdi->setBackground(QBrush(Qt::NoBrush));
|
|
QTextStream str(&f);
|
|
|
|
QString styleSheetContent = replaceVariablesInQss(str.readAll());
|
|
|
|
qApp->setStyleSheet(styleSheetContent);
|
|
|
|
ActionStyleEvent e(ActionStyleEvent::Clear);
|
|
qApp->sendEvent(mw, &e);
|
|
|
|
// This is a way to retrieve the link color of a .qss file when it's defined there.
|
|
// The color will then be set to the application's palette.
|
|
// Limitation: it doesn't work if the .qss file on purpose sets the same color as
|
|
// for normal text. In this case the default link color is used.
|
|
{
|
|
QLabel l1, l2;
|
|
l2.setProperty("haslink", QByteArray("true"));
|
|
|
|
l1.show();
|
|
l2.show();
|
|
QColor text = l1.palette().color(QPalette::Text);
|
|
QColor link = l2.palette().color(QPalette::Text);
|
|
|
|
if (text != link) {
|
|
QPalette newPal(qApp->palette());
|
|
newPal.setColor(QPalette::Link, link);
|
|
qApp->setPalette(newPal);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (tiledBackground) {
|
|
qApp->setStyleSheet(QString());
|
|
ActionStyleEvent e(ActionStyleEvent::Restore);
|
|
qApp->sendEvent(getMainWindow(), &e);
|
|
mdi->setBackground(QPixmap(QLatin1String("images:background.png")));
|
|
}
|
|
else {
|
|
qApp->setStyleSheet(QString());
|
|
ActionStyleEvent e(ActionStyleEvent::Restore);
|
|
qApp->sendEvent(getMainWindow(), &e);
|
|
mdi->setBackground(QBrush(QColor(160, 160, 160)));
|
|
}
|
|
}
|
|
|
|
// At startup time unpolish() mustn't be executed because otherwise the QSint widget
|
|
// appear incorrect due to an outdated cache.
|
|
// See https://doc.qt.io/qt-5/qstyle.html#unpolish-1
|
|
// See https://forum.freecad.org/viewtopic.php?f=17&t=50783
|
|
if (!d->startingUp) {
|
|
if (mdi->style()) {
|
|
mdi->style()->unpolish(qApp);
|
|
}
|
|
}
|
|
}
|
|
|
|
QString Application::replaceVariablesInQss(QString qssText)
|
|
{
|
|
// First we fetch the colors from preferences,
|
|
ParameterGrp::handle hGrp =
|
|
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Themes");
|
|
unsigned long longAccentColor1 = hGrp->GetUnsigned("ThemeAccentColor1", 0);
|
|
unsigned long longAccentColor2 = hGrp->GetUnsigned("ThemeAccentColor2", 0);
|
|
unsigned long longAccentColor3 = hGrp->GetUnsigned("ThemeAccentColor3", 0);
|
|
|
|
// convert them to hex.
|
|
// Note: the ulong contains alpha channels so 8 hex characters when we need 6 here.
|
|
QString accentColor1 = QString::fromLatin1("#%1")
|
|
.arg(longAccentColor1, 8, 16, QLatin1Char('0'))
|
|
.toUpper()
|
|
.mid(0, 7);
|
|
QString accentColor2 = QString::fromLatin1("#%1")
|
|
.arg(longAccentColor2, 8, 16, QLatin1Char('0'))
|
|
.toUpper()
|
|
.mid(0, 7);
|
|
QString accentColor3 = QString::fromLatin1("#%1")
|
|
.arg(longAccentColor3, 8, 16, QLatin1Char('0'))
|
|
.toUpper()
|
|
.mid(0, 7);
|
|
|
|
qssText = qssText.replace(QString::fromLatin1("@ThemeAccentColor1"), accentColor1);
|
|
qssText = qssText.replace(QString::fromLatin1("@ThemeAccentColor2"), accentColor2);
|
|
qssText = qssText.replace(QString::fromLatin1("@ThemeAccentColor3"), accentColor3);
|
|
|
|
// Base::Console().Warning("%s\n", qssText.toStdString());
|
|
return qssText;
|
|
}
|
|
|
|
void Application::checkForDeprecatedSettings()
|
|
{
|
|
// From 0.21, `FCBak` will be the intended default backup format
|
|
bool makeBackups = App::GetApplication()
|
|
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")
|
|
->GetBool("CreateBackupFiles", true);
|
|
if (makeBackups) {
|
|
bool useFCBakExtension =
|
|
App::GetApplication()
|
|
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")
|
|
->GetBool("UseFCBakExtension", true);
|
|
if (!useFCBakExtension) {
|
|
// TODO: This should be translated
|
|
Base::Console().Warning("The `.FCStd#` backup format is deprecated and may "
|
|
"be removed in future versions.\n"
|
|
"To update, check the 'Preferences->General->Document->Use "
|
|
"date and FCBak extension' option.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
void Application::checkForPreviousCrashes()
|
|
{
|
|
try {
|
|
Gui::Dialog::DocumentRecoveryFinder finder;
|
|
if (!finder.checkForPreviousCrashes()) {
|
|
|
|
// If the recovery dialog wasn't shown check the cache size periodically
|
|
Gui::Dialog::ApplicationCache cache;
|
|
cache.applyUserSettings();
|
|
if (cache.periodicCheckOfSize()) {
|
|
qint64 total = cache.size();
|
|
cache.performAction(total);
|
|
}
|
|
}
|
|
}
|
|
catch (const boost::interprocess::interprocess_exception& e) {
|
|
QString msg = QString::fromLocal8Bit(e.what());
|
|
Base::Console().Warning("Failed check for previous crashes because of IPC error: %s\n",
|
|
msg.toUtf8().constData());
|
|
}
|
|
}
|
|
|
|
App::Document* Application::reopen(App::Document* doc)
|
|
{
|
|
if (!doc) {
|
|
return nullptr;
|
|
}
|
|
std::string name = doc->FileName.getValue();
|
|
std::set<const Gui::Document*> 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<std::string> docs;
|
|
for (auto d : doc->getDependentDocuments(true)) {
|
|
if (d->testStatus(App::Document::PartialDoc)
|
|
|| d->testStatus(App::Document::PartialRestore)) {
|
|
docs.emplace_back(d->FileName.getValue());
|
|
}
|
|
}
|
|
|
|
if (docs.empty()) {
|
|
Document* gdoc = getDocument(doc);
|
|
if (gdoc) {
|
|
setActiveDocument(gdoc);
|
|
if (!gdoc->setActiveView()) {
|
|
gdoc->setActiveView(nullptr, View3DInventor::getClassTypeId());
|
|
}
|
|
}
|
|
return doc;
|
|
}
|
|
|
|
for (auto& file : docs) {
|
|
App::GetApplication().openDocument(file.c_str(), false);
|
|
}
|
|
}
|
|
|
|
doc = nullptr;
|
|
for (auto& v : d->documents) {
|
|
if (name == v.first->FileName.getValue()) {
|
|
doc = const_cast<App::Document*>(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<App::Property*> props;
|
|
obj->getPropertyList(props);
|
|
for (auto prop : props) {
|
|
auto link = dynamic_cast<App::PropertyLinkBase*>(prop);
|
|
if (link && link->checkRestore()) {
|
|
reset = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!reset) {
|
|
break;
|
|
}
|
|
}
|
|
if (reset) {
|
|
v.second->getDocument()->purgeTouched();
|
|
v.second->setModified(false);
|
|
}
|
|
}
|
|
}
|
|
return doc;
|
|
}
|