/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2013-2023 CERN * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors. * * @author Tomasz Wlostowski * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef __TOOL_EVENT_H #define __TOOL_EVENT_H #include #include #include #include #include #include #include #include #include class COMMIT; class TOOL_ACTION; class TOOL_MANAGER; class TOOL_BASE; class TOOLS_HOLDER; /** * Internal (GUI-independent) event definitions. */ enum TOOL_EVENT_CATEGORY { TC_NONE = 0x00, TC_MOUSE = 0x01, TC_KEYBOARD = 0x02, TC_COMMAND = 0x04, TC_MESSAGE = 0x08, TC_VIEW = 0x10, TC_ANY = 0xffffffff }; enum TOOL_ACTIONS { // UI input events TA_NONE = 0x0000, TA_MOUSE_CLICK = 0x0001, TA_MOUSE_DBLCLICK = 0x0002, TA_MOUSE_UP = 0x0004, TA_MOUSE_DOWN = 0x0008, TA_MOUSE_DRAG = 0x0010, TA_MOUSE_MOTION = 0x0020, TA_MOUSE_WHEEL = 0x0040, TA_MOUSE = 0x007f, TA_KEY_PRESSED = 0x0080, TA_KEYBOARD = TA_KEY_PRESSED, // View related events TA_VIEW_REFRESH = 0x0100, TA_VIEW_ZOOM = 0x0200, TA_VIEW_PAN = 0x0400, TA_VIEW_DIRTY = 0x0800, TA_VIEW = 0x0f00, TA_CHANGE_LAYER = 0x1000, // Tool cancel event. Issued automagically when the user hits escape or selects End Tool from // the context menu. TA_CANCEL_TOOL = 0x2000, // Context menu update. Issued whenever context menu is open and the user hovers the mouse // over one of choices. Used in dynamic highlighting in disambiguation menu TA_CHOICE_MENU_UPDATE = 0x4000, // Context menu choice. Sent if the user picked something from the context menu or // closed it without selecting anything. TA_CHOICE_MENU_CHOICE = 0x8000, // Context menu is closed, no matter whether anything has been chosen or not. TA_CHOICE_MENU_CLOSED = 0x10000, TA_CHOICE_MENU = TA_CHOICE_MENU_UPDATE | TA_CHOICE_MENU_CHOICE | TA_CHOICE_MENU_CLOSED, // This event is sent *before* undo/redo command is performed. TA_UNDO_REDO_PRE = 0x20000, // This event is sent *after* undo/redo command is performed. TA_UNDO_REDO_POST = 0x40000, // Tool action (allows one to control tools). TA_ACTION = 0x80000, // Tool activation event. TA_ACTIVATE = 0x100000, // Tool re-activation event for tools already on the stack TA_REACTIVATE = 0x200000, // Model has changed (partial update). TA_MODEL_CHANGE = 0x400000, // Tool priming event (a special mouse click) TA_PRIME = 0x800001, TA_ANY = 0xffffffff }; enum TOOL_MOUSE_BUTTONS { BUT_NONE = 0x0, BUT_LEFT = 0x1, BUT_RIGHT = 0x2, BUT_MIDDLE = 0x4, BUT_AUX1 = 0x8, BUT_AUX2 = 0x10, BUT_BUTTON_MASK = BUT_LEFT | BUT_RIGHT | BUT_MIDDLE | BUT_AUX1 | BUT_AUX2, BUT_ANY = 0xffffffff }; enum TOOL_MODIFIERS { MD_SHIFT = 0x1000, MD_CTRL = 0x2000, MD_ALT = 0x4000, MD_MODIFIER_MASK = MD_SHIFT | MD_CTRL | MD_ALT, }; /// Defines when a context menu is opened. enum CONTEXT_MENU_TRIGGER { CMENU_BUTTON = 0, // On the right button CMENU_NOW, // Right now (after TOOL_INTERACTIVE::SetContextMenu) CMENU_OFF // Never }; enum SYNCRONOUS_TOOL_STATE { STS_RUNNING, STS_FINISHED, STS_CANCELLED }; /** * Generic, UI-independent tool event. */ class TOOL_EVENT { public: /** * Return information about event in form of a human-readable string. * * @return Event information. */ const std::string Format() const; TOOL_EVENT( TOOL_EVENT_CATEGORY aCategory = TC_NONE, TOOL_ACTIONS aAction = TA_NONE, TOOL_ACTION_SCOPE aScope = AS_GLOBAL ) : m_category( aCategory ), m_actions( aAction ), m_scope( aScope ), m_mouseButtons( 0 ), m_keyCode( 0 ), m_modifiers( 0 ), m_synchronousState( nullptr ), m_commit( nullptr ), m_firstResponder( nullptr ) { init(); } TOOL_EVENT( TOOL_EVENT_CATEGORY aCategory, TOOL_ACTIONS aAction, int aExtraParam, TOOL_ACTION_SCOPE aScope = AS_GLOBAL ) : m_category( aCategory ), m_actions( aAction ), m_scope( aScope ), m_mouseButtons( 0 ), m_keyCode( 0 ), m_modifiers( 0 ), m_synchronousState( nullptr ), m_commit( nullptr ), m_firstResponder( nullptr ) { if( aCategory == TC_MOUSE ) { setMouseButtons( aExtraParam & BUT_BUTTON_MASK ); } else if( aCategory == TC_KEYBOARD ) { m_keyCode = aExtraParam & ~MD_MODIFIER_MASK; // Filter out modifiers } else if( aCategory == TC_COMMAND ) { m_commandId = aExtraParam; } if( aCategory & ( TC_MOUSE | TC_KEYBOARD ) ) { m_modifiers = aExtraParam & MD_MODIFIER_MASK; } init(); } TOOL_EVENT( TOOL_EVENT_CATEGORY aCategory, TOOL_ACTIONS aAction, const std::string& aExtraParam, TOOL_ACTION_SCOPE aScope = AS_GLOBAL ) : m_category( aCategory ), m_actions( aAction ), m_scope( aScope ), m_mouseButtons( 0 ), m_keyCode( 0 ), m_modifiers( 0 ), m_synchronousState( nullptr ), m_commit( nullptr ), m_firstResponder( nullptr ) { if( aCategory == TC_COMMAND || aCategory == TC_MESSAGE ) m_commandStr = aExtraParam; init(); } ///< Returns the category (eg. mouse/keyboard/action) of an event.. TOOL_EVENT_CATEGORY Category() const { return m_category; } ///< Returns more specific information about the type of an event. TOOL_ACTIONS Action() const { return m_actions; } ///< These give a tool a method of informing the TOOL_MANAGER that a particular event should ///< be passed on to subsequent tools on the stack. Defaults to true for TC_MESSAGES; false ///< for everything else. bool PassEvent() const { return m_passEvent; } void SetPassEvent( bool aPass = true ) { m_passEvent = aPass; } ///< Returns if it this event has a valid position (true for mouse events and context-menu ///< or hotkey-based command events) bool HasPosition() const { return m_hasPosition; } void SetHasPosition( bool aHasPosition ) { m_hasPosition = aHasPosition; } ///< Returns if the action associated with this event should be treated as immediate regardless ///< of the current immediate action settings. bool ForceImmediate() const { return m_forceImmediate; } void SetForceImmediate( bool aForceImmediate = true ) { m_forceImmediate = aForceImmediate; } TOOL_BASE* FirstResponder() const { return m_firstResponder; } void SetFirstResponder( TOOL_BASE* aTool ) { m_firstResponder = aTool; } ///< Controls whether the tool is first being pushed to the stack or being reactivated after a pause bool IsReactivate() const { return m_reactivate; } void SetReactivate( bool aReactivate = true ) { m_reactivate = aReactivate; } void SetSynchronous( std::atomic* aState ) { m_synchronousState = aState; } std::atomic* SynchronousState() const { return m_synchronousState; } void SetCommit( COMMIT* aCommit ) { m_commit = aCommit; } COMMIT* Commit() const { return m_commit; } ///< Returns information about difference between current mouse cursor position and the place ///< where dragging has started. const VECTOR2D Delta() const { return returnCheckedPosition( m_mouseDelta ); } ///< Returns mouse cursor position in world coordinates. const VECTOR2D Position() const { return returnCheckedPosition( m_mousePos ); } ///< Returns the point where dragging has started. const VECTOR2D DragOrigin() const { return returnCheckedPosition( m_mouseDragOrigin ); } ///< Returns information about mouse buttons state. int Buttons() const { assert( m_category == TC_MOUSE ); // this should be used only with mouse events return m_mouseButtons; } bool IsClick( int aButtonMask = BUT_ANY ) const; bool IsDblClick( int aButtonMask = BUT_ANY ) const; bool IsDrag( int aButtonMask = BUT_ANY ) const { return m_actions == TA_MOUSE_DRAG && ( m_mouseButtons & aButtonMask ) == m_mouseButtons; } bool IsMouseDown( int aButtonMask = BUT_ANY ) const { return m_actions == TA_MOUSE_DOWN && ( m_mouseButtons & aButtonMask ) == m_mouseButtons; } bool IsMouseUp( int aButtonMask = BUT_ANY ) const { return m_actions == TA_MOUSE_UP && ( m_mouseButtons & aButtonMask ) == m_mouseButtons; } bool IsMotion() const { return m_actions == TA_MOUSE_MOTION; } bool IsMouseAction() const { return ( m_actions & TA_MOUSE ); } bool IsCancel() const { return m_actions == TA_CANCEL_TOOL; } bool IsActivate() const { return m_actions == TA_ACTIVATE; } bool IsUndoRedo() const { return m_actions & ( TA_UNDO_REDO_PRE | TA_UNDO_REDO_POST ); } bool IsChoiceMenu() const { return m_actions & TA_CHOICE_MENU; } bool IsPrime() const { return m_actions == TA_PRIME; } ///< Returns information about key modifiers state (Ctrl, Alt, etc.) int Modifier( int aMask = MD_MODIFIER_MASK ) const { return m_modifiers & aMask; } bool DisableGridSnapping() const { return Modifier( MD_CTRL ); } int KeyCode() const { return m_keyCode; } bool IsKeyPressed() const { return m_actions == TA_KEY_PRESSED; } /** * Test whether two events match in terms of category & action or command. * * @param aEvent is the event to test against. * @return True if two events match, false otherwise. */ bool Matches( const TOOL_EVENT& aEvent ) const { if( !( m_category & aEvent.m_category ) ) return false; if( m_category == TC_COMMAND || m_category == TC_MESSAGE ) { if( !m_commandStr.empty() && !aEvent.getCommandStr().empty() ) return m_commandStr == aEvent.m_commandStr; if( (bool) m_commandId && (bool) aEvent.m_commandId ) return *m_commandId == *aEvent.m_commandId; } // BUGFIX: TA_ANY should match EVERYTHING, even TA_NONE (for TC_MESSAGE) if( m_actions == TA_ANY && aEvent.m_actions == TA_NONE && aEvent.m_category == TC_MESSAGE ) return true; // BUGFIX: This check must happen after the TC_COMMAND check because otherwise events of // the form { TC_COMMAND, TA_NONE } will be incorrectly skipped if( !( m_actions & aEvent.m_actions ) ) return false; return true; } /** * Test if the event contains an action issued upon activation of the given #TOOL_ACTION. * * @param aAction is the TOOL_ACTION to be checked against. * @return True if it matches the given TOOL_ACTION. */ bool IsAction( const TOOL_ACTION* aAction ) const; /** * Indicate the event should restart/end an ongoing interactive tool's event loop (eg esc * key, click cancel, start different tool). */ bool IsCancelInteractive() const; /** * Indicate an selection-changed notification event. */ bool IsSelectionEvent() const; /** * Indicate if the event is from one of the point editors. * * Usually used to allow the point editor to activate itself without de-activating the * current drawing tool. */ bool IsPointEditor() const; /** * Indicate if the event is from one of the move tools. * * Usually used to allow move to be done without de-activating the current drawing tool. */ bool IsMoveTool() const; /** * Indicate if the event is asking for an editor tool. * * Used to allow deleting an element without de-activating the current tool. */ bool IsEditorTool() const; /** * Indicate if the event is from the simulator. */ bool IsSimulator() const; /** * Return a parameter assigned to the event. Its meaning depends on the target tool. */ template::value>* = nullptr > T Parameter() const { T param; wxCHECK_MSG( m_param.has_value(), T(), "Attempted to get a parameter from an event with " "no parameter." ); try { param = std::any_cast( m_param ); } catch( const std::bad_any_cast& ) { wxCHECK_MSG( false, T(), wxString::Format( "Requested parameter type %s from event " "with parameter type %s.", typeid(T).name(), m_param.type().name() ) ); } return param; } /** * Return pointer parameter assigned to the event. Its meaning depends on the target tool. */ template::value>* = nullptr> T Parameter() const { T param = nullptr; wxCHECK_MSG( m_param.has_value(), param, "Attempted to get a parameter from an event with " "no parameter." ); try { param = std::any_cast( m_param ); } catch( const std::bad_any_cast& ) { wxCHECK_MSG( false, param, wxString::Format( "Requested parameter type %s from event " "with parameter type %s.", typeid(T).name(), m_param.type().name() ) ); } return param; } /** * Set a non-standard parameter assigned to the event. Its meaning depends on the * target tool. * * @param aParam is the new parameter. */ template void SetParameter(T aParam) { m_param = aParam; } std::optional GetCommandId() const { return m_commandId; } void SetMousePosition( const VECTOR2D& aP ) { m_mousePos = aP; } void SetActionGroup( const TOOL_ACTION_GROUP& aGroup ) { m_actionGroup = aGroup; } bool IsActionInGroup( const TOOL_ACTION_GROUP& aGroup ) const; private: friend class TOOL_DISPATCHER; friend class TOOL_MANAGER; friend class TOOLS_HOLDER; void init(); const std::string& getCommandStr() const { return m_commandStr; } void setMouseDragOrigin( const VECTOR2D& aP ) { m_mouseDragOrigin = aP; } void setMouseDelta( const VECTOR2D& aP ) { m_mouseDelta = aP; } void setMouseButtons( int aButtons ) { assert( ( aButtons & ~BUT_BUTTON_MASK ) == 0 ); m_mouseButtons = aButtons; } void setModifiers( int aMods ) { assert( ( aMods & ~MD_MODIFIER_MASK ) == 0 ); m_modifiers = aMods; } /** * Ensure that the event is a type that has a position before returning a * position, otherwise return a null-constructed position. * * Used to defend the position accessors from runtime access when the event * does not have a valid position. * * @param aPos the position to return if the event is valid * @return the checked position */ VECTOR2D returnCheckedPosition( const VECTOR2D& aPos ) const; TOOL_EVENT_CATEGORY m_category; TOOL_ACTIONS m_actions; TOOL_ACTION_SCOPE m_scope; bool m_passEvent; bool m_hasPosition; bool m_forceImmediate; ///< Optional group that the parent action for the event belongs to std::optional m_actionGroup; ///< True when the tool is being re-activated from the stack bool m_reactivate; ///< Difference between mouse cursor position and ///< the point where dragging event has started VECTOR2D m_mouseDelta; ///< Current mouse cursor position VECTOR2D m_mousePos; ///< Point where dragging has started VECTOR2D m_mouseDragOrigin; ///< State of mouse buttons int m_mouseButtons; ///< Stores code of pressed/released key int m_keyCode; ///< State of key modifiers (Ctrl/Alt/etc.) int m_modifiers; std::atomic* m_synchronousState; /// Commit the tool handling the event should add to COMMIT* m_commit; ///< Generic parameter used for passing non-standard data. std::any m_param; ///< The first tool to receive the event TOOL_BASE* m_firstResponder; std::optional m_commandId; std::string m_commandStr; }; typedef std::optional OPT_TOOL_EVENT; /** * A list of TOOL_EVENTs, with overloaded || operators allowing for concatenating TOOL_EVENTs * with little code. */ class TOOL_EVENT_LIST { public: typedef TOOL_EVENT value_type; typedef std::deque::iterator iterator; typedef std::deque::const_iterator const_iterator; ///< Default constructor. Creates an empty list. TOOL_EVENT_LIST() {} ///< Constructor for a list containing only one TOOL_EVENT. TOOL_EVENT_LIST( const TOOL_EVENT& aSingleEvent ) { m_events.push_back( aSingleEvent ); } ///< Copy an existing TOOL_EVENT_LIST TOOL_EVENT_LIST( const TOOL_EVENT_LIST& aEventList ) { m_events.clear(); for( const TOOL_EVENT& event : aEventList.m_events ) m_events.push_back( event ); } /** * Function Format() * Returns information about event in form of a human-readable string. * * @return Event information. */ const std::string Format() const; OPT_TOOL_EVENT Matches( const TOOL_EVENT& aEvent ) const { for( const TOOL_EVENT& event : m_events ) { if( event.Matches( aEvent ) ) return event; } return OPT_TOOL_EVENT(); } /** * Add a tool event to the list. * * @param aEvent is the tool event to be added. */ void Add( const TOOL_EVENT& aEvent ) { m_events.push_back( aEvent ); } iterator begin() { return m_events.begin(); } iterator end() { return m_events.end(); } const_iterator cbegin() const { return m_events.begin(); } const_iterator cend() const { return m_events.end(); } int size() const { return m_events.size(); } void clear() { m_events.clear(); } TOOL_EVENT_LIST& operator=( const TOOL_EVENT_LIST& aEventList ) { m_events.clear(); for( const TOOL_EVENT& event : aEventList.m_events ) m_events.push_back( event ); return *this; } TOOL_EVENT_LIST& operator=( const TOOL_EVENT& aEvent ) { m_events.clear(); m_events.push_back( aEvent ); return *this; } TOOL_EVENT_LIST& operator||( const TOOL_EVENT& aEvent ) { Add( aEvent ); return *this; } TOOL_EVENT_LIST& operator||( const TOOL_EVENT_LIST& aEvent ) { std::copy( aEvent.m_events.begin(), aEvent.m_events.end(), std::back_inserter( m_events ) ); return *this; } private: std::deque m_events; }; inline const TOOL_EVENT_LIST operator||( const TOOL_EVENT& aEventA, const TOOL_EVENT& aEventB ) { TOOL_EVENT_LIST l; l.Add( aEventA ); l.Add( aEventB ); return l; } inline const TOOL_EVENT_LIST operator||( const TOOL_EVENT& aEvent, const TOOL_EVENT_LIST& aEventList ) { TOOL_EVENT_LIST l( aEventList ); l.Add( aEvent ); return l; } #endif