// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (C) 2015 Alexander Golubev (Fat-Zer) * * * * 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 #include #include #include #include #include #include #include #include #include "WorkflowManager.h" using namespace PartDesignGui; namespace sp = std::placeholders; WorkflowManager* WorkflowManager::_instance = nullptr; WorkflowManager::WorkflowManager() { // Fill the map with already opened documents for (auto doc : App::GetApplication().getDocuments()) { slotFinishRestoreDocument(*doc); } // NOLINTBEGIN connectNewDocument = App::GetApplication().signalNewDocument.connect( std::bind(&WorkflowManager::slotNewDocument, this, sp::_1) ); connectFinishRestoreDocument = App::GetApplication().signalFinishRestoreDocument.connect( std::bind(&WorkflowManager::slotFinishRestoreDocument, this, sp::_1) ); connectDeleteDocument = App::GetApplication().signalDeleteDocument.connect( std::bind(&WorkflowManager::slotDeleteDocument, this, sp::_1) ); // NOLINTEND } WorkflowManager::~WorkflowManager() { // they won't be automatically disconnected on destruction! connectNewDocument.disconnect(); connectFinishRestoreDocument.disconnect(); connectDeleteDocument.disconnect(); } // Those destruction/construction is not really needed and could be done in the instance() // but to make things a bit more clear, better to keep them around. void WorkflowManager::init() { if (!_instance) { _instance = new WorkflowManager(); } else { // throw Base::RuntimeError( "Trying to init the workflow manager second time." ); } } WorkflowManager* WorkflowManager::instance() { if (!_instance) { WorkflowManager::init(); } return _instance; } void WorkflowManager::destruct() { if (_instance) { delete _instance; _instance = nullptr; } } void WorkflowManager::slotNewDocument(const App::Document& doc) { // new document always uses new workflow dwMap[&doc] = Workflow::Modern; } void WorkflowManager::slotFinishRestoreDocument(const App::Document& doc) { Workflow wf = guessWorkflow(&doc); // Mark document as undetermined if the guessed workflow is not new if (wf != Workflow::Modern) { wf = Workflow::Undetermined; } dwMap[&doc] = wf; } void WorkflowManager::slotDeleteDocument(const App::Document& doc) { dwMap.erase(&doc); } Workflow WorkflowManager::getWorkflowForDocument(App::Document* doc) { assert(doc); auto it = dwMap.find(doc); if (it != dwMap.end()) { return it->second; } else { // We haven't yet checked the file workflow // May happen if e.g. file not completely loaded yet return Workflow::Undetermined; } } Workflow WorkflowManager::determineWorkflow(App::Document* doc) { Workflow rv = getWorkflowForDocument(doc); if (rv != Workflow::Undetermined) { // Return if workflow is known return rv; } // Guess the workflow again rv = guessWorkflow(doc); if (rv != Workflow::Modern) { QMessageBox msgBox(Gui::getMainWindow()); if (rv == Workflow::Legacy) { // legacy messages msgBox.setText( QObject::tr( "The document \"%1\" you are editing was designed with an old version of " "Part Design workbench." ) .arg(QString::fromStdString(doc->getName())) ); msgBox.setInformativeText( QObject::tr("Migrate in order to use modern Part Design features?") ); } else { // The document is already in the middle of migration msgBox.setText( QObject::tr( "The document \"%1\" seems to be either in the middle of" " the migration process from legacy Part Design or have a slightly broken " "structure." ) .arg(QString::fromStdString(doc->getName())) ); msgBox.setInformativeText(QObject::tr("Make the migration automatically?")); } msgBox.setDetailedText( QObject::tr( "Note: If you choose to migrate you won't be able to edit" " the file with an older FreeCAD version.\n" "If you refuse to migrate you won't be able to use new PartDesign features" " like Bodies and Parts. As a result you also won't be able to use your parts" " in the assembly workbench.\n" "Although you will be able to migrate any moment later with 'Part Design -> " "Migrate'." ) ); msgBox.setIcon(QMessageBox::Question); QPushButton* yesBtn = msgBox.addButton(QMessageBox::Yes); QPushButton* manuallyBtn = msgBox.addButton(QObject::tr("Migrate Manually"), QMessageBox::YesRole); // If it is already a document in the middle of the migration the user shouldn't refuse to // migrate if (rv != Workflow::Undetermined) { msgBox.addButton(QMessageBox::No); } msgBox.setDefaultButton(yesBtn); // TODO: Add some description of manual migration mode (2015-08-09, Fat-Zer) msgBox.exec(); if (msgBox.clickedButton() == yesBtn) { Gui::Application::Instance->commandManager().runCommandByName("PartDesign_Migrate"); rv = Workflow::Modern; } else if (msgBox.clickedButton() == manuallyBtn) { rv = Workflow::Undetermined; } else { rv = Workflow::Legacy; } } // Actually set the result in our map dwMap[doc] = rv; return rv; } void WorkflowManager::forceWorkflow(const App::Document* doc, Workflow wf) { dwMap[doc] = wf; } Workflow WorkflowManager::guessWorkflow(const App::Document* doc) { // Retrieve bodies of the document auto features = doc->getObjectsOfType(PartDesign::Feature::getClassTypeId()); if (features.empty()) { // a new file should be done in the new workflow return Workflow::Modern; } else { auto bodies = doc->getObjectsOfType(PartDesign::Body::getClassTypeId()); if (bodies.empty()) { // If there are no bodies workflow is legacy return Workflow::Legacy; } else { bool features_without_bodies = false; for (auto feat : features) { if (!PartDesign::Body::findBodyOf(feat)) { features_without_bodies = true; break; } } // if there are features not belonging to any body it means that migration was // incomplete, otherwise it's Modern return features_without_bodies ? Workflow::Undetermined : Workflow::Modern; } } }