tool_dispatcher.cpp 10.2 KB
Newer Older
Maciej Suminski's avatar
Maciej Suminski committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2013 CERN
 * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
 *
 * 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
 */

25 26 27 28 29
#include <wxPcbStruct.h>
#include <wxBasePcbFrame.h>

#include <tool/tool_manager.h>
#include <tool/tool_dispatcher.h>
30
#include <tools/common_actions.h>
31
#include <view/view.h>
Maciej Suminski's avatar
Maciej Suminski committed
32
#include <view/wx_view_controls.h>
33

34
#include <class_draw_panel_gal.h>
35
#include <pcbnew_id.h>
36 37 38 39

#include <boost/optional.hpp>
#include <boost/foreach.hpp>

40
///> Stores information about a mouse button state
Maciej Suminski's avatar
Maciej Suminski committed
41
struct TOOL_DISPATCHER::BUTTON_STATE
42
{
Maciej Suminski's avatar
Maciej Suminski committed
43
    BUTTON_STATE( TOOL_MOUSE_BUTTONS aButton, const wxEventType& aDownEvent,
44
                 const wxEventType& aUpEvent, const wxEventType& aDblClickEvent ) :
45 46
        button( aButton ),
        downEvent( aDownEvent ),
47 48
        upEvent( aUpEvent ),
        dblClickEvent( aDblClickEvent )
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
    {};

    ///> Flag indicating that dragging is active for the given button.
    bool dragging;

    ///> Flag indicating that the given button is pressed.
    bool pressed;

    ///> Point where dragging has started (in world coordinates).
    VECTOR2D dragOrigin;

    ///> Point where click event has occurred.
    VECTOR2D downPosition;

    ///> Difference between drag origin point and current mouse position (expressed as distance in
    ///> pixels).
    double dragMaxDelta;

    ///> Determines the mouse button for which information are stored.
Maciej Suminski's avatar
Maciej Suminski committed
68
    TOOL_MOUSE_BUTTONS button;
69 70 71 72 73 74 75

    ///> The type of wxEvent that determines mouse button press.
    wxEventType downEvent;

    ///> The type of wxEvent that determines mouse button release.
    wxEventType upEvent;

76 77 78
    ///> The type of wxEvent that determines mouse button double click.
    wxEventType dblClickEvent;

79 80 81 82 83 84 85 86 87
    ///> Time stamp for the last mouse button press event.
    wxLongLong downTimestamp;

    ///> Restores initial state.
    void Reset()
    {
        dragging = false;
        pressed = false;
    }
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111

    ///> Checks the current state of the button.
    bool GetState() const
    {
        wxMouseState mouseState = wxGetMouseState();

        switch( button )
        {
        case BUT_LEFT:
            return mouseState.LeftIsDown();

        case BUT_MIDDLE:
            return mouseState.MiddleIsDown();

        case BUT_RIGHT:
            return mouseState.RightIsDown();

        default:
            assert( false );
            break;
        }

        return false;
    }
112 113
};

Maciej Suminski's avatar
Maciej Suminski committed
114

115 116
TOOL_DISPATCHER::TOOL_DISPATCHER( TOOL_MANAGER* aToolMgr ) :
    m_toolMgr( aToolMgr )
Maciej Suminski's avatar
Maciej Suminski committed
117
{
118 119 120 121 122 123
    m_buttons.push_back( new BUTTON_STATE( BUT_LEFT, wxEVT_LEFT_DOWN,
                         wxEVT_LEFT_UP, wxEVT_LEFT_DCLICK ) );
    m_buttons.push_back( new BUTTON_STATE( BUT_RIGHT, wxEVT_RIGHT_DOWN,
                         wxEVT_RIGHT_UP, wxEVT_RIGHT_DCLICK ) );
    m_buttons.push_back( new BUTTON_STATE( BUT_MIDDLE, wxEVT_MIDDLE_DOWN,
                         wxEVT_MIDDLE_UP, wxEVT_MIDDLE_DCLICK ) );
Maciej Suminski's avatar
Maciej Suminski committed
124 125 126 127 128

    ResetState();
}


129 130
TOOL_DISPATCHER::~TOOL_DISPATCHER()
{
Maciej Suminski's avatar
Maciej Suminski committed
131
    BOOST_FOREACH( BUTTON_STATE* st, m_buttons )
132
        delete st;
133 134
}

Maciej Suminski's avatar
Maciej Suminski committed
135

