diff --git a/src/Mod/PartDesign/Gui/CMakeLists.txt b/src/Mod/PartDesign/Gui/CMakeLists.txt index 90579ba3a4..b6869b2f0c 100644 --- a/src/Mod/PartDesign/Gui/CMakeLists.txt +++ b/src/Mod/PartDesign/Gui/CMakeLists.txt @@ -233,6 +233,8 @@ SET(PartDesignGuiModule_SRCS Utils.h Workbench.cpp Workbench.h + WorkflowManager.cpp + WorkflowManager.h ) SOURCE_GROUP("Module" FILES ${PartDesignGuiModule_SRCS}) diff --git a/src/Mod/PartDesign/Gui/CommandBody.cpp b/src/Mod/PartDesign/Gui/CommandBody.cpp index 7370887dbc..c0d5ef6168 100644 --- a/src/Mod/PartDesign/Gui/CommandBody.cpp +++ b/src/Mod/PartDesign/Gui/CommandBody.cpp @@ -39,6 +39,7 @@ #include #include "Utils.h" +#include "WorkflowManager.h" @@ -61,6 +62,9 @@ CmdPartDesignPart::CmdPartDesignPart() void CmdPartDesignPart::activated(int iMsg) { + if ( PartDesignGui::assureModernWorkflow( getDocument() ) ) + return; + openCommand("Add a part"); std::string FeatName = getUniqueObjectName("Part"); @@ -76,7 +80,7 @@ void CmdPartDesignPart::activated(int iMsg) bool CmdPartDesignPart::isActive(void) { - return hasActiveDocument(); + return hasActiveDocument() && !PartDesignGui::isLegacyWorkflow ( getDocument () ); } //=========================================================================== @@ -98,6 +102,8 @@ CmdPartDesignBody::CmdPartDesignBody() void CmdPartDesignBody::activated(int iMsg) { + if ( PartDesignGui::assureModernWorkflow( getDocument() ) ) + return; std::vector features = getSelection().getObjectsOfType(Part::Feature::getClassTypeId()); App::DocumentObject* baseFeature = nullptr; @@ -154,7 +160,40 @@ void CmdPartDesignBody::activated(int iMsg) bool CmdPartDesignBody::isActive(void) { - return hasActiveDocument(); + return hasActiveDocument() && !PartDesignGui::isLegacyWorkflow ( getDocument () ); +} + +//=========================================================================== +// PartDesign_Migrate +//=========================================================================== + +DEF_STD_CMD_A(CmdPartDesignMigrate); + +CmdPartDesignMigrate::CmdPartDesignMigrate() + : Command("PartDesign_Migrate") +{ + sAppModule = "PartDesign"; + sGroup = QT_TR_NOOP("PartDesign"); + sMenuText = QT_TR_NOOP("Migrate"); + sToolTipText = QT_TR_NOOP("Migrate document to the new workflow"); + sWhatsThis = sToolTipText; + sStatusTip = sToolTipText; +} + +void CmdPartDesignMigrate::activated(int iMsg) +{ + App::Document *doc = getDocument(); + // TODO make a proper implementation + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Not implemented yet"), + QObject::tr("The migration not implemented yet, just force-switching to the new workflow.\n" + "Previous workflow was: %1").arg(int( + PartDesignGui::WorkflowManager::instance()->determinWorkflow( doc ) ))); + PartDesignGui::WorkflowManager::instance()->forceWorkflow(doc, PartDesignGui::Workflow::Modern); +} + +bool CmdPartDesignMigrate::isActive(void) +{ + return hasActiveDocument() && !PartDesignGui::isLegacyWorkflow ( getDocument () ); } //=========================================================================== @@ -290,10 +329,7 @@ void CmdPartDesignDuplicateSelection::activated(int iMsg) { bool CmdPartDesignDuplicateSelection::isActive(void) { - if (getActiveGuiDocument()) - return true; - else - return false; + return hasActiveDocument(); } //=========================================================================== @@ -387,7 +423,7 @@ void CmdPartDesignMoveFeature::activated(int iMsg) } catch (Base::Exception &) { QMessageBox::warning( Gui::getMainWindow(), QObject::tr("Sketch plane cannot be migrated"), QObject::tr("Please edit '%1' and redefine it to use a Base or Datum plane as the sketch plane."). - arg( QString::fromAscii( sketch->getNameInDocument() ) ) ); + arg( QString::fromAscii( sketch->Label.getValue () ) ) ); } } } @@ -397,6 +433,7 @@ void CmdPartDesignMoveFeature::activated(int iMsg) bool CmdPartDesignMoveFeature::isActive(void) { + return hasActiveDocument () && !PartDesignGui::isLegacyWorkflow ( getDocument () ); return hasActiveDocument (); } @@ -494,7 +531,7 @@ void CmdPartDesignMoveFeatureInTree::activated(int iMsg) bool CmdPartDesignMoveFeatureInTree::isActive(void) { - return hasActiveDocument (); + return hasActiveDocument () && !PartDesignGui::isLegacyWorkflow ( getDocument () ); } @@ -508,6 +545,7 @@ void CreatePartDesignBodyCommands(void) rcCmdMgr.addCommand(new CmdPartDesignPart()); rcCmdMgr.addCommand(new CmdPartDesignBody()); + rcCmdMgr.addCommand(new CmdPartDesignMigrate()); rcCmdMgr.addCommand(new CmdPartDesignMoveTip()); rcCmdMgr.addCommand(new CmdPartDesignDuplicateSelection()); diff --git a/src/Mod/PartDesign/Gui/Workbench.cpp b/src/Mod/PartDesign/Gui/Workbench.cpp index dd05cc1049..3c64c9e7cd 100644 --- a/src/Mod/PartDesign/Gui/Workbench.cpp +++ b/src/Mod/PartDesign/Gui/Workbench.cpp @@ -44,6 +44,8 @@ #include "Workbench.h" +#include "WorkflowManager.h" + using namespace PartDesignGui; #if 0 // needed for Qt's lupdate utility @@ -56,12 +58,11 @@ using namespace PartDesignGui; /// @namespace PartDesignGui @class Workbench TYPESYSTEM_SOURCE(PartDesignGui::Workbench, Gui::StdWorkbench) -Workbench::Workbench() -{ +Workbench::Workbench() { } -Workbench::~Workbench() -{ +Workbench::~Workbench() { + WorkflowManager::destruct(); } // Commented out due to later to be moves and/or generall rewrighted from scratch (Fat-Zer 2015-08-08) @@ -370,6 +371,7 @@ void Workbench::activated() { Gui::Workbench::activated(); + WorkflowManager::init(); std::vector Watcher; @@ -630,7 +632,9 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "PartDesign_Boolean" << "Separator" //<< "PartDesign_Hole" - << "PartDesign_InvoluteGear"; + << "PartDesign_InvoluteGear" + << "Separator" + << "PartDesign_Migrate"; // For 0.13 a couple of python packages like numpy, matplotlib and others // are not deployed with the installer on Windows. Thus, the WizardShaft is diff --git a/src/Mod/PartDesign/Gui/WorkflowManager.cpp b/src/Mod/PartDesign/Gui/WorkflowManager.cpp new file mode 100644 index 0000000000..4402bad8f1 --- /dev/null +++ b/src/Mod/PartDesign/Gui/WorkflowManager.cpp @@ -0,0 +1,218 @@ +/*************************************************************************** + * 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 "PreCompiled.h" + +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include "WorkflowManager.h" + + +using namespace PartDesignGui; + + +WorkflowManager * WorkflowManager::_instance = nullptr; + + +WorkflowManager::WorkflowManager() { + // Fill the map with already opened documents + for ( auto doc : App::GetApplication().getDocuments() ) { + slotFinishRestoreDocument ( *doc ); + } + + connectNewDocument = App::GetApplication().signalNewDocument.connect( + boost::bind( &WorkflowManager::slotNewDocument, this, _1 ) ); + connectFinishRestoreDocument = App::GetApplication().signalFinishRestoreDocument.connect( + boost::bind( &WorkflowManager::slotFinishRestoreDocument, this, _1 ) ); + connectDeleteDocument = App::GetApplication().signalDeleteDocument.connect( + boost::bind( &WorkflowManager::slotDeleteDocument, this, _1 ) ); +} + +WorkflowManager::~WorkflowManager() { + // won't they will be disconnected on destruction? + connectNewDocument.disconnect (); + connectFinishRestoreDocument.disconnect (); +} + + +// Those destruction/construction is not really needed and could be done in the instance() +// but to make things a bit more cleare better to keep them around. +void WorkflowManager::init() { + if (_instance) { + throw Base::Exception( "Trying to init the workflow manager second time." ); + } + _instance = new WorkflowManager(); +} + +WorkflowManager *WorkflowManager::instance() { + if (!_instance) { + throw Base::Exception( "Trying to instance the WorkflowManager manager before init() was called." ); + } + 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 compleatly loaded yet + return Workflow::Undetermined; + } +} + +Workflow WorkflowManager::determinWorkflow( 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; + + if ( rv == Workflow::Legacy ) { // legacy messages + msgBox.setText( QObject::tr( "The document \"%1\" you are editing was design with old version of " + "PartDesign workbench." ).arg( QString::fromStdString ( doc->getName()) ) ); + msgBox.setInformativeText ( + QObject::tr( "Do you want to migrate in order to use modern PartDesign 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 PartDesign or have a slightly broken structure." + ).arg( QString::fromStdString ( doc->getName()) ) ); + msgBox.setInformativeText ( + QObject::tr( "Do you want to make the migration automatically?" ) ); + } + msgBox.setDetailedText( QObject::tr( "Note If you choose to migrate you won't be able to edit" + " the file wtih old FreeCAD versions.\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 ) { + // TODO Assure that this will actually work (2015-08-02, Fat-Zer) + Gui::Application::Instance->commandManager().runCommandByName("PartDesign_Migrate"); + rv = Workflow::Modern; + } else if ( msgBox.clickedButton() == manuallyBtn ) { + rv = Workflow::Modern; + } 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) { + // Retrive 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 itmeans that migration was incomplete, otherwice it's Modern + return features_without_bodies ? Workflow::Undetermined : Workflow::Modern; + } + } +} diff --git a/src/Mod/PartDesign/Gui/WorkflowManager.h b/src/Mod/PartDesign/Gui/WorkflowManager.h new file mode 100644 index 0000000000..27532ffafd --- /dev/null +++ b/src/Mod/PartDesign/Gui/WorkflowManager.h @@ -0,0 +1,131 @@ +/*************************************************************************** + * 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 * + * * + ***************************************************************************/ + +#ifndef WORKFLOWMANAGER_H_PB7A5GCM +#define WORKFLOWMANAGER_H_PB7A5GCM + +#include +#include + +namespace App { + class Document; +} + +namespace PartDesignGui { + +/** + * Defines allowded tool set provided by the workbench + * Legacy mode provides a free PartDesign features but forbids bodies and parts + */ +enum class Workflow { + Undetermined = 0, ///< No workflow was choosen yet + Legacy = 1<<0, ///< Old-style workflow with free features and no bodies + Modern = 1<<1, ///< New-style workflow with bodies, parts etc +}; + +/** + * This class controls the workflow of each file. + * It has been introdused to support legacy files migrating to the new workflow. + */ +class PartDesignGuiExport WorkflowManager { +public: + virtual ~WorkflowManager (); + + /** + * Lookup the workflow of the document in the map. + * If the document not in the map yet return Workflow::Undetermined. + */ + + Workflow getWorkflowForDocument(App::Document *doc); + + /** + * Asserts the workflow of the document to be determined and prompt user to migrate if it is not modern. + * + * If workflow was already choosen return it. + * If the guesed workflow is Workflow::Legacy or Workflow::Mixed the user will be prompted to migrate. + * If the user agrees the file will be migrated and the workflow will be set as modern. + * If the user refuses to migrate use the old workflow. + */ + Workflow determinWorkflow(App::Document *doc); + + /** + * Force the desired workflow in document + * Note: currently added for testing purpose; May be removed later + */ + void forceWorkflow (const App::Document *doc, Workflow wf); + + /** @name Init, Destruct an Access methods */ + //@{ + /// Creates an instance of the manager, should be called before any instance() + static void init (); + /// Return an instance of the WorkflofManager. + static WorkflowManager* instance(); + /// destroy the manager + static void destruct (); + //@} + +private: + /// The class is not intended to be constructed outside of itself + WorkflowManager (); + /// Get the signal on New document created + void slotNewDocument (const App::Document& doc); + /// Get the signal on document getting loaded + void slotFinishRestoreDocument (const App::Document& doc); + /// Get the signal on document close and remove it from our list + void slotDeleteDocument (const App::Document& doc); + + /// Guess the Workflow of the document out of it's content + Workflow guessWorkflow(const App::Document *doc); + +private: + std::map dwMap; + + boost::signals::connection connectNewDocument; + boost::signals::connection connectFinishRestoreDocument; + boost::signals::connection connectDeleteDocument; + + static WorkflowManager* _instance; +}; + +/// Assures that workflow of the given document is determined and returns true if it is Workflow::Legacy +inline bool assureLegacyWorkflow (App::Document *doc) { + return WorkflowManager::instance()->determinWorkflow( doc ) == Workflow::Legacy ; +} + +/// Assures that workflow of the given document is determined and returns true if it is Workflow::Modern +inline bool assureModernWorkflow (App::Document *doc) { + return WorkflowManager::instance()->determinWorkflow( doc ) == Workflow::Modern ; +} + +/// Returns true if the workflow of the given document is Workflow::Legacy +inline bool isLegacyWorkflow (App::Document *doc) { + return WorkflowManager::instance()->getWorkflowForDocument( doc ) == Workflow::Legacy ; +} + +/// Returns true if the workflow of the given document is Workflow::Modern +inline bool isModernWorkflow (App::Document *doc) { + return WorkflowManager::instance()->getWorkflowForDocument( doc ) == Workflow::Modern ; +} + +} /* PartDesignGui */ + +#endif /* end of include guard: WORKFLOWMANAGER_H_PB7A5GCM */