/*************************************************************************** * Copyright (c) 2011 Jürgen Riegel * * Copyright (c) 2011 Werner Mayer * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #ifndef GUI_SELECTION_H #define GUI_SELECTION_H #include #include #include #include #include #include #include #include "SelectionObject.h" using PyObject = struct _object; namespace App { class DocumentObject; class Document; class PropertyLinkSubList; } namespace Gui { enum class ResolveMode { NoResolve, OldStyleElement, NewStyleElement, FollowLink }; class SelectionObject; class SelectionFilter; /** Transport the changes of the Selection * This class transports closer information what was changed in the * selection. It's an optional information and not all commands set this * information. If not set all observer of the selection assume a full change * and update everything (e.g 3D view). This is not a very good idea if, e.g. only * a small parameter has changed. Therefore one can use this class and make the * update of the document much faster! * @see Base::Observer */ class GuiExport SelectionChanges { public: enum MsgType { AddSelection, RmvSelection, SetSelection, ClrSelection, SetPreselect, // to signal observer the preselect has changed RmvPreselect, SetPreselectSignal, // to request 3D view to change preselect PickedListChanged, ShowSelection, // to show a selection HideSelection, // to hide a selection RmvPreselectSignal, // to request 3D view to remove preselect MovePreselect, // to signal observer the mouse movement when preselect }; enum class MsgSource { Any = 0, Internal = 1, TreeView = 2 }; SelectionChanges(MsgType type = ClrSelection, const char *docName=nullptr, const char *objName=nullptr, const char *subName=nullptr, const char *typeName=nullptr, float x=0, float y=0, float z=0, MsgSource subtype=MsgSource::Any) : Type(type) , SubType(subtype) , x(x),y(y),z(z) , Object(docName,objName,subName) { pDocName = Object.getDocumentName().c_str(); pObjectName = Object.getObjectName().c_str(); pSubName = Object.getSubName().c_str(); if (typeName) TypeName = typeName; pTypeName = TypeName.c_str(); }//explicit bombs SelectionChanges(MsgType type, const std::string &docName, const std::string &objName, const std::string &subName, const std::string &typeName = std::string(), float x=0,float y=0,float z=0, MsgSource subtype=MsgSource::Any) : Type(type) , SubType(subtype) , x(x),y(y),z(z) , Object(docName.c_str(), objName.c_str(), subName.c_str()) , TypeName(typeName) { pDocName = Object.getDocumentName().c_str(); pObjectName = Object.getObjectName().c_str(); pSubName = Object.getSubName().c_str(); pTypeName = TypeName.c_str(); } SelectionChanges(const SelectionChanges &other) { *this = other; } SelectionChanges &operator=(const SelectionChanges &other) { Type = other.Type; SubType = other.SubType; x = other.x; y = other.y; z = other.z; Object = other.Object; TypeName = other.TypeName; pDocName = Object.getDocumentName().c_str(); pObjectName = Object.getObjectName().c_str(); pSubName = Object.getSubName().c_str(); pTypeName = TypeName.c_str(); pOriginalMsg = other.pOriginalMsg; return *this; } SelectionChanges(SelectionChanges &&other) { *this = std::move(other); } SelectionChanges &operator=(SelectionChanges &&other) { Type = other.Type; SubType = other.SubType; x = other.x; y = other.y; z = other.z; Object = std::move(other.Object); TypeName = std::move(other.TypeName); pDocName = Object.getDocumentName().c_str(); pObjectName = Object.getObjectName().c_str(); pSubName = Object.getSubName().c_str(); pTypeName = TypeName.c_str(); pOriginalMsg = other.pOriginalMsg; return *this; } MsgType Type; MsgSource SubType; const char* pDocName; const char* pObjectName; const char* pSubName; const char* pTypeName; float x; float y; float z; App::SubObjectT Object; std::string TypeName; // Original selection message in case resolve!=0 const SelectionChanges *pOriginalMsg = nullptr; }; } //namespace Gui // Export an instance of the base class (to avoid warning C4275, see also // C++ Language Reference/General Rules and Limitations on MSDN for more details.) // // For compiler gcc4.1 we need to define the template class outside namespace 'Gui' // otherwise we get the compiler error: // 'explicit instantiation of 'class Base::Subject' // in namespace 'Gui' (which does not enclose namespace 'Base') // // It seems that this construct is not longer needed for gcc4.4 and even leads to // errors under Mac OS X. Thus, we check for version between 4.1 and 4.4. // It seems that for Mac OS X this can be completely ignored #if defined(__GNUC__) && defined(__GNUC_MINOR__) && !defined(FC_OS_MACOSX) #define GNUC_VERSION (((__GNUC__)<<16)+((__GNUC_MINOR__)<<8)) #if GNUC_VERSION >= 0x040100 && GNUC_VERSION < 0x040400 template class GuiExport Base::Subject; #endif #undef GNUC_VERSION #endif namespace Gui { class ViewProviderDocumentObject; /** * The SelectionObserver class simplifies the step to write classes that listen * to what happens to the selection. * * @author Werner Mayer */ class GuiExport SelectionObserver { public: /** Constructor * * @param attach: whether to attach this observer on construction * @param resolve: sub-object resolving mode. * 0 no resolve, * 1 resolve sub-object with old style element name * 2 resolve sub-object with new style element name */ explicit SelectionObserver(bool attach = true, ResolveMode resolve = ResolveMode::OldStyleElement); /** Constructor * * @param vp: filtering view object. * @param attach: whether to attach this observer on construction * @param resolve: sub-object resolving mode. * 0 no resolve, * 1 resolve sub-object with old style element name * 2 resolve sub-object with new style element name * * Constructs an selection observer that receives only selection event of * objects within the same document as the input view object. */ explicit SelectionObserver(const Gui::ViewProviderDocumentObject *vp, bool attach=true, ResolveMode resolve = ResolveMode::OldStyleElement); virtual ~SelectionObserver(); bool blockSelection(bool block); bool isSelectionBlocked() const; bool isSelectionAttached() const; /** Attaches to the selection. */ void attachSelection(); /** Detaches from the selection. */ void detachSelection(); private: virtual void onSelectionChanged(const SelectionChanges& msg) = 0; void _onSelectionChanged(const SelectionChanges& msg); private: using Connection = boost::signals2::connection; Connection connectSelection; std::string filterDocName; std::string filterObjName; ResolveMode resolve; bool blockedSelection; }; /** SelectionGate * The selection gate allows or disallows selection of certain types. * It has to be registered to the selection. */ class GuiExport SelectionGate { public: virtual ~SelectionGate() = default; virtual bool allow(App::Document*,App::DocumentObject*, const char*)=0; /** * @brief notAllowedReason is a string that sets the message to be * displayed in statusbar for cluing the user on why is the selection not * allowed. Set this variable in allow() implementation. Enclose the * literal into QT_TR_NOOP() for translatability. */ std::string notAllowedReason; }; /** SelectionGateFilterExternal * The selection gate disallows any external object */ class GuiExport SelectionGateFilterExternal: public SelectionGate { public: explicit SelectionGateFilterExternal(const char *docName, const char *objName=nullptr); bool allow(App::Document*,App::DocumentObject*, const char*) override; private: std::string DocName; std::string ObjName; }; /** The Selection class * The selection singleton keeps track of the selection state of * the whole application. It gets messages from all entities which can * alter the selection (e.g. tree view and 3D-view) and sends messages * to entities which need to keep track on the selection state. * * The selection consists mainly out of following information per selected object: * - document (pointer) * - Object (pointer) * - list of subelements (list of strings) * - 3D coordinates where the user clicks to select (Vector3d) * * Also the preselection is managed. That means you can add a filter to prevent selection * of unwanted objects or subelements. */ class GuiExport SelectionSingleton : public Base::Subject { public: struct SelObj { const char* DocName; const char* FeatName; const char* SubName; const char* TypeName; App::Document* pDoc; App::DocumentObject* pObject; App::DocumentObject* pResolvedObject; float x,y,z; }; /// Add to selection bool addSelection(const char* pDocName, const char* pObjectName=nullptr, const char* pSubName=nullptr, float x=0, float y=0, float z=0, const std::vector *pickedList = nullptr, bool clearPreSelect=true); bool addSelection2(const char* pDocName, const char* pObjectName=nullptr, const char* pSubName=nullptr, float x=0, float y=0, float z=0, const std::vector *pickedList = nullptr) { return addSelection(pDocName,pObjectName,pSubName,x,y,z,pickedList,false); } /// Add to selection bool addSelection(const SelectionObject&, bool clearPreSelect=true); /// Add to selection with several sub-elements bool addSelections(const char* pDocName, const char* pObjectName, const std::vector& pSubNames); /// Update a selection bool updateSelection(bool show, const char* pDocName, const char* pObjectName=nullptr, const char* pSubName=nullptr); /// Remove from selection (for internal use) void rmvSelection(const char* pDocName, const char* pObjectName=nullptr, const char* pSubName=nullptr, const std::vector *pickedList = nullptr); /// Set the selection for a document void setSelection(const char* pDocName, const std::vector&); /// Clear the selection of document \a pDocName. If the document name is not given the selection of the active document is cleared. void clearSelection(const char* pDocName=nullptr, bool clearPreSelect=true); /// Clear the selection of all documents void clearCompleteSelection(bool clearPreSelect=true); /// Check if selected bool isSelected(const char* pDocName, const char* pObjectName=nullptr, const char* pSubName=nullptr, ResolveMode resolve = ResolveMode::OldStyleElement) const; /// Check if selected bool isSelected(App::DocumentObject*, const char* pSubName=nullptr, ResolveMode resolve = ResolveMode::OldStyleElement) const; const char *getSelectedElement(App::DocumentObject*, const char* pSubName) const; /// set the preselected object (mostly by the 3D view) int setPreselect(const char* pDocName, const char* pObjectName, const char* pSubName, float x=0, float y=0, float z=0, SelectionChanges::MsgSource signal=SelectionChanges::MsgSource::Any); /// remove the present preselection void rmvPreselect(bool signal=false); /// sets different coords for the preselection void setPreselectCoord(float x, float y, float z); /// returns the present preselection const SelectionChanges& getPreselection() const; /// add a SelectionGate to control what is selectable void addSelectionGate(Gui::SelectionGate *gate, ResolveMode resolve = ResolveMode::OldStyleElement); /// remove the active SelectionGate void rmvSelectionGate(); int disableCommandLog(); int enableCommandLog(bool silent=false); /** Returns the number of selected objects with a special object type * It's the convenient way to check if the right objects are selected to * perform an operation (GuiCommand). The check also detects base types. * E.g. "Part" also fits on "PartImport" or "PartTransform types. * If no document name is given the active document is assumed. * * Set 'resolve' to true to resolve any sub object inside selection SubName * field */ unsigned int countObjectsOfType(const Base::Type& typeId=App::DocumentObject::getClassTypeId(), const char* pDocName=nullptr, ResolveMode resolve = ResolveMode::OldStyleElement) const; /** * Does basically the same as the method above unless that it accepts a string literal as first argument. * \a typeName must be a registered type, otherwise 0 is returned. */ unsigned int countObjectsOfType(const char* typeName, const char* pDocName=nullptr, ResolveMode resolve = ResolveMode::OldStyleElement) const; /** Returns a vector of objects of type \a TypeName selected for the given document name \a pDocName. * If no document name is specified the objects from the active document are regarded. * If no objects of this document are selected an empty vector is returned. * @note The vector reflects the sequence of selection. */ std::vector getObjectsOfType(const Base::Type& typeId, const char* pDocName=nullptr, ResolveMode resolve = ResolveMode::OldStyleElement) const; /** * Does basically the same as the method above unless that it accepts a string literal as first argument. * \a typeName must be a registered type otherwise an empty array is returned. */ std::vector getObjectsOfType(const char* typeName, const char* pDocName=nullptr, ResolveMode resolve = ResolveMode::OldStyleElement) const; /** * A convenience template-based method that returns an array with the correct types already. */ template inline std::vector getObjectsOfType( const char* pDocName=nullptr, ResolveMode resolve = ResolveMode::OldStyleElement) const; /// Visible state used by setVisible() enum VisibleState { /// Hide the selection VisHide = 0, /// Show the selection VisShow = 1, /// Toggle visibility of the selection VisToggle = -1, }; /** Set selection object visibility * * @param visible: see VisibleState */ void setVisible(VisibleState visible); /// signal on new object boost::signals2::signal signalSelectionChanged; /// signal on selection change with resolved object boost::signals2::signal signalSelectionChanged2; /// signal on selection change with resolved object and sub element map boost::signals2::signal signalSelectionChanged3; /** Returns a vector of selection objects * * @param pDocName: document name. If no document name is given the objects * of the active are returned. If nothing for this Document is selected an * empty vector is returned. If document name is "*", then all document is * considered. * @param resolve: sub-object resolving mode * 0 no resolve, * 1 resolve sub-object with old style element name * 2 resolve sub-object with new style element name * @param single: if set to true, then it will return an empty vector if * there is more than one selections. * * @return The returned vector reflects the sequence of selection. */ std::vector getSelection(const char* pDocName=nullptr, ResolveMode resolve = ResolveMode::OldStyleElement, bool single=false) const; /** Returns a vector of selection objects * * @param pDocName: document name. If no document name is given the objects * of the active are returned. If nothing for this Document is selected an * empty vector is returned. If document name is "*", then all document is * considered. * @param typeId: specify the type of object to be returned. * @param resolve: sub-object resolving mode. * 0 no resolve, * 1 resolve sub-object with old style element name * 2 resolve sub-object with new style element name * @param single: if set to true, then it will return an empty vector if * there is more than one selections. * * @return The returned vector reflects the sequence of selection. */ std::vector getSelectionEx(const char* pDocName=nullptr, Base::Type typeId=App::DocumentObject::getClassTypeId(), ResolveMode resolve = ResolveMode::OldStyleElement, bool single=false) const; /** * @brief getAsPropertyLinkSubList fills PropertyLinkSubList with current selection. * @param prop (output). The property object to receive links * @return the number of items written to the link */ int getAsPropertyLinkSubList(App::PropertyLinkSubList &prop) const; /** Returns a vector of all selection objects of all documents. */ std::vector getCompleteSelection(ResolveMode resolve = ResolveMode::OldStyleElement) const; /// Check if there is any selection bool hasSelection() const; /** Check if there is any selection within a given document * * @param doc: specify the document to check for selection. If NULL, then * check the current active document. * @param resolve: whether to resolve the selected sub-object * * If \c resolve is true, then the selection is first resolved before * matching its owner document. So in case the selected sub-object is * linked from an external document, it may not match the input \c doc. * If \c resolve is false, then the match is only done with the top * level parent object. */ bool hasSelection(const char* doc, ResolveMode resolve = ResolveMode::OldStyleElement) const; /** Check if there is any sub-element selection * * @param doc: optional document to check for selection * @param subElement: whether to count sub-element only selection * * Example sub selections are face, edge or vertex. If \c subElement is false, * then sub-object (i.e. a group child object) selection is also counted * even if it selects the whole sub-object. */ bool hasSubSelection(const char *doc=nullptr, bool subElement=false) const; /// Check if there is any pre-selection bool hasPreselection() const; /// Size of selected entities for all documents unsigned int size() const { return static_cast(_SelList.size()); } /** @name Selection stack functions * * Selection stack is for storing selection history so that the user can go * back and forward to previous selections. */ //@{ /// Return the current selection stack size std::size_t selStackBackSize() const { return _SelStackBack.size(); } /// Return the current forward selection stack size std::size_t selStackForwardSize() const { return _SelStackForward.size(); } /** Obtain selected objects from stack * * @param pDocName: optional filtering document, NULL for current active * document * @param resolve: sub-object resolving mode. * 0 no resolve, * 1 resolve sub-object with old style element name * 2 resolve sub-object with new style element name * @param index: optional position in the stack */ std::vector selStackGet(const char* pDocName=nullptr, ResolveMode resolve = ResolveMode::OldStyleElement, int index=0) const; /** Go back selection history * * @param count: optional number of steps to go back * * This function pops the selection stack, and populate the current * selection with the content of the last pop'd entry */ void selStackGoBack(int count=1); /** Go forward selection history * * @param count: optional number of steps to go back * * This function pops the selection stack, and populate the current * selection with the content of the last pop'd entry */ void selStackGoForward(int count=1); /** Save the current selection on to the stack * * @param clearForward: whether to clear forward selection stack * @param overwrite: whether to overwrite the current top entry of the * stack instead of pushing a new entry. */ void selStackPush(bool clearForward=true, bool overwrite=false); //@} /** @name Picked list functions * * Picked list stores all selected geometry elements that intersects the * 3D pick point. The list population is done by SoFCUnifiedSelection through * addSelection() with the pickedList argument. */ //@{ /// Check whether picked list is enabled bool needPickedList() const; /// Turn on or off picked list void enablePickedList(bool); /// Check if there is any selection inside picked list bool hasPickedList() const; /// Return select objects inside picked list std::vector getPickedList(const char* pDocName) const; /// Return selected object inside picked list grouped by top level parents std::vector getPickedListEx( const char* pDocName=nullptr, Base::Type typeId=App::DocumentObject::getClassTypeId()) const; //@} /** @name Selection style functions * * The selection style changes the way selection works. In Greedy selection * it is as if you were pressing Ctrl. */ //@{ enum class SelectionStyle { NormalSelection, GreedySelection }; /// Changes the style of selection between greedy and normal. void setSelectionStyle(SelectionStyle selStyle); /// Get the style of selection. SelectionStyle getSelectionStyle(); //@} static SelectionSingleton& instance(); static void destruct (); friend class SelectionFilter; // Python interface static PyMethodDef Methods[]; protected: static PyObject *sAddSelection (PyObject *self,PyObject *args); static PyObject *sUpdateSelection (PyObject *self,PyObject *args); static PyObject *sRemoveSelection (PyObject *self,PyObject *args); static PyObject *sClearSelection (PyObject *self,PyObject *args); static PyObject *sIsSelected (PyObject *self,PyObject *args); static PyObject *sCountObjectsOfType (PyObject *self,PyObject *args); static PyObject *sGetSelection (PyObject *self,PyObject *args); static PyObject *sSetPreselection (PyObject *self,PyObject *args,PyObject *kwd); static PyObject *sGetPreselection (PyObject *self,PyObject *args); static PyObject *sRemPreselection (PyObject *self,PyObject *args); static PyObject *sGetCompleteSelection(PyObject *self,PyObject *args); static PyObject *sGetSelectionEx (PyObject *self,PyObject *args); static PyObject *sGetSelectionObject (PyObject *self,PyObject *args); static PyObject *sSetSelectionStyle (PyObject *self,PyObject *args); static PyObject *sAddSelObserver (PyObject *self,PyObject *args); static PyObject *sRemSelObserver (PyObject *self,PyObject *args); static PyObject *sAddSelectionGate (PyObject *self,PyObject *args); static PyObject *sRemoveSelectionGate (PyObject *self,PyObject *args); static PyObject *sGetPickedList (PyObject *self,PyObject *args); static PyObject *sEnablePickedList (PyObject *self,PyObject *args); static PyObject *sPreselect (PyObject *self,PyObject *args); static PyObject *sSetVisible (PyObject *self,PyObject *args); static PyObject *sPushSelStack (PyObject *self,PyObject *args); static PyObject *sHasSelection (PyObject *self,PyObject *args); static PyObject *sHasSubSelection (PyObject *self,PyObject *args); static PyObject *sGetSelectionFromStack(PyObject *self,PyObject *args); protected: /// Construction SelectionSingleton(); /// Destruction ~SelectionSingleton() override; /// Observer message from the App doc void slotDeletedObject(const App::DocumentObject&); /// helper to retrieve document by name App::Document* getDocument(const char* pDocName=nullptr) const; void slotSelectionChanged(const SelectionChanges& msg); SelectionChanges CurrentPreselection; std::deque NotificationQueue; bool Notifying = false; void notify(SelectionChanges &&Chng); void notify(const SelectionChanges &Chng) { notify(SelectionChanges(Chng)); } struct _SelObj { std::string DocName; std::string FeatName; std::string SubName; std::string TypeName; App::Document* pDoc = nullptr; App::DocumentObject* pObject = nullptr; float x = 0.0f; float y = 0.0f; float z = 0.0f; bool logged = false; std::pair elementName; App::DocumentObject* pResolvedObject = nullptr; void log(bool remove=false, bool clearPreselect=true); }; mutable std::list<_SelObj> _SelList; mutable std::list<_SelObj> _PickedList; bool _needPickedList{false}; using SelStackItem = std::set; std::deque _SelStackBack; std::deque _SelStackForward; int checkSelection(const char *pDocName, const char *pObjectName, const char *pSubName, ResolveMode resolve, _SelObj &sel, const std::list<_SelObj> *selList=nullptr) const; std::vector getObjectList(const char* pDocName,Base::Type typeId, std::list<_SelObj> &objs, ResolveMode resolve, bool single=false) const; static App::DocumentObject *getObjectOfType(_SelObj &sel, Base::Type type, ResolveMode resolve, const char **subelement=nullptr); static SelectionSingleton* _pcSingleton; std::string DocName; std::string FeatName; std::string SubName; float hx,hy,hz; Gui::SelectionGate *ActiveGate; ResolveMode gateResolve; int logDisabled = 0; bool logHasSelection = false; SelectionStyle selectionStyle; }; /** * A convenience template-based method that returns an array with the correct types already. */ template inline std::vector SelectionSingleton::getObjectsOfType(const char* pDocName, ResolveMode resolve) const { std::vector type; std::vector obj = this->getObjectsOfType(T::getClassTypeId(), pDocName, resolve); type.reserve(obj.size()); for (auto it = obj.begin(); it != obj.end(); ++it) type.push_back(static_cast(*it)); return type; } /// Get the global instance inline SelectionSingleton& Selection() { return SelectionSingleton::instance(); } /** Helper class to disable logging selection action to MacroManager */ class GuiExport SelectionLogDisabler { public: explicit SelectionLogDisabler(bool silent=false) :silent(silent) { Selection().disableCommandLog(); } ~SelectionLogDisabler() { Selection().enableCommandLog(silent); } private: bool silent; }; } //namespace Gui #endif // GUI_SELECTION_H