136 137
void TOOL_DISPATCHER::ResetState()
{
Maciej Suminski's avatar
Maciej Suminski committed
138
    BOOST_FOREACH( BUTTON_STATE* st, m_buttons )
139
        st->Reset();
140 141 142
}


143
KIGFX::VIEW* TOOL_DISPATCHER::getView()
144
{
145
    return static_cast<PCB_BASE_FRAME*>( m_toolMgr->GetEditFrame() )->GetGalCanvas()->GetView();
146 147
}

Maciej Suminski's avatar
Maciej Suminski committed
148

149
bool TOOL_DISPATCHER::handleMouseButton( wxEvent& aEvent, int aIndex, bool aMotion )
150
{
Maciej Suminski's avatar
Maciej Suminski committed
151
    BUTTON_STATE* st = m_buttons[aIndex];
152
    wxEventType type = aEvent.GetEventType();
153
    boost::optional<TOOL_EVENT> evt;
154 155
    bool isClick = false;

156 157 158
//    bool up = type == st->upEvent;
//    bool down = type == st->downEvent;
    bool up = false, down = false;
159
    bool dblClick = type == st->dblClickEvent;
160 161 162 163 164 165 166 167 168 169 170
    bool state = st->GetState();

    if( !dblClick )
    {
        // Sometimes the dispatcher does not receive mouse button up event, so it stays
        // in the dragging mode even if the mouse button is not held anymore
        if( st->pressed && !state )
            up = true;
        else if( !st->pressed && state )
            down = true;
    }
171 172 173 174 175 176 177 178 179 180 181

    int mods = decodeModifiers<wxMouseEvent>( static_cast<wxMouseEvent*>( &aEvent ) );
    int args = st->button | mods;

    if( down )      // Handle mouse button press
    {
        st->downTimestamp = wxGetLocalTimeMillis();
        st->dragOrigin = m_lastMousePos;
        st->downPosition = m_lastMousePos;
        st->dragMaxDelta = 0;
        st->pressed = true;
Maciej Suminski's avatar
Maciej Suminski committed
182
        evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DOWN, args );
183
    }
184
    else if( up )   // Handle mouse button release
185 186 187 188 189 190 191 192 193 194 195 196
    {
        st->pressed = false;

        if( st->dragging )
        {
            wxLongLong t = wxGetLocalTimeMillis();

            // Determine if it was just a single click or beginning of dragging
            if( t - st->downTimestamp < DragTimeThreshold &&
                    st->dragMaxDelta < DragDistanceThreshold )
                isClick = true;
            else
Maciej Suminski's avatar
Maciej Suminski committed
197
                evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_UP, args );
198 199 200 201 202
        }
        else
            isClick = true;

        if( isClick )
Maciej Suminski's avatar
Maciej Suminski committed
203
            evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_CLICK, args );
204 205 206

        st->dragging = false;
    }
207 208 209 210
    else if( dblClick )
    {
        evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DBLCLICK, args );
    }
211 212 213 214

    if( st->pressed && aMotion )
    {
        st->dragging = true;
215 216
        double dragPixelDistance =
            getView()->ToScreen( m_lastMousePos - st->dragOrigin, false ).EuclideanNorm();
217 218 219 220 221 222
        st->dragMaxDelta = std::max( st->dragMaxDelta, dragPixelDistance );

        wxLongLong t = wxGetLocalTimeMillis();

        if( t - st->downTimestamp > DragTimeThreshold || st->dragMaxDelta > DragDistanceThreshold )
        {
Maciej Suminski's avatar
Maciej Suminski committed
223
            evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_DRAG, args );
224 225 226 227 228 229 230 231 232 233 234 235 236 237
            evt->SetMouseDragOrigin( st->dragOrigin );
            evt->SetMouseDelta( m_lastMousePos - st->dragOrigin );
        }
    }

    if( evt )
    {
        evt->SetMousePosition( isClick ? st->downPosition : m_lastMousePos );
        m_toolMgr->ProcessEvent( *evt );

        return true;
    }

    return false;
238 239
}

Maciej Suminski's avatar
Maciej Suminski committed
240

