KiCad PCB EDA Suite
wx_view_controls.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
5 * Copyright (C) 2013-2015 CERN
6 * Copyright (C) 2012-2022 KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * @author Tomasz Wlostowski <[email protected]>
9 * @author Maciej Suminski <[email protected]>
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, you may find one here:
23 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
24 * or you may search the http://www.gnu.org website for the version 2 license,
25 * or you may write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
27 */
28
29#include <pgm_base.h>
30#include <profile.h>
31#include <view/view.h>
36#include <trace_helpers.h>
38#include <math/util.h> // for KiROUND
40#include <widgets/ui_common.h>
42#include <eda_draw_frame.h>
43#include <kiway.h>
44#include <kiplatform/ui.h>
45#include <wx/log.h>
46
47#ifdef __WXMSW__
48 #define USE_MOUSE_CAPTURE
49#endif
50
51using namespace KIGFX;
52
53const wxEventType WX_VIEW_CONTROLS::EVT_REFRESH_MOUSE = wxNewEventType();
54
55
56static std::unique_ptr<ZOOM_CONTROLLER> GetZoomControllerForPlatform( bool aAcceleration )
57{
58#ifdef __WXMAC__
59 // On Apple pointer devices, wheel events occur frequently and with
60 // smaller rotation values. For those devices, let's handle zoom
61 // based on the rotation amount rather than the time difference.
62 return std::make_unique<CONSTANT_ZOOM_CONTROLLER>( CONSTANT_ZOOM_CONTROLLER::MAC_SCALE );
63#elif __WXGTK3__
64 // GTK3 is similar, but the scale constant is smaller
65 return std::make_unique<CONSTANT_ZOOM_CONTROLLER>( CONSTANT_ZOOM_CONTROLLER::GTK3_SCALE );
66#else
67 if( aAcceleration )
68 return std::make_unique<ACCELERATING_ZOOM_CONTROLLER>();
69 else
70 return std::make_unique<CONSTANT_ZOOM_CONTROLLER>( CONSTANT_ZOOM_CONTROLLER::MSW_SCALE );
71#endif
72}
73
74
75WX_VIEW_CONTROLS::WX_VIEW_CONTROLS( VIEW* aView, EDA_DRAW_PANEL_GAL* aParentPanel ) :
76 VIEW_CONTROLS( aView ),
77 m_state( IDLE ),
78 m_parentPanel( aParentPanel ),
79 m_scrollScale( 1.0, 1.0 ),
80#ifdef __WXGTK3__
81 m_lastTimestamp( 0 ),
82#endif
83 m_cursorPos( 0, 0 ),
84 m_updateCursor( true )
85{
87
88 m_MotionEventCounter = std::make_unique<PROF_COUNTER>( "Mouse motion events" );
89
90 m_parentPanel->Connect( wxEVT_MOTION,
91 wxMouseEventHandler( WX_VIEW_CONTROLS::onMotion ), nullptr, this );
92#if wxCHECK_VERSION( 3, 1, 0 ) || defined( USE_OSX_MAGNIFY_EVENT )
93 m_parentPanel->Connect( wxEVT_MAGNIFY,
94 wxMouseEventHandler( WX_VIEW_CONTROLS::onMagnify ), nullptr, this );
95#endif
96 m_parentPanel->Connect( wxEVT_MOUSEWHEEL,
97 wxMouseEventHandler( WX_VIEW_CONTROLS::onWheel ), nullptr, this );
98 m_parentPanel->Connect( wxEVT_MIDDLE_UP,
99 wxMouseEventHandler( WX_VIEW_CONTROLS::onButton ), nullptr, this );
100 m_parentPanel->Connect( wxEVT_MIDDLE_DOWN,
101 wxMouseEventHandler( WX_VIEW_CONTROLS::onButton ), nullptr, this );
102 m_parentPanel->Connect( wxEVT_LEFT_UP,
103 wxMouseEventHandler( WX_VIEW_CONTROLS::onButton ), nullptr, this );
104 m_parentPanel->Connect( wxEVT_LEFT_DOWN,
105 wxMouseEventHandler( WX_VIEW_CONTROLS::onButton ), nullptr, this );
106 m_parentPanel->Connect( wxEVT_RIGHT_UP,
107 wxMouseEventHandler( WX_VIEW_CONTROLS::onButton ), nullptr, this );
108 m_parentPanel->Connect( wxEVT_RIGHT_DOWN,
109 wxMouseEventHandler( WX_VIEW_CONTROLS::onButton ), nullptr, this );
110#if defined __WXMSW__
111 m_parentPanel->Connect( wxEVT_ENTER_WINDOW,
112 wxMouseEventHandler( WX_VIEW_CONTROLS::onEnter ), nullptr, this );
113#endif
114 m_parentPanel->Connect( wxEVT_LEAVE_WINDOW,
115 wxMouseEventHandler( WX_VIEW_CONTROLS::onLeave ), nullptr, this );
116 m_parentPanel->Connect( wxEVT_SCROLLWIN_THUMBTRACK,
117 wxScrollWinEventHandler( WX_VIEW_CONTROLS::onScroll ), nullptr, this );
118 m_parentPanel->Connect( wxEVT_SCROLLWIN_PAGEUP,
119 wxScrollWinEventHandler( WX_VIEW_CONTROLS::onScroll ), nullptr, this );
120 m_parentPanel->Connect( wxEVT_SCROLLWIN_PAGEDOWN,
121 wxScrollWinEventHandler( WX_VIEW_CONTROLS::onScroll ), nullptr, this );
122
123 m_parentPanel->Connect( wxEVT_SCROLLWIN_BOTTOM,
124 wxScrollWinEventHandler( WX_VIEW_CONTROLS::onScroll ), nullptr, this );
125 m_parentPanel->Connect( wxEVT_SCROLLWIN_TOP,
126 wxScrollWinEventHandler( WX_VIEW_CONTROLS::onScroll ), nullptr, this );
127 m_parentPanel->Connect( wxEVT_SCROLLWIN_LINEUP,
128 wxScrollWinEventHandler( WX_VIEW_CONTROLS::onScroll ), nullptr, this );
129 m_parentPanel->Connect( wxEVT_SCROLLWIN_LINEDOWN,
130 wxScrollWinEventHandler( WX_VIEW_CONTROLS::onScroll ), nullptr, this );
131#if defined USE_MOUSE_CAPTURE
132 m_parentPanel->Connect( wxEVT_MOUSE_CAPTURE_LOST,
133 wxMouseEventHandler( WX_VIEW_CONTROLS::onCaptureLost ), nullptr, this );
134#endif
135
136 m_cursorWarped = false;
137
138 m_panTimer.SetOwner( this );
139 this->Connect( wxEVT_TIMER, wxTimerEventHandler( WX_VIEW_CONTROLS::onTimer ), nullptr, this );
140
144}
145
146
148{
149#if defined USE_MOUSE_CAPTURE
150 if( m_parentPanel->HasCapture() )
151 m_parentPanel->ReleaseMouse();
152#endif
153}
154
155
157{
158 COMMON_SETTINGS* cfg = Pgm().GetCommonSettings();
159
174
175 m_zoomController.reset();
176
177 if( cfg->m_Input.zoom_speed_auto )
178 {
180 }
181 else
182 {
183 if( cfg->m_Input.zoom_acceleration )
184 {
186 std::make_unique<ACCELERATING_ZOOM_CONTROLLER>( cfg->m_Input.zoom_speed );
187 }
188 else
189 {
191
192 m_zoomController = std::make_unique<CONSTANT_ZOOM_CONTROLLER>( scale );
193 }
194 }
195}
196
197
198void WX_VIEW_CONTROLS::onMotion( wxMouseEvent& aEvent )
199{
200 ( *m_MotionEventCounter )++;
201
202 bool isAutoPanning = false;
203 int x = aEvent.GetX();
204 int y = aEvent.GetY();
205 VECTOR2D mousePos( x, y );
206
207 // Automatic focus switching between SCH and PCB windows on canvas mouse motion
209 {
211 {
212 KIWAY_PLAYER* otherFrame = nullptr;
213
214 if( frame->IsType( FRAME_PCB_EDITOR ) )
215 {
216 otherFrame = frame->Kiway().Player( FRAME_SCH, false );
217 }
218 else if( frame->IsType( FRAME_SCH ) )
219 {
220 otherFrame = frame->Kiway().Player( FRAME_PCB_EDITOR, false );
221 }
222
223 if( otherFrame && KIPLATFORM::UI::IsWindowActive( otherFrame )
224 && !KIPLATFORM::UI::IsWindowActive( frame ) )
225 {
226 frame->Raise();
227 }
228 }
229 }
230
232 handleCursorCapture( x, y );
233
235 isAutoPanning = handleAutoPanning( aEvent );
236
237 if( !isAutoPanning && aEvent.Dragging() )
238 {
239 if( m_state == DRAG_PANNING )
240 {
241 static bool justWarped = false;
242 int warpX = 0;
243 int warpY = 0;
244 wxSize parentSize = m_parentPanel->GetClientSize();
245
246 if( x < 0 )
247 {
248 warpX = parentSize.x;
249 }
250 else if(x >= parentSize.x )
251 {
252 warpX = -parentSize.x;
253 }
254
255 if( y < 0 )
256 {
257 warpY = parentSize.y;
258 }
259 else if( y >= parentSize.y )
260 {
261 warpY = -parentSize.y;
262 }
263
264 if( !justWarped )
265 {
266 VECTOR2D d = m_dragStartPoint - mousePos;
267 m_dragStartPoint = mousePos;
268 VECTOR2D delta = m_view->ToWorld( d, false );
270 aEvent.StopPropagation();
271 }
272
273 if( warpX || warpY )
274 {
275 if( !justWarped )
276 {
277 KIPLATFORM::UI::WarpPointer( m_parentPanel, x + warpX, y + warpY );
278 m_dragStartPoint += VECTOR2D( warpX, warpY );
279 justWarped = true;
280 }
281 else
282 justWarped = false;
283 }
284 else
285 justWarped = false;
286 }
287 else if( m_state == DRAG_ZOOMING )
288 {
289 static bool justWarped = false;
290 int warpY = 0;
291 wxSize parentSize = m_parentPanel->GetClientSize();
292
293 if( y < 0 )
294 {
295 warpY = parentSize.y;
296 }
297 else if( y >= parentSize.y )
298 {
299 warpY = -parentSize.y;
300 }
301
302 if( !justWarped )
303 {
304 VECTOR2D d = m_dragStartPoint - mousePos;
305 m_dragStartPoint = mousePos;
306
307 double scale = exp( d.y * m_settings.m_zoomSpeed * 0.001 );
308
309 wxLogTrace( traceZoomScroll, wxString::Format( "dy: %f scale: %f", d.y, scale ) );
310
312 aEvent.StopPropagation();
313 }
314
315 if( warpY )
316 {
317 if( !justWarped )
318 {
320 m_dragStartPoint += VECTOR2D( 0, warpY );
321 justWarped = true;
322 }
323 else
324 justWarped = false;
325 }
326 else
327 justWarped = false;
328 }
329 }
330
331 if( m_updateCursor ) // do not update the cursor position if it was explicitly set
332 m_cursorPos = GetClampedCoords( m_view->ToWorld( mousePos ) );
333 else
334 m_updateCursor = true;
335
336 aEvent.Skip();
337}
338
339
340void WX_VIEW_CONTROLS::onWheel( wxMouseEvent& aEvent )
341{
342#ifdef __WXGTK3__
343 if( aEvent.GetTimestamp() == m_lastTimestamp )
344 {
345 aEvent.Skip( false );
346 return;
347 }
348
349 m_lastTimestamp = aEvent.GetTimestamp();
350#endif
351
352 const double wheelPanSpeed = 0.001;
353 const int axis = aEvent.GetWheelAxis();
354
355 if( axis == wxMOUSE_WHEEL_HORIZONTAL && !m_settings.m_horizontalPan )
356 return;
357
358 // Pick the modifier, if any. Shift beats control beats alt, we don't support more than one.
359 int modifiers =
360 aEvent.ShiftDown() ? WXK_SHIFT :
361 ( aEvent.ControlDown() ? WXK_CONTROL : ( aEvent.AltDown() ? WXK_ALT : 0 ) );
362
363 // Restrict zoom handling to the vertical axis, otherwise horizontal
364 // scrolling events (e.g. touchpads and some mice) end up interpreted
365 // as vertical scroll events and confuse the user.
366 if( axis == wxMOUSE_WHEEL_VERTICAL && modifiers == m_settings.m_scrollModifierZoom )
367 {
368 const int rotation = aEvent.GetWheelRotation();
369 const double zoomScale = m_zoomController->GetScaleForRotation( rotation );
370
372 {
374 m_view->SetScale( m_view->GetScale() * zoomScale );
375 }
376 else
377 {
378 const VECTOR2D anchor = m_view->ToWorld( VECTOR2D( aEvent.GetX(), aEvent.GetY() ) );
379 m_view->SetScale( m_view->GetScale() * zoomScale, anchor );
380 }
381
382 // Refresh the zoom level and mouse position on message panel
383 // (mouse position has not changed, only the zoom level has changed):
384 refreshMouse();
385 }
386 else
387 {
388 // Scrolling
389 VECTOR2D scrollVec = m_view->ToWorld( m_view->GetScreenPixelSize(), false ) *
390 ( (double) aEvent.GetWheelRotation() * wheelPanSpeed );
391 double scrollX = 0.0;
392 double scrollY = 0.0;
393
394 if( axis == wxMOUSE_WHEEL_HORIZONTAL || modifiers == m_settings.m_scrollModifierPanH )
395 scrollX = scrollVec.x;
396 else
397 scrollY = -scrollVec.y;
398
399 VECTOR2D delta( scrollX, scrollY );
400
402 refreshMouse();
403 }
404
405 // Do not skip this event, otherwise wxWidgets will fire
406 // 3 wxEVT_SCROLLWIN_LINEUP or wxEVT_SCROLLWIN_LINEDOWN (normal wxWidgets behavior)
407 // and we do not want that.
409}
410
411
412#if wxCHECK_VERSION( 3, 1, 0 ) || defined( USE_OSX_MAGNIFY_EVENT )
413void WX_VIEW_CONTROLS::onMagnify( wxMouseEvent& aEvent )
414{
415 // Scale based on the magnification from our underlying magnification event.
416 VECTOR2D anchor = m_view->ToWorld( VECTOR2D( aEvent.GetX(), aEvent.GetY() ) );
417 m_view->SetScale( m_view->GetScale() * ( aEvent.GetMagnification() + 1.0f ), anchor );
418
419 aEvent.Skip();
420}
421#endif
422
423
425{
426 m_state = aNewState;
427}
428
429void WX_VIEW_CONTROLS::onButton( wxMouseEvent& aEvent )
430{
431 switch( m_state )
432 {
433 case IDLE:
434 case AUTO_PANNING:
435 if( ( aEvent.MiddleDown() && m_settings.m_dragMiddle == MOUSE_DRAG_ACTION::PAN ) ||
436 ( aEvent.RightDown() && m_settings.m_dragRight == MOUSE_DRAG_ACTION::PAN ) )
437 {
438 m_dragStartPoint = VECTOR2D( aEvent.GetX(), aEvent.GetY() );
440
441#if defined USE_MOUSE_CAPTURE
442 if( !m_parentPanel->HasCapture() )
443 m_parentPanel->CaptureMouse();
444#endif
445 }
446 else if( ( aEvent.MiddleDown() && m_settings.m_dragMiddle == MOUSE_DRAG_ACTION::ZOOM ) ||
447 ( aEvent.RightDown() && m_settings.m_dragRight == MOUSE_DRAG_ACTION::ZOOM ) )
448 {
449 m_dragStartPoint = VECTOR2D( aEvent.GetX(), aEvent.GetY() );
452
453#if defined USE_MOUSE_CAPTURE
454 if( !m_parentPanel->HasCapture() )
455 m_parentPanel->CaptureMouse();
456#endif
457 }
458
459 if( aEvent.LeftUp() )
460 setState( IDLE ); // Stop autopanning when user release left mouse button
461
462 break;
463
464 case DRAG_ZOOMING:
465 case DRAG_PANNING:
466 if( aEvent.MiddleUp() || aEvent.LeftUp() || aEvent.RightUp() )
467 {
468 setState( IDLE );
469
470#if defined USE_MOUSE_CAPTURE
471 if( !m_settings.m_cursorCaptured && m_parentPanel->HasCapture() )
472 m_parentPanel->ReleaseMouse();
473#endif
474 }
475
476 break;
477 }
478
479 aEvent.Skip();
480}
481
482
483void WX_VIEW_CONTROLS::onEnter( wxMouseEvent& aEvent )
484{
485 // Avoid stealing focus from text controls
486 // This is particularly important for users using On-Screen-Keyboards
487 // They may move the mouse over the canvas to reach the keyboard
489 {
490 return;
491 }
492
493#if defined( _WIN32 ) || defined( __WXGTK__ )
494 // Win32 and some *nix WMs transmit mouse move and wheel events to all controls below the mouse regardless
495 // of focus. Forcing the focus here will cause the EDA FRAMES to immediately become the
496 // top level active window.
497 if( m_parentPanel->GetParent() != nullptr )
498 {
499 // this assumes the parent panel's parent is the eda window
501 {
503 }
504 }
505#else
507#endif
508}
509
510
511void WX_VIEW_CONTROLS::onLeave( wxMouseEvent& aEvent )
512{
513#if !defined USE_MOUSE_CAPTURE
514 onMotion( aEvent );
515#endif
516}
517
518void WX_VIEW_CONTROLS::onCaptureLost( wxMouseEvent& aEvent )
519{
520 // This method must be present to suppress the capture-lost assertion
521
522 // Set the flag to allow calling m_parentPanel->CaptureMouse()
523 // Note: One cannot call m_parentPanel->CaptureMouse() twice, this is not accepted
524 // by wxWidgets (MSW specific) so we need this guard
526}
527
528
529void WX_VIEW_CONTROLS::onTimer( wxTimerEvent& aEvent )
530{
531 switch( m_state )
532 {
533 case AUTO_PANNING:
534 {
536 {
537 setState( IDLE );
538 return;
539 }
540
541 #ifdef __WXMSW__
542 // Hackfix: It's possible for the mouse to leave the canvas
543 // without triggering any leave events on windows
544 // Use a MSW only wx function
545 if( !m_parentPanel->IsMouseInWindow() )
546 {
547 m_panTimer.Stop();
548 setState( IDLE );
549 return;
550 }
551 #endif
552
553 if( !m_parentPanel->HasFocus() && !m_parentPanel->StatusPopupHasFocus() )
554 {
555 setState( IDLE );
556 return;
557 }
558
559 double borderSize = std::min( m_settings.m_autoPanMargin * m_view->GetScreenPixelSize().x,
561
562 // When the mouse cursor is outside the area with no pan,
563 // m_panDirection is the dist to this area limit ( in pixels )
564 // It will be used also as pan value (the pan speed depends on this dist).
566
567 // When the mouse cursor is outside the area with no pan, the pan value
568 // is accelerated depending on the dist between the area and the cursor
569 float accel = 0.5f + ( m_settings.m_autoPanAcceleration / 5.0f );
570
571 // For a small mouse cursor dist to area, just use the distance.
572 // But for a dist > borderSize / 2, use an accelerated pan value
573
574 if( dir.EuclideanNorm() >= borderSize ) // far from area limits
575 dir = dir.Resize( borderSize * accel );
576 else if( dir.EuclideanNorm() > borderSize / 2 ) // Near from area limits
577 dir = dir.Resize( borderSize );
578
579 dir = m_view->ToWorld( dir, false );
580 m_view->SetCenter( m_view->GetCenter() + dir );
581
582 refreshMouse();
583 }
584 break;
585
586 case IDLE: // Just remove unnecessary warnings
587 case DRAG_PANNING:
588 case DRAG_ZOOMING:
589 break;
590 }
591}
592
593
594void WX_VIEW_CONTROLS::onScroll( wxScrollWinEvent& aEvent )
595{
596 const double linePanDelta = 0.05;
597 const double pagePanDelta = 0.5;
598
599 int type = aEvent.GetEventType();
600 int dir = aEvent.GetOrientation();
601
602 if( type == wxEVT_SCROLLWIN_THUMBTRACK )
603 {
604 auto center = m_view->GetCenter();
605 const auto& boundary = m_view->GetBoundary();
606
607 // Flip scroll direction in flipped view
608 const double xstart = ( m_view->IsMirroredX() ?
609 boundary.GetRight() : boundary.GetLeft() );
610 const double xdelta = ( m_view->IsMirroredX() ? -1 : 1 );
611
612 if( dir == wxHORIZONTAL )
613 center.x = xstart + xdelta * ( aEvent.GetPosition() / m_scrollScale.x );
614 else
615 center.y = boundary.GetTop() + aEvent.GetPosition() / m_scrollScale.y;
616
617 m_view->SetCenter( center );
618 }
619 else
620 {
621 double dist = 0;
622
623 if( type == wxEVT_SCROLLWIN_PAGEUP )
624 dist = pagePanDelta;
625 else if( type == wxEVT_SCROLLWIN_PAGEDOWN )
626 dist = -pagePanDelta;
627 else if( type == wxEVT_SCROLLWIN_LINEUP )
628 dist = linePanDelta;
629 else if( type == wxEVT_SCROLLWIN_LINEDOWN )
630 dist = -linePanDelta;
631 else
632 wxCHECK_MSG( false, /* void */, wxT( "Unhandled event type" ) );
633
634 VECTOR2D scroll = m_view->ToWorld( m_view->GetScreenPixelSize(), false ) * dist;
635
636 double scrollX = 0.0;
637 double scrollY = 0.0;
638
639 if ( dir == wxHORIZONTAL )
640 scrollX = -scroll.x;
641 else
642 scrollY = -scroll.y;
643
644 VECTOR2D delta( scrollX, scrollY );
645
647 }
648
650}
651
652
654{
655#if defined USE_MOUSE_CAPTURE
656 // Note: for some reason, m_parentPanel->HasCapture() can be false even if CaptureMouse()
657 // was called (i.e. mouse was captured, so when need to test m_MouseCapturedLost to be
658 // sure a wxEVT_MOUSE_CAPTURE_LOST event was fired before. Otherwise wxMSW complains
659 if( aEnabled && !m_parentPanel->HasCapture() && m_parentPanel->m_MouseCapturedLost )
660 {
661 m_parentPanel->CaptureMouse();
662
663 // Clear the flag to allow calling m_parentPanel->CaptureMouse()
664 // Calling it without calling ReleaseMouse() is not accepted by wxWidgets (MSW specific)
666 }
667 else if( !aEnabled && m_parentPanel->HasCapture()
669 {
670 m_parentPanel->ReleaseMouse();
671
672 // Mouse is released, calling CaptureMouse() is allowed now:
674 }
675#endif
677}
678
679
681{
683 {
684 setState( IDLE );
685#if defined USE_MOUSE_CAPTURE
686 if( !m_settings.m_cursorCaptured && m_parentPanel->HasCapture() )
687 m_parentPanel->ReleaseMouse();
688#endif
689 }
690}
691
692
693VECTOR2D WX_VIEW_CONTROLS::GetMousePosition( bool aWorldCoordinates ) const
694{
695 wxPoint msp = getMouseScreenPosition();
696 VECTOR2D screenPos( msp.x, msp.y );
697
698 return aWorldCoordinates ? GetClampedCoords( m_view->ToWorld( screenPos ) ) : screenPos;
699}
700
701
703{
704 GAL* gal = m_view->GetGAL();
705
706 if( aEnableSnapping && gal->GetGridSnapping() )
707 {
708 return gal->GetGridPoint( m_cursorPos );
709 }
710 else
711 {
712 return m_cursorPos;
713 }
714}
715
716
718{
720 {
722 }
723 else
724 {
725 return GetClampedCoords( GetRawCursorPosition( aEnableSnapping ) );
726 }
727}
728
729
730void WX_VIEW_CONTROLS::SetCursorPosition( const VECTOR2D& aPosition, bool aWarpView,
731 bool aTriggeredByArrows, long aArrowCommand )
732{
733 m_updateCursor = false;
734
735 VECTOR2D clampedPosition = GetClampedCoords( aPosition );
736
737 if( aTriggeredByArrows )
738 {
742 m_cursorWarped = false;
743 }
744 else
745 {
749 m_cursorWarped = true;
750 }
751
752 WarpMouseCursor( clampedPosition, true, aWarpView );
753 m_cursorPos = clampedPosition;
754}
755
756
758 bool aWarpView = true )
759{
760 m_updateCursor = false;
761
762 VECTOR2D clampedPosition = GetClampedCoords( aPosition );
763
764 const VECTOR2I& screenSize = m_view->GetGAL()->GetScreenPixelSize();
765 BOX2I screen( VECTOR2I( 0, 0 ), screenSize );
766 VECTOR2D screenPos = m_view->ToScreen( clampedPosition );
767
768 if( aWarpView && !screen.Contains( screenPos ) )
769 m_view->SetCenter( clampedPosition );
770
771 m_cursorPos = clampedPosition;
772}
773
774
775void WX_VIEW_CONTROLS::WarpMouseCursor( const VECTOR2D& aPosition, bool aWorldCoordinates,
776 bool aWarpView )
777{
778 if( aWorldCoordinates )
779 {
780 const VECTOR2I& screenSize = m_view->GetGAL()->GetScreenPixelSize();
781 BOX2I screen( VECTOR2I( 0, 0 ), screenSize );
782 VECTOR2D clampedPosition = GetClampedCoords( aPosition );
783 VECTOR2D screenPos = m_view->ToScreen( clampedPosition );
784
785 if( !screen.Contains( screenPos ) )
786 {
787 if( aWarpView )
788 {
789 m_view->SetCenter( clampedPosition );
790 KIPLATFORM::UI::WarpPointer( m_parentPanel, screenSize.x / 2, screenSize.y / 2 );
791 }
792 }
793 else
794 {
795 KIPLATFORM::UI::WarpPointer( m_parentPanel, screenPos.x, screenPos.y );
796 }
797 }
798 else
799 {
800 KIPLATFORM::UI::WarpPointer( m_parentPanel, aPosition.x, aPosition.y );
801 }
802
803 refreshMouse();
804}
805
806
808{
809 const VECTOR2I& screenSize = m_view->GetGAL()->GetScreenPixelSize();
810 VECTOR2I screenCenter( screenSize / 2 );
811
812 if( GetMousePosition( false ) != screenCenter )
813 {
815 KIPLATFORM::UI::WarpPointer( m_parentPanel, KiROUND( screenSize.x / 2 ), KiROUND( screenSize.y / 2 ) );
816 }
817}
818
819
821{
822 int border = std::min( m_settings.m_autoPanMargin * m_view->GetScreenPixelSize().x,
824 border += 2;
825
826 VECTOR2D topLeft( border, border );
827 VECTOR2D botRight( m_view->GetScreenPixelSize().x - border,
828 m_view->GetScreenPixelSize().y - border );
829
830 topLeft = m_view->ToWorld( topLeft );
831 botRight = m_view->ToWorld( botRight );
832
833 VECTOR2D pos = GetMousePosition( true );
834
835 if( pos.x < topLeft.x )
836 pos.x = topLeft.x;
837 else if( pos.x > botRight.x )
838 pos.x = botRight.x;
839
840 if( pos.y < topLeft.y )
841 pos.y = topLeft.y;
842 else if( pos.y > botRight.y )
843 pos.y = botRight.y;
844
845 SetCursorPosition( pos, false, false, 0 );
846
847 if( aWarpMouseCursor )
848 WarpMouseCursor( pos, true );
849}
850
851
852bool WX_VIEW_CONTROLS::handleAutoPanning( const wxMouseEvent& aEvent )
853{
854 VECTOR2I p( aEvent.GetX(), aEvent.GetY() );
856
858 {
859 // last cursor move event came from keyboard cursor control. If auto-panning is enabled
860 // and the next position is inside the autopan zone, check if it really came from a mouse
861 // event, otherwise disable autopan temporarily. Also temporarily disable autopan if the
862 // cursor is in the autopan zone because the application warped the cursor.
863
864 m_cursorWarped = false;
865 return true;
866 }
867
868 m_cursorWarped = false;
869
870 // Compute areas where autopanning is active
871 int borderStart = std::min( m_settings.m_autoPanMargin * m_view->GetScreenPixelSize().x,
873 borderStart = std::max( borderStart, 2 );
874 int borderEndX = m_view->GetScreenPixelSize().x - borderStart;
875 int borderEndY = m_view->GetScreenPixelSize().y - borderStart;
876
877 if( p.x < borderStart )
878 m_panDirection.x = -( borderStart - p.x );
879 else if( p.x > borderEndX )
880 m_panDirection.x = ( p.x - borderEndX );
881 else
882 m_panDirection.x = 0;
883
884 if( p.y < borderStart )
885 m_panDirection.y = -( borderStart - p.y );
886 else if( p.y > borderEndY )
887 m_panDirection.y = ( p.y - borderEndY );
888 else
889 m_panDirection.y = 0;
890
891 bool borderHit = ( m_panDirection.x != 0 || m_panDirection.y != 0 );
892
893 switch( m_state )
894 {
895 case AUTO_PANNING:
896 if( !borderHit )
897 {
898 m_panTimer.Stop();
899 setState( IDLE );
900
901 return false;
902 }
903
904 return true;
905
906 case IDLE:
907 if( borderHit )
908 {
910 m_panTimer.Start( (int) ( 250.0 / 60.0 ) );
911
912 return true;
913 }
914
915 return false;
916
917 case DRAG_PANNING:
918 case DRAG_ZOOMING:
919 return false;
920 }
921
922 wxCHECK_MSG( false, false, wxT( "This line should never be reached" ) );
923
924 return false;
925}
926
927
929{
931 {
932 bool warp = false;
933 wxSize parentSize = m_parentPanel->GetClientSize();
934
935 if( x < 0 )
936 {
937 x = 0;
938 warp = true;
939 }
940 else if( x >= parentSize.x )
941 {
942 x = parentSize.x - 1;
943 warp = true;
944 }
945
946 if( y < 0 )
947 {
948 y = 0;
949 warp = true;
950 }
951 else if( y >= parentSize.y )
952 {
953 y = parentSize.y - 1;
954 warp = true;
955 }
956
957 if( warp )
959 }
960}
961
962
964{
965 // Notify tools that the cursor position has changed in the world coordinates
966 wxMouseEvent moveEvent( EVT_REFRESH_MOUSE );
967 wxPoint msp = getMouseScreenPosition();
968 moveEvent.SetX( msp.x );
969 moveEvent.SetY( msp.y );
970
971 // Set the modifiers state
972 moveEvent.SetControlDown( wxGetKeyState( WXK_CONTROL ) );
973 moveEvent.SetShiftDown( wxGetKeyState( WXK_SHIFT ) );
974 moveEvent.SetAltDown( wxGetKeyState( WXK_ALT ) );
975
976 m_cursorPos = GetClampedCoords( m_view->ToWorld( VECTOR2D( msp.x, msp.y ) ) );
977 wxPostEvent( m_parentPanel, moveEvent );
978}
979
980
982{
983 wxPoint msp = wxGetMousePosition();
984 m_parentPanel->ScreenToClient( &msp.x, &msp.y );
985 return msp;
986}
987
988
990{
991 const BOX2D viewport = m_view->GetViewport();
992 const BOX2D& boundary = m_view->GetBoundary();
993
994 m_scrollScale.x = 2e3 / viewport.GetWidth(); // TODO it does not have to be updated so often
995 m_scrollScale.y = 2e3 / viewport.GetHeight();
996 VECTOR2I newScroll( ( viewport.Centre().x - boundary.GetLeft() ) * m_scrollScale.x,
997 ( viewport.Centre().y - boundary.GetTop() ) * m_scrollScale.y );
998
999 // We add the width of the scroll bar thumb to the range because the scroll range is given by
1000 // the full bar while the position is given by the left/top position of the thumb
1001 VECTOR2I newRange( m_scrollScale.x * boundary.GetWidth() +
1002 m_parentPanel->GetScrollThumb( wxSB_HORIZONTAL ),
1003 m_scrollScale.y * boundary.GetHeight() +
1004 m_parentPanel->GetScrollThumb( wxSB_VERTICAL ) );
1005
1006 // Flip scroll direction in flipped view
1007 if( m_view->IsMirroredX() )
1008 newScroll.x = ( boundary.GetRight() - viewport.Centre().x ) * m_scrollScale.x;
1009
1010 // Adjust scrollbars only if it is needed. Otherwise there are cases when canvas is continuously
1011 // refreshed (Windows)
1012 if( m_scrollPos != newScroll || newRange.x != m_parentPanel->GetScrollRange( wxSB_HORIZONTAL )
1013 || newRange.y != m_parentPanel->GetScrollRange( wxSB_VERTICAL ) )
1014 {
1015 m_parentPanel->SetScrollbars( 1, 1, newRange.x, newRange.y, newScroll.x, newScroll.y,
1016 true );
1017 m_scrollPos = newScroll;
1018
1019#if !defined( __APPLE__ ) && !defined( WIN32 )
1020 // Trigger a mouse refresh to get the canvas update in GTK (re-draws the scrollbars).
1021 // Note that this causes an infinite loop on OSX and Windows (in certain cases) as it
1022 // generates a paint event.
1023 refreshMouse();
1024#endif
1025 }
1026}
1027
1028void WX_VIEW_CONTROLS::ForceCursorPosition( bool aEnabled, const VECTOR2D& aPosition )
1029{
1030 VECTOR2D clampedPosition = GetClampedCoords( aPosition );
1031
1033 m_settings.m_forcedPosition = clampedPosition;
1034}
coord_type GetTop() const
Definition: box2.h:194
coord_type GetHeight() const
Definition: box2.h:188
coord_type GetWidth() const
Definition: box2.h:187
bool Contains(const Vec &aPoint) const
Definition: box2.h:141
Vec Centre() const
Definition: box2.h:70
coord_type GetRight() const
Definition: box2.h:189
coord_type GetLeft() const
Definition: box2.h:193
The base class for create windows for drawing purpose.
virtual void Refresh(bool aEraseBackground=true, const wxRect *aRect=nullptr) override
Update the board display after modifying it by a python script (note: it is automatically called by a...
bool m_MouseCapturedLost
used on wxMSW: true after a wxEVT_MOUSE_CAPTURE_LOST was received false after the mouse is recaptured...
void SetFocus() override
EDA_DRAW_FRAME * GetParentEDAFrame() const
Returns parent EDA_DRAW_FRAME, if available or NULL otherwise.
static constexpr double MANUAL_SCALE_FACTOR
Abstract interface for drawing on a 2D-surface.
VECTOR2D GetGridPoint(const VECTOR2D &aPoint) const
For a given point it returns the nearest point belonging to the grid in world coordinates.
bool GetGridSnapping() const
const VECTOR2I & GetScreenPixelSize() const
Return GAL canvas size in pixels.
An interface for classes handling user events controlling the view behavior such as zooming,...
virtual void CaptureCursor(bool aEnabled)
Force the cursor to stay within the drawing panel area.
bool m_cursorWarped
Current VIEW_CONTROLS settings.
bool IsCursorWarpingEnabled() const
VC_SETTINGS m_settings
VIEW * m_view
< Pointer to controlled VIEW.
Hold a (potentially large) number of VIEW_ITEMs and renders them on a graphics device provided by the...
Definition: view.h:69
double GetScale() const
Definition: view.h:269
BOX2D GetViewport() const
Return the current viewport visible area rectangle.
Definition: view.cpp:508
const VECTOR2D & GetCenter() const
Return the center point of this VIEW (in world space coordinates).
Definition: view.h:339
virtual void SetScale(double aScale, VECTOR2D aAnchor={ 0, 0 })
Set the scaling factor, zooming around a given anchor point.
Definition: view.cpp:548
VECTOR2D ToScreen(const VECTOR2D &aCoord, bool aAbsolute=true) const
Convert a world space point/vector to a point/vector in screen space coordinates.
Definition: view.cpp:464
const VECTOR2I & GetScreenPixelSize() const
Return the size of the our rendering area in pixels.
Definition: view.cpp:1163
GAL * GetGAL() const
Return the #GAL this view is using to draw graphical primitives.
Definition: view.h:195
VECTOR2D ToWorld(const VECTOR2D &aCoord, bool aAbsolute=true) const
Converts a screen space point/vector to a point/vector in world space coordinates.
Definition: view.cpp:445
bool IsMirroredX() const
Return true if view is flipped across the X axis.
Definition: view.h:243
const BOX2D & GetBoundary() const
Definition: view.h:298
void SetCenter(const VECTOR2D &aCenter)
Set the center point of the VIEW (i.e.
Definition: view.cpp:574
bool m_updateCursor
A #ZOOM_CONTROLLER that determines zoom steps. This is platform-specific.
void SetCrossHairCursorPosition(const VECTOR2D &aPosition, bool aWarpView) override
void CenterOnCursor() const override
Adjusts the scrollbars position to match the current viewport.
void LoadSettings() override
Event that forces mouse move event in the dispatcher (eg.
void setState(STATE aNewState)
Sets the interaction state, simply a internal setter to make it easier to debug changes.
STATE m_state
Panel that is affected by VIEW_CONTROLS.
void CaptureCursor(bool aEnabled) override
Force the cursor to stay within the drawing panel area.
void onScroll(wxScrollWinEvent &aEvent)
wxTimer m_panTimer
Ratio used for scaling world coordinates to scrollbar position.
void onButton(wxMouseEvent &aEvent)
void onEnter(wxMouseEvent &WXUNUSED(aEvent))
void onWheel(wxMouseEvent &aEvent)
Handler functions.
VECTOR2I m_scrollPos
The mouse position when a drag zoom started.
void PinCursorInsideNonAutoscrollArea(bool aWarpMouseCursor) override
Return the current mouse pointer position.
void SetCursorPosition(const VECTOR2D &aPosition, bool warpView, bool aTriggeredByArrows, long aArrowCommand) override
Move the graphic crosshair cursor to the requested position expressed in world coordinates.
VECTOR2D GetMousePosition(bool aWorldCoordinates=true) const override
Return the current mouse pointer position.
void WarpMouseCursor(const VECTOR2D &aPosition, bool aWorldCoordinates=false, bool aWarpView=false) override
Set the viewport center to the current cursor position and warps the cursor to the screen center.
VECTOR2D GetRawCursorPosition(bool aSnappingEnabled=true) const override
Return the current cursor position in world coordinates ignoring the cursorUp position force mode.
VECTOR2D m_scrollScale
Current scrollbar position.
bool handleAutoPanning(const wxMouseEvent &aEvent)
Compute new viewport settings while in autopanning mode.
wxPoint getMouseScreenPosition() const
Get the cursor position in the screen coordinates.
void onTimer(wxTimerEvent &WXUNUSED(aEvent))
std::unique_ptr< ZOOM_CONTROLLER > m_zoomController
STATE
< Possible states for WX_VIEW_CONTROLS.
@ DRAG_PANNING
Panning with mouse button pressed.
@ AUTO_PANNING
Panning on approaching borders of the frame.
@ DRAG_ZOOMING
Zooming with mouse button pressed.
@ IDLE
Nothing is happening.
VECTOR2D GetCursorPosition() const
Return the current cursor position in world coordinates.
VECTOR2D m_cursorPos
Flag deciding whether the cursor position should be calculated using the mouse position.
void onCaptureLost(wxMouseEvent &WXUNUSED(aEvent))
void onMotion(wxMouseEvent &aEvent)
std::unique_ptr< PROF_COUNTER > m_MotionEventCounter
EDA_DRAW_PANEL_GAL * m_parentPanel
Store information about point where dragging has started.
void UpdateScrollbars()
End any mouse drag action still in progress.
void ForceCursorPosition(bool aEnabled, const VECTOR2D &aPosition=VECTOR2D(0, 0)) override
Applies VIEW_CONTROLS settings from the program COMMON_SETTINGS.
void refreshMouse()
Send an event to refresh mouse position.
void handleCursorCapture(int x, int y)
Limit the cursor position to within the canvas by warping it.
void onLeave(wxMouseEvent &WXUNUSED(aEvent))
VECTOR2D m_zoomStartPoint
Current cursor position (world coordinates).
static const wxEventType EVT_REFRESH_MOUSE
VECTOR2D m_panDirection
Timer responsible for handling autopanning.
VECTOR2D m_dragStartPoint
Current direction of panning (only autopanning mode).
KIWAY & Kiway() const
Return a reference to the KIWAY that this object has an opportunity to participate in.
Definition: kiway_holder.h:53
A wxFrame capable of the OpenProjectFiles function, meaning it can load a portion of a KiCad project.
Definition: kiway_player.h:66
virtual KIWAY_PLAYER * Player(FRAME_T aFrameType, bool doCreate=true, wxTopLevelWindow *aParent=nullptr)
Return the KIWAY_PLAYER* given a FRAME_T.
Definition: kiway.cpp:394
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition: vector2d.h:293
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition: vector2d.h:378
@ FRAME_PCB_EDITOR
Definition: frame_type.h:40
@ FRAME_SCH
Definition: frame_type.h:34
a few functions useful in geometry calculations.
VECTOR2< ret_type > GetClampedCoords(const VECTOR2< in_type > &aCoords, pad_type aPadding=0u)
Clamps a vector to values that can be negated, respecting numeric limits of coordinates data type wit...
const wxChar *const traceZoomScroll
Flag to enable debug output of zoom-scrolling calculations in KIGFX::ZOOM_CONTROLLER and derivatives.
The Cairo implementation of the graphics abstraction layer.
Definition: color4d.cpp:266
void WarpPointer(wxWindow *aWindow, int aX, int aY)
Move the mouse cursor to a specific position relative to the window.
Definition: gtk/ui.cpp:150
bool IsWindowActive(wxWindow *aWindow)
Check to see if the given window is the currently active window (e.g.
Definition: gtk/ui.cpp:50
bool IsInputControlFocused(wxWindow *aFocus=nullptr)
Check if a input control has focus.
Definition: ui_common.cpp:253
see class PGM_BASE
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:111
const int scale
MOUSE_DRAG_ACTION drag_right
MOUSE_DRAG_ACTION drag_middle
MOUSE_DRAG_ACTION drag_left
float m_autoPanMargin
How fast is panning when in auto mode.
Definition: view_controls.h:79
VECTOR2D m_forcedPosition
Is the forced cursor position enabled.
Definition: view_controls.h:55
MOUSE_DRAG_ACTION m_dragLeft
bool m_horizontalPan
Enable the accelerating zoom controller.
Definition: view_controls.h:91
bool m_autoPanSettingEnabled
Distance from cursor to VIEW edge when panning is active.
Definition: view_controls.h:76
bool m_focusFollowSchPcb
Flag for turning on autopanning.
Definition: view_controls.h:70
float m_autoPanAcceleration
If the cursor is allowed to be warped.
Definition: view_controls.h:85
MOUSE_DRAG_ACTION m_dragMiddle
bool m_cursorCaptured
Should the cursor snap to grid or move freely.
Definition: view_controls.h:61
int m_zoomSpeed
When true, ignore zoom_speed and pick a platform-specific default.
Definition: view_controls.h:97
int m_scrollModifierZoom
What modifier key to enable horizontal pan with the (vertical) scroll wheel.
int m_scrollModifierPanH
What modifier key to enable vertical with the (vertical) scroll wheel.
VECTOR2D m_lastKeyboardCursorPosition
bool m_warpCursor
Enable horizontal panning with the horizontal scroll/trackpad input.
Definition: view_controls.h:88
MOUSE_DRAG_ACTION m_dragRight
Is last cursor motion event coming from keyboard arrow cursor motion action.
bool m_zoomAcceleration
Zoom speed for the non-accelerating zoom controller.
Definition: view_controls.h:94
bool m_lastKeyboardCursorPositionValid
ACTIONS::CURSOR_UP, ACTIONS::CURSOR_DOWN, etc.
bool m_zoomSpeedAuto
What modifier key to enable zoom with the (vertical) scroll wheel.
bool m_autoPanEnabled
Flag for turning on autopanning.
Definition: view_controls.h:73
long m_lastKeyboardCursorCommand
Position of the above event.
bool m_forceCursorPosition
Should the cursor be locked within the parent window area.
Definition: view_controls.h:58
constexpr int delta
wxLogTrace helper definitions.
Functions to provide common constants and other functions to assist in making a consistent UI.
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:85
VECTOR2< double > VECTOR2D
Definition: vector2d.h:617
VECTOR2< int > VECTOR2I
Definition: vector2d.h:618
static std::unique_ptr< ZOOM_CONTROLLER > GetZoomControllerForPlatform(bool aAcceleration)
WX_VIEW_CONTROLS class definition.