/*************************************************************************** * Copyright (c) 2023 Abdullah Tahiri * * * * 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 SKETCHERGUI_DrawSketchController_H #define SKETCHERGUI_DrawSketchController_H #include #include #include "DrawSketchDefaultHandler.h" #include "SketcherToolDefaultWidget.h" #include "DrawSketchKeyboardManager.h" namespace SketcherGui { /** @brief template class for creating a type encapsulating an int value associated to each of the possible construction modes supported by the tool. @details Different construction modes of a DSH may use different types of controls. This class allows to instantiate a handler template class to provide such construction mode specific controls. Each different type of control is a template class deriving from this. */ template // Initial sizes for each mode class ControlAmount { public: template static constexpr int size(constructionT constructionmethod) { auto modeint = static_cast(constructionmethod); return constructionMethodParameters[modeint]; } static constexpr int defaultMethodSize() { return size(0); } private: static constexpr std::array constructionMethodParameters = {{sizes...}}; }; /** @brief Type encapsulating the number of On view parameters*/ template // Initial sizes for each mode class OnViewParameters: public ControlAmount { }; namespace sp = std::placeholders; /** @brief Class defining a generic handler controller operable with a DrawSketchControllableHandler * * @details * This class is intended as a parent for controller classes. This controller class provides the * essential controller functionalities, including on-view parameters. This controller class does * NOT control based on (taskbox) widgets. * * For an example of a tool using directly this class see DrawSketchHandlerPoint. * * Controls based on taskbox widgets may derive from this class and add on top any widget mandated * functionality. For the default widget (SketcherToolDefaultWidget), see * DrawSketchDefaultWidgetController. For custom widgets, an appropriate class, preferably deriving * from this controller needs to be provided. */ template // The enum comprising all the // supported construction methods class DrawSketchController { public: /** @name Meta-programming definitions and members */ //@{ using ControllerBase = void; // No base controller for parent class. using HandlerType = HandlerT; using SelectModeType = SelectModeT; using ContructionMethodType = ConstructionMethodT; static constexpr const int AutoConstraintInitialSize = PAutoConstraintSize; //@} /** @name Convenience definitions */ //@{ using DSDefaultHandler = DrawSketchDefaultHandler; using ConstructionMachine = ConstructionMethodMachine; using ConstructionMethod = ConstructionMethodT; using SelectMode = SelectModeT; //@} protected: // NOLINTBEGIN HandlerT* handler; // real derived type std::vector> onViewParameters; // NOLINTEND bool init = false; // true if the controls have been initialised. int parameterWithFocus = 0; // track the index of the parameter having the focus /** @name Named indices for controlling on-view controls */ //@{ enum OnViewParameter { First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth, Ninth, Tenth, nOnViewParameters // Must Always be the last one }; //@} private: Base::Vector2d prevCursorPosition; Base::Vector2d lastControlEnforcedPosition; int nOnViewParameter = OnViewParametersT::defaultMethodSize(); /// Class to keep track of colors used by the on-view parameters class ColorManager { public: SbColor dimConstrColor, dimConstrDeactivatedColor; ColorManager() { init(); } private: void init() { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/View"); dimConstrColor = SbColor(1.0f, 0.149f, 0.0f); // NOLINT dimConstrDeactivatedColor = SbColor(0.8f, 0.8f, 0.8f); // NOLINT float transparency = 0.f; unsigned long color = (unsigned long)(dimConstrColor.getPackedValue()); color = hGrp->GetUnsigned("ConstrainedDimColor", color); dimConstrColor.setPackedValue((uint32_t)color, transparency); color = (unsigned long)(dimConstrDeactivatedColor.getPackedValue()); color = hGrp->GetUnsigned("DeactivatedConstrDimColor", color); dimConstrDeactivatedColor.setPackedValue((uint32_t)color, transparency); } }; class OnViewParameterVisibilityManager { public: enum class OnViewParameterVisibility { Hidden = 0, OnlyDimensional = 1, ShowAll = 2 }; OnViewParameterVisibilityManager() { init(); } OnViewParameterVisibility visibility() const { return onViewParameterVisibility; } bool isVisibility(OnViewParameterVisibility visibility) const { return onViewParameterVisibility == visibility; } bool isVisible(Gui::EditableDatumLabel* ovp) const { switch (onViewParameterVisibility) { case OnViewParameterVisibility::Hidden: return dynamicOverride; case OnViewParameterVisibility::OnlyDimensional: { auto isDimensional = (ovp->getFunction() == Gui::EditableDatumLabel::Function::Dimensioning); return isDimensional != dynamicOverride; } case OnViewParameterVisibility::ShowAll: return !dynamicOverride; } return false; } void toggleDynamicOverride() { dynamicOverride = !dynamicOverride; } void resetDynamicOverride() { dynamicOverride = false; } private: void init() { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Sketcher/Tools"); onViewParameterVisibility = static_cast( hGrp->GetInt("OnViewParameterVisibility", 1)); } OnViewParameterVisibility onViewParameterVisibility; bool dynamicOverride = false; }; public: /** Creates the controller. * @param dshandler a controllable DSH handler */ explicit DrawSketchController(HandlerT* dshandler) : handler(dshandler) , keymanager(std::make_unique()) {} DrawSketchController(const DrawSketchController&) = delete; DrawSketchController(DrawSketchController&&) = delete; DrawSketchController& operator=(const DrawSketchController&) = delete; DrawSketchController& operator=(DrawSketchController&&) = delete; virtual ~DrawSketchController() {} /** @name functions NOT intended for specialisation offering a NVI for extension */ /** These functions offer a NVI to ensure the order on initialisation. It is heavily encouraged * to extend functionality using the NVI interface (by overriding the NVI functions). */ //@{ /** @brief Initialises controls, such as the widget and the on-view parameters via NVI.*/ void initControls(QWidget* widget) { doInitControls(widget); // NVI resetControls(); init = true; } /** @brief Resets the controls, such as the widget and the on-view parameters */ void resetControls() { // Make sure we do not loose focus if next methode does not have OVP that take focus. handler->ensureFocus(); doResetControls(); // NVI firstMoveInit = false; } /** @brief function triggered by the handler when the mouse has been moved */ void mouseMoved(Base::Vector2d originalSketchPosition) { onMouseMoved(originalSketchPosition); // NVI if (!firstMoveInit) { firstMoveInit = true; } } /** @brief function triggered by the handler to ensure its operating position takes into * account widget mandated parameters */ void enforceControlParameters(Base::Vector2d& onSketchPos) { prevCursorPosition = onSketchPos; doEnforceControlParameters(onSketchPos); // specialisation interface lastControlEnforcedPosition = onSketchPos; // store enforced cursor position. afterEnforceControlParameters(); // NVI } /** function that is called by the handler when the construction mode changed */ void onConstructionMethodChanged() { doConstructionMethodChanged(); // NVI handler->updateCursor(); if (resetOnConstructionMethodeChanged()) { handler->reset(); // reset of handler to restart. } handler->mouseMove(prevCursorPosition); } //@} /** function that define if the handler should be reset on construction methode change */ virtual bool resetOnConstructionMethodeChanged() { return true; } //@} /** @name functions NOT intended for specialisation offering specialisation interface for * extension */ /** These functions offer a specialisation interface to ensure the order on initialisation. It * is heavily encouraged to extend functionality using the specialisation interface (by * specialising the NVI functions). */ //@{ /** slot triggering when a on view parameter has changed * It is intended to remote control the DrawSketchDefaultWidgetHandler */ void onViewValueChanged(int onviewparameterindex, double value) { int nextindex = onviewparameterindex + 1; if (isOnViewParameterOfCurrentMode(nextindex)) { setFocusToOnViewParameter(nextindex); } /* That is not supported with on-view parameters. // -> A machine does not forward to a next state when adapting the parameter (though it // may forward to // a next state if all the parameters are fulfilled, see // doChangeDrawSketchHandlerMode). This ensures that the geometry has been defined // (either by mouse clicking or by widget). Autoconstraints on point should be picked // when the state is reached upon machine state advancement. // // -> A machine goes back to a previous state if a parameter of a previous state is // modified. This ensures // that appropriate autoconstraints are picked. if (isOnViewParameterOfPreviousMode(onviewparameterindex)) { // change to previous state handler->setState(getState(onviewparameterindex)); }*/ adaptDrawingToOnViewParameterChange(onviewparameterindex, value); // specialisation interface finishControlsChanged(); } void adaptParameters() { adaptParameters(lastControlEnforcedPosition); // specialisation interface } //@} /** @name Specialisation Interface */ /** These functions offer a specialisation interface. Non-virtual functions are specific to * this controller. Virtual functions may depend on input from a derived controller, and thus * the specialisation needs to be of an overridden version (so as to be able to access members * of the derived controller). */ //@{ /// Change DSH to reflect a value entered in the view void adaptDrawingToOnViewParameterChange(int onviewparameterindex, double value) { Q_UNUSED(onviewparameterindex); Q_UNUSED(value); } /** Returns the state to which the on-view parameter corresponds in the current construction * method. */ auto getState(int parameterindex) const { Q_UNUSED(parameterindex); return handler->getFirstState(); } /// function to create constraints based on control information. virtual void addConstraints() {} /// Configures on-view parameters void configureOnViewParameters() {} /** Change DSH to reflect the SelectMode it should be in based on values entered in the * controls */ virtual void doChangeDrawSketchHandlerMode() {} /** function that is called by the handler when the selection mode changed */ void onHandlerModeChanged() { setModeOnViewParameters(); } /** function that is called by the handler with a Vector2d position to update the widget * * It MUST be specialised if you want the parameters to update on mouseMove */ virtual void adaptParameters(Base::Vector2d onSketchPos) { Q_UNUSED(onSketchPos) } /** function that is called by the handler with a mouse position, enabling the * widget to override it having regard to the widget information. * * It MUST be specialised if you want to override mouse position based on parameters. */ void doEnforceControlParameters(Base::Vector2d& onSketchPos) { Q_UNUSED(onSketchPos) } /** on first shortcut, it toggles the first checkbox if there is go. Must be specialised if * this is not intended */ virtual void firstKeyShortcut() {} virtual void secondKeyShortcut() {} virtual void thirdKeyShortcut() {} virtual void fourthKeyShortcut() {} virtual void tabShortcut() { passFocusToNextOnViewParameter(); } //@} /// triggered by the controllable DSH after a mode change has been effected virtual void afterHandlerModeChanged() { if (handler && (!handler->isState(SelectModeT::End) || handler->continuousMode)) { handler->mouseMove(prevCursorPosition); } } void drawPositionAtCursor(const Base::Vector2d& position) { if (shouldDrawPositionAtCursor()) { handler->drawPositionAtCursor(position); } } void drawDirectionAtCursor(const Base::Vector2d& position, const Base::Vector2d& origin) { if (shouldDrawDimensionsAtCursor()) { handler->drawDirectionAtCursor(position, origin); } } void drawWidthHeightAtCursor(const Base::Vector2d& position, const double val1, const double val2) { if (shouldDrawDimensionsAtCursor()) { handler->drawWidthHeightAtCursor(position, val1, val2); } } void drawDoubleAtCursor(const Base::Vector2d& position, const double radius, Base::Unit unit = Base::Unit::Length) { if (shouldDrawDimensionsAtCursor()) { handler->drawDoubleAtCursor(position, radius, unit); } } protected: /** @name NVI for extension of controller functionality in derived classes */ //@{ virtual void doInitControls(QWidget* widget) { Q_UNUSED(widget) } virtual void doResetControls() { resetOnViewParameters(); } virtual void onMouseMoved(Base::Vector2d originalSketchPosition) { Q_UNUSED(originalSketchPosition) if (!firstMoveInit) { setModeOnViewParameters(); } } virtual void afterEnforceControlParameters() { // Give focus to current on-view parameter. In case user interacted outside of 3dview. if (focusAutoPassing && parameterWithFocus >= 0) { setFocusToOnViewParameter(parameterWithFocus); } } virtual void doConstructionMethodChanged() {} //@} protected: /** @name helper functions */ //@{ /// function to assist in adaptDrawingToComboboxChange specialisation /// assigns the modevalue to the modeenum /// it also triggers an update of the cursor template void setMode(T& modeenum, int modevalue) { auto mode = static_cast(modevalue); modeenum = mode; handler->updateCursor(); handler->resetControls(); // resetControls of handler to restart. } /// function to redraw before and after any eventual mode change in reaction to a control /// change void finishControlsChanged() { handler->mouseMove(prevCursorPosition); auto currentstate = handler->state(); // ensure that object at point is preselected, so that autoconstraints are generated handler->preselectAtPoint(lastControlEnforcedPosition); doChangeDrawSketchHandlerMode(); // if the state changed and is not the last state (End). And is init (ie tool has not // reset) if (!handler->isLastState() && handler->state() != currentstate && firstMoveInit) { // mode has changed, so reprocess the previous position to the new widget state handler->mouseMove(prevCursorPosition); } } /** @brief Initialises on-screen parameters */ void initNOnViewParameters(int n) { Gui::View3DInventorViewer* viewer = handler->getViewer(); Base::Placement placement = handler->sketchgui->getSketchObject()->globalPlacement(); onViewParameters.clear(); for (int i = 0; i < n; i++) { // the returned is a naked pointer auto parameter = onViewParameters .emplace_back(std::make_unique( viewer, placement, colorManager.dimConstrDeactivatedColor, /*autoDistance = */ true, /*avoidMouseCursor = */ true)) .get(); QObject::connect(parameter, &Gui::EditableDatumLabel::valueChanged, [this, parameter, i](double value) { parameter->setColor(colorManager.dimConstrColor); onViewValueChanged(i, value); }); } } /// Allows a on-view parameter to take any mouse mandated value (as opposed to enforce one) void unsetOnViewParameter(Gui::EditableDatumLabel* onViewParameter) { onViewParameter->isSet = false; onViewParameter->setColor(colorManager.dimConstrDeactivatedColor); } void setOnViewParameterValue(OnViewParameter index, double val, const Base::Unit& unit = Base::Unit::Length) { bool visible = isOnViewParameterVisible(index); if (visible) { onViewParameters[index]->setSpinboxValue(val, unit); } } /** Activates the correct set of on-view parameters corresponding to current * mode. It may be specialized if necessary.*/ void setModeOnViewParameters() { // before each mode change we reset the dynamic override ovpVisibilityManager.resetDynamicOverride(); bool firstOfMode = true; parameterWithFocus = -1; for (size_t i = 0; i < onViewParameters.size(); i++) { if (!isOnViewParameterOfCurrentMode(i)) { onViewParameters[i]->stopEdit(); if (!onViewParameters[i]->isSet || handler->state() == SelectMode::End) { onViewParameters[i]->deactivate(); } } else { if (firstOfMode) { parameterWithFocus = static_cast(i); firstOfMode = false; } bool visible = isOnViewParameterVisible(i); if (visible) { onViewParameters[i]->activate(); // points/value will be overridden by the mouseMove triggered by the mode // change. onViewParameters[i]->setPoints(Base::Vector3d(), Base::Vector3d()); onViewParameters[i]->startEdit(0.0, keymanager.get()); } } } } /// This function gives the focus to a spinbox and tracks the focus. bool setFocusToOnViewParameter(unsigned int onviewparameterindex) { if (onviewparameterindex < onViewParameters.size()) { bool visible = isOnViewParameterVisible(onviewparameterindex); if (visible) { onViewParameters[onviewparameterindex]->setFocusToSpinbox(); parameterWithFocus = static_cast(onviewparameterindex); return true; } } return false; } /// Switches focus to the next parameter in the current state machine. void passFocusToNextOnViewParameter() { unsigned int index = parameterWithFocus + 1; if (index >= onViewParameters.size()) { index = 0; } auto trySetFocus = [this](unsigned int& idx) -> bool { while (idx < onViewParameters.size()) { if (isOnViewParameterOfCurrentMode(idx) && isOnViewParameterVisible(idx)) { setFocusToOnViewParameter(idx); return true; } idx++; } return false; }; if (!trySetFocus(index)) { // We have not found a parameter in this mode after current. // So we go back to start and retry. index = 0; trySetFocus(index); } // At that point if no onViewParameter is found, there is none. } /** Returns whether the provided on-view parameter index belongs to the current state of the * state machine */ bool isOnViewParameterOfCurrentMode(unsigned int onviewparameterindex) const { return onviewparameterindex < onViewParameters.size() && getState(onviewparameterindex) == handler->state(); } /** Returns whether the provided on-view parameter index belongs to the previous state of the * state machine */ bool isOnViewParameterOfPreviousMode(unsigned int onviewparameterindex) const { return onviewparameterindex < onViewParameters.size() && getState(onviewparameterindex) < handler->state(); } bool isOnViewParameterVisible(unsigned int onviewparameterindex) { return ovpVisibilityManager.isVisible(onViewParameters[onviewparameterindex].get()); } /** Resets the on-view parameter controls */ void resetOnViewParameters() { nOnViewParameter = OnViewParametersT::size(handler->constructionMethod()); initNOnViewParameters(nOnViewParameter); parameterWithFocus = 0; configureOnViewParameters(); } //@} // makes keymanager available to derived classes so that they install it as event handler // where necessary, without allowing them to manage resource ownership DrawSketchKeyboardManager* getKeyManager() { return keymanager.get(); } bool focusAutoPassing = true; private: /** @name helper functions */ //@{ bool shouldDrawPositionAtCursor() const { return !(ovpVisibilityManager.isVisibility( OnViewParameterVisibilityManager::OnViewParameterVisibility::ShowAll)); } bool shouldDrawDimensionsAtCursor() const { return (ovpVisibilityManager.isVisibility( OnViewParameterVisibilityManager::OnViewParameterVisibility::Hidden)); } //@} private: OnViewParameterVisibilityManager ovpVisibilityManager; ColorManager colorManager; std::unique_ptr keymanager; bool firstMoveInit = false; // true if first mouse movement not yet performed (resets) }; } // namespace SketcherGui #endif // SKETCHERGUI_DrawSketchController_H