241
void TOOL_DISPATCHER::DispatchWxEvent( wxEvent& aEvent )
242
{
243
    bool motion = false, buttonEvents = false;
244
    boost::optional<TOOL_EVENT> evt;
245 246 247 248 249

    int type = aEvent.GetEventType();

    // Mouse handling
    if( type == wxEVT_MOTION || type == wxEVT_MOUSEWHEEL ||
250 251 252
        type == wxEVT_LEFT_DOWN || type == wxEVT_LEFT_UP ||
        type == wxEVT_MIDDLE_DOWN || type == wxEVT_MIDDLE_UP ||
        type == wxEVT_RIGHT_DOWN || type == wxEVT_RIGHT_UP ||
253
        type == wxEVT_LEFT_DCLICK || type == wxEVT_MIDDLE_DCLICK || type == wxEVT_RIGHT_DCLICK ||
254
        // Event issued whem mouse retains position in screen coordinates,
255
        // but changes in world coordinates (e.g. autopanning)
256
        type == KIGFX::WX_VIEW_CONTROLS::EVT_REFRESH_MOUSE )
257
    {
258 259 260
        wxMouseEvent* me = static_cast<wxMouseEvent*>( &aEvent );
        int mods = decodeModifiers<wxMouseEvent>( me );

261
        VECTOR2D screenPos = m_toolMgr->GetViewControls()->GetMousePosition();
262
        VECTOR2D pos = getView()->ToWorld( screenPos );
263

264
        if( pos != m_lastMousePos )
265 266 267
        {
            motion = true;
            m_lastMousePos = pos;
268
            static_cast<PCB_BASE_FRAME*>( m_toolMgr->GetEditFrame() )->UpdateStatusBar();
269
        }
270

271 272
        for( unsigned int i = 0; i < m_buttons.size(); i++ )
            buttonEvents |= handleMouseButton( aEvent, i, motion );
273

274 275
        if( !buttonEvents && motion )
        {
276
            evt = TOOL_EVENT( TC_MOUSE, TA_MOUSE_MOTION, mods );
277 278
            evt->SetMousePosition( pos );
        }
279 280 281 282 283

#ifdef __APPLE__
        // TODO That's a big ugly workaround, somehow DRAWPANEL_GAL loses focus
        // after second LMB click and currently I have no means to do better debugging
        if( type == wxEVT_LEFT_UP )
284
            static_cast<PCB_BASE_FRAME*>( m_toolMgr->GetEditFrame() )->GetGalCanvas()->SetFocus();
285
#endif /* __APPLE__ */
286
    }
287

288
    // Keyboard handling
289
    else if( type == wxEVT_CHAR )
290 291 292
    {
        wxKeyEvent* ke = static_cast<wxKeyEvent*>( &aEvent );
        int key = ke->GetKeyCode();
293
        int mods = decodeModifiers<wxKeyEvent>( ke );
294

295
        if( mods & MD_CTRL )
296
        {
297 298 299 300 301 302
#if !wxCHECK_VERSION( 2, 9, 0 )
            // I really look forward to the day when we will use only one version of wxWidgets..
            const int WXK_CONTROL_A = 1;
            const int WXK_CONTROL_Z = 26;
#endif

303 304 305 306 307 308
            // wxWidgets have a quirk related to Ctrl+letter hot keys handled by CHAR_EVT
            // http://docs.wxwidgets.org/trunk/classwx_key_event.html:
            // "char events for ASCII letters in this case carry codes corresponding to the ASCII
            // value of Ctrl-Latter, i.e. 1 for Ctrl-A, 2 for Ctrl-B and so on until 26 for Ctrl-Z."
            if( key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z )
                key += 'A' - 1;
309
        }
310 311 312

        if( key == WXK_ESCAPE ) // ESC is the special key for cancelling tools
            evt = TOOL_EVENT( TC_COMMAND, TA_CANCEL_TOOL );
313
        else
314
            evt = TOOL_EVENT( TC_KEYBOARD, TA_KEY_PRESSED, key | mods );
315 316
    }

317 318
    if( evt )
        m_toolMgr->ProcessEvent( *evt );
319

320 321
    // pass the event to the GUI, it might still be interested in it
    aEvent.Skip();
322 323
}

Maciej Suminski's avatar
Maciej Suminski committed
324

325
void TOOL_DISPATCHER::DispatchWxCommand( wxCommandEvent& aEvent )
326
{
327
    boost::optional<TOOL_EVENT> evt = COMMON_ACTIONS::TranslateLegacyId( aEvent.GetId() );
328 329 330

    if( evt )
        m_toolMgr->ProcessEvent( *evt );
331 332
    else
        aEvent.Skip();
Maciej Suminski's avatar
Maciej Suminski committed
333
}