OpenCPN Partial API docs
Loading...
Searching...
No Matches
chcanv.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: Chart Canvas
5 * Author: David Register
6 *
7 ***************************************************************************
8 * Copyright (C) 2018 by David S. Register *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 **************************************************************************/
25
26// For compilers that support precompilation, includes "wx.h".
27#include <wx/wxprec.h>
28
29#ifndef WX_PRECOMP
30#include <wx/wx.h>
31#endif // precompiled headers
32#include <wx/image.h>
33#include <wx/graphics.h>
34#include <wx/clipbrd.h>
35#include <wx/aui/aui.h>
36
37#include "config.h"
38
39#include <wx/listimpl.cpp>
40
41#include "model/ais_decoder.h"
43#include "model/ais_target_data.h"
44#include "model/cmdline.h"
45#include "model/conn_params.h"
46#include "model/cutil.h"
47#include "model/geodesic.h"
48#include "model/gui.h"
49#include "model/idents.h"
50#include "model/multiplexer.h"
52#include "model/nav_object_database.h"
53#include "model/navobj_db.h"
54#include "model/navutil_base.h"
55#include "model/own_ship.h"
56#include "model/plugin_comm.h"
57#include "model/route.h"
58#include "model/routeman.h"
59#include "model/select.h"
60#include "model/select_item.h"
61#include "model/track.h"
62#include "model/wx28compat.h"
63
64#include "ais.h"
65#include "AISTargetAlertDialog.h"
66#include "CanvasConfig.h"
67#include "canvasMenu.h"
68#include "CanvasOptions.h"
69#include "chartdb.h"
70#include "chartimg.h"
71#include "chcanv.h"
72#include "ChInfoWin.h"
73#include "cm93.h" // for chart outline draw
74#include "compass.h"
75#include "concanv.h"
76#include "displays.h"
77#include "hotkeys_dlg.h"
78#include "FontMgr.h"
79#include "glTextureDescriptor.h"
80#include "gshhs.h"
81#include "iENCToolbar.h"
82#include "kml.h"
83#include "line_clip.h"
84#include "MarkInfo.h"
85#include "mbtiles.h"
86#include "MUIBar.h"
87#include "navutil.h"
88#include "OCPN_AUIManager.h"
89#include "ocpndc.h"
90#include "ocpn_frame.h"
91#include "ocpn_pixel.h"
92#include "OCPNRegion.h"
93#include "options.h"
94#include "piano.h"
95#include "pluginmanager.h"
96#include "Quilt.h"
97#include "route_gui.h"
98#include "routemanagerdialog.h"
99#include "route_point_gui.h"
100#include "route_validator.h"
101#include "RoutePropDlgImpl.h"
102#include "s52plib.h"
103#include "s52utils.h"
104#include "s57chart.h" // for ArrayOfS57Obj
105#include "SendToGpsDlg.h"
106#include "shapefile_basemap.h"
107#include "styles.h"
108#include "SystemCmdSound.h"
109#include "tcmgr.h"
110#include "TCWin.h"
111#include "thumbwin.h"
112#include "tide_time.h"
113#include "timers.h"
114#include "toolbar.h"
115#include "track_gui.h"
116#include "TrackPropDlg.h"
117#include "undo.h"
118
119#include "s57_ocpn_utils.h"
120
121#ifdef __ANDROID__
122#include "androidUTIL.h"
123#endif
124
125#ifdef ocpnUSE_GL
126#include "glChartCanvas.h"
127#include "notification_manager_gui.h"
129#endif
130
131#ifdef __VISUALC__
132#include <wx/msw/msvcrt.h>
133#endif
134
135#ifndef __WXMSW__
136#include <signal.h>
137#include <setjmp.h>
138
139#endif
140
141extern float g_ShipScaleFactorExp;
142extern double g_mouse_zoom_sensitivity;
143
144#include <vector>
145
146#ifdef __WXMSW__
147#define printf printf2
148
149int __cdecl printf2(const char *format, ...) {
150 char str[1024];
151
152 va_list argptr;
153 va_start(argptr, format);
154 int ret = vsnprintf(str, sizeof(str), format, argptr);
155 va_end(argptr);
156 OutputDebugStringA(str);
157 return ret;
158}
159#endif
160
161#if defined(__MSVC__) && (_MSC_VER < 1700)
162#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
163#endif
164
165// Define to enable the invocation of a temporary menubar by pressing the Alt
166// key. Not implemented for Windows XP, as it interferes with Alt-Tab
167// processing.
168#define OCPN_ALT_MENUBAR 1
169
170// Profiling support
171// #include "/usr/include/valgrind/callgrind.h"
172
173// ----------------------------------------------------------------------------
174// Useful Prototypes
175// ----------------------------------------------------------------------------
176extern bool G_FloatPtInPolygon(MyFlPoint *rgpts, int wnumpts, float x, float y);
177extern void catch_signals(int signo);
178
179extern void AlphaBlending(ocpnDC &dc, int x, int y, int size_x, int size_y,
180 float radius, wxColour color,
181 unsigned char transparency);
182
183extern double g_ChartNotRenderScaleFactor;
184extern ChartDB *ChartData;
185extern bool bDBUpdateInProgress;
186extern ColorScheme global_color_scheme;
187extern int g_nbrightness;
188
189extern APConsole *console;
190extern OCPNPlatform *g_Platform;
191
192extern RouteList *pRouteList;
193extern std::vector<Track *> g_TrackList;
194extern MyConfig *pConfig;
195extern Routeman *g_pRouteMan;
196extern ThumbWin *pthumbwin;
197extern TCMgr *ptcmgr;
198extern Select *pSelectTC;
199extern MarkInfoDlg *g_pMarkInfoDialog;
200extern RoutePropDlgImpl *pRoutePropDialog;
201extern TrackPropDlg *pTrackPropDialog;
202extern ActiveTrack *g_pActiveTrack;
203
204extern double AnchorPointMinDist;
205extern bool AnchorAlertOn1;
206extern bool AnchorAlertOn2;
207extern int g_nAWMax;
208
209extern RouteManagerDialog *pRouteManagerDialog;
210extern GoToPositionDialog *pGoToPositionDialog;
211extern wxString GetLayerName(int id);
212extern wxString g_uploadConnection;
213extern bool g_bsimplifiedScalebar;
214
215extern bool bDrawCurrentValues;
216
217extern s52plib *ps52plib;
218
219extern bool g_bTempShowMenuBar;
220extern bool g_bShowMenuBar;
221extern bool g_bShowCompassWin;
222
223extern MyFrame *gFrame;
224extern options *g_options;
225
226extern int g_iNavAidRadarRingsNumberVisible;
227extern bool g_bNavAidRadarRingsShown;
228extern float g_fNavAidRadarRingsStep;
229extern int g_pNavAidRadarRingsStepUnits;
230extern bool g_bWayPointPreventDragging;
231extern bool g_bEnableZoomToCursor;
232extern bool g_bShowChartBar;
233extern int g_ENCSoundingScaleFactor;
234extern int g_ENCTextScaleFactor;
235extern int g_maxzoomin;
236
237bool g_bShowShipToActive;
238int g_shipToActiveStyle;
239int g_shipToActiveColor;
240
241extern AISTargetQueryDialog *g_pais_query_dialog_active;
242
243extern int g_S57_dialog_sx, g_S57_dialog_sy;
244
245extern PopUpDSlide *pPopupDetailSlider;
246extern int g_detailslider_dialog_x, g_detailslider_dialog_y;
247
248extern bool g_b_overzoom_x; // Allow high overzoom
249extern double g_plus_minus_zoom_factor;
250
251extern int g_OwnShipIconType;
252extern double g_n_ownship_length_meters;
253extern double g_n_ownship_beam_meters;
254extern double g_n_gps_antenna_offset_y;
255extern double g_n_gps_antenna_offset_x;
256extern int g_n_ownship_min_mm;
257
258extern double g_COGAvg; // only needed for debug....
259
260extern int g_click_stop;
261
262extern double g_ownship_predictor_minutes;
263extern int g_cog_predictor_style;
264extern wxString g_cog_predictor_color;
265extern int g_cog_predictor_endmarker;
266extern int g_ownship_HDTpredictor_style;
267extern wxString g_ownship_HDTpredictor_color;
268extern int g_ownship_HDTpredictor_endmarker;
269extern int g_ownship_HDTpredictor_width;
270extern double g_ownship_HDTpredictor_miles;
271
272extern bool g_bquiting;
273extern AISTargetListDialog *g_pAISTargetList;
274
275extern PlugInManager *g_pi_manager;
276
277extern OCPN_AUIManager *g_pauimgr;
278
279extern bool g_bopengl;
280
281extern bool g_bFullScreenQuilt;
282
283extern bool g_bsmoothpanzoom;
284extern bool g_bSmoothRecenter;
285
286bool g_bDebugOGL;
287
288extern bool g_b_assume_azerty;
289
290extern ChartGroupArray *g_pGroupArray;
291
292extern S57QueryDialog *g_pObjectQueryDialog;
293extern ocpnStyle::StyleManager *g_StyleManager;
294
295extern OcpnSound *g_anchorwatch_sound;
296
297extern bool g_bresponsive;
298extern int g_chart_zoom_modifier_raster;
299extern int g_chart_zoom_modifier_vector;
300extern int g_ChartScaleFactor;
301
302#ifdef ocpnUSE_GL
303#endif
304
305extern double g_gl_ms_per_frame;
306extern bool g_benable_rotate;
307extern bool g_bRollover;
308
309extern bool g_bSpaceDropMark;
310extern bool g_bAutoHideToolbar;
311extern int g_nAutoHideToolbar;
312extern bool g_bDeferredInitDone;
313
314extern wxString g_CmdSoundString;
315ShapeBaseChartSet gShapeBasemap;
316extern bool g_CanvasHideNotificationIcon;
317extern bool g_bhide_context_menus;
318extern bool g_bhide_depth_units;
319extern bool g_bhide_overzoom_flag;
320
321// TODO why are these static?
322
332static int mouse_x;
342static int mouse_y;
343static bool mouse_leftisdown;
344
345bool g_brouteCreating;
346
347bool g_bShowTrackPointTime;
348
349int r_gamma_mult;
350int g_gamma_mult;
351int b_gamma_mult;
352int gamma_state;
353bool g_brightness_init;
354int last_brightness;
355
356int g_cog_predictor_width;
357extern double g_display_size_mm;
358
359extern ocpnFloatingToolbarDialog *g_MainToolbar;
360extern iENCToolbar *g_iENCToolbar;
361extern wxColour g_colourOwnshipRangeRingsColour;
362
363// LIVE ETA OPTION
364bool g_bShowLiveETA;
365extern double g_defaultBoatSpeed;
366double g_defaultBoatSpeedUserUnit;
367
368extern int g_nAIS_activity_timer;
369extern bool g_bskew_comp;
370extern float g_compass_scalefactor;
371extern int g_COGAvgSec; // COG average period (sec.) for Course Up Mode
372extern bool g_btenhertz;
373
374wxGLContext *g_pGLcontext; // shared common context
375
376extern bool g_useMUI;
377extern unsigned int g_canvasConfig;
378
379extern ChartCanvas *g_focusCanvas;
380extern ChartCanvas *g_overlayCanvas;
381
382extern float g_toolbar_scalefactor;
383extern SENCThreadManager *g_SencThreadManager;
384
385wxString g_ObjQFileExt;
386
387// "Curtain" mode parameters
388wxDialog *g_pcurtain;
389
390extern int g_GUIScaleFactor;
391// Win DPI scale factor
392double g_scaler;
393wxString g_lastS52PLIBPluginMessage;
394extern bool g_bChartBarEx;
395bool g_PrintingInProgress;
396
397#define MIN_BRIGHT 10
398#define MAX_BRIGHT 100
399
400//------------------------------------------------------------------------------
401// ChartCanvas Implementation
402//------------------------------------------------------------------------------
403BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
404EVT_PAINT(ChartCanvas::OnPaint)
405EVT_ACTIVATE(ChartCanvas::OnActivate)
406EVT_SIZE(ChartCanvas::OnSize)
407#ifndef HAVE_WX_GESTURE_EVENTS
408EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
409#endif
410EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
411EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
412EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
413EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
414EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
415EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
416EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
417EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
418EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
419EVT_KEY_UP(ChartCanvas::OnKeyUp)
420EVT_CHAR(ChartCanvas::OnKeyChar)
421EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
422EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
423EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
424EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
425EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
426EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
427EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
428EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
429
430END_EVENT_TABLE()
431
432// Define a constructor for my canvas
433ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
434 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
435 m_nmea_log(nmea_log) {
436 parent_frame = (MyFrame *)frame; // save a pointer to parent
437 m_canvasIndex = canvasIndex;
438
439 pscratch_bm = NULL;
440
441 SetBackgroundColour(wxColour(0, 0, 0));
442 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
443 // color scheme change
444
445 m_groupIndex = 0;
446 m_bDrawingRoute = false;
447 m_bRouteEditing = false;
448 m_bMarkEditing = false;
449 m_bRoutePoinDragging = false;
450 m_bIsInRadius = false;
451 m_bMayToggleMenuBar = true;
452
453 m_bFollow = false;
454 m_bShowNavobjects = true;
455 m_bTCupdate = false;
456 m_bAppendingRoute = false; // was true in MSW, why??
457 pThumbDIBShow = NULL;
458 m_bShowCurrent = false;
459 m_bShowTide = false;
460 bShowingCurrent = false;
461 pCwin = NULL;
462 warp_flag = false;
463 m_bzooming = false;
464 m_b_paint_enable = true;
465 m_routeState = 0;
466
467 pss_overlay_bmp = NULL;
468 pss_overlay_mask = NULL;
469 m_bChartDragging = false;
470 m_bMeasure_Active = false;
471 m_bMeasure_DistCircle = false;
472 m_pMeasureRoute = NULL;
473 m_pTrackRolloverWin = NULL;
474 m_pRouteRolloverWin = NULL;
475 m_pAISRolloverWin = NULL;
476 m_bedge_pan = false;
477 m_disable_edge_pan = false;
478 m_dragoffsetSet = false;
479 m_bautofind = false;
480 m_bFirstAuto = true;
481 m_groupIndex = 0;
482 m_singleChart = NULL;
483 m_upMode = NORTH_UP_MODE;
484 m_bShowAIS = true;
485 m_bShowAISScaled = false;
486 m_timed_move_vp_active = false;
487
488 m_vLat = 0.;
489 m_vLon = 0.;
490
491 m_pCIWin = NULL;
492
493 m_pSelectedRoute = NULL;
494 m_pSelectedTrack = NULL;
495 m_pRoutePointEditTarget = NULL;
496 m_pFoundPoint = NULL;
497 m_pMouseRoute = NULL;
498 m_prev_pMousePoint = NULL;
499 m_pEditRouteArray = NULL;
500 m_pFoundRoutePoint = NULL;
501 m_FinishRouteOnKillFocus = true;
502
503 m_pRolloverRouteSeg = NULL;
504 m_pRolloverTrackSeg = NULL;
505 m_bsectors_shown = false;
506
507 m_bbrightdir = false;
508 r_gamma_mult = 1;
509 g_gamma_mult = 1;
510 b_gamma_mult = 1;
511
512 m_pos_image_user_day = NULL;
513 m_pos_image_user_dusk = NULL;
514 m_pos_image_user_night = NULL;
515 m_pos_image_user_grey_day = NULL;
516 m_pos_image_user_grey_dusk = NULL;
517 m_pos_image_user_grey_night = NULL;
518
519 m_zoom_factor = 1;
520 m_rotation_speed = 0;
521 m_mustmove = 0;
522
523 m_OSoffsetx = 0.;
524 m_OSoffsety = 0.;
525
526 m_pos_image_user_yellow_day = NULL;
527 m_pos_image_user_yellow_dusk = NULL;
528 m_pos_image_user_yellow_night = NULL;
529
530 SetOwnShipState(SHIP_INVALID);
531
532 undo = new Undo(this);
533
534 VPoint.Invalidate();
535
536 m_glcc = NULL;
537
538 m_focus_indicator_pix = 1;
539
540 m_pCurrentStack = NULL;
541 m_bpersistent_quilt = false;
542 m_piano_ctx_menu = NULL;
543 m_Compass = NULL;
544 m_NotificationsList = NULL;
545 m_notification_button = NULL;
546
547 g_ChartNotRenderScaleFactor = 2.0;
548 m_bShowScaleInStatusBar = true;
549
550 m_muiBar = NULL;
551 m_bShowScaleInStatusBar = false;
552 m_show_focus_bar = true;
553
554 m_bShowOutlines = false;
555 m_bDisplayGrid = false;
556 m_bShowDepthUnits = true;
557 m_encDisplayCategory = (int)STANDARD;
558
559 m_encShowLights = true;
560 m_encShowAnchor = true;
561 m_encShowDataQual = false;
562 m_bShowGPS = true;
563 m_pQuilt = new Quilt(this);
564 SetQuiltMode(true);
565 SetAlertString(_T(""));
566 m_sector_glat = 0;
567 m_sector_glon = 0;
568 g_PrintingInProgress = false;
569
570#ifdef HAVE_WX_GESTURE_EVENTS
571 m_oldVPSScale = -1.0;
572 m_popupWanted = false;
573 m_leftdown = false;
574#endif /* HAVE_WX_GESTURE_EVENTS */
575
576 SetupGlCanvas();
577
578 singleClickEventIsValid = false;
579
580 // Build the cursors
581
582 pCursorLeft = NULL;
583 pCursorRight = NULL;
584 pCursorUp = NULL;
585 pCursorDown = NULL;
586 pCursorArrow = NULL;
587 pCursorPencil = NULL;
588 pCursorCross = NULL;
589
590 RebuildCursors();
591
592 SetCursor(*pCursorArrow);
593
594 pPanTimer = new wxTimer(this, m_MouseDragging);
595 pPanTimer->Stop();
596
597 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
598 pMovementTimer->Stop();
599
600 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
601 pMovementStopTimer->Stop();
602
603 pRotDefTimer = new wxTimer(this, ROT_TIMER);
604 pRotDefTimer->Stop();
605
606 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
607 m_DoubleClickTimer->Stop();
608
609 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
610 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
611 m_chart_drag_inertia_active = false;
612
613 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
614 m_animationActive = false;
615
616 m_panx = m_pany = 0;
617 m_panspeed = 0;
618 m_panx_target_final = m_pany_target_final = 0;
619 m_panx_target_now = m_pany_target_now = 0;
620
621 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
622 pCurTrackTimer->Stop();
623 m_curtrack_timer_msec = 10;
624
625 m_wheelzoom_stop_oneshot = 0;
626 m_last_wheel_dir = 0;
627
628 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
629
630 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
631
632 m_rollover_popup_timer_msec = 20;
633
634 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
635
636 m_b_rot_hidef = true;
637
638 proute_bm = NULL;
639 m_prot_bm = NULL;
640
641 m_upMode = NORTH_UP_MODE;
642 m_bLookAhead = false;
643
644 // Set some benign initial values
645
646 m_cs = GLOBAL_COLOR_SCHEME_DAY;
647 VPoint.clat = 0;
648 VPoint.clon = 0;
649 VPoint.view_scale_ppm = 1;
650 VPoint.Invalidate();
651 m_nMeasureState = 0;
652
653 m_canvas_scale_factor = 1.;
654
655 m_canvas_width = 1000;
656
657 m_overzoomTextWidth = 0;
658 m_overzoomTextHeight = 0;
659
660 // Create the default world chart
661 pWorldBackgroundChart = new GSHHSChart;
662 gShapeBasemap.Reset();
663
664 // Create the default depth unit emboss maps
665 m_pEM_Feet = NULL;
666 m_pEM_Meters = NULL;
667 m_pEM_Fathoms = NULL;
668
669 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
670
671 m_pEM_OverZoom = NULL;
672 SetOverzoomFont();
673 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
674
675 // Build icons for tide/current points
676 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
677 m_bmTideDay = style->GetIconScaled(_T("tidesml"),
678 1. / g_Platform->GetDisplayDIPMult(this));
679
680 // Dusk
681 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
682
683 // Night
684 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
685
686 // Build Dusk/Night ownship icons
687 double factor_dusk = 0.5;
688 double factor_night = 0.25;
689
690 // Red
691 m_os_image_red_day = style->GetIcon(_T("ship-red")).ConvertToImage();
692
693 int rimg_width = m_os_image_red_day.GetWidth();
694 int rimg_height = m_os_image_red_day.GetHeight();
695
696 m_os_image_red_dusk = m_os_image_red_day.Copy();
697 m_os_image_red_night = m_os_image_red_day.Copy();
698
699 for (int iy = 0; iy < rimg_height; iy++) {
700 for (int ix = 0; ix < rimg_width; ix++) {
701 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
702 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
703 m_os_image_red_day.GetGreen(ix, iy),
704 m_os_image_red_day.GetBlue(ix, iy));
705 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
706 hsv.value = hsv.value * factor_dusk;
707 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
708 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
709
710 hsv = wxImage::RGBtoHSV(rgb);
711 hsv.value = hsv.value * factor_night;
712 nrgb = wxImage::HSVtoRGB(hsv);
713 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
714 }
715 }
716 }
717
718 // Grey
719 m_os_image_grey_day =
720 style->GetIcon(_T("ship-red")).ConvertToImage().ConvertToGreyscale();
721
722 int gimg_width = m_os_image_grey_day.GetWidth();
723 int gimg_height = m_os_image_grey_day.GetHeight();
724
725 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
726 m_os_image_grey_night = m_os_image_grey_day.Copy();
727
728 for (int iy = 0; iy < gimg_height; iy++) {
729 for (int ix = 0; ix < gimg_width; ix++) {
730 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
731 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
732 m_os_image_grey_day.GetGreen(ix, iy),
733 m_os_image_grey_day.GetBlue(ix, iy));
734 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
735 hsv.value = hsv.value * factor_dusk;
736 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
737 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
738
739 hsv = wxImage::RGBtoHSV(rgb);
740 hsv.value = hsv.value * factor_night;
741 nrgb = wxImage::HSVtoRGB(hsv);
742 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
743 }
744 }
745 }
746
747 // Yellow
748 m_os_image_yellow_day = m_os_image_red_day.Copy();
749
750 gimg_width = m_os_image_yellow_day.GetWidth();
751 gimg_height = m_os_image_yellow_day.GetHeight();
752
753 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
754 m_os_image_yellow_night = m_os_image_red_day.Copy();
755
756 for (int iy = 0; iy < gimg_height; iy++) {
757 for (int ix = 0; ix < gimg_width; ix++) {
758 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
759 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
760 m_os_image_yellow_day.GetGreen(ix, iy),
761 m_os_image_yellow_day.GetBlue(ix, iy));
762 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
763 hsv.hue += 60. / 360.; // shift to yellow
764 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
765 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
766
767 hsv = wxImage::RGBtoHSV(rgb);
768 hsv.value = hsv.value * factor_dusk;
769 hsv.hue += 60. / 360.; // shift to yellow
770 nrgb = wxImage::HSVtoRGB(hsv);
771 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
772
773 hsv = wxImage::RGBtoHSV(rgb);
774 hsv.hue += 60. / 360.; // shift to yellow
775 hsv.value = hsv.value * factor_night;
776 nrgb = wxImage::HSVtoRGB(hsv);
777 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
778 }
779 }
780 }
781
782 // Set initial pointers to ownship images
783 m_pos_image_red = &m_os_image_red_day;
784 m_pos_image_yellow = &m_os_image_yellow_day;
785 m_pos_image_grey = &m_os_image_grey_day;
786
787 SetUserOwnship();
788
789 m_pBrightPopup = NULL;
790
791#ifdef ocpnUSE_GL
792 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
793#endif
794
795 SetupGridFont();
796
797 m_Piano = new Piano(this);
798
799 m_bShowCompassWin = true;
800 m_Compass = new ocpnCompass(this);
801 m_Compass->SetScaleFactor(g_compass_scalefactor);
802 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
803
804 m_notification_button = new NotificationButton(this);
805 m_notification_button->SetScaleFactor(g_compass_scalefactor);
806 m_notification_button->Show(true);
807
808 m_pianoFrozen = false;
809
810 SetMinSize(wxSize(200, 200));
811
812 m_displayScale = 1.0;
813#if defined(__WXOSX__) || defined(__WXGTK3__)
814 // Support scaled HDPI displays.
815 m_displayScale = GetContentScaleFactor();
816#endif
817 VPoint.SetPixelScale(m_displayScale);
818
819#ifdef HAVE_WX_GESTURE_EVENTS
820 // if (!m_glcc)
821 {
822 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
823 wxLogError("Failed to enable touch events");
824 }
825
826 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
827
828 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
829 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
830
831 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
832 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
833
834 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
835 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
836
837 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
838 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
839 }
840#endif
841
842 // Listen for notification events
843 auto &noteman = NotificationManager::GetInstance();
844
845 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
846 evt_notificationlist_change_listener.Listen(
847 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
848 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
849 if (m_NotificationsList && m_NotificationsList->IsShown()) {
850 m_NotificationsList->ReloadNotificationList();
851 }
852 Refresh();
853 });
854}
855
856ChartCanvas::~ChartCanvas() {
857 delete pThumbDIBShow;
858
859 // Delete Cursors
860 delete pCursorLeft;
861 delete pCursorRight;
862 delete pCursorUp;
863 delete pCursorDown;
864 delete pCursorArrow;
865 delete pCursorPencil;
866 delete pCursorCross;
867
868 delete pPanTimer;
869 delete pMovementTimer;
870 delete pMovementStopTimer;
871 delete pCurTrackTimer;
872 delete pRotDefTimer;
873 delete m_DoubleClickTimer;
874
875 delete m_pTrackRolloverWin;
876 delete m_pRouteRolloverWin;
877 delete m_pAISRolloverWin;
878 delete m_pBrightPopup;
879
880 delete m_pCIWin;
881
882 delete pscratch_bm;
883
884 m_dc_route.SelectObject(wxNullBitmap);
885 delete proute_bm;
886
887 delete pWorldBackgroundChart;
888 delete pss_overlay_bmp;
889
890 delete m_pEM_Feet;
891 delete m_pEM_Meters;
892 delete m_pEM_Fathoms;
893
894 delete m_pEM_OverZoom;
895 // delete m_pEM_CM93Offset;
896
897 delete m_prot_bm;
898
899 delete m_pos_image_user_day;
900 delete m_pos_image_user_dusk;
901 delete m_pos_image_user_night;
902 delete m_pos_image_user_grey_day;
903 delete m_pos_image_user_grey_dusk;
904 delete m_pos_image_user_grey_night;
905 delete m_pos_image_user_yellow_day;
906 delete m_pos_image_user_yellow_dusk;
907 delete m_pos_image_user_yellow_night;
908
909 delete undo;
910#ifdef ocpnUSE_GL
911 if (!g_bdisable_opengl) {
912 delete m_glcc;
913
914#if wxCHECK_VERSION(2, 9, 0)
915 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
916#endif
917 }
918#endif
919
920 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
921 // wx tries to deliver events to this canvas during destroy.
922 MUIBar *muiBar = m_muiBar;
923 m_muiBar = 0;
924 delete muiBar;
925 delete m_pQuilt;
926 delete m_pCurrentStack;
927 delete m_Compass;
928 delete m_Piano;
929}
930
931void ChartCanvas::SetupGridFont() {
932 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
933 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
934 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
935 m_pgridFont = FontMgr::Get().FindOrCreateFont(
936 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
937 FALSE, wxString(_T ( "Arial" )));
938}
939
940void ChartCanvas::RebuildCursors() {
941 delete pCursorLeft;
942 delete pCursorRight;
943 delete pCursorUp;
944 delete pCursorDown;
945 delete pCursorArrow;
946 delete pCursorPencil;
947 delete pCursorCross;
948
949 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
950 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
951
952 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
953
954 wxImage ICursorLeft = style->GetIcon(_T("left")).ConvertToImage();
955 wxImage ICursorRight = style->GetIcon(_T("right")).ConvertToImage();
956 wxImage ICursorUp = style->GetIcon(_T("up")).ConvertToImage();
957 wxImage ICursorDown = style->GetIcon(_T("down")).ConvertToImage();
958 wxImage ICursorPencil =
959 style->GetIconScaled(_T("pencil"), pencilScale).ConvertToImage();
960 wxImage ICursorCross = style->GetIcon(_T("cross")).ConvertToImage();
961
962#if !defined(__WXMSW__) && !defined(__WXQT__)
963 ICursorLeft.ConvertAlphaToMask(128);
964 ICursorRight.ConvertAlphaToMask(128);
965 ICursorUp.ConvertAlphaToMask(128);
966 ICursorDown.ConvertAlphaToMask(128);
967 ICursorPencil.ConvertAlphaToMask(10);
968 ICursorCross.ConvertAlphaToMask(10);
969#endif
970
971 if (ICursorLeft.Ok()) {
972 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
973 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
974 pCursorLeft = new wxCursor(ICursorLeft);
975 } else
976 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
977
978 if (ICursorRight.Ok()) {
979 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
980 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
981 pCursorRight = new wxCursor(ICursorRight);
982 } else
983 pCursorRight = new wxCursor(wxCURSOR_ARROW);
984
985 if (ICursorUp.Ok()) {
986 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
987 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
988 pCursorUp = new wxCursor(ICursorUp);
989 } else
990 pCursorUp = new wxCursor(wxCURSOR_ARROW);
991
992 if (ICursorDown.Ok()) {
993 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
994 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
995 pCursorDown = new wxCursor(ICursorDown);
996 } else
997 pCursorDown = new wxCursor(wxCURSOR_ARROW);
998
999 if (ICursorPencil.Ok()) {
1000 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
1001 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
1002 pCursorPencil = new wxCursor(ICursorPencil);
1003 } else
1004 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
1005
1006 if (ICursorCross.Ok()) {
1007 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
1008 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
1009 pCursorCross = new wxCursor(ICursorCross);
1010 } else
1011 pCursorCross = new wxCursor(wxCURSOR_ARROW);
1012
1013 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
1014 pPlugIn_Cursor = NULL;
1015}
1016
1017void ChartCanvas::CanvasApplyLocale() {
1018 CreateDepthUnitEmbossMaps(m_cs);
1019 CreateOZEmbossMapData(m_cs);
1020}
1021
1022void ChartCanvas::SetupGlCanvas() {
1023#ifndef __ANDROID__
1024#ifdef ocpnUSE_GL
1025 if (!g_bdisable_opengl) {
1026 if (g_bopengl) {
1027 wxLogMessage(_T("Creating glChartCanvas"));
1028 m_glcc = new glChartCanvas(this);
1029
1030 // We use one context for all GL windows, so that textures etc will be
1031 // automatically shared
1032 if (IsPrimaryCanvas()) {
1033 // qDebug() << "Creating Primary Context";
1034
1035 // wxGLContextAttrs ctxAttr;
1036 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
1037 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
1038 // NULL, &ctxAttr);
1039 wxGLContext *pctx = new wxGLContext(m_glcc);
1040 m_glcc->SetContext(pctx);
1041 g_pGLcontext = pctx; // Save a copy of the common context
1042 } else {
1043#ifdef __WXOSX__
1044 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
1045#else
1046 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
1047 // saved common context
1048#endif
1049 }
1050 }
1051 }
1052#endif
1053#endif
1054
1055#ifdef __ANDROID__ // ocpnUSE_GL
1056 if (!g_bdisable_opengl) {
1057 if (g_bopengl) {
1058 // qDebug() << "SetupGlCanvas";
1059 wxLogMessage(_T("Creating glChartCanvas"));
1060
1061 // We use one context for all GL windows, so that textures etc will be
1062 // automatically shared
1063 if (IsPrimaryCanvas()) {
1064 qDebug() << "Creating Primary glChartCanvas";
1065
1066 // wxGLContextAttrs ctxAttr;
1067 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
1068 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
1069 // NULL, &ctxAttr);
1070 m_glcc = new glChartCanvas(this);
1071
1072 wxGLContext *pctx = new wxGLContext(m_glcc);
1073 m_glcc->SetContext(pctx);
1074 g_pGLcontext = pctx; // Save a copy of the common context
1075 m_glcc->m_pParentCanvas = this;
1076 // m_glcc->Reparent(this);
1077 } else {
1078 qDebug() << "Creating Secondary glChartCanvas";
1079 // QGLContext *pctx =
1080 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
1081 // << "pctx: " << pctx;
1082
1083 m_glcc = new glChartCanvas(
1084 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
1085 // m_glcc = new glChartCanvas(this, pctx); //Shared
1086 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
1087 wxGLContext *pwxctx = new wxGLContext(m_glcc);
1088 m_glcc->SetContext(pwxctx);
1089 m_glcc->m_pParentCanvas = this;
1090 // m_glcc->Reparent(this);
1091 }
1092 }
1093 }
1094#endif
1095}
1096
1097void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
1098 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1099
1100 // On Android, we get a KillFocus on just about every keystroke.
1101 // Why?
1102#ifdef __ANDROID__
1103 return;
1104#endif
1105
1106 // Special logic:
1107 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
1108 // canvas focus. Why??? Who knows... So, we provide for this case by
1109 // starting a timer if required to actually Finish() a route on a legitimate
1110 // focus change, but not if the focus is quickly regained ( <20 msec.) on
1111 // this canvas.
1112#ifdef __WXOSX__
1113 if (m_routeState && m_FinishRouteOnKillFocus)
1114 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
1115#else
1116 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1117#endif
1118}
1119
1120void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
1121 m_routeFinishTimer.Stop();
1122
1123 // Try to keep the global top-line menubar selections up to date with the
1124 // current "focus" canvas
1125 gFrame->UpdateGlobalMenuItems(this);
1126
1127 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1128}
1129
1130void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
1131 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1132}
1133
1134#ifdef HAVE_WX_GESTURE_EVENTS
1135void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
1136 /* we defer the popup menu call upon the leftup event
1137 else the menu disappears immediately,
1138 (see
1139 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
1140 */
1141 m_popupWanted = true;
1142}
1143
1144void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
1145 // not implemented yet
1146}
1147
1148void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1149
1150void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1151
1152void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1153 wxPoint pos = event.GetPosition();
1154
1155 m_leftdown = false;
1156
1157 if (!m_popupWanted) {
1158 wxMouseEvent ev(wxEVT_LEFT_UP);
1159 ev.m_x = pos.x;
1160 ev.m_y = pos.y;
1161 MouseEvent(ev);
1162 return;
1163 }
1164
1165 m_popupWanted = false;
1166
1167 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1168 ev.m_x = pos.x;
1169 ev.m_y = pos.y;
1170
1171 MouseEvent(ev);
1172}
1173
1174void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1175 m_leftdown = true;
1176
1177 wxPoint pos = event.GetPosition();
1178 MouseEvent(event);
1179}
1180
1181void ChartCanvas::OnMotion(wxMouseEvent &event) {
1182 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1183 dragging, upon simple click, and without the OnLeftDown event before Thus,
1184 this consists in skiping it, and setting the leftdown bit according to a
1185 status that we trust */
1186 event.m_leftDown = m_leftdown;
1187 MouseEvent(event);
1188}
1189
1190void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1191 /* there are spurious end zoom events upon right-click */
1192 if (event.IsGestureEnd()) return;
1193
1194 double factor = event.GetZoomFactor();
1195
1196 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1197 m_oldVPSScale = GetVPScale();
1198 }
1199
1200 double current_vps = GetVPScale();
1201 double wanted_factor = m_oldVPSScale / current_vps * factor;
1202
1203 ZoomCanvas(wanted_factor, true, false);
1204
1205 // Allow combined zoom/pan operation
1206 if (event.IsGestureStart()) {
1207 m_zoomStartPoint = event.GetPosition();
1208 } else {
1209 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1210 PanCanvas(-delta.x, -delta.y);
1211 m_zoomStartPoint = event.GetPosition();
1212 }
1213}
1214
1215void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1216
1217void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1218 DoRotateCanvas(0.0);
1219}
1220#endif /* HAVE_WX_GESTURE_EVENTS */
1221
1222void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1223 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1224 m_vLat = pcc->iLat;
1225 m_vLon = pcc->iLon;
1226
1227 m_restore_dbindex = pcc->DBindex;
1228 m_bFollow = pcc->bFollow;
1229 if (pcc->GroupID < 0) pcc->GroupID = 0;
1230
1231 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1232 m_groupIndex = 0;
1233 else
1234 m_groupIndex = pcc->GroupID;
1235
1236 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1237
1238 ShowTides(pcc->bShowTides);
1239 ShowCurrents(pcc->bShowCurrents);
1240
1241 SetShowDepthUnits(pcc->bShowDepthUnits);
1242 SetShowGrid(pcc->bShowGrid);
1243 SetShowOutlines(pcc->bShowOutlines);
1244
1245 SetShowAIS(pcc->bShowAIS);
1246 SetAttenAIS(pcc->bAttenAIS);
1247
1248 // ENC options
1249 SetShowENCText(pcc->bShowENCText);
1250 m_encDisplayCategory = pcc->nENCDisplayCategory;
1251 m_encShowDepth = pcc->bShowENCDepths;
1252 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1253 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1254 m_encShowLights = pcc->bShowENCLights;
1255 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1256 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1257 m_encShowDataQual = pcc->bShowENCDataQuality;
1258
1259 bool courseUp = pcc->bCourseUp;
1260 bool headUp = pcc->bHeadUp;
1261 m_upMode = NORTH_UP_MODE;
1262 if (courseUp)
1263 m_upMode = COURSE_UP_MODE;
1264 else if (headUp)
1265 m_upMode = HEAD_UP_MODE;
1266
1267 m_bLookAhead = pcc->bLookahead;
1268
1269 m_singleChart = NULL;
1270}
1271
1272void ChartCanvas::ApplyGlobalSettings() {
1273 // GPS compas window
1274 if (m_Compass) {
1275 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1276 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1277 }
1278 m_notification_button->UpdateStatus();
1279}
1280
1281void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1282 bool groupOK = CheckGroup(m_groupIndex);
1283
1284 if (!groupOK) {
1285 SetGroupIndex(m_groupIndex, true);
1286 }
1287}
1288
1289void ChartCanvas::SetShowGPS(bool bshow) {
1290 if (m_bShowGPS != bshow) {
1291 delete m_Compass;
1292 m_Compass = new ocpnCompass(this, bshow);
1293 m_Compass->SetScaleFactor(g_compass_scalefactor);
1294 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1295 }
1296 m_bShowGPS = bshow;
1297}
1298
1299void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1300 m_bShowCompassWin = bshow;
1301 if (m_Compass) {
1302 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1303 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1304 }
1305}
1306
1307int ChartCanvas::GetPianoHeight() {
1308 int height = 0;
1309 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1310
1311 return height;
1312}
1313
1314void ChartCanvas::ConfigureChartBar() {
1315 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1316
1317 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
1318 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
1319
1320 if (GetQuiltMode()) {
1321 m_Piano->SetRoundedRectangles(true);
1322 }
1323 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
1324 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon(_T("polyprj"))));
1325 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
1326}
1327
1328void ChartCanvas::ShowTides(bool bShow) {
1329 gFrame->LoadHarmonics();
1330
1331 if (ptcmgr->IsReady()) {
1332 SetbShowTide(bShow);
1333
1334 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1335 } else {
1336 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1337 SetbShowTide(false);
1338 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1339 }
1340
1341 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1342 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1343
1344 // TODO
1345 // if( GetbShowTide() ) {
1346 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1347 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1348 // update
1349 // } else
1350 // FrameTCTimer.Stop();
1351}
1352
1353void ChartCanvas::ShowCurrents(bool bShow) {
1354 gFrame->LoadHarmonics();
1355
1356 if (ptcmgr->IsReady()) {
1357 SetbShowCurrent(bShow);
1358 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1359 } else {
1360 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1361 SetbShowCurrent(false);
1362 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1363 }
1364
1365 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1366 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1367
1368 // TODO
1369 // if( GetbShowCurrent() ) {
1370 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1371 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1372 // update
1373 // } else
1374 // FrameTCTimer.Stop();
1375}
1376
1377// TODO
1378extern bool g_bPreserveScaleOnX;
1379extern ChartDummy *pDummyChart;
1380extern int g_sticky_chart;
1381
1382void ChartCanvas::canvasRefreshGroupIndex(void) { SetGroupIndex(m_groupIndex); }
1383
1384void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1385 SetAlertString(_T(""));
1386
1387 int new_index = index;
1388 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1389
1390 bool bgroup_override = false;
1391 int old_group_index = new_index;
1392
1393 if (!CheckGroup(new_index)) {
1394 new_index = 0;
1395 bgroup_override = true;
1396 }
1397
1398 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1399 new_index = index;
1400
1401 // Get the currently displayed chart native scale, and the current ViewPort
1402 int current_chart_native_scale = GetCanvasChartNativeScale();
1403 ViewPort vp = GetVP();
1404
1405 m_groupIndex = new_index;
1406
1407 // Are there ENCs in this group
1408 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1409
1410 // Update the MUIBar for ENC availability
1411 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1412
1413 // Allow the chart database to pre-calculate the MBTile inclusion test
1414 // boolean...
1415 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1416
1417 // Invalidate the "sticky" chart on group change, since it might not be in
1418 // the new group
1419 g_sticky_chart = -1;
1420
1421 // We need a chartstack and quilt to figure out which chart to open in the
1422 // new group
1423 UpdateCanvasOnGroupChange();
1424
1425 int dbi_now = -1;
1426 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1427
1428 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1429
1430 // If a new reference chart is indicated, set a good scale for it.
1431 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1432 double best_scale = GetBestStartScale(dbi_hint, vp);
1433 SetVPScale(best_scale);
1434 }
1435
1436 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1437
1438 // Refresh the canvas, selecting the "best" chart,
1439 // applying the prior ViewPort exactly
1440 canvasChartsRefresh(dbi_hint);
1441
1442 UpdateCanvasControlBar();
1443
1444 if (!autoSwitch && bgroup_override) {
1445 // show a short timed message box
1446 wxString msg(_("Group \""));
1447
1448 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1449 msg += pGroup->m_group_name;
1450
1451 msg += _("\" is empty.");
1452
1453 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1454
1455 return;
1456 }
1457
1458 // Message box is deferred so that canvas refresh occurs properly before
1459 // dialog
1460 if (bgroup_override) {
1461 wxString msg(_("Group \""));
1462
1463 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1464 msg += pGroup->m_group_name;
1465
1466 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1467
1468 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1469 }
1470}
1471
1472bool ChartCanvas::CheckGroup(int igroup) {
1473 if (!ChartData) return true; // Not known yet...
1474
1475 if (igroup == 0) return true; // "all charts" is always OK
1476
1477 if (igroup < 0) // negative group is an error
1478 return false;
1479
1480 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1481
1482 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1483 // and auto-shift to group 0
1484 return false;
1485
1486 for (const auto &elem : pGroup->m_element_array) {
1487 for (unsigned int ic = 0;
1488 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1489 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1490 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1491
1492 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1493 }
1494 }
1495
1496 // If necessary, check for GSHHS
1497 for (const auto &elem : pGroup->m_element_array) {
1498 const wxString &element_root = elem.m_element_name;
1499 wxString test_string = _T("GSHH");
1500 if (element_root.Upper().Contains(test_string)) return true;
1501 }
1502
1503 return false;
1504}
1505
1506void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1507 if (!ChartData) return;
1508
1509 AbstractPlatform::ShowBusySpinner();
1510
1511 double old_scale = GetVPScale();
1512 InvalidateQuilt();
1513 SetQuiltRefChart(-1);
1514
1515 m_singleChart = NULL;
1516
1517 // delete m_pCurrentStack;
1518 // m_pCurrentStack = NULL;
1519
1520 // Build a new ChartStack
1521 if (!m_pCurrentStack) {
1522 m_pCurrentStack = new ChartStack;
1523 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1524 }
1525
1526 if (-1 != dbi_hint) {
1527 if (GetQuiltMode()) {
1528 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1529 SetQuiltRefChart(dbi_hint);
1530 } else {
1531 // Open the saved chart
1532 ChartBase *pTentative_Chart;
1533 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1534
1535 if (pTentative_Chart) {
1536 /* m_singleChart is always NULL here, (set above) should this go before
1537 * that? */
1538 if (m_singleChart) m_singleChart->Deactivate();
1539
1540 m_singleChart = pTentative_Chart;
1541 m_singleChart->Activate();
1542
1543 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1544 GetpCurrentStack(), m_singleChart->GetFullPath());
1545 }
1546 }
1547
1548 // refresh_Piano();
1549 } else {
1550 // Select reference chart from the stack, as though clicked by user
1551 // Make it the smallest scale chart on the stack
1552 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1553 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1554 SetQuiltRefChart(selected_index);
1555 }
1556
1557 // Validate the correct single chart, or set the quilt mode as appropriate
1558 SetupCanvasQuiltMode();
1559 if (!GetQuiltMode() && m_singleChart == 0) {
1560 // use a dummy like in DoChartUpdate
1561 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1562 m_singleChart = pDummyChart;
1563 SetVPScale(old_scale);
1564 }
1565
1566 ReloadVP();
1567
1568 UpdateCanvasControlBar();
1569 UpdateGPSCompassStatusBox(true);
1570
1571 SetCursor(wxCURSOR_ARROW);
1572
1573 AbstractPlatform::HideBusySpinner();
1574}
1575
1576bool ChartCanvas::DoCanvasUpdate(void) {
1577 double tLat, tLon; // Chart Stack location
1578 double vpLat, vpLon; // ViewPort location
1579 bool blong_jump = false;
1580 meters_to_shift = 0;
1581 dir_to_shift = 0;
1582
1583 bool bNewChart = false;
1584 bool bNewView = false;
1585 bool bCanvasChartAutoOpen = true; // debugging
1586
1587 bool bNewPiano = false;
1588 bool bOpenSpecified;
1589 ChartStack LastStack;
1590 ChartBase *pLast_Ch;
1591
1592 ChartStack WorkStack;
1593
1594 if (bDBUpdateInProgress) return false;
1595 if (!ChartData) return false;
1596
1597 if (ChartData->IsBusy()) return false;
1598
1599 // Startup case:
1600 // Quilting is enabled, but the last chart seen was not quiltable
1601 // In this case, drop to single chart mode, set persistence flag,
1602 // And open the specified chart
1603 // TODO implement this
1604 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1605 // if( GetQuiltMode() ) {
1606 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1607 // gFrame->ToggleQuiltMode();
1608 // m_bpersistent_quilt = true;
1609 // m_singleChart = NULL;
1610 // }
1611 // }
1612 // }
1613
1614 // If in auto-follow mode, use the current glat,glon to build chart
1615 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1616 // other means
1617
1618 if (m_bFollow) {
1619 tLat = gLat;
1620 tLon = gLon;
1621
1622 // Set the ViewPort center based on the OWNSHIP offset
1623 double dx = m_OSoffsetx;
1624 double dy = m_OSoffsety;
1625 double d_east = dx / GetVP().view_scale_ppm;
1626 double d_north = dy / GetVP().view_scale_ppm;
1627
1628 if (GetUpMode() == NORTH_UP_MODE) {
1629 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1630 } else {
1631 double offset_angle = atan2(d_north, d_east);
1632 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1633 double chart_angle = GetVPRotation();
1634 double target_angle = chart_angle + offset_angle;
1635 double d_east_mod = offset_distance * cos(target_angle);
1636 double d_north_mod = offset_distance * sin(target_angle);
1637 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1638 }
1639
1640 extern double gCog_gt;
1641
1642 // on lookahead mode, adjust the vp center point
1643 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1644 double cog_to_use = gCog;
1645 if (g_btenhertz &&
1646 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1647 cog_to_use = gCog_gt;
1648 blong_jump = true;
1649 }
1650 if (!g_btenhertz) cog_to_use = g_COGAvg;
1651
1652 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1653
1654 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1655 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1656
1657 double pixel_delta_tent =
1658 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1659
1660 double pixel_delta = 0;
1661
1662 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1663 // avoid jumping of the vp center point during slow maneuvering, or at
1664 // anchor....
1665 if (!std::isnan(gSog)) {
1666 if (gSog < 2.0)
1667 pixel_delta = 0.;
1668 else
1669 pixel_delta = pixel_delta_tent;
1670 }
1671
1672 meters_to_shift = 0;
1673 dir_to_shift = 0;
1674 if (!std::isnan(gCog)) {
1675 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1676 dir_to_shift = cog_to_use;
1677 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1678 &vpLon);
1679 } else {
1680 vpLat = gLat;
1681 vpLon = gLon;
1682 }
1683 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1684 m_OSoffsetx = 0; // center ownship on loss of GPS
1685 m_OSoffsety = 0;
1686 vpLat = gLat;
1687 vpLon = gLon;
1688 }
1689
1690 } else {
1691 tLat = m_vLat;
1692 tLon = m_vLon;
1693 vpLat = m_vLat;
1694 vpLon = m_vLon;
1695 }
1696
1697 if (GetQuiltMode()) {
1698 int current_db_index = -1;
1699 if (m_pCurrentStack)
1700 current_db_index =
1701 m_pCurrentStack
1702 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1703 // chart dbIndex
1704 else
1705 m_pCurrentStack = new ChartStack;
1706
1707 // This logic added to enable opening a chart when there is no
1708 // previous chart indication, either from inital startup, or from adding
1709 // new chart directory
1710 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1711 m_pCurrentStack) {
1712 if (m_pCurrentStack->nEntry) {
1713 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1714 1); // smallest scale
1715 SelectQuiltRefdbChart(new_dbIndex, true);
1716 m_bautofind = false;
1717 }
1718 }
1719
1720 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1721 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1722
1723 if (m_bFirstAuto) {
1724 // Allow the chart database to pre-calculate the MBTile inclusion test
1725 // boolean...
1726 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1727
1728 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1729 // physical pixels. On standard DPI displays where logical = physical
1730 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1731 // logical pixels, this ratio would be 0.5.
1732 double proposed_scale_onscreen =
1733 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1734
1735 int initial_db_index = m_restore_dbindex;
1736 if (initial_db_index < 0) {
1737 if (m_pCurrentStack->nEntry) {
1738 initial_db_index =
1739 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1740 } else
1741 m_bautofind = true; // initial_db_index = 0;
1742 }
1743
1744 if (m_pCurrentStack->nEntry) {
1745 int initial_type = ChartData->GetDBChartType(initial_db_index);
1746
1747 // Check to see if the target new chart is quiltable as a reference
1748 // chart
1749
1750 if (!IsChartQuiltableRef(initial_db_index)) {
1751 // If it is not quiltable, then walk the stack up looking for a
1752 // satisfactory chart i.e. one that is quiltable and of the same type
1753 // XXX if there's none?
1754 int stack_index = 0;
1755
1756 if (stack_index >= 0) {
1757 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1758 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1759 if (IsChartQuiltableRef(test_db_index) &&
1760 (initial_type ==
1761 ChartData->GetDBChartType(initial_db_index))) {
1762 initial_db_index = test_db_index;
1763 break;
1764 }
1765 stack_index++;
1766 }
1767 }
1768 }
1769
1770 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1771 if (pc) {
1772 SetQuiltRefChart(initial_db_index);
1773 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1774 }
1775 }
1776 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1777 // just GetVPScale(), so I'm not sure why it's necessary to define the
1778 // proposed_scale_onscreen variable.
1779 bNewView |= SetViewPoint(vpLat, vpLon,
1780 GetCanvasScaleFactor() / proposed_scale_onscreen,
1781 0, GetVPRotation());
1782 }
1783 // Measure rough jump distance if in bfollow mode
1784 // No good reason to do smooth pan for
1785 // jump distance more than one screen width at scale.
1786 bool super_jump = false;
1787 if (m_bFollow) {
1788 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1789 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1790 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1791 }
1792 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead) {
1793 int nstep = 5;
1794 if (blong_jump) nstep = 20;
1795 StartTimedMovementVP(vpLat, vpLon, nstep);
1796 } else {
1797 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1798 }
1799
1800 goto update_finish;
1801 }
1802
1803 // Single Chart Mode from here....
1804 pLast_Ch = m_singleChart;
1805 ChartTypeEnum new_open_type;
1806 ChartFamilyEnum new_open_family;
1807 if (pLast_Ch) {
1808 new_open_type = pLast_Ch->GetChartType();
1809 new_open_family = pLast_Ch->GetChartFamily();
1810 } else {
1811 new_open_type = CHART_TYPE_KAP;
1812 new_open_family = CHART_FAMILY_RASTER;
1813 }
1814
1815 bOpenSpecified = m_bFirstAuto;
1816
1817 // Make sure the target stack is valid
1818 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1819
1820 // Build a chart stack based on tLat, tLon
1821 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1822 m_groupIndex)) { // Bogus Lat, Lon?
1823 if (NULL == pDummyChart) {
1824 pDummyChart = new ChartDummy;
1825 bNewChart = true;
1826 }
1827
1828 if (m_singleChart)
1829 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1830
1831 m_singleChart = pDummyChart;
1832
1833 // If the current viewpoint is invalid, set the default scale to
1834 // something reasonable.
1835 double set_scale = GetVPScale();
1836 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1837
1838 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1839
1840 // If the chart stack has just changed, there is new status
1841 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1842 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1843 bNewPiano = true;
1844 bNewChart = true;
1845 }
1846 }
1847
1848 // Copy the new (by definition empty) stack into the target stack
1849 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1850
1851 goto update_finish;
1852 }
1853
1854 // Check to see if Chart Stack has changed
1855 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1856 // New chart stack, so...
1857 bNewPiano = true;
1858
1859 // Save a copy of the current stack
1860 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1861
1862 // Copy the new stack into the target stack
1863 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1864
1865 // Is Current Chart in new stack?
1866
1867 int tEntry = -1;
1868 if (NULL != m_singleChart) // this handles startup case
1869 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1870 m_singleChart->GetFullPath());
1871
1872 if (tEntry != -1) { // m_singleChart is in the new stack
1873 m_pCurrentStack->CurrentStackEntry = tEntry;
1874 bNewChart = false;
1875 }
1876
1877 else // m_singleChart is NOT in new stack
1878 { // So, need to open a new chart
1879 // Find the largest scale raster chart that opens OK
1880
1881 ChartBase *pProposed = NULL;
1882
1883 if (bCanvasChartAutoOpen) {
1884 bool search_direction =
1885 false; // default is to search from lowest to highest
1886 int start_index = 0;
1887
1888 // A special case: If panning at high scale, open largest scale
1889 // chart first
1890 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1891 (LastStack.nEntry == 0)) {
1892 search_direction = true;
1893 start_index = m_pCurrentStack->nEntry - 1;
1894 }
1895
1896 // Another special case, open specified index on program start
1897 if (bOpenSpecified) {
1898 search_direction = false;
1899 start_index = 0;
1900 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1901 start_index = 0;
1902
1903 new_open_type = CHART_TYPE_DONTCARE;
1904 }
1905
1906 pProposed = ChartData->OpenStackChartConditional(
1907 m_pCurrentStack, start_index, search_direction, new_open_type,
1908 new_open_family);
1909
1910 // Try to open other types/families of chart in some priority
1911 if (NULL == pProposed)
1912 pProposed = ChartData->OpenStackChartConditional(
1913 m_pCurrentStack, start_index, search_direction,
1914 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1915
1916 if (NULL == pProposed)
1917 pProposed = ChartData->OpenStackChartConditional(
1918 m_pCurrentStack, start_index, search_direction,
1919 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1920
1921 bNewChart = true;
1922
1923 } // bCanvasChartAutoOpen
1924
1925 else
1926 pProposed = NULL;
1927
1928 // If no go, then
1929 // Open a Dummy Chart
1930 if (NULL == pProposed) {
1931 if (NULL == pDummyChart) {
1932 pDummyChart = new ChartDummy;
1933 bNewChart = true;
1934 }
1935
1936 if (pLast_Ch)
1937 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1938
1939 pProposed = pDummyChart;
1940 }
1941
1942 // Arriving here, pProposed points to an opened chart, or NULL.
1943 if (m_singleChart) m_singleChart->Deactivate();
1944 m_singleChart = pProposed;
1945
1946 if (m_singleChart) {
1947 m_singleChart->Activate();
1948 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1949 m_pCurrentStack, m_singleChart->GetFullPath());
1950 }
1951 } // need new chart
1952
1953 // Arriving here, m_singleChart is opened and OK, or NULL
1954 if (NULL != m_singleChart) {
1955 // Setup the view using the current scale
1956 double set_scale = GetVPScale();
1957
1958 // If the current viewpoint is invalid, set the default scale to
1959 // something reasonable.
1960 if (!GetVP().IsValid())
1961 set_scale = 1. / 20000.;
1962 else { // otherwise, match scale if elected.
1963 double proposed_scale_onscreen;
1964
1965 if (m_bFollow) { // autoset the scale only if in autofollow
1966 double new_scale_ppm =
1967 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1968 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1969 } else
1970 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1971
1972 // This logic will bring a new chart onscreen at roughly twice the true
1973 // paper scale equivalent. Note that first chart opened on application
1974 // startup (bOpenSpecified = true) will open at the config saved scale
1975 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1976 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1977 double equivalent_vp_scale =
1978 GetCanvasScaleFactor() / proposed_scale_onscreen;
1979 double new_scale_ppm =
1980 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1981 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1982 }
1983
1984 if (m_bFollow) { // bounds-check the scale only if in autofollow
1985 proposed_scale_onscreen =
1986 wxMin(proposed_scale_onscreen,
1987 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1988 GetCanvasWidth()));
1989 proposed_scale_onscreen =
1990 wxMax(proposed_scale_onscreen,
1991 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1992 g_b_overzoom_x));
1993 }
1994
1995 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1996 }
1997
1998 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1999 m_singleChart->GetChartSkew() * PI / 180.,
2000 GetVPRotation());
2001 }
2002 } // new stack
2003
2004 else // No change in Chart Stack
2005 {
2006 if ((m_bFollow) && m_singleChart)
2007 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
2008 m_singleChart->GetChartSkew() * PI / 180.,
2009 GetVPRotation());
2010 }
2011
2012update_finish:
2013
2014 // TODO
2015 // if( bNewPiano ) UpdateControlBar();
2016
2017 m_bFirstAuto = false; // Auto open on program start
2018
2019 // If we need a Refresh(), do it here...
2020 // But don't duplicate a Refresh() done by SetViewPoint()
2021 if (bNewChart && !bNewView) Refresh(false);
2022
2023#ifdef ocpnUSE_GL
2024 // If a new chart, need to invalidate gl viewport for refresh
2025 // so the fbo gets flushed
2026 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
2027#endif
2028
2029 return bNewChart | bNewView;
2030}
2031
2032void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
2033 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
2034
2035 SetQuiltRefChart(db_index);
2036 if (ChartData) {
2037 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
2038 if (pc) {
2039 if (b_autoscale) {
2040 double best_scale_ppm = GetBestVPScale(pc);
2041 SetVPScale(best_scale_ppm);
2042 }
2043 } else
2044 SetQuiltRefChart(-1);
2045 } else
2046 SetQuiltRefChart(-1);
2047}
2048
2049void ChartCanvas::SelectQuiltRefChart(int selected_index) {
2050 std::vector<int> piano_chart_index_array =
2051 GetQuiltExtendedStackdbIndexArray();
2052 int current_db_index = piano_chart_index_array[selected_index];
2053
2054 SelectQuiltRefdbChart(current_db_index);
2055}
2056
2057double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
2058 if (pchart) {
2059 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
2060
2061 if ((g_bPreserveScaleOnX) ||
2062 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2063 double new_scale_ppm = GetVPScale();
2064 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2065 } else {
2066 // This logic will bring the new chart onscreen at roughly twice the true
2067 // paper scale equivalent.
2068 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2069 double equivalent_vp_scale =
2070 GetCanvasScaleFactor() / proposed_scale_onscreen;
2071 double new_scale_ppm =
2072 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2073 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2074 }
2075
2076 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2077 // set. Otherwise, we get severe performance problems on all platforms
2078
2079 double max_underzoom_multiplier = 2.0;
2080 if (GetVP().b_quilt) {
2081 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2082 pchart->GetChartType(),
2083 pchart->GetChartFamily());
2084 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2085 }
2086
2087 proposed_scale_onscreen = wxMin(
2088 proposed_scale_onscreen,
2089 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2090 max_underzoom_multiplier);
2091
2092 // And, do not allow excessive overzoom either
2093 proposed_scale_onscreen =
2094 wxMax(proposed_scale_onscreen,
2095 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2096
2097 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2098 } else
2099 return 1.0;
2100}
2101
2102void ChartCanvas::SetupCanvasQuiltMode(void) {
2103 if (GetQuiltMode()) // going to quilt mode
2104 {
2105 ChartData->LockCache();
2106
2107 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2108
2109 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2110
2111 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2112 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2113 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2114 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2115
2116 m_Piano->SetRoundedRectangles(true);
2117
2118 // Select the proper Ref chart
2119 int target_new_dbindex = -1;
2120 if (m_pCurrentStack) {
2121 target_new_dbindex =
2122 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2123
2124 if (-1 != target_new_dbindex) {
2125 if (!IsChartQuiltableRef(target_new_dbindex)) {
2126 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2127 int type = ChartData->GetDBChartType(target_new_dbindex);
2128
2129 // walk the stack up looking for a satisfactory chart
2130 int stack_index = m_pCurrentStack->CurrentStackEntry;
2131
2132 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2133 (stack_index >= 0)) {
2134 int proj_tent = ChartData->GetDBChartProj(
2135 m_pCurrentStack->GetDBIndex(stack_index));
2136 int type_tent = ChartData->GetDBChartType(
2137 m_pCurrentStack->GetDBIndex(stack_index));
2138
2139 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2140 if ((proj == proj_tent) && (type_tent == type)) {
2141 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2142 break;
2143 }
2144 }
2145 stack_index++;
2146 }
2147 }
2148 }
2149 }
2150
2151 if (IsChartQuiltableRef(target_new_dbindex))
2152 SelectQuiltRefdbChart(target_new_dbindex,
2153 false); // Try not to allow a scale change
2154 else
2155 SelectQuiltRefdbChart(-1, false);
2156
2157 m_singleChart = NULL; // Bye....
2158
2159 // Re-qualify the quilt reference chart selection
2160 AdjustQuiltRefChart();
2161
2162 // Restore projection type saved on last quilt mode toggle
2163 // TODO
2164 // if(g_sticky_projection != -1)
2165 // GetVP().SetProjectionType(g_sticky_projection);
2166 // else
2167 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2168 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2169
2170 } else // going to SC Mode
2171 {
2172 std::vector<int> empty_array;
2173 m_Piano->SetActiveKeyArray(empty_array);
2174 m_Piano->SetNoshowIndexArray(empty_array);
2175 m_Piano->SetEclipsedIndexArray(empty_array);
2176
2177 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2178 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2179 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2180 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2181 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2182
2183 m_Piano->SetRoundedRectangles(false);
2184 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2185 }
2186
2187 // When shifting from quilt to single chart mode, select the "best" single
2188 // chart to show
2189 if (!GetQuiltMode()) {
2190 if (ChartData && ChartData->IsValid()) {
2191 UnlockQuilt();
2192
2193 double tLat, tLon;
2194 if (m_bFollow == true) {
2195 tLat = gLat;
2196 tLon = gLon;
2197 } else {
2198 tLat = m_vLat;
2199 tLon = m_vLon;
2200 }
2201
2202 if (!m_singleChart) {
2203 // Build a temporary chart stack based on tLat, tLon
2204 ChartStack TempStack;
2205 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2206 m_groupIndex);
2207
2208 // Iterate over the quilt charts actually shown, looking for the
2209 // largest scale chart that will be in the new chartstack.... This
2210 // will (almost?) always be the reference chart....
2211
2212 ChartBase *Candidate_Chart = NULL;
2213 int cur_max_scale = (int)1e8;
2214
2215 ChartBase *pChart = GetFirstQuiltChart();
2216 while (pChart) {
2217 // Is this pChart in new stack?
2218 int tEntry =
2219 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2220 if (tEntry != -1) {
2221 if (pChart->GetNativeScale() < cur_max_scale) {
2222 Candidate_Chart = pChart;
2223 cur_max_scale = pChart->GetNativeScale();
2224 }
2225 }
2226 pChart = GetNextQuiltChart();
2227 }
2228
2229 m_singleChart = Candidate_Chart;
2230
2231 // If the quilt is empty, there is no "best" chart.
2232 // So, open the smallest scale chart in the current stack
2233 if (NULL == m_singleChart) {
2234 m_singleChart = ChartData->OpenStackChartConditional(
2235 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2236 CHART_FAMILY_DONTCARE);
2237 }
2238 }
2239
2240 // Invalidate all the charts in the quilt,
2241 // as any cached data may be region based and not have fullscreen coverage
2242 InvalidateAllQuiltPatchs();
2243
2244 if (m_singleChart) {
2245 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2246 std::vector<int> one_array;
2247 one_array.push_back(dbi);
2248 m_Piano->SetActiveKeyArray(one_array);
2249 }
2250
2251 if (m_singleChart) {
2252 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2253 }
2254 }
2255 // Invalidate the current stack so that it will be rebuilt on next tick
2256 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2257 }
2258}
2259
2260bool ChartCanvas::IsTempMenuBarEnabled() {
2261#ifdef __WXMSW__
2262 int major;
2263 wxGetOsVersion(&major);
2264 return (major >
2265 5); // For Windows, function is only available on Vista and above
2266#else
2267 return true;
2268#endif
2269}
2270
2271double ChartCanvas::GetCanvasRangeMeters() {
2272 int width, height;
2273 GetSize(&width, &height);
2274 int minDimension = wxMin(width, height);
2275
2276 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2277 range *= cos(GetVP().clat * PI / 180.);
2278 return range;
2279}
2280
2281void ChartCanvas::SetCanvasRangeMeters(double range) {
2282 int width, height;
2283 GetSize(&width, &height);
2284 int minDimension = wxMin(width, height);
2285
2286 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2287 SetVPScale(scale_ppm / 2);
2288}
2289
2290bool ChartCanvas::SetUserOwnship() {
2291 // Look for user defined ownship image
2292 // This may be found in the shared data location along with other user
2293 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2294 if (pWayPointMan && pWayPointMan->DoesIconExist(_T("ownship"))) {
2295 double factor_dusk = 0.5;
2296 double factor_night = 0.25;
2297
2298 wxBitmap *pbmp = pWayPointMan->GetIconBitmap(_T("ownship"));
2299 m_pos_image_user_day = new wxImage;
2300 *m_pos_image_user_day = pbmp->ConvertToImage();
2301 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2302
2303 int gimg_width = m_pos_image_user_day->GetWidth();
2304 int gimg_height = m_pos_image_user_day->GetHeight();
2305
2306 // Make dusk and night images
2307 m_pos_image_user_dusk = new wxImage;
2308 m_pos_image_user_night = new wxImage;
2309
2310 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2311 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2312
2313 for (int iy = 0; iy < gimg_height; iy++) {
2314 for (int ix = 0; ix < gimg_width; ix++) {
2315 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2316 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2317 m_pos_image_user_day->GetGreen(ix, iy),
2318 m_pos_image_user_day->GetBlue(ix, iy));
2319 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2320 hsv.value = hsv.value * factor_dusk;
2321 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2322 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2323 nrgb.blue);
2324
2325 hsv = wxImage::RGBtoHSV(rgb);
2326 hsv.value = hsv.value * factor_night;
2327 nrgb = wxImage::HSVtoRGB(hsv);
2328 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2329 nrgb.blue);
2330 }
2331 }
2332 }
2333
2334 // Make some alternate greyed out day/dusk/night images
2335 m_pos_image_user_grey_day = new wxImage;
2336 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2337
2338 m_pos_image_user_grey_dusk = new wxImage;
2339 m_pos_image_user_grey_night = new wxImage;
2340
2341 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2342 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2343
2344 for (int iy = 0; iy < gimg_height; iy++) {
2345 for (int ix = 0; ix < gimg_width; ix++) {
2346 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2347 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2348 m_pos_image_user_grey_day->GetGreen(ix, iy),
2349 m_pos_image_user_grey_day->GetBlue(ix, iy));
2350 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2351 hsv.value = hsv.value * factor_dusk;
2352 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2353 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2354 nrgb.blue);
2355
2356 hsv = wxImage::RGBtoHSV(rgb);
2357 hsv.value = hsv.value * factor_night;
2358 nrgb = wxImage::HSVtoRGB(hsv);
2359 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2360 nrgb.blue);
2361 }
2362 }
2363 }
2364
2365 // Make a yellow image for rendering under low accuracy chart conditions
2366 m_pos_image_user_yellow_day = new wxImage;
2367 m_pos_image_user_yellow_dusk = new wxImage;
2368 m_pos_image_user_yellow_night = new wxImage;
2369
2370 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2371 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2372 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2373
2374 for (int iy = 0; iy < gimg_height; iy++) {
2375 for (int ix = 0; ix < gimg_width; ix++) {
2376 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2377 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2378 m_pos_image_user_grey_day->GetGreen(ix, iy),
2379 m_pos_image_user_grey_day->GetBlue(ix, iy));
2380
2381 // Simply remove all "blue" from the greyscaled image...
2382 // so, what is not black becomes yellow.
2383 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2384 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2385 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2386
2387 hsv = wxImage::RGBtoHSV(rgb);
2388 hsv.value = hsv.value * factor_dusk;
2389 nrgb = wxImage::HSVtoRGB(hsv);
2390 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2391
2392 hsv = wxImage::RGBtoHSV(rgb);
2393 hsv.value = hsv.value * factor_night;
2394 nrgb = wxImage::HSVtoRGB(hsv);
2395 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2396 0);
2397 }
2398 }
2399 }
2400
2401 return true;
2402 } else
2403 return false;
2404}
2405
2407 m_display_size_mm = size;
2408
2409 // int sx, sy;
2410 // wxDisplaySize( &sx, &sy );
2411
2412 // Calculate logical pixels per mm for later reference.
2413 wxSize sd = g_Platform->getDisplaySize();
2414 double horizontal = sd.x;
2415 // Set DPI (Win) scale factor
2416 g_scaler = g_Platform->GetDisplayDIPMult(this);
2417
2418 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2419 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2420
2421 if (ps52plib) {
2422 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2423 ps52plib->SetPPMM(m_pix_per_mm);
2424 }
2425
2426 wxString msg;
2427 msg.Printf(
2428 _T("Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): ")
2429 _T("%d:%d "),
2430 m_display_size_mm, sd.x, sd.y);
2431 wxLogMessage(msg);
2432
2433 int ssx, ssy;
2434 ssx = g_monitor_info[g_current_monitor].width;
2435 ssy = g_monitor_info[g_current_monitor].height;
2436 msg.Printf(_T("monitor size: %d %d"), ssx, ssy);
2437 wxLogMessage(msg);
2438
2439 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2440}
2441#if 0
2442void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2443{
2444 wxString msg(event.m_string.c_str(), wxConvUTF8);
2445 // if cpus are removed between runs
2446 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2447 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2448 }
2449
2450 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2451 {
2452 compress_msg_array.RemoveAt(event.thread);
2453 compress_msg_array.Insert( msg, event.thread);
2454 }
2455 else
2456 compress_msg_array.Add(msg);
2457
2458
2459 wxString combined_msg;
2460 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2461 combined_msg += compress_msg_array[i];
2462 combined_msg += _T("\n");
2463 }
2464
2465 bool skip = false;
2466 pprog->Update(pprog_count, combined_msg, &skip );
2467 pprog->SetSize(pprog_size);
2468 if(skip)
2469 b_skipout = skip;
2470}
2471#endif
2472void ChartCanvas::InvalidateGL() {
2473 if (!m_glcc) return;
2474#ifdef ocpnUSE_GL
2475 if (g_bopengl) m_glcc->Invalidate();
2476#endif
2477 if (m_Compass) m_Compass->UpdateStatus(true);
2478}
2479
2480int ChartCanvas::GetCanvasChartNativeScale() {
2481 int ret = 1;
2482 if (!VPoint.b_quilt) {
2483 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2484 } else
2485 ret = (int)m_pQuilt->GetRefNativeScale();
2486
2487 return ret;
2488}
2489
2490ChartBase *ChartCanvas::GetChartAtCursor() {
2491 ChartBase *target_chart;
2492 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2493 target_chart = m_singleChart;
2494 else if (VPoint.b_quilt)
2495 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2496 else
2497 target_chart = NULL;
2498 return target_chart;
2499}
2500
2501ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2502 ChartBase *target_chart;
2503 if (VPoint.b_quilt)
2504 target_chart =
2505 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2506 else
2507 target_chart = NULL;
2508 return target_chart;
2509}
2510
2511int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2512 int new_dbIndex = -1;
2513 if (!VPoint.b_quilt) {
2514 if (m_pCurrentStack) {
2515 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2516 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2517 if (sc >= scale) {
2518 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2519 break;
2520 }
2521 }
2522 }
2523 } else {
2524 // Using the current quilt, select a useable reference chart
2525 // Said chart will be in the extended (possibly full-screen) stack,
2526 // And will have a scale equal to or just greater than the stipulated
2527 // value
2528 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2529 if (im > 0) {
2530 for (unsigned int is = 0; is < im; is++) {
2531 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2532 m_pQuilt->GetExtendedStackIndexArray()[is]);
2533 if ((m.Scale_ge(
2534 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2535 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2536 break;
2537 }
2538 }
2539 }
2540 }
2541
2542 return new_dbIndex;
2543}
2544
2545void ChartCanvas::EnablePaint(bool b_enable) {
2546 m_b_paint_enable = b_enable;
2547#ifdef ocpnUSE_GL
2548 if (m_glcc) m_glcc->EnablePaint(b_enable);
2549#endif
2550}
2551
2552bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2553
2554void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2555
2556std::vector<int> ChartCanvas::GetQuiltIndexArray(void) {
2557 return m_pQuilt->GetQuiltIndexArray();
2558 ;
2559}
2560
2561void ChartCanvas::SetQuiltMode(bool b_quilt) {
2562 VPoint.b_quilt = b_quilt;
2563 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2564}
2565
2566bool ChartCanvas::GetQuiltMode(void) { return VPoint.b_quilt; }
2567
2568int ChartCanvas::GetQuiltReferenceChartIndex(void) {
2569 return m_pQuilt->GetRefChartdbIndex();
2570}
2571
2572void ChartCanvas::InvalidateAllQuiltPatchs(void) {
2573 m_pQuilt->InvalidateAllQuiltPatchs();
2574}
2575
2576ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2577 return m_pQuilt->GetLargestScaleChart();
2578}
2579
2580ChartBase *ChartCanvas::GetFirstQuiltChart() {
2581 return m_pQuilt->GetFirstChart();
2582}
2583
2584ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2585
2586int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2587
2588void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2589 m_pQuilt->SetHiliteIndex(dbIndex);
2590}
2591
2592void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2593 m_pQuilt->SetHiliteIndexArray(hilite_array);
2594}
2595
2596void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2597 m_pQuilt->ClearHiliteIndexArray();
2598}
2599
2600std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2601 bool flag2) {
2602 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2603}
2604
2605int ChartCanvas::GetQuiltRefChartdbIndex(void) {
2606 return m_pQuilt->GetRefChartdbIndex();
2607}
2608
2609std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2610 return m_pQuilt->GetExtendedStackIndexArray();
2611}
2612
2613std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2614 return m_pQuilt->GetFullscreenIndexArray();
2615}
2616
2617std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2618 return m_pQuilt->GetEclipsedStackIndexArray();
2619}
2620
2621void ChartCanvas::InvalidateQuilt(void) { return m_pQuilt->Invalidate(); }
2622
2623double ChartCanvas::GetQuiltMaxErrorFactor() {
2624 return m_pQuilt->GetMaxErrorFactor();
2625}
2626
2627bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2628 return m_pQuilt->IsChartQuiltableRef(db_index);
2629}
2630
2631bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2632 double chartMaxScale =
2633 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2634 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2635}
2636
2637void ChartCanvas::StartMeasureRoute() {
2638 if (!m_routeState) { // no measure tool if currently creating route
2639 if (m_bMeasure_Active) {
2640 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2641 m_pMeasureRoute = NULL;
2642 }
2643
2644 m_bMeasure_Active = true;
2645 m_nMeasureState = 1;
2646 m_bDrawingRoute = false;
2647
2648 SetCursor(*pCursorPencil);
2649 Refresh();
2650 }
2651}
2652
2653void ChartCanvas::CancelMeasureRoute() {
2654 m_bMeasure_Active = false;
2655 m_nMeasureState = 0;
2656 m_bDrawingRoute = false;
2657
2658 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2659 m_pMeasureRoute = NULL;
2660
2661 SetCursor(*pCursorArrow);
2662}
2663
2664ViewPort &ChartCanvas::GetVP() { return VPoint; }
2665
2666void ChartCanvas::SetVP(ViewPort &vp) {
2667 VPoint = vp;
2668 VPoint.SetPixelScale(m_displayScale);
2669}
2670
2671// void ChartCanvas::SetFocus()
2672// {
2673// printf("set %d\n", m_canvasIndex);
2674// //wxWindow:SetFocus();
2675// }
2676
2677void ChartCanvas::TriggerDeferredFocus() {
2678 // #if defined(__WXGTK__) || defined(__WXOSX__)
2679
2680 m_deferredFocusTimer.Start(20, true);
2681
2682#if defined(__WXGTK__) || defined(__WXOSX__)
2683 gFrame->Raise();
2684#endif
2685
2686 // gFrame->Raise();
2687 // #else
2688 // SetFocus();
2689 // Refresh(true);
2690 // #endif
2691}
2692
2693void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2694 SetFocus();
2695 Refresh(true);
2696}
2697
2698void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2699 if (SendKeyEventToPlugins(event))
2700 return; // PlugIn did something, and does not want the canvas to do
2701 // anything else
2702
2703 int key_char = event.GetKeyCode();
2704 switch (key_char) {
2705 case '?':
2706 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2707 break;
2708 case '+':
2709 ZoomCanvas(g_plus_minus_zoom_factor, false);
2710 break;
2711 case '-':
2712 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2713 break;
2714 default:
2715 break;
2716 }
2717 if (g_benable_rotate) {
2718 switch (key_char) {
2719 case ']':
2720 RotateCanvas(1);
2721 Refresh();
2722 break;
2723
2724 case '[':
2725 RotateCanvas(-1);
2726 Refresh();
2727 break;
2728
2729 case '\\':
2730 DoRotateCanvas(0);
2731 break;
2732 }
2733 }
2734
2735 event.Skip();
2736}
2737
2738void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2739 if (SendKeyEventToPlugins(event))
2740 return; // PlugIn did something, and does not want the canvas to do
2741 // anything else
2742
2743 bool b_handled = false;
2744
2745 m_modkeys = event.GetModifiers();
2746
2747 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2748
2749#ifdef OCPN_ALT_MENUBAR
2750#ifndef __WXOSX__
2751 // If the permanent menubar is disabled, we show it temporarily when Alt is
2752 // pressed or when Alt + a letter is presssed (for the top-menu-level
2753 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2754 // some special cases.
2755 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2756 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2757 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2758 if (!g_bTempShowMenuBar) {
2759 g_bTempShowMenuBar = true;
2760 parent_frame->ApplyGlobalSettings(false);
2761 }
2762 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2763 event.Skip();
2764 return;
2765 }
2766 // If another key is pressed while Alt is down, do NOT toggle the menus when
2767 // Alt is released
2768 if (event.GetKeyCode() != WXK_ALT) {
2769 m_bMayToggleMenuBar = false;
2770 }
2771 }
2772#endif
2773#endif
2774
2775 // HOTKEYS
2776 switch (event.GetKeyCode()) {
2777 case WXK_TAB:
2778 // parent_frame->SwitchKBFocus( this );
2779 break;
2780
2781 case WXK_MENU:
2782 int x, y;
2783 event.GetPosition(&x, &y);
2784 m_FinishRouteOnKillFocus = false;
2785 CallPopupMenu(x, y);
2786 m_FinishRouteOnKillFocus = true;
2787 break;
2788
2789 case WXK_ALT:
2790 m_modkeys |= wxMOD_ALT;
2791 break;
2792
2793 case WXK_CONTROL:
2794 m_modkeys |= wxMOD_CONTROL;
2795 break;
2796
2797#ifdef __WXOSX__
2798 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2799 case WXK_RAW_CONTROL:
2800 m_modkeys |= wxMOD_RAW_CONTROL;
2801 break;
2802#endif
2803
2804 case WXK_LEFT:
2805 if (m_modkeys == wxMOD_CONTROL)
2806 parent_frame->DoStackDown(this);
2807 else if (g_bsmoothpanzoom) {
2808 StartTimedMovement();
2809 m_panx = -1;
2810 } else {
2811 PanCanvas(-panspeed, 0);
2812 }
2813 b_handled = true;
2814 break;
2815
2816 case WXK_UP:
2817 if (g_bsmoothpanzoom) {
2818 StartTimedMovement();
2819 m_pany = -1;
2820 } else
2821 PanCanvas(0, -panspeed);
2822 b_handled = true;
2823 break;
2824
2825 case WXK_RIGHT:
2826 if (m_modkeys == wxMOD_CONTROL)
2827 parent_frame->DoStackUp(this);
2828 else if (g_bsmoothpanzoom) {
2829 StartTimedMovement();
2830 m_panx = 1;
2831 } else
2832 PanCanvas(panspeed, 0);
2833 b_handled = true;
2834
2835 break;
2836
2837 case WXK_DOWN:
2838 if (g_bsmoothpanzoom) {
2839 StartTimedMovement();
2840 m_pany = 1;
2841 } else
2842 PanCanvas(0, panspeed);
2843 b_handled = true;
2844 break;
2845
2846 case WXK_F2:
2847 TogglebFollow();
2848 break;
2849
2850 case WXK_F3: {
2851 SetShowENCText(!GetShowENCText());
2852 Refresh(true);
2853 InvalidateGL();
2854 break;
2855 }
2856 case WXK_F4:
2857 if (!m_bMeasure_Active) {
2858 if (event.ShiftDown())
2859 m_bMeasure_DistCircle = true;
2860 else
2861 m_bMeasure_DistCircle = false;
2862
2863 StartMeasureRoute();
2864 } else {
2865 CancelMeasureRoute();
2866
2867 SetCursor(*pCursorArrow);
2868
2869 // SurfaceToolbar();
2870 InvalidateGL();
2871 Refresh(false);
2872 }
2873
2874 break;
2875
2876 case WXK_F5:
2877 parent_frame->ToggleColorScheme();
2878 gFrame->Raise();
2879 TriggerDeferredFocus();
2880 break;
2881
2882 case WXK_F6: {
2883 int mod = m_modkeys & wxMOD_SHIFT;
2884 if (mod != m_brightmod) {
2885 m_brightmod = mod;
2886 m_bbrightdir = !m_bbrightdir;
2887 }
2888
2889 if (!m_bbrightdir) {
2890 g_nbrightness -= 10;
2891 if (g_nbrightness <= MIN_BRIGHT) {
2892 g_nbrightness = MIN_BRIGHT;
2893 m_bbrightdir = true;
2894 }
2895 } else {
2896 g_nbrightness += 10;
2897 if (g_nbrightness >= MAX_BRIGHT) {
2898 g_nbrightness = MAX_BRIGHT;
2899 m_bbrightdir = false;
2900 }
2901 }
2902
2903 SetScreenBrightness(g_nbrightness);
2904 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2905
2906 SetFocus(); // just in case the external program steals it....
2907 gFrame->Raise(); // And reactivate the application main
2908
2909 break;
2910 }
2911
2912 case WXK_F7:
2913 parent_frame->DoStackDown(this);
2914 break;
2915
2916 case WXK_F8:
2917 parent_frame->DoStackUp(this);
2918 break;
2919
2920#ifndef __WXOSX__
2921 case WXK_F9: {
2922 ToggleCanvasQuiltMode();
2923 break;
2924 }
2925#endif
2926
2927 case WXK_F11:
2928 parent_frame->ToggleFullScreen();
2929 b_handled = true;
2930 break;
2931
2932 case WXK_F12: {
2933 if (m_modkeys == wxMOD_ALT) {
2934 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2935 } else {
2936 ToggleChartOutlines();
2937 }
2938 break;
2939 }
2940
2941 case WXK_PAUSE: // Drop MOB
2942 parent_frame->ActivateMOB();
2943 break;
2944
2945 // NUMERIC PAD
2946 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2947 case WXK_PAGEUP: {
2948 ZoomCanvas(g_plus_minus_zoom_factor, false);
2949 break;
2950 }
2951 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2952 case WXK_PAGEDOWN: {
2953 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2954 break;
2955 }
2956 case WXK_DELETE:
2957 case WXK_BACK:
2958 if (m_bMeasure_Active) {
2959 if (m_nMeasureState > 2) {
2960 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2961 m_pMeasureRoute->m_lastMousePointIndex =
2962 m_pMeasureRoute->GetnPoints();
2963 m_nMeasureState--;
2964 gFrame->RefreshAllCanvas();
2965 } else {
2966 CancelMeasureRoute();
2967 StartMeasureRoute();
2968 }
2969 }
2970 break;
2971 default:
2972 break;
2973 }
2974
2975 if (event.GetKeyCode() < 128) // ascii
2976 {
2977 int key_char = event.GetKeyCode();
2978
2979 // Handle both QWERTY and AZERTY keyboard separately for a few control
2980 // codes
2981 if (!g_b_assume_azerty) {
2982#ifdef __WXMAC__
2983 if (g_benable_rotate) {
2984 switch (key_char) {
2985 // On other platforms these are handled in OnKeyChar, which
2986 // (apparently) works better in some locales. On OS X it is better
2987 // to handle them here, since pressing Alt (which should change the
2988 // rotation speed) changes the key char and so prevents the keys
2989 // from working.
2990 case ']':
2991 RotateCanvas(1);
2992 b_handled = true;
2993 break;
2994
2995 case '[':
2996 RotateCanvas(-1);
2997 b_handled = true;
2998 break;
2999
3000 case '\\':
3001 DoRotateCanvas(0);
3002 b_handled = true;
3003 break;
3004 }
3005 }
3006#endif
3007 } else { // AZERTY
3008 switch (key_char) {
3009 case 43:
3010 ZoomCanvas(g_plus_minus_zoom_factor, false);
3011 break;
3012
3013 case 54: // '-' alpha/num pad
3014 // case 56: // '_' alpha/num pad
3015 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
3016 break;
3017 }
3018 }
3019
3020#ifdef __WXOSX__
3021 // Ctrl+Cmd+F toggles fullscreen on macOS
3022 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
3023 m_modkeys & wxMOD_RAW_CONTROL) {
3024 parent_frame->ToggleFullScreen();
3025 return;
3026 }
3027#endif
3028
3029 if (event.ControlDown()) key_char -= 64;
3030
3031 if (key_char >= '0' && key_char <= '9')
3032 SetGroupIndex(key_char - '0');
3033 else
3034
3035 switch (key_char) {
3036 case 'A':
3037 SetShowENCAnchor(!GetShowENCAnchor());
3038 ReloadVP();
3039
3040 break;
3041
3042 case 'C':
3043 parent_frame->ToggleColorScheme();
3044 break;
3045
3046 case 'D': {
3047 int x, y;
3048 event.GetPosition(&x, &y);
3049 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3050 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3051 // First find out what kind of chart is being used
3052 if (!pPopupDetailSlider) {
3053 if (VPoint.b_quilt) {
3054 if (m_pQuilt) {
3055 if (m_pQuilt->GetChartAtPix(
3056 VPoint,
3057 wxPoint(
3058 x, y))) // = null if no chart loaded for this point
3059 {
3060 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3061 ->GetChartType();
3062 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3063 ->GetChartFamily();
3064 }
3065 }
3066 } else {
3067 if (m_singleChart) {
3068 ChartType = m_singleChart->GetChartType();
3069 ChartFam = m_singleChart->GetChartFamily();
3070 }
3071 }
3072 // If a charttype is found show the popupslider
3073 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3074 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3075 pPopupDetailSlider = new PopUpDSlide(
3076 this, -1, ChartType, ChartFam,
3077 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3078 wxDefaultSize, wxSIMPLE_BORDER, _T(""));
3079 if (pPopupDetailSlider) pPopupDetailSlider->Show();
3080 }
3081 } else //( !pPopupDetailSlider ) close popupslider
3082 {
3083 if (pPopupDetailSlider) pPopupDetailSlider->Close();
3084 pPopupDetailSlider = NULL;
3085 }
3086 break;
3087 }
3088
3089 case 'E':
3090 m_nmea_log->Show();
3091 m_nmea_log->Raise();
3092 break;
3093
3094 case 'L':
3095 SetShowENCLights(!GetShowENCLights());
3096 ReloadVP();
3097
3098 break;
3099
3100 case 'M':
3101 if (event.ShiftDown())
3102 m_bMeasure_DistCircle = true;
3103 else
3104 m_bMeasure_DistCircle = false;
3105
3106 StartMeasureRoute();
3107 break;
3108
3109 case 'N':
3110 if (g_bInlandEcdis && ps52plib) {
3111 SetENCDisplayCategory((_DisCat)STANDARD);
3112 }
3113 break;
3114
3115 case 'O':
3116 ToggleChartOutlines();
3117 break;
3118
3119 case 'Q':
3120 ToggleCanvasQuiltMode();
3121 break;
3122
3123 case 'P':
3124 parent_frame->ToggleTestPause();
3125 break;
3126 case 'R':
3127 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3128 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3129 g_iNavAidRadarRingsNumberVisible = 1;
3130 else if (!g_bNavAidRadarRingsShown &&
3131 g_iNavAidRadarRingsNumberVisible == 1)
3132 g_iNavAidRadarRingsNumberVisible = 0;
3133 break;
3134 case 'S':
3135 SetShowENCDepth(!m_encShowDepth);
3136 ReloadVP();
3137 break;
3138
3139 case 'T':
3140 SetShowENCText(!GetShowENCText());
3141 ReloadVP();
3142 break;
3143
3144 case 'U':
3145 SetShowENCDataQual(!GetShowENCDataQual());
3146 ReloadVP();
3147 break;
3148
3149 case 'V':
3150 m_bShowNavobjects = !m_bShowNavobjects;
3151 Refresh(true);
3152 break;
3153
3154 case 'W': // W Toggle CPA alarm
3155 ToggleCPAWarn();
3156
3157 break;
3158
3159 case 1: // Ctrl A
3160 TogglebFollow();
3161
3162 break;
3163
3164 case 2: // Ctrl B
3165 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3166 break;
3167
3168 case 13: // Ctrl M // Drop Marker at cursor
3169 {
3170 if (event.ControlDown()) gFrame->DropMarker(false);
3171 break;
3172 }
3173
3174 case 14: // Ctrl N - Activate next waypoint in a route
3175 {
3176 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3177 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3178 if ((indexActive + 1) <= r->GetnPoints()) {
3179 g_pRouteMan->ActivateNextPoint(r, true);
3180 InvalidateGL();
3181 Refresh(false);
3182 }
3183 }
3184 break;
3185 }
3186
3187 case 15: // Ctrl O - Drop Marker at boat's position
3188 {
3189 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3190 break;
3191 }
3192
3193 case 32: // Special needs use space bar
3194 {
3195 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3196 break;
3197 }
3198
3199 case -32: // Ctrl Space // Drop MOB
3200 {
3201 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3202
3203 break;
3204 }
3205
3206 case -20: // Ctrl ,
3207 {
3208 parent_frame->DoSettings();
3209 break;
3210 }
3211 case 17: // Ctrl Q
3212 parent_frame->Close();
3213 return;
3214
3215 case 18: // Ctrl R
3216 StartRoute();
3217 return;
3218
3219 case 20: // Ctrl T
3220 if (NULL == pGoToPositionDialog) // There is one global instance of
3221 // the Go To Position Dialog
3222 pGoToPositionDialog = new GoToPositionDialog(this);
3223 pGoToPositionDialog->SetCanvas(this);
3224 pGoToPositionDialog->Show();
3225 break;
3226
3227 case 25: // Ctrl Y
3228 if (undo->AnythingToRedo()) {
3229 undo->RedoNextAction();
3230 InvalidateGL();
3231 Refresh(false);
3232 }
3233 break;
3234
3235 case 26:
3236 if (event.ShiftDown()) { // Shift-Ctrl-Z
3237 if (undo->AnythingToRedo()) {
3238 undo->RedoNextAction();
3239 InvalidateGL();
3240 Refresh(false);
3241 }
3242 } else { // Ctrl Z
3243 if (undo->AnythingToUndo()) {
3244 undo->UndoLastAction();
3245 InvalidateGL();
3246 Refresh(false);
3247 }
3248 }
3249 break;
3250
3251 case 27:
3252 // Generic break
3253 if (m_bMeasure_Active) {
3254 CancelMeasureRoute();
3255
3256 SetCursor(*pCursorArrow);
3257
3258 // SurfaceToolbar();
3259 gFrame->RefreshAllCanvas();
3260 }
3261
3262 if (m_routeState) // creating route?
3263 {
3264 FinishRoute();
3265 // SurfaceToolbar();
3266 InvalidateGL();
3267 Refresh(false);
3268 }
3269
3270 break;
3271
3272 case 7: // Ctrl G
3273 switch (gamma_state) {
3274 case (0):
3275 r_gamma_mult = 0;
3276 g_gamma_mult = 1;
3277 b_gamma_mult = 0;
3278 gamma_state = 1;
3279 break;
3280 case (1):
3281 r_gamma_mult = 1;
3282 g_gamma_mult = 0;
3283 b_gamma_mult = 0;
3284 gamma_state = 2;
3285 break;
3286 case (2):
3287 r_gamma_mult = 1;
3288 g_gamma_mult = 1;
3289 b_gamma_mult = 1;
3290 gamma_state = 0;
3291 break;
3292 }
3293 SetScreenBrightness(g_nbrightness);
3294
3295 break;
3296
3297 case 9: // Ctrl I
3298 if (event.ControlDown()) {
3299 m_bShowCompassWin = !m_bShowCompassWin;
3300 SetShowGPSCompassWindow(m_bShowCompassWin);
3301 Refresh(false);
3302 }
3303 break;
3304
3305 default:
3306 break;
3307
3308 } // switch
3309 }
3310
3311 // Allow OnKeyChar to catch the key events too.
3312 if (!b_handled) {
3313 event.Skip();
3314 }
3315}
3316
3317void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3318 if (SendKeyEventToPlugins(event))
3319 return; // PlugIn did something, and does not want the canvas to do
3320 // anything else
3321
3322 switch (event.GetKeyCode()) {
3323 case WXK_TAB:
3324 parent_frame->SwitchKBFocus(this);
3325 break;
3326
3327 case WXK_LEFT:
3328 case WXK_RIGHT:
3329 m_panx = 0;
3330 if (!m_pany) m_panspeed = 0;
3331 break;
3332
3333 case WXK_UP:
3334 case WXK_DOWN:
3335 m_pany = 0;
3336 if (!m_panx) m_panspeed = 0;
3337 break;
3338
3339 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3340 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3341 case WXK_PAGEUP:
3342 case WXK_PAGEDOWN:
3343 if (m_mustmove) DoMovement(m_mustmove);
3344
3345 m_zoom_factor = 1;
3346 break;
3347
3348 case WXK_ALT:
3349 m_modkeys &= ~wxMOD_ALT;
3350#ifdef OCPN_ALT_MENUBAR
3351#ifndef __WXOSX__
3352 // If the permanent menu bar is disabled, and we are not in the middle of
3353 // another key combo, then show the menu bar temporarily when Alt is
3354 // released (or hide it if already visible).
3355 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3356 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3357 parent_frame->ApplyGlobalSettings(false);
3358 }
3359 m_bMayToggleMenuBar = true;
3360#endif
3361#endif
3362 break;
3363
3364 case WXK_CONTROL:
3365 m_modkeys &= ~wxMOD_CONTROL;
3366 break;
3367 }
3368
3369 if (event.GetKeyCode() < 128) // ascii
3370 {
3371 int key_char = event.GetKeyCode();
3372
3373 // Handle both QWERTY and AZERTY keyboard separately for a few control
3374 // codes
3375 if (!g_b_assume_azerty) {
3376 switch (key_char) {
3377 case '+':
3378 case '=':
3379 case '-':
3380 case '_':
3381 case 54:
3382 case 56: // '_' alpha/num pad
3383 DoMovement(m_mustmove);
3384
3385 // m_zoom_factor = 1;
3386 break;
3387 case '[':
3388 case ']':
3389 DoMovement(m_mustmove);
3390 m_rotation_speed = 0;
3391 break;
3392 }
3393 } else {
3394 switch (key_char) {
3395 case 43:
3396 case 54: // '-' alpha/num pad
3397 case 56: // '_' alpha/num pad
3398 DoMovement(m_mustmove);
3399
3400 m_zoom_factor = 1;
3401 break;
3402 }
3403 }
3404 }
3405 event.Skip();
3406}
3407
3408void ChartCanvas::ToggleChartOutlines(void) {
3409 m_bShowOutlines = !m_bShowOutlines;
3410
3411 Refresh(false);
3412
3413#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3414 // needs a full refresh
3415 if (g_bopengl) InvalidateGL();
3416#endif
3417}
3418
3419void ChartCanvas::ToggleLookahead() {
3420 m_bLookAhead = !m_bLookAhead;
3421 m_OSoffsetx = 0; // center ownship
3422 m_OSoffsety = 0;
3423}
3424
3425void ChartCanvas::SetUpMode(int mode) {
3426 m_upMode = mode;
3427
3428 if (mode != NORTH_UP_MODE) {
3429 // Stuff the COGAvg table in case COGUp is selected
3430 double stuff = 0;
3431 if (!std::isnan(gCog)) stuff = gCog;
3432
3433 if (g_COGAvgSec > 0) {
3434 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3435 }
3436 g_COGAvg = stuff;
3437 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3438 } else {
3439 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3440 SetVPRotation(GetVPSkew());
3441 else
3442 SetVPRotation(0); /* reset to north up */
3443 }
3444
3445 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3446 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3447
3448 UpdateGPSCompassStatusBox(true);
3449 gFrame->DoChartUpdate();
3450}
3451
3452bool ChartCanvas::DoCanvasCOGSet(void) {
3453 if (GetUpMode() == NORTH_UP_MODE) return false;
3454 double cog_use = g_COGAvg;
3455 if (g_btenhertz) cog_use = gCog;
3456
3457 double rotation = 0;
3458 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3459 rotation = -gHdt * PI / 180.;
3460 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3461 rotation = -cog_use * PI / 180.;
3462
3463 SetVPRotation(rotation);
3464 return true;
3465}
3466
3467double easeOutCubic(double t) {
3468 // Starts quickly and slows down toward the end
3469 return 1.0 - pow(1.0 - t, 3.0);
3470}
3471
3472void ChartCanvas::StartChartDragInertia() {
3473 //
3474 // printf("\nStart ChartDragInertia\n");
3475 m_bChartDragging = false;
3476
3477 // Set some parameters
3478 m_chart_drag_inertia_time = 750; // msec
3479 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3480 m_last_elapsed = 0;
3481
3482 // Calculate ending drag velocity
3483 size_t n_vel = 10;
3484 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3485 int xacc = 0;
3486 int yacc = 0;
3487 double tacc = 0;
3488 size_t length = m_drag_vec_t.size();
3489 for (size_t i = 0; i < n_vel; i++) {
3490 xacc += m_drag_vec_x.at(length - 1 - i);
3491 yacc += m_drag_vec_y.at(length - 1 - i);
3492 tacc += m_drag_vec_t.at(length - 1 - i);
3493 // printf("%d %g\n", xacc, tacc);
3494 }
3495 m_chart_drag_velocity_x = xacc / tacc;
3496 m_chart_drag_velocity_y = yacc / tacc;
3497
3498 m_chart_drag_inertia_active = true;
3499
3500 // First callback as fast as possible.
3501 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3502
3503 // printf(" Drag parms %d %d %g\n", m_chart_drag_total_x,
3504 // m_chart_drag_total_y, m_chart_drag_total_time);
3505}
3506
3507void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3508 if (!m_chart_drag_inertia_active) return;
3509
3510 // Calculate time fraction from 0..1
3511 wxLongLong now = wxGetLocalTimeMillis();
3512 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3513 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3514 if (t > 1.0) t = 1.0;
3515 double e = 1.0 - easeOutCubic(t); // 0..1
3516
3517 double dx =
3518 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3519 double dy =
3520 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3521
3522 // double distance = pow((pow(dx, 2) + pow(dy, 2)), 0.5);
3523 // printf(" %5g %5g %5g %5g pix/sec\n", elapsed,
3524 // elapsed - m_last_elapsed, distance, distance * 1000 / elapsed);
3525
3526 m_last_elapsed = elapsed;
3527
3528 // Ensure that target destination lies on whole-pixel boundary
3529 // This allows the render engine to use a faster FBO copy method for drawing
3530 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3531 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3532 double inertia_lat, inertia_lon;
3533 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3534 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3535
3536 Refresh(false);
3537
3538 // Stop condition
3539 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3540 m_chart_drag_inertia_timer.Stop();
3541 m_chart_drag_inertia_active = false;
3542
3543 // Disable chart pan movement logic
3544 m_target_lat = GetVP().clat;
3545 m_target_lon = GetVP().clon;
3546 m_pan_drag.x = m_pan_drag.y = 0;
3547 m_panx = m_pany = 0;
3548
3549 } else {
3550 int target_redraw_interval = 40; // msec
3551 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3552 }
3553}
3554
3555void ChartCanvas::StopMovement() {
3556 m_panx = m_pany = 0;
3557 m_panspeed = 0;
3558 m_zoom_factor = 1;
3559 m_rotation_speed = 0;
3560 m_mustmove = 0;
3561#if 0
3562#if !defined(__WXGTK__) && !defined(__WXQT__)
3563 SetFocus();
3564 gFrame->Raise();
3565#endif
3566#endif
3567}
3568
3569/* instead of integrating in timer callbacks
3570 (which do not always get called fast enough)
3571 we can perform the integration of movement
3572 at each render frame based on the time change */
3573bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3574 // Start/restart the stop movement timer
3575 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3576
3577 if (!pMovementTimer->IsRunning()) {
3578 // printf("timer not running, starting\n");
3579 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3580 }
3581
3582 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3583 // already moving, gets called again because of key-repeat event
3584 return false;
3585 }
3586
3587 m_last_movement_time = wxDateTime::UNow();
3588
3589 return true;
3590}
3591void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3592 int nstep) {
3593 // Save the target
3594 m_target_lat = target_lat;
3595 m_target_lon = target_lon;
3596
3597 // Save the start point
3598 m_start_lat = GetVP().clat;
3599 m_start_lon = GetVP().clon;
3600
3601 m_VPMovementTimer.Start(1, true); // oneshot
3602 m_timed_move_vp_active = true;
3603 m_stvpc = 0;
3604 m_timedVP_step = nstep;
3605}
3606
3607void ChartCanvas::DoTimedMovementVP() {
3608 if (!m_timed_move_vp_active) return; // not active
3609 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3610 StopMovement();
3611 return;
3612 }
3613 // Stop condition
3614 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3615 double d2 =
3616 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3617 d2 = pow(d2, 0.5);
3618
3619 if (d2 < one_pix) {
3620 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3621 StopMovementVP();
3622 return;
3623 }
3624
3625 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3626 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3627 // StopMovementVP();
3628 // return;
3629 // }
3630
3631 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3632 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3633
3634 m_run_lat = new_lat;
3635 m_run_lon = new_lon;
3636
3637 // printf(" Timed\n");
3638 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3639}
3640
3641void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3642
3643void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3644
3645void ChartCanvas::StartTimedMovementTarget() {}
3646
3647void ChartCanvas::DoTimedMovementTarget() {}
3648
3649void ChartCanvas::StopMovementTarget() {}
3650
3651void ChartCanvas::DoTimedMovement() {
3652 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3653 !m_rotation_speed)
3654 return; /* not moving */
3655
3656 wxDateTime now = wxDateTime::UNow();
3657 long dt = 0;
3658 if (m_last_movement_time.IsValid())
3659 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3660
3661 m_last_movement_time = now;
3662
3663 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3664 dt = 500;
3665
3666 DoMovement(dt);
3667}
3668
3670 /* if we get here quickly assume 1ms so that some movement occurs */
3671 if (dt == 0) dt = 1;
3672
3673 m_mustmove -= dt;
3674 if (m_mustmove < 0) m_mustmove = 0;
3675
3676 if (m_pan_drag.x || m_pan_drag.y) {
3677 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3678 m_pan_drag.x = m_pan_drag.y = 0;
3679 }
3680
3681 if (m_panx || m_pany) {
3682 const double slowpan = .1, maxpan = 2;
3683 if (m_modkeys == wxMOD_ALT)
3684 m_panspeed = slowpan;
3685 else {
3686 m_panspeed += (double)dt / 500; /* apply acceleration */
3687 m_panspeed = wxMin(maxpan, m_panspeed);
3688 }
3689 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3690 }
3691
3692 if (m_zoom_factor != 1) {
3693 double alpha = 400, beta = 1.5;
3694 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3695
3696 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3697
3698 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3699
3700 // Try to hit the zoom target exactly.
3701 // if(m_wheelzoom_stop_oneshot > 0)
3702 {
3703 if (zoom_factor > 1) {
3704 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3705 zoom_factor = VPoint.chart_scale / m_zoom_target;
3706 }
3707
3708 else if (zoom_factor < 1) {
3709 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3710 zoom_factor = VPoint.chart_scale / m_zoom_target;
3711 }
3712 }
3713
3714 if (fabs(zoom_factor - 1) > 1e-4) {
3715 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3716 } else {
3717 StopMovement();
3718 }
3719
3720 if (m_wheelzoom_stop_oneshot > 0) {
3721 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3722 m_wheelzoom_stop_oneshot = 0;
3723 StopMovement();
3724 }
3725
3726 // Don't overshoot the zoom target.
3727 if (zoom_factor > 1) {
3728 if (VPoint.chart_scale <= m_zoom_target) {
3729 m_wheelzoom_stop_oneshot = 0;
3730 StopMovement();
3731 }
3732 } else if (zoom_factor < 1) {
3733 if (VPoint.chart_scale >= m_zoom_target) {
3734 m_wheelzoom_stop_oneshot = 0;
3735 StopMovement();
3736 }
3737 }
3738 }
3739 }
3740
3741 if (m_rotation_speed) { /* in degrees per second */
3742 double speed = m_rotation_speed;
3743 if (m_modkeys == wxMOD_ALT) speed /= 10;
3744 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3745 }
3746}
3747
3748void ChartCanvas::SetColorScheme(ColorScheme cs) {
3749 SetAlertString(_T(""));
3750
3751 // Setup ownship image pointers
3752 switch (cs) {
3753 case GLOBAL_COLOR_SCHEME_DAY:
3754 m_pos_image_red = &m_os_image_red_day;
3755 m_pos_image_grey = &m_os_image_grey_day;
3756 m_pos_image_yellow = &m_os_image_yellow_day;
3757 m_pos_image_user = m_pos_image_user_day;
3758 m_pos_image_user_grey = m_pos_image_user_grey_day;
3759 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3760 m_cTideBitmap = m_bmTideDay;
3761 m_cCurrentBitmap = m_bmCurrentDay;
3762
3763 break;
3764 case GLOBAL_COLOR_SCHEME_DUSK:
3765 m_pos_image_red = &m_os_image_red_dusk;
3766 m_pos_image_grey = &m_os_image_grey_dusk;
3767 m_pos_image_yellow = &m_os_image_yellow_dusk;
3768 m_pos_image_user = m_pos_image_user_dusk;
3769 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3770 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3771 m_cTideBitmap = m_bmTideDusk;
3772 m_cCurrentBitmap = m_bmCurrentDusk;
3773 break;
3774 case GLOBAL_COLOR_SCHEME_NIGHT:
3775 m_pos_image_red = &m_os_image_red_night;
3776 m_pos_image_grey = &m_os_image_grey_night;
3777 m_pos_image_yellow = &m_os_image_yellow_night;
3778 m_pos_image_user = m_pos_image_user_night;
3779 m_pos_image_user_grey = m_pos_image_user_grey_night;
3780 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3781 m_cTideBitmap = m_bmTideNight;
3782 m_cCurrentBitmap = m_bmCurrentNight;
3783 break;
3784 default:
3785 m_pos_image_red = &m_os_image_red_day;
3786 m_pos_image_grey = &m_os_image_grey_day;
3787 m_pos_image_yellow = &m_os_image_yellow_day;
3788 m_pos_image_user = m_pos_image_user_day;
3789 m_pos_image_user_grey = m_pos_image_user_grey_day;
3790 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3791 m_cTideBitmap = m_bmTideDay;
3792 m_cCurrentBitmap = m_bmCurrentDay;
3793 break;
3794 }
3795
3796 CreateDepthUnitEmbossMaps(cs);
3797 CreateOZEmbossMapData(cs);
3798
3799 // Set up fog effect base color
3800 m_fog_color = wxColor(
3801 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3802 float dim = 1.0;
3803 switch (cs) {
3804 case GLOBAL_COLOR_SCHEME_DUSK:
3805 dim = 0.5;
3806 break;
3807 case GLOBAL_COLOR_SCHEME_NIGHT:
3808 dim = 0.25;
3809 break;
3810 default:
3811 break;
3812 }
3813 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3814 m_fog_color.Blue() * dim);
3815
3816 // Really dark
3817#if 0
3818 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3819 SetBackgroundColour( wxColour(0,0,0) );
3820
3821 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3822 }
3823 else{
3824 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3825#ifndef __WXMAC__
3826 SetBackgroundColour( wxNullColour );
3827#endif
3828 }
3829#endif
3830
3831 // UpdateToolbarColorScheme(cs);
3832
3833 m_Piano->SetColorScheme(cs);
3834
3835 m_Compass->SetColorScheme(cs);
3836
3837 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3838
3839 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3840
3841 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3842 if (m_notification_button) {
3843 m_notification_button->SetColorScheme(cs);
3844 }
3845
3846#ifdef ocpnUSE_GL
3847 if (g_bopengl && m_glcc) {
3848 m_glcc->SetColorScheme(cs);
3849 g_glTextureManager->ClearAllRasterTextures();
3850 // m_glcc->FlushFBO();
3851 }
3852#endif
3853 SetbTCUpdate(true); // force re-render of tide/current locators
3854 m_brepaint_piano = true;
3855
3856 ReloadVP();
3857
3858 m_cs = cs;
3859}
3860
3861wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3862 wxImage img = Bitmap.ConvertToImage();
3863 int sx = img.GetWidth();
3864 int sy = img.GetHeight();
3865
3866 wxImage new_img(img);
3867
3868 for (int i = 0; i < sx; i++) {
3869 for (int j = 0; j < sy; j++) {
3870 if (!img.IsTransparent(i, j)) {
3871 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3872 (unsigned char)(img.GetGreen(i, j) * factor),
3873 (unsigned char)(img.GetBlue(i, j) * factor));
3874 }
3875 }
3876 }
3877
3878 wxBitmap ret = wxBitmap(new_img);
3879
3880 return ret;
3881}
3882
3883void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3884 int max) {
3885 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3886 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3887
3888 if (!m_pBrightPopup) {
3889 // Calculate size
3890 int x, y;
3891 GetTextExtent(_T("MAX"), &x, &y, NULL, NULL, pfont);
3892
3893 m_pBrightPopup = new TimedPopupWin(this, 3);
3894
3895 m_pBrightPopup->SetSize(x, y);
3896 m_pBrightPopup->Move(120, 120);
3897 }
3898
3899 int bmpsx = m_pBrightPopup->GetSize().x;
3900 int bmpsy = m_pBrightPopup->GetSize().y;
3901
3902 wxBitmap bmp(bmpsx, bmpsx);
3903 wxMemoryDC mdc(bmp);
3904
3905 mdc.SetTextForeground(GetGlobalColor(_T("GREEN4")));
3906 mdc.SetBackground(wxBrush(GetGlobalColor(_T("UINFD"))));
3907 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3908 mdc.SetBrush(wxBrush(GetGlobalColor(_T("UINFD"))));
3909 mdc.Clear();
3910
3911 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3912
3913 mdc.SetFont(*pfont);
3914 wxString val;
3915
3916 if (brightness == max)
3917 val = _T("MAX");
3918 else if (brightness == min)
3919 val = _T("MIN");
3920 else
3921 val.Printf(_T("%3d"), brightness);
3922
3923 mdc.DrawText(val, 0, 0);
3924
3925 mdc.SelectObject(wxNullBitmap);
3926
3927 m_pBrightPopup->SetBitmap(bmp);
3928 m_pBrightPopup->Show();
3929 m_pBrightPopup->Refresh();
3930}
3931
3932void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3933 m_b_rot_hidef = true;
3934 ReloadVP();
3935}
3936
3937void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3938 if (!g_bRollover) return;
3939
3940 bool b_need_refresh = false;
3941
3942 wxSize win_size = GetSize() * m_displayScale;
3943 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3944
3945 // Handle the AIS Rollover Window first
3946 bool showAISRollover = false;
3947 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3948 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3949 SelectItem *pFind = pSelectAIS->FindSelection(
3950 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3951 if (pFind) {
3952 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3953 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3954
3955 if (ptarget) {
3956 showAISRollover = true;
3957
3958 if (NULL == m_pAISRolloverWin) {
3959 m_pAISRolloverWin = new RolloverWin(this);
3960 m_pAISRolloverWin->IsActive(false);
3961 b_need_refresh = true;
3962 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3963 m_AISRollover_MMSI != FoundAIS_MMSI) {
3964 // Sometimes the mouse moves fast enough to get over a new AIS
3965 // target before the one-shot has fired to remove the old target.
3966 // Result: wrong target data is shown.
3967 // Detect this case,close the existing rollover ASAP, and restart
3968 // the timer.
3969 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3970 m_pAISRolloverWin->IsActive(false);
3971 m_AISRollover_MMSI = 0;
3972 Refresh();
3973 return;
3974 }
3975
3976 m_AISRollover_MMSI = FoundAIS_MMSI;
3977
3978 if (!m_pAISRolloverWin->IsActive()) {
3979 wxString s = ptarget->GetRolloverString();
3980 m_pAISRolloverWin->SetString(s);
3981
3982 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3983 AIS_ROLLOVER, win_size);
3984 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3985 m_pAISRolloverWin->IsActive(true);
3986 b_need_refresh = true;
3987 }
3988 }
3989 } else {
3990 m_AISRollover_MMSI = 0;
3991 showAISRollover = false;
3992 }
3993 }
3994
3995 // Maybe turn the rollover off
3996 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3997 m_pAISRolloverWin->IsActive(false);
3998 m_AISRollover_MMSI = 0;
3999 b_need_refresh = true;
4000 }
4001
4002 // Now the Route info rollover
4003 // Show the route segment info
4004 bool showRouteRollover = false;
4005
4006 if (NULL == m_pRolloverRouteSeg) {
4007 // Get a list of all selectable sgements, and search for the first
4008 // visible segment as the rollover target.
4009
4010 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4011 SelectableItemList SelList = pSelect->FindSelectionList(
4012 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
4013 wxSelectableItemListNode *node = SelList.GetFirst();
4014 while (node) {
4015 SelectItem *pFindSel = node->GetData();
4016
4017 Route *pr = (Route *)pFindSel->m_pData3; // candidate
4018
4019 if (pr && pr->IsVisible()) {
4020 m_pRolloverRouteSeg = pFindSel;
4021 showRouteRollover = true;
4022
4023 if (NULL == m_pRouteRolloverWin) {
4024 m_pRouteRolloverWin = new RolloverWin(this, 10);
4025 m_pRouteRolloverWin->IsActive(false);
4026 }
4027
4028 if (!m_pRouteRolloverWin->IsActive()) {
4029 wxString s;
4030 RoutePoint *segShow_point_a =
4031 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4032 RoutePoint *segShow_point_b =
4033 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4034
4035 double brg, dist;
4036 DistanceBearingMercator(
4037 segShow_point_b->m_lat, segShow_point_b->m_lon,
4038 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4039
4040 if (!pr->m_bIsInLayer)
4041 s.Append(_("Route") + _T(": "));
4042 else
4043 s.Append(_("Layer Route: "));
4044
4045 if (pr->m_RouteNameString.IsEmpty())
4046 s.Append(_("(unnamed)"));
4047 else
4048 s.Append(pr->m_RouteNameString);
4049
4050 s << _T("\n") << _("Total Length: ")
4051 << FormatDistanceAdaptive(pr->m_route_length) << _T("\n")
4052 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4053 << segShow_point_b->GetName() << _T("\n");
4054
4055 if (g_bShowTrue)
4056 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4057 (int)floor(brg + 0.5), 0x00B0);
4058 if (g_bShowMag) {
4059 double latAverage =
4060 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4061 double lonAverage =
4062 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4063 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4064
4065 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4066 (int)floor(varBrg + 0.5), 0x00B0);
4067 }
4068
4069 s << FormatDistanceAdaptive(dist);
4070
4071 // Compute and display cumulative distance from route start point to
4072 // current leg end point and RNG,TTG,ETA from ship to current leg end
4073 // point for active route
4074 double shiptoEndLeg = 0.;
4075 bool validActive = false;
4076 if (pr->IsActive() &&
4077 pr->pRoutePointList->GetFirst()->GetData()->m_bIsActive)
4078 validActive = true;
4079
4080 if (segShow_point_a != pr->pRoutePointList->GetFirst()->GetData()) {
4081 wxRoutePointListNode *node =
4082 (pr->pRoutePointList)->GetFirst()->GetNext();
4083 RoutePoint *prp;
4084 float dist_to_endleg = 0;
4085 wxString t;
4086
4087 while (node) {
4088 prp = node->GetData();
4089 if (validActive)
4090 shiptoEndLeg += prp->m_seg_len;
4091 else if (prp->m_bIsActive)
4092 validActive = true;
4093 dist_to_endleg += prp->m_seg_len;
4094 if (prp->IsSame(segShow_point_a)) break;
4095 node = node->GetNext();
4096 }
4097 s << _T(" (+") << FormatDistanceAdaptive(dist_to_endleg) << _T(")");
4098 }
4099 // write from ship to end selected leg point data if the route is
4100 // active
4101 if (validActive) {
4102 s << _T("\n") << _("From Ship To") << _T(" ")
4103 << segShow_point_b->GetName() << _T("\n");
4104 shiptoEndLeg +=
4105 g_pRouteMan
4106 ->GetCurrentRngToActivePoint(); // add distance from ship
4107 // to active point
4108 shiptoEndLeg +=
4109 segShow_point_b
4110 ->m_seg_len; // add the lenght of the selected leg
4111 s << FormatDistanceAdaptive(shiptoEndLeg);
4112 // ensure sog/cog are valid and vmg is positive to keep data
4113 // coherent
4114 double vmg = 0.;
4115 if (!std::isnan(gCog) && !std::isnan(gSog))
4116 vmg = gSog *
4117 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4118 PI / 180.);
4119 if (vmg > 0.) {
4120 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4121 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4122 s << _T(" - ")
4123 << wxString(ttg_sec > SECONDS_PER_DAY
4124 ? ttg_span.Format(_("%Dd %H:%M"))
4125 : ttg_span.Format(_("%H:%M")));
4126 wxDateTime dtnow, eta;
4127 eta = dtnow.SetToCurrent().Add(ttg_span);
4128 s << _T(" - ") << eta.Format(_T("%b")).Mid(0, 4)
4129 << eta.Format(_T(" %d %H:%M"));
4130 } else
4131 s << _T(" ---- ----");
4132 }
4133 m_pRouteRolloverWin->SetString(s);
4134
4135 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4136 LEG_ROLLOVER, win_size);
4137 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4138 m_pRouteRolloverWin->IsActive(true);
4139 b_need_refresh = true;
4140 showRouteRollover = true;
4141 break;
4142 }
4143 } else
4144 node = node->GetNext();
4145 }
4146 } else {
4147 // Is the cursor still in select radius, and not timed out?
4148 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4149 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4150 m_pRolloverRouteSeg))
4151 showRouteRollover = false;
4152 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4153 showRouteRollover = false;
4154 else
4155 showRouteRollover = true;
4156 }
4157
4158 // If currently creating a route, do not show this rollover window
4159 if (m_routeState) showRouteRollover = false;
4160
4161 // Similar for AIS target rollover window
4162 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4163 showRouteRollover = false;
4164
4165 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4166 !showRouteRollover) {
4167 m_pRouteRolloverWin->IsActive(false);
4168 m_pRolloverRouteSeg = NULL;
4169 m_pRouteRolloverWin->Destroy();
4170 m_pRouteRolloverWin = NULL;
4171 b_need_refresh = true;
4172 } else if (m_pRouteRolloverWin && showRouteRollover) {
4173 m_pRouteRolloverWin->IsActive(true);
4174 b_need_refresh = true;
4175 }
4176
4177 // Now the Track info rollover
4178 // Show the track segment info
4179 bool showTrackRollover = false;
4180
4181 if (NULL == m_pRolloverTrackSeg) {
4182 // Get a list of all selectable sgements, and search for the first
4183 // visible segment as the rollover target.
4184
4185 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4186 SelectableItemList SelList = pSelect->FindSelectionList(
4187 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4188 wxSelectableItemListNode *node = SelList.GetFirst();
4189 while (node) {
4190 SelectItem *pFindSel = node->GetData();
4191
4192 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4193
4194 if (pt && pt->IsVisible()) {
4195 m_pRolloverTrackSeg = pFindSel;
4196 showTrackRollover = true;
4197
4198 if (NULL == m_pTrackRolloverWin) {
4199 m_pTrackRolloverWin = new RolloverWin(this, 10);
4200 m_pTrackRolloverWin->IsActive(false);
4201 }
4202
4203 if (!m_pTrackRolloverWin->IsActive()) {
4204 wxString s;
4205 TrackPoint *segShow_point_a =
4206 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4207 TrackPoint *segShow_point_b =
4208 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4209
4210 double brg, dist;
4211 DistanceBearingMercator(
4212 segShow_point_b->m_lat, segShow_point_b->m_lon,
4213 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4214
4215 if (!pt->m_bIsInLayer)
4216 s.Append(_("Track") + _T(": "));
4217 else
4218 s.Append(_("Layer Track: "));
4219
4220 if (pt->GetName().IsEmpty())
4221 s.Append(_("(unnamed)"));
4222 else
4223 s.Append(pt->GetName());
4224 double tlenght = pt->Length();
4225 s << _T("\n") << _("Total Track: ")
4226 << FormatDistanceAdaptive(tlenght);
4227 if (pt->GetLastPoint()->GetTimeString() &&
4228 pt->GetPoint(0)->GetTimeString()) {
4229 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4230 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4231 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4232 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4233 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4234 s << wxString::Format(_T(" %.1f "), (float)(tlenght / htime))
4235 << getUsrSpeedUnit();
4236 s << wxString(htime > 24. ? ttime.Format(_T(" %Dd %H:%M"))
4237 : ttime.Format(_T(" %H:%M")));
4238 }
4239 }
4240
4241 if (g_bShowTrackPointTime &&
4242 strlen(segShow_point_b->GetTimeString())) {
4243 wxString stamp = segShow_point_b->GetTimeString();
4244 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4245 if (timestamp.IsValid()) {
4246 // Format track rollover timestamp to OCPN global TZ setting
4249 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4250 }
4251 s << _T("\n") << _("Segment Created: ") << stamp;
4252 }
4253
4254 s << _T("\n");
4255 if (g_bShowTrue)
4256 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4257 0x00B0);
4258
4259 if (g_bShowMag) {
4260 double latAverage =
4261 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4262 double lonAverage =
4263 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4264 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4265
4266 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4267 0x00B0);
4268 }
4269
4270 s << FormatDistanceAdaptive(dist);
4271
4272 if (segShow_point_a->GetTimeString() &&
4273 segShow_point_b->GetTimeString()) {
4274 wxDateTime apoint = segShow_point_a->GetCreateTime();
4275 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4276 if (apoint.IsValid() && bpoint.IsValid()) {
4277 double segmentSpeed = toUsrSpeed(
4278 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4279 s << wxString::Format(_T(" %.1f "), (float)segmentSpeed)
4280 << getUsrSpeedUnit();
4281 }
4282 }
4283
4284 m_pTrackRolloverWin->SetString(s);
4285
4286 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4287 LEG_ROLLOVER, win_size);
4288 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4289 m_pTrackRolloverWin->IsActive(true);
4290 b_need_refresh = true;
4291 showTrackRollover = true;
4292 break;
4293 }
4294 } else
4295 node = node->GetNext();
4296 }
4297 } else {
4298 // Is the cursor still in select radius, and not timed out?
4299 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4300 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4301 m_pRolloverTrackSeg))
4302 showTrackRollover = false;
4303 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4304 showTrackRollover = false;
4305 else
4306 showTrackRollover = true;
4307 }
4308
4309 // Similar for AIS target rollover window
4310 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4311 showTrackRollover = false;
4312
4313 // Similar for route rollover window
4314 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4315 showTrackRollover = false;
4316
4317 // TODO We onlt show tracks on primary canvas....
4318 // if(!IsPrimaryCanvas())
4319 // showTrackRollover = false;
4320
4321 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4322 !showTrackRollover) {
4323 m_pTrackRolloverWin->IsActive(false);
4324 m_pRolloverTrackSeg = NULL;
4325 m_pTrackRolloverWin->Destroy();
4326 m_pTrackRolloverWin = NULL;
4327 b_need_refresh = true;
4328 } else if (m_pTrackRolloverWin && showTrackRollover) {
4329 m_pTrackRolloverWin->IsActive(true);
4330 b_need_refresh = true;
4331 }
4332
4333 if (b_need_refresh) Refresh();
4334}
4335
4336void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4337 if ((GetShowENCLights() || m_bsectors_shown) &&
4338 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4339 extendedSectorLegs)) {
4340 if (!m_bsectors_shown) {
4341 ReloadVP(false);
4342 m_bsectors_shown = true;
4343 }
4344 } else {
4345 if (m_bsectors_shown) {
4346 ReloadVP(false);
4347 m_bsectors_shown = false;
4348 }
4349 }
4350
4351// This is here because GTK status window update is expensive..
4352// cairo using pango rebuilds the font every time so is very
4353// inefficient
4354// Anyway, only update the status bar when this timer expires
4355#if defined(__WXGTK__) || defined(__WXQT__)
4356 {
4357 // Check the absolute range of the cursor position
4358 // There could be a window wherein the chart geoereferencing is not
4359 // valid....
4360 double cursor_lat, cursor_lon;
4361 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4362
4363 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4364 while (cursor_lon < -180.) cursor_lon += 360.;
4365
4366 while (cursor_lon > 180.) cursor_lon -= 360.;
4367
4368 SetCursorStatus(cursor_lat, cursor_lon);
4369 }
4370 }
4371#endif
4372}
4373
4374void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4375 if (!parent_frame->m_pStatusBar) return;
4376
4377 wxString s1;
4378 s1 += _T(" ");
4379 s1 += toSDMM(1, cursor_lat);
4380 s1 += _T(" ");
4381 s1 += toSDMM(2, cursor_lon);
4382
4383 if (STAT_FIELD_CURSOR_LL >= 0)
4384 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4385
4386 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4387
4388 double brg, dist;
4389 wxString sm;
4390 wxString st;
4391 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4392 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4393 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4394
4395 wxString s = st + sm;
4396 s << FormatDistanceAdaptive(dist);
4397
4398 // CUSTOMIZATION - LIVE ETA OPTION
4399 // -------------------------------------------------------
4400 // Calculate an "live" ETA based on route starting from the current
4401 // position of the boat and goes to the cursor of the mouse.
4402 // In any case, an standard ETA will be calculated with a default speed
4403 // of the boat to give an estimation of the route (in particular if GPS
4404 // is off).
4405
4406 // Display only if option "live ETA" is selected in Settings > Display >
4407 // General.
4408 if (g_bShowLiveETA) {
4409 float realTimeETA;
4410 float boatSpeed;
4411 float boatSpeedDefault = g_defaultBoatSpeed;
4412
4413 // Calculate Estimate Time to Arrival (ETA) in minutes
4414 // Check before is value not closed to zero (it will make an very big
4415 // number...)
4416 if (!std::isnan(gSog)) {
4417 boatSpeed = gSog;
4418 if (boatSpeed < 0.5) {
4419 realTimeETA = 0;
4420 } else {
4421 realTimeETA = dist / boatSpeed * 60;
4422 }
4423 } else {
4424 realTimeETA = 0;
4425 }
4426
4427 // Add space after distance display
4428 s << " ";
4429 // Display ETA
4430 s << minutesToHoursDays(realTimeETA);
4431
4432 // In any case, display also an ETA with default speed at 6knts
4433
4434 s << " [@";
4435 s << wxString::Format(_T("%d"), (int)toUsrSpeed(boatSpeedDefault, -1));
4436 s << wxString::Format(_T("%s"), getUsrSpeedUnit(-1));
4437 s << " ";
4438 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4439 s << "]";
4440 }
4441 // END OF - LIVE ETA OPTION
4442
4443 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4444}
4445
4446// CUSTOMIZATION - FORMAT MINUTES
4447// -------------------------------------------------------
4448// New function to format minutes into a more readable format:
4449// * Hours + minutes, or
4450// * Days + hours.
4451wxString minutesToHoursDays(float timeInMinutes) {
4452 wxString s;
4453
4454 if (timeInMinutes == 0) {
4455 s << "--min";
4456 }
4457
4458 // Less than 60min, keep time in minutes
4459 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4460 s << wxString::Format(_T("%d"), (int)timeInMinutes);
4461 s << "min";
4462 }
4463
4464 // Between 1h and less than 24h, display time in hours, minutes
4465 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4466 int hours;
4467 int min;
4468 hours = (int)timeInMinutes / 60;
4469 min = (int)timeInMinutes % 60;
4470
4471 if (min == 0) {
4472 s << wxString::Format(_T("%d"), hours);
4473 s << "h";
4474 } else {
4475 s << wxString::Format(_T("%d"), hours);
4476 s << "h";
4477 s << wxString::Format(_T("%d"), min);
4478 s << "min";
4479 }
4480
4481 }
4482
4483 // More than 24h, display time in days, hours
4484 else if (timeInMinutes > 24 * 60) {
4485 int days;
4486 int hours;
4487 days = (int)(timeInMinutes / 60) / 24;
4488 hours = (int)(timeInMinutes / 60) % 24;
4489
4490 if (hours == 0) {
4491 s << wxString::Format(_T("%d"), days);
4492 s << "d";
4493 } else {
4494 s << wxString::Format(_T("%d"), days);
4495 s << "d";
4496 s << wxString::Format(_T("%d"), hours);
4497 s << "h";
4498 }
4499 }
4500
4501 return s;
4502}
4503
4504// END OF CUSTOMIZATION - FORMAT MINUTES
4505// Thanks open source code ;-)
4506// -------------------------------------------------------
4507
4508void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4509 double clat, clon;
4510 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4511 *lat = clat;
4512 *lon = clon;
4513}
4514
4515void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4516 wxPoint2DDouble *r) {
4517 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4518}
4519
4521 double rlon, wxPoint2DDouble *r) {
4522 // If the Current Chart is a raster chart, and the
4523 // requested lat/long is within the boundaries of the chart,
4524 // and the VP is not rotated,
4525 // then use the embedded BSB chart georeferencing algorithm
4526 // for greater accuracy
4527 // Additionally, use chart embedded georef if the projection is TMERC
4528 // i.e. NOT MERCATOR and NOT POLYCONIC
4529
4530 // If for some reason the chart rejects the request by returning an error,
4531 // then fall back to Viewport Projection estimate from canvas parameters
4532 if (!g_bopengl && m_singleChart &&
4533 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4534 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4535 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4536 (m_singleChart->GetChartProjectionType() !=
4537 PROJECTION_TRANSVERSE_MERCATOR) &&
4538 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4539 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4540 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4541 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4542 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4543 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4544 // Cur_BSB_Ch->GetCOVRTablenPoints
4545 // ( 0 ), rlon,
4546 // rlat );
4547 // bInside = true;
4548 // if ( bInside )
4549 if (Cur_BSB_Ch) {
4550 // This is a Raster chart....
4551 // If the VP is changing, the raster chart parameters may not yet be
4552 // setup So do that before accessing the chart's embedded
4553 // georeferencing
4554 Cur_BSB_Ch->SetVPRasterParms(vp);
4555 double rpixxd, rpixyd;
4556 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4557 r->m_x = rpixxd;
4558 r->m_y = rpixyd;
4559 return;
4560 }
4561 }
4562 }
4563
4564 // if needed, use the VPoint scaling estimator,
4565 *r = vp.GetDoublePixFromLL(rlat, rlon);
4566}
4567
4568// This routine might be deleted and all of the rendering improved
4569// to have floating point accuracy
4570bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4571 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4572}
4573
4574bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4575 wxPoint *r) {
4576 wxPoint2DDouble p;
4577 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4578
4579 // some projections give nan values when invisible values (other side of
4580 // world) are requested we should stop using integer coordinates or return
4581 // false here (and test it everywhere)
4582 if (std::isnan(p.m_x)) {
4583 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4584 return false;
4585 }
4586
4587 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4588 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4589 else
4590 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4591
4592 return true;
4593}
4594
4595void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4596 double &lon) {
4597 // If the Current Chart is a raster chart, and the
4598 // requested x,y is within the boundaries of the chart,
4599 // and the VP is not rotated,
4600 // then use the embedded BSB chart georeferencing algorithm
4601 // for greater accuracy
4602 // Additionally, use chart embedded georef if the projection is TMERC
4603 // i.e. NOT MERCATOR and NOT POLYCONIC
4604
4605 // If for some reason the chart rejects the request by returning an error,
4606 // then fall back to Viewport Projection estimate from canvas parameters
4607 bool bUseVP = true;
4608
4609 if (!g_bopengl && m_singleChart &&
4610 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4611 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4612 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4613 (m_singleChart->GetChartProjectionType() !=
4614 PROJECTION_TRANSVERSE_MERCATOR) &&
4615 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4616 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4617 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4618 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4619
4620 // TODO maybe need iterative process to validate bInside
4621 // first pass is mercator, then check chart boundaries
4622
4623 if (Cur_BSB_Ch) {
4624 // This is a Raster chart....
4625 // If the VP is changing, the raster chart parameters may not yet be
4626 // setup So do that before accessing the chart's embedded
4627 // georeferencing
4628 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4629
4630 double slat, slon;
4631 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4632 lat = slat;
4633
4634 if (slon < -180.)
4635 slon += 360.;
4636 else if (slon > 180.)
4637 slon -= 360.;
4638
4639 lon = slon;
4640 bUseVP = false;
4641 }
4642 }
4643 }
4644
4645 // if needed, use the VPoint scaling estimator
4646 if (bUseVP) {
4647 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4648 }
4649}
4650
4652 StopMovement();
4653 DoZoomCanvas(factor, false);
4654 extendedSectorLegs.clear();
4655}
4656
4657void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4658 bool stoptimer) {
4659 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4660
4661 if (g_bsmoothpanzoom) {
4662 if (StartTimedMovement(stoptimer)) {
4663 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4664 m_zoom_factor = factor;
4665 }
4666
4667 m_zoom_target = VPoint.chart_scale / factor;
4668 } else {
4669 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4670
4671 DoZoomCanvas(factor, can_zoom_to_cursor);
4672 }
4673
4674 extendedSectorLegs.clear();
4675}
4676
4677void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4678 // possible on startup
4679 if (!ChartData) return;
4680 if (!m_pCurrentStack) return;
4681
4682 /* TODO: queue the quilted loading code to a background thread
4683 so yield is never called from here, and also rendering is not delayed */
4684
4685 // Cannot allow Yield() re-entrancy here
4686 if (m_bzooming) return;
4687 m_bzooming = true;
4688
4689 double old_ppm = GetVP().view_scale_ppm;
4690
4691 // Capture current cursor position for zoom to cursor
4692 double zlat = m_cursor_lat;
4693 double zlon = m_cursor_lon;
4694
4695 double proposed_scale_onscreen =
4696 GetVP().chart_scale /
4697 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4698 bool b_do_zoom = false;
4699
4700 if (factor > 1) {
4701 b_do_zoom = true;
4702
4703 // double zoom_factor = factor;
4704
4705 ChartBase *pc = NULL;
4706
4707 if (!VPoint.b_quilt) {
4708 pc = m_singleChart;
4709 } else {
4710 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4711 if (new_db_index >= 0)
4712 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4713 else { // for whatever reason, no reference chart is known
4714 // Choose the smallest scale chart on the current stack
4715 // and then adjust for scale range
4716 int current_ref_stack_index = -1;
4717 if (m_pCurrentStack->nEntry) {
4718 int trial_index =
4719 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4720 m_pQuilt->SetReferenceChart(trial_index);
4721 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4722 if (new_db_index >= 0)
4723 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4724 }
4725 }
4726
4727 if (m_pCurrentStack)
4728 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4729 new_db_index); // highlite the correct bar entry
4730 }
4731
4732 if (pc) {
4733 // double target_scale_ppm = GetVPScale() * zoom_factor;
4734 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4735 // target_scale_ppm;
4736
4737 // Query the chart to determine the appropriate zoom range
4738 double min_allowed_scale =
4739 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4740
4741 if (proposed_scale_onscreen < min_allowed_scale) {
4742 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4743 m_zoom_factor = 1; /* stop zooming */
4744 b_do_zoom = false;
4745 } else
4746 proposed_scale_onscreen = min_allowed_scale;
4747 }
4748
4749 } else {
4750 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4751 }
4752
4753 } else if (factor < 1) {
4754 b_do_zoom = true;
4755
4756 ChartBase *pc = NULL;
4757
4758 bool b_smallest = false;
4759
4760 if (!VPoint.b_quilt) { // not quilted
4761 pc = m_singleChart;
4762
4763 if (pc) {
4764 // If m_singleChart is not on the screen, unbound the zoomout
4765 LLBBox viewbox = VPoint.GetBBox();
4766 // BoundingBox chart_box;
4767 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4768 double max_allowed_scale;
4769
4770 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4771
4772 // We can allow essentially unbounded zoomout in single chart mode
4773 // if( ChartData->GetDBBoundingBox( current_index,
4774 // &chart_box ) &&
4775 // !viewbox.IntersectOut( chart_box ) )
4776 // // Clamp the minimum scale zoom-out to the value
4777 // specified by the chart max_allowed_scale =
4778 // wxMin(max_allowed_scale, 4.0 *
4779 // pc->GetNormalScaleMax(
4780 // GetCanvasScaleFactor(),
4781 // GetCanvasWidth() ) );
4782 if (proposed_scale_onscreen > max_allowed_scale) {
4783 m_zoom_factor = 1; /* stop zooming */
4784 proposed_scale_onscreen = max_allowed_scale;
4785 }
4786 }
4787
4788 } else {
4789 int new_db_index = m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4790 if (new_db_index >= 0)
4791 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4792
4793 if (m_pCurrentStack)
4794 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4795 new_db_index); // highlite the correct bar entry
4796
4797 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4798
4799 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4800 proposed_scale_onscreen =
4801 wxMin(proposed_scale_onscreen,
4802 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4803 }
4804
4805 // set a minimum scale
4806 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4807 m_absolute_min_scale_ppm)
4808 proposed_scale_onscreen =
4809 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4810 }
4811
4812 double new_scale =
4813 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4814
4815 if (b_do_zoom) {
4816 // Disable ZTC if lookahead is ON, and currently b_follow is active
4817 bool b_allow_ztc = true;
4818 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4819 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4820 if (m_bLookAhead) {
4821 double brg, distance;
4822 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4823 &distance);
4824 dir_to_shift = brg;
4825 meters_to_shift = distance * 1852;
4826 }
4827 // Arrange to combine the zoom and pan into one operation for smoother
4828 // appearance
4829 SetVPScale(new_scale, false); // adjust, but deferred refresh
4830 wxPoint r;
4831 GetCanvasPointPix(zlat, zlon, &r);
4832 // this will emit the Refresh()
4833 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4834 } else {
4835 SetVPScale(new_scale);
4836 if (m_bFollow) DoCanvasUpdate();
4837 }
4838 }
4839
4840 m_bzooming = false;
4841}
4842
4843void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4844 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4845 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4846}
4847
4848int rot;
4849void ChartCanvas::RotateCanvas(double dir) {
4850 // SetUpMode(NORTH_UP_MODE);
4851
4852 if (g_bsmoothpanzoom) {
4853 if (StartTimedMovement()) {
4854 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4855 m_rotation_speed = dir * 60;
4856 }
4857 } else {
4858 double speed = dir * 10;
4859 if (m_modkeys == wxMOD_ALT) speed /= 20;
4860 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4861 }
4862}
4863
4864void ChartCanvas::DoRotateCanvas(double rotation) {
4865 while (rotation < 0) rotation += 2 * PI;
4866 while (rotation > 2 * PI) rotation -= 2 * PI;
4867
4868 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4869
4870 SetVPRotation(rotation);
4871 parent_frame->UpdateRotationState(VPoint.rotation);
4872}
4873
4874void ChartCanvas::DoTiltCanvas(double tilt) {
4875 while (tilt < 0) tilt = 0;
4876 while (tilt > .95) tilt = .95;
4877
4878 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4879
4880 VPoint.tilt = tilt;
4881 Refresh(false);
4882}
4883
4884void ChartCanvas::TogglebFollow(void) {
4885 if (!m_bFollow)
4886 SetbFollow();
4887 else
4888 ClearbFollow();
4889}
4890
4891void ChartCanvas::ClearbFollow(void) {
4892 m_bFollow = false; // update the follow flag
4893
4894 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4895
4896 UpdateFollowButtonState();
4897
4898 DoCanvasUpdate();
4899 ReloadVP();
4900 parent_frame->SetChartUpdatePeriod();
4901}
4902
4903void ChartCanvas::SetbFollow(void) {
4904 // Is the OWNSHIP on-screen?
4905 // If not, then reset the OWNSHIP offset to 0 (center screen)
4906 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4907 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4908 m_OSoffsetx = 0;
4909 m_OSoffsety = 0;
4910 }
4911
4912 // Apply the present b_follow offset values to ship position
4913 wxPoint2DDouble p;
4914 GetDoubleCanvasPointPix(gLat, gLon, &p);
4915 p.m_x += m_OSoffsetx;
4916 p.m_y -= m_OSoffsety;
4917
4918 // compute the target center screen lat/lon
4919 double dlat, dlon;
4920 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4921
4922 JumpToPosition(dlat, dlon, GetVPScale());
4923 m_bFollow = true;
4924
4925 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4926 UpdateFollowButtonState();
4927
4928 if (!g_bSmoothRecenter) {
4929 DoCanvasUpdate();
4930 ReloadVP();
4931 }
4932 parent_frame->SetChartUpdatePeriod();
4933}
4934
4935void ChartCanvas::UpdateFollowButtonState(void) {
4936 if (m_muiBar) {
4937 if (!m_bFollow)
4938 m_muiBar->SetFollowButtonState(0);
4939 else {
4940 if (m_bLookAhead)
4941 m_muiBar->SetFollowButtonState(2);
4942 else
4943 m_muiBar->SetFollowButtonState(1);
4944 }
4945 }
4946
4947#ifdef __ANDROID__
4948 if (!m_bFollow)
4949 androidSetFollowTool(0);
4950 else {
4951 if (m_bLookAhead)
4952 androidSetFollowTool(2);
4953 else
4954 androidSetFollowTool(1);
4955 }
4956#endif
4957
4958 // Look for plugin using API-121 or later
4959 // If found, make the follow state callback.
4960 if (g_pi_manager) {
4961 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4962 if (pic->m_enabled && pic->m_init_state) {
4963 switch (pic->m_api_version) {
4964 case 121: {
4965 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4966 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4967 break;
4968 }
4969 default:
4970 break;
4971 }
4972 }
4973 }
4974 }
4975}
4976
4977void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4978 if (g_bSmoothRecenter && !m_routeState) {
4979 if (StartSmoothJump(lat, lon, scale_ppm))
4980 return;
4981 else {
4982 // move closer to the target destination, and try again
4983 double gcDist, gcBearingEnd;
4984 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4985 &gcBearingEnd);
4986 gcBearingEnd += 180;
4987 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4988 GetCanvasWidth() / GetVPScale(); // meters
4989 double lon_offset =
4990 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4991 double new_lat = lat + (lat_offset / (1852 * 60));
4992 double new_lon = lon + (lon_offset / (1852 * 60));
4993 SetViewPoint(new_lat, new_lon);
4994 ReloadVP();
4995 StartSmoothJump(lat, lon, scale_ppm);
4996 return;
4997 }
4998 }
4999
5000 if (lon > 180.0) lon -= 360.0;
5001 m_vLat = lat;
5002 m_vLon = lon;
5003 StopMovement();
5004 m_bFollow = false;
5005
5006 if (!GetQuiltMode()) {
5007 double skew = 0;
5008 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
5009 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
5010 } else {
5011 if (scale_ppm != GetVPScale()) {
5012 // XXX should be done in SetViewPoint
5013 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5014 AdjustQuiltRefChart();
5015 }
5016 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
5017 }
5018
5019 ReloadVP();
5020
5021 UpdateFollowButtonState();
5022
5023 // TODO
5024 // if( g_pi_manager ) {
5025 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
5026 // }
5027}
5028
5029bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
5030 // Check distance to jump, in pixels at current chart scale
5031 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5032 // width.
5033 double gcDist;
5034 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5035 double distance_pixels = gcDist * GetVPScale();
5036 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5037 // Jump is too far, try again
5038 return false;
5039 }
5040
5041 // Save where we're coming from
5042 m_startLat = m_vLat;
5043 m_startLon = m_vLon;
5044 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5045
5046 // Save where we want to end up
5047 m_endLat = lat;
5048 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5049 m_endScale = scale_ppm;
5050
5051 // Setup timing
5052 m_animationDuration = 600; // ms
5053 m_animationStart = wxGetLocalTimeMillis();
5054
5055 // Stop any previous movement, ensure no conflicts
5056 StopMovement();
5057 m_bFollow = false;
5058
5059 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5060 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5061 m_animationActive = true;
5062
5063 return true;
5064}
5065
5066void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5067 // Calculate time fraction from 0..1
5068 wxLongLong now = wxGetLocalTimeMillis();
5069 double elapsed = (now - m_animationStart).ToDouble();
5070 double t = elapsed / m_animationDuration.ToDouble();
5071 if (t > 1.0) t = 1.0;
5072
5073 // Ease function for smoother movement
5074 double e = easeOutCubic(t);
5075
5076 // Interpolate lat/lon/scale
5077 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5078 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5079 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5080
5081 // Update viewpoint
5082 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5083 // portion)
5084 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5085 ReloadVP();
5086
5087 // If we reached the end, stop the timer and finalize
5088 if (t >= 1.0) {
5089 m_easeTimer.Stop();
5090 m_animationActive = false;
5091 UpdateFollowButtonState();
5092 ZoomCanvasSimple(1.0001);
5093 DoCanvasUpdate();
5094 ReloadVP();
5095 }
5096}
5097
5098bool ChartCanvas::PanCanvas(double dx, double dy) {
5099 if (!ChartData) return false;
5100
5101 extendedSectorLegs.clear();
5102
5103 double dlat, dlon;
5104 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5105
5106 int iters = 0;
5107 for (;;) {
5108 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5109
5110 if (iters++ > 5) return false;
5111 if (!std::isnan(dlat)) break;
5112
5113 dx *= .5, dy *= .5;
5114 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5115 }
5116
5117 // avoid overshooting the poles
5118 if (dlat > 90)
5119 dlat = 90;
5120 else if (dlat < -90)
5121 dlat = -90;
5122
5123 if (dlon > 360.) dlon -= 360.;
5124 if (dlon < -360.) dlon += 360.;
5125
5126 // This should not really be necessary, but round-trip georef on some
5127 // charts is not perfect, So we can get creep on repeated unidimensional
5128 // pans, and corrupt chart cacheing.......
5129
5130 // But this only works on north-up projections
5131 // TODO: can we remove this now?
5132 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5133 // .001 ) ) {
5134 //
5135 // if( dx == 0 ) dlon = clon;
5136 // if( dy == 0 ) dlat = clat;
5137 // }
5138
5139 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5140
5141 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5142
5143 if (VPoint.b_quilt) {
5144 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5145 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5146 // Tweak the scale slightly for a new ref chart
5147 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5148 if (pc) {
5149 double tweak_scale_ppm =
5150 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5151 SetVPScale(tweak_scale_ppm);
5152 }
5153 }
5154
5155 if (new_ref_dbIndex == -1) {
5156#pragma GCC diagnostic push
5157#pragma GCC diagnostic ignored "-Warray-bounds"
5158 // The compiler sees a -1 index being used. Does not happen, though.
5159
5160 // for whatever reason, no reference chart is known
5161 // Probably panned out of the coverage region
5162 // If any charts are anywhere on-screen, choose the smallest
5163 // scale chart on the screen to be a new reference chart.
5164 int trial_index = -1;
5165 if (m_pCurrentStack->nEntry) {
5166 int trial_index =
5167 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5168 }
5169
5170 if (trial_index < 0) {
5171 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5172 if (full_screen_array.size())
5173 trial_index = full_screen_array[full_screen_array.size() - 1];
5174 }
5175
5176 if (trial_index >= 0) {
5177 m_pQuilt->SetReferenceChart(trial_index);
5178 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5179 VPoint.rotation);
5180 ReloadVP();
5181 }
5182#pragma GCC diagnostic pop
5183 }
5184 }
5185
5186 // Turn off bFollow only if the ownship has left the screen
5187 if (m_bFollow) {
5188 double offx, offy;
5189 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5190
5191 double offset_angle = atan2(offy, offx);
5192 double offset_distance = sqrt((offy * offy) + (offx * offx));
5193 double chart_angle = GetVPRotation();
5194 double target_angle = chart_angle - offset_angle;
5195 double d_east_mod = offset_distance * cos(target_angle);
5196 double d_north_mod = offset_distance * sin(target_angle);
5197
5198 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5199 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5200
5201 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5202 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5203 m_bFollow = false; // update the follow flag
5204 UpdateFollowButtonState();
5205 }
5206 }
5207
5208 Refresh(false);
5209
5210 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5211
5212 return true;
5213}
5214
5215void ChartCanvas::ReloadVP(bool b_adjust) {
5216 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5217
5218 LoadVP(VPoint, b_adjust);
5219}
5220
5221void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5222#ifdef ocpnUSE_GL
5223 if (g_bopengl && m_glcc) {
5224 m_glcc->Invalidate();
5225 if (m_glcc->GetSize() != GetSize()) {
5226 m_glcc->SetSize(GetSize());
5227 }
5228 } else
5229#endif
5230 {
5231 m_cache_vp.Invalidate();
5232 m_bm_cache_vp.Invalidate();
5233 }
5234
5235 VPoint.Invalidate();
5236
5237 if (m_pQuilt) m_pQuilt->Invalidate();
5238
5239 // Make sure that the Selected Group is sensible...
5240 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5241 // m_groupIndex = 0;
5242 // if( !CheckGroup( m_groupIndex ) )
5243 // m_groupIndex = 0;
5244
5245 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5246 vp.m_projection_type, b_adjust);
5247}
5248
5249void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5250 m_pQuilt->SetReferenceChart(dbIndex);
5251 VPoint.Invalidate();
5252 m_pQuilt->Invalidate();
5253}
5254
5255double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5256 if (m_pQuilt)
5257 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5258 else
5259 return vp.view_scale_ppm;
5260}
5261
5262// Verify and adjust the current reference chart,
5263// so that it will not lead to excessive overzoom or underzoom onscreen
5264int ChartCanvas::AdjustQuiltRefChart() {
5265 int ret = -1;
5266 if (m_pQuilt) {
5267 wxASSERT(ChartData);
5268 ChartBase *pc =
5269 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5270 if (pc) {
5271 double min_ref_scale =
5272 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5273 double max_ref_scale =
5274 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5275
5276 if (VPoint.chart_scale < min_ref_scale) {
5277 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5278 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5279 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5280 } else {
5281 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5282
5283 if (!brender_ok) {
5284 int target_stack_index = wxNOT_FOUND;
5285 int il = 0;
5286 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5287 if (index == m_pQuilt->GetRefChartdbIndex()) {
5288 target_stack_index = il;
5289 break;
5290 }
5291 il++;
5292 }
5293 if (wxNOT_FOUND == target_stack_index) // should never happen...
5294 target_stack_index = 0;
5295
5296 int ref_family = pc->GetChartFamily();
5297 int extended_array_count =
5298 m_pQuilt->GetExtendedStackIndexArray().size();
5299 while ((!brender_ok) &&
5300 ((int)target_stack_index < (extended_array_count - 1))) {
5301 target_stack_index++;
5302 int test_db_index =
5303 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5304
5305 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5306 IsChartQuiltableRef(test_db_index)) {
5307 // open the target, and check the min_scale
5308 ChartBase *ptest_chart =
5309 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5310 if (ptest_chart) {
5311 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5312 }
5313 }
5314 }
5315
5316 if (brender_ok) { // found a better reference chart
5317 int new_db_index =
5318 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5319 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5320 IsChartQuiltableRef(new_db_index)) {
5321 m_pQuilt->SetReferenceChart(new_db_index);
5322 ret = new_db_index;
5323 } else
5324 ret = m_pQuilt->GetRefChartdbIndex();
5325 } else
5326 ret = m_pQuilt->GetRefChartdbIndex();
5327
5328 } else
5329 ret = m_pQuilt->GetRefChartdbIndex();
5330 }
5331 } else
5332 ret = -1;
5333 }
5334
5335 return ret;
5336}
5337
5338void ChartCanvas::UpdateCanvasOnGroupChange(void) {
5339 delete m_pCurrentStack;
5340 m_pCurrentStack = new ChartStack;
5341 wxASSERT(ChartData);
5342 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5343 m_groupIndex);
5344
5345 if (m_pQuilt) {
5346 m_pQuilt->Compose(VPoint);
5347 SetFocus();
5348 }
5349}
5350
5351bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5352 double latNE, double lonNE) {
5353 // Center Point
5354 double latc = (latSW + latNE) / 2.0;
5355 double lonc = (lonSW + lonNE) / 2.0;
5356
5357 // Get scale in ppm (latitude)
5358 double ne_easting, ne_northing;
5359 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5360
5361 double sw_easting, sw_northing;
5362 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5363
5364 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5365
5366 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5367}
5368
5369bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5370 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5371 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5372}
5373
5374bool ChartCanvas::SetVPProjection(int projection) {
5375 if (!g_bopengl) // alternative projections require opengl
5376 return false;
5377
5378 // the view scale varies depending on geographic location and projection
5379 // rescale to keep the relative scale on the screen the same
5380 double prev_true_scale_ppm = m_true_scale_ppm;
5381 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5382 VPoint.skew, VPoint.rotation, projection) &&
5383 SetVPScale(wxMax(
5384 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5385 m_absolute_min_scale_ppm));
5386}
5387
5388bool ChartCanvas::SetViewPoint(double lat, double lon) {
5389 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5390 VPoint.rotation);
5391}
5392
5393bool ChartCanvas::SetVPRotation(double angle) {
5394 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5395 VPoint.skew, angle);
5396}
5397bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5398 double skew, double rotation, int projection,
5399 bool b_adjust, bool b_refresh) {
5400 bool b_ret = false;
5401 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5402 skew -= 2 * PI;
5403 // Any sensible change?
5404 if (VPoint.IsValid()) {
5405 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5406 (fabs(VPoint.skew - skew) < 1e-9) &&
5407 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5408 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5409 (VPoint.m_projection_type == projection ||
5410 projection == PROJECTION_UNKNOWN))
5411 return false;
5412 }
5413 if (VPoint.m_projection_type != projection)
5414 VPoint.InvalidateTransformCache(); // invalidate
5415
5416 // Take a local copy of the last viewport
5417 ViewPort last_vp = VPoint;
5418
5419 VPoint.skew = skew;
5420 VPoint.clat = lat;
5421 VPoint.clon = lon;
5422 VPoint.rotation = rotation;
5423 VPoint.view_scale_ppm = scale_ppm;
5424 if (projection != PROJECTION_UNKNOWN)
5425 VPoint.SetProjectionType(projection);
5426 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5427 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5428
5429 // don't allow latitude above 88 for mercator (90 is infinity)
5430 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5431 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5432 if (VPoint.clat > 89.5)
5433 VPoint.clat = 89.5;
5434 else if (VPoint.clat < -89.5)
5435 VPoint.clat = -89.5;
5436 }
5437
5438 // don't zoom out too far for transverse mercator polyconic until we resolve
5439 // issues
5440 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5441 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5442 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5443
5444 // SetVPRotation(rotation);
5445
5446 if (!g_bopengl) // tilt is not possible without opengl
5447 VPoint.tilt = 0;
5448
5449 if ((VPoint.pix_width <= 0) ||
5450 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5451 return false;
5452
5453 bool bwasValid = VPoint.IsValid();
5454 VPoint.Validate(); // Mark this ViewPoint as OK
5455
5456 // Has the Viewport scale changed? If so, invalidate the vp
5457 if (last_vp.view_scale_ppm != scale_ppm) {
5458 m_cache_vp.Invalidate();
5459 InvalidateGL();
5460 }
5461
5462 // A preliminary value, may be tweaked below
5463 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5464
5465 // recompute cursor position
5466 // and send to interested plugins if the mouse is actually in this window
5467 int mouseX = mouse_x;
5468 int mouseY = mouse_y;
5469 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5470 (mouseY < VPoint.pix_height)) {
5471 double lat, lon;
5472 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5473 m_cursor_lat = lat;
5474 m_cursor_lon = lon;
5475 SendCursorLatLonToAllPlugIns(lat, lon);
5476 }
5477
5478 if (!VPoint.b_quilt && m_singleChart) {
5479 VPoint.SetBoxes();
5480
5481 // Allow the chart to adjust the new ViewPort for performance optimization
5482 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5483 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5484
5485 // If there is a sensible change in the chart render, refresh the whole
5486 // screen
5487 if ((!m_cache_vp.IsValid()) ||
5488 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5489 Refresh(false);
5490 b_ret = true;
5491 } else {
5492 wxPoint cp_last, cp_this;
5493 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5494 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5495
5496 if (cp_last != cp_this) {
5497 Refresh(false);
5498 b_ret = true;
5499 }
5500 }
5501 // Create the stack
5502 if (m_pCurrentStack) {
5503 assert(ChartData != 0);
5504 int current_db_index;
5505 current_db_index =
5506 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5507
5508 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5509 m_groupIndex);
5510 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5511 }
5512
5513 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5514 }
5515
5516 // Handle the quilted case
5517 if (VPoint.b_quilt) {
5518 if (last_vp.view_scale_ppm != scale_ppm)
5519 m_pQuilt->InvalidateAllQuiltPatchs();
5520
5521 // Create the quilt
5522 if (ChartData /*&& ChartData->IsValid()*/) {
5523 if (!m_pCurrentStack) return false;
5524
5525 int current_db_index;
5526 current_db_index =
5527 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5528
5529 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5530 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5531
5532 // Check to see if the current quilt reference chart is in the new stack
5533 int current_ref_stack_index = -1;
5534 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5535 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5536 current_ref_stack_index = i;
5537 }
5538
5539 if (g_bFullScreenQuilt) {
5540 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5541 }
5542
5543 // We might need a new Reference Chart
5544 bool b_needNewRef = false;
5545
5546 // If the new stack does not contain the current ref chart....
5547 if ((-1 == current_ref_stack_index) &&
5548 (m_pQuilt->GetRefChartdbIndex() >= 0))
5549 b_needNewRef = true;
5550
5551 // Would the current Ref Chart be excessively underzoomed?
5552 // We need to check this here to be sure, since we cannot know where the
5553 // reference chart was assigned. For instance, the reference chart may
5554 // have been selected from the config file, or from a long jump with a
5555 // chart family switch implicit. Anyway, we check to be sure....
5556 bool renderable = true;
5557 ChartBase *referenceChart =
5558 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5559 if (referenceChart) {
5560 double chartMaxScale = referenceChart->GetNormalScaleMax(
5561 GetCanvasScaleFactor(), GetCanvasWidth());
5562 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5563 }
5564 if (!renderable) b_needNewRef = true;
5565
5566 // Need new refchart?
5567 if (b_needNewRef) {
5568 const ChartTableEntry &cte_ref =
5569 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5570 int target_scale = cte_ref.GetScale();
5571 int target_type = cte_ref.GetChartType();
5572 int candidate_stack_index;
5573
5574 // reset the ref chart in a way that does not lead to excessive
5575 // underzoom, for performance reasons Try to find a chart that is the
5576 // same type, and has a scale of just smaller than the current ref
5577 // chart
5578
5579 candidate_stack_index = 0;
5580 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5581 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5582 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5583 int candidate_scale = cte_candidate.GetScale();
5584 int candidate_type = cte_candidate.GetChartType();
5585
5586 if ((candidate_scale >= target_scale) &&
5587 (candidate_type == target_type)) {
5588 bool renderable = true;
5589 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5590 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5591 if (tentative_referenceChart) {
5592 double chartMaxScale =
5593 tentative_referenceChart->GetNormalScaleMax(
5594 GetCanvasScaleFactor(), GetCanvasWidth());
5595 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5596 }
5597
5598 if (renderable) break;
5599 }
5600
5601 candidate_stack_index++;
5602 }
5603
5604 // If that did not work, look for a chart of just larger scale and
5605 // same type
5606 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5607 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5608 while (candidate_stack_index >= 0) {
5609 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5610 if (idx >= 0) {
5611 const ChartTableEntry &cte_candidate =
5612 ChartData->GetChartTableEntry(idx);
5613 int candidate_scale = cte_candidate.GetScale();
5614 int candidate_type = cte_candidate.GetChartType();
5615
5616 if ((candidate_scale <= target_scale) &&
5617 (candidate_type == target_type))
5618 break;
5619 }
5620 candidate_stack_index--;
5621 }
5622 }
5623
5624 // and if that did not work, chose stack entry 0
5625 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5626 (candidate_stack_index < 0))
5627 candidate_stack_index = 0;
5628
5629 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5630
5631 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5632 }
5633
5634 if (!g_bopengl) {
5635 // Preset the VPoint projection type to match what the quilt projection
5636 // type will be
5637 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5638
5639 // Always keep the default Mercator projection if the reference chart is
5640 // not in the PatchList or the scale is too small for it to render.
5641
5642 bool renderable = true;
5643 ChartBase *referenceChart =
5644 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5645 if (referenceChart) {
5646 double chartMaxScale = referenceChart->GetNormalScaleMax(
5647 GetCanvasScaleFactor(), GetCanvasWidth());
5648 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5649 proj = ChartData->GetDBChartProj(ref_db_index);
5650 } else
5651 proj = PROJECTION_MERCATOR;
5652
5653 VPoint.b_MercatorProjectionOverride =
5654 (m_pQuilt->GetnCharts() == 0 || !renderable);
5655
5656 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5657
5658 VPoint.SetProjectionType(proj);
5659 }
5660
5661 VPoint.SetBoxes();
5662
5663 // If this quilt will be a perceptible delta from the existing quilt,
5664 // then refresh the entire screen
5665 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5666 // Allow the quilt to adjust the new ViewPort for performance
5667 // optimization This will normally be only a fractional (i.e.
5668 // sub-pixel) adjustment...
5669 if (b_adjust) {
5670 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5671 }
5672
5673 // ChartData->ClearCacheInUseFlags();
5674 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5675
5676 // wxStopWatch sw;
5677
5678#ifdef __ANDROID__
5679 // This is an optimization for panning on touch screen systems.
5680 // The quilt composition is deferred until the OnPaint() message gets
5681 // finally removed and processed from the message queue.
5682 // Takes advantage of the fact that touch-screen pan gestures are
5683 // usually short in distance,
5684 // so not requiring a full quilt rebuild until the pan gesture is
5685 // complete.
5686 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5687 // qDebug() << "Force compose";
5688 m_pQuilt->Compose(VPoint);
5689 } else {
5690 m_pQuilt->Invalidate();
5691 }
5692#else
5693 m_pQuilt->Compose(VPoint);
5694#endif
5695
5696 // printf("comp time %ld\n", sw.Time());
5697
5698 // If the extended chart stack has changed, invalidate any cached
5699 // render bitmap
5700 // if(m_pQuilt->GetXStackHash() != hash1) {
5701 // m_bm_cache_vp.Invalidate();
5702 // InvalidateGL();
5703 // }
5704
5705 ChartData->PurgeCacheUnusedCharts(0.7);
5706
5707 if (b_refresh) Refresh(false);
5708
5709 b_ret = true;
5710 }
5711 }
5712
5713 VPoint.skew = 0.; // Quilting supports 0 Skew
5714 } else if (!g_bopengl) {
5715 OcpnProjType projection = PROJECTION_UNKNOWN;
5716 if (m_singleChart) // viewport projection must match chart projection
5717 // without opengl
5718 projection = m_singleChart->GetChartProjectionType();
5719 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5720 VPoint.SetProjectionType(projection);
5721 }
5722
5723 // Has the Viewport projection changed? If so, invalidate the vp
5724 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5725 m_cache_vp.Invalidate();
5726 InvalidateGL();
5727 }
5728
5729 UpdateCanvasControlBar(); // Refresh the Piano
5730
5731 VPoint.chart_scale = 1.0; // fallback default value
5732
5733 /*if (!VPoint.GetBBox().GetValid())*/ VPoint.SetBoxes();
5734
5735 if (VPoint.GetBBox().GetValid()) {
5736 // Update the viewpoint reference scale
5737 if (m_singleChart)
5738 VPoint.ref_scale = m_singleChart->GetNativeScale();
5739 else {
5740#ifdef __ANDROID__
5741 // This is an optimization for panning on touch screen systems.
5742 // See above.
5743 // Quilt might not be fully composed at this point, so for cm93
5744 // the reference scale may not be known.
5745 // In this case, do not update the VP ref_scale.
5746 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5747 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5748 }
5749#else
5750 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5751#endif
5752 }
5753
5754 // Calculate the on-screen displayed actual scale
5755 // by a simple traverse northward from the center point
5756 // of roughly one eighth of the canvas height
5757 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5758
5759 double delta_check =
5760 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5761 delta_check /= 8.;
5762
5763 double check_point = wxMin(89., VPoint.clat);
5764
5765 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5766
5767 double rhumbDist;
5768 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5769 VPoint.clon, 0, &rhumbDist);
5770
5771 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5772 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5773 // Calculate the distance between r1 and r in physical pixels.
5774 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5775 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5776
5777 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5778
5779 // A fall back in case of very high zoom-out, giving delta_y == 0
5780 // which can probably only happen with vector charts
5781 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5782
5783 // Another fallback, for highly zoomed out charts
5784 // This adjustment makes the displayed TrueScale correspond to the
5785 // same algorithm used to calculate the chart zoom-out limit for
5786 // ChartDummy.
5787 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5788
5789 if (m_true_scale_ppm)
5790 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5791 else
5792 VPoint.chart_scale = 1.0;
5793
5794 // Create a nice renderable string
5795 double round_factor = 1000.;
5796 if (VPoint.chart_scale <= 1000.)
5797 round_factor = 10.;
5798 else if (VPoint.chart_scale <= 10000.)
5799 round_factor = 100.;
5800 else if (VPoint.chart_scale <= 100000.)
5801 round_factor = 1000.;
5802
5803 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5804 double retina_coef = 1;
5805#ifdef ocpnUSE_GL
5806#ifdef __WXOSX__
5807 if (g_bopengl) {
5808 retina_coef = GetContentScaleFactor();
5809 }
5810#endif
5811#endif
5812
5813 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5814 // rounded to the nearest 10, 100 or 1000.
5815 //
5816 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5817 // true_scale_display. That does not make sense. The chart scale should be
5818 // the same as the true scale within the limits of the rounding factor.
5819 double true_scale_display =
5820 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5821 wxString text;
5822
5823 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5824
5825 if (m_displayed_scale_factor > 10.0)
5826 text.Printf(_T("%s %4.0f (%1.0fx)"), _("Scale"), true_scale_display,
5827 m_displayed_scale_factor);
5828 else if (m_displayed_scale_factor > 1.0)
5829 text.Printf(_T("%s %4.0f (%1.1fx)"), _("Scale"), true_scale_display,
5830 m_displayed_scale_factor);
5831 else if (m_displayed_scale_factor > 0.1) {
5832 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5833 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5834 } else if (m_displayed_scale_factor > 0.01) {
5835 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5836 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5837 } else {
5838 text.Printf(
5839 _T("%s %4.0f (---)"), _("Scale"),
5840 true_scale_display); // Generally, no chart, so no chart scale factor
5841 }
5842
5843 m_scaleValue = true_scale_display;
5844 m_scaleText = text;
5845 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5846
5847 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5848 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5849 // Check to see if the text will fit in the StatusBar field...
5850 bool b_noshow = false;
5851 {
5852 int w = 0;
5853 int h;
5854 wxClientDC dc(parent_frame->GetStatusBar());
5855 if (dc.IsOk()) {
5856 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5857 dc.SetFont(*templateFont);
5858 dc.GetTextExtent(text, &w, &h);
5859
5860 // If text is too long for the allocated field, try to reduce the text
5861 // string a bit.
5862 wxRect rect;
5863 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5864 if (w && w > rect.width) {
5865 text.Printf(_T("%s (%1.1fx)"), _("Scale"),
5866 m_displayed_scale_factor);
5867 }
5868
5869 // Test again...if too big still, then give it up.
5870 dc.GetTextExtent(text, &w, &h);
5871
5872 if (w && w > rect.width) {
5873 b_noshow = true;
5874 }
5875 }
5876 }
5877
5878 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5879 }
5880 }
5881
5882 // Maintain member vLat/vLon
5883 m_vLat = VPoint.clat;
5884 m_vLon = VPoint.clon;
5885
5886 return b_ret;
5887}
5888
5889// Static Icon definitions for some symbols requiring
5890// scaling/rotation/translation Very specific wxDC draw commands are
5891// necessary to properly render these icons...See the code in
5892// ShipDraw()
5893
5894// This icon was adapted and scaled from the S52 Presentation Library
5895// version 3_03.
5896// Symbol VECGND02
5897
5898static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5899
5900// This ownship icon was adapted and scaled from the S52 Presentation
5901// Library version 3_03 Symbol OWNSHP05
5902static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5903 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5904
5905wxColour ChartCanvas::PredColor() {
5906 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5907 // visibility.
5908 if (SHIP_NORMAL == m_ownship_state)
5909 return GetGlobalColor(_T ( "URED" ));
5910
5911 else if (SHIP_LOWACCURACY == m_ownship_state)
5912 return GetGlobalColor(_T ( "YELO1" ));
5913
5914 return GetGlobalColor(_T ( "NODTA" ));
5915}
5916
5917wxColour ChartCanvas::ShipColor() {
5918 // Establish ship color
5919 // It changes color based on GPS and Chart accuracy/availability
5920
5921 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor(_T ( "GREY1" ));
5922
5923 if (SHIP_LOWACCURACY == m_ownship_state)
5924 return GetGlobalColor(_T ( "YELO1" ));
5925
5926 return GetGlobalColor(_T ( "URED" )); // default is OK
5927}
5928
5929void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5930 wxPoint2DDouble lShipMidPoint) {
5931 dc.SetPen(wxPen(PredColor(), 2));
5932
5933 if (SHIP_NORMAL == m_ownship_state)
5934 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5935 else
5936 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "YELO1" ))));
5937
5938 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5939 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5940
5941 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5942 lShipMidPoint.m_y);
5943 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5944 lShipMidPoint.m_y + 12);
5945}
5946
5947void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5948 wxPoint GPSOffsetPixels,
5949 wxPoint2DDouble lGPSPoint) {
5950 // if (m_animationActive) return;
5951 // Develop a uniform length for course predictor line dash length, based on
5952 // physical display size Use this reference length to size all other graphics
5953 // elements
5954 float ref_dim = m_display_size_mm / 24;
5955 ref_dim = wxMin(ref_dim, 12);
5956 ref_dim = wxMax(ref_dim, 6);
5957
5958 wxColour cPred;
5959 cPred.Set(g_cog_predictor_color);
5960 if (cPred == wxNullColour) cPred = PredColor();
5961
5962 // Establish some graphic element line widths dependent on the platform
5963 // display resolution
5964 // double nominal_line_width_pix = wxMax(1.0,
5965 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5966 // not less than 1 pixel
5967 double nominal_line_width_pix = wxMax(
5968 1.0,
5969 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5970
5971 // If the calculated value is greater than the config file spec value, then
5972 // use it.
5973 if (nominal_line_width_pix > g_cog_predictor_width)
5974 g_cog_predictor_width = nominal_line_width_pix;
5975
5976 // Calculate ownship Position Predictor
5977 wxPoint lPredPoint, lHeadPoint;
5978
5979 float pCog = std::isnan(gCog) ? 0 : gCog;
5980 float pSog = std::isnan(gSog) ? 0 : gSog;
5981
5982 double pred_lat, pred_lon;
5983 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5984 &pred_lat, &pred_lon);
5985 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5986
5987 // test to catch the case where COG/HDG line crosses the screen
5988 LLBBox box;
5989
5990 // Should we draw the Head vector?
5991 // Compare the points lHeadPoint and lPredPoint
5992 // If they differ by more than n pixels, and the head vector is valid, then
5993 // render the head vector
5994
5995 float ndelta_pix = 10.;
5996 double hdg_pred_lat, hdg_pred_lon;
5997 bool b_render_hdt = false;
5998 if (!std::isnan(gHdt)) {
5999 // Calculate ownship Heading pointer as a predictor
6000 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
6001 &hdg_pred_lon);
6002 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
6003 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
6004 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
6005 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
6006 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
6007 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
6008 }
6009 }
6010
6011 // draw course over ground if they are longer than the ship
6012 wxPoint lShipMidPoint;
6013 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
6014 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
6015 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
6016 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
6017
6018 if (lpp >= img_height / 2) {
6019 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
6020 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
6021 !std::isnan(gSog)) {
6022 // COG Predictor
6023 float dash_length = ref_dim;
6024 wxDash dash_long[2];
6025 dash_long[0] =
6026 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6027 g_cog_predictor_width); // Long dash , in mm <---------+
6028 dash_long[1] = dash_long[0] / 2.0; // Short gap
6029
6030 // On ultra-hi-res displays, do not allow the dashes to be greater than
6031 // 250, since it is defined as (char)
6032 if (dash_length > 250.) {
6033 dash_long[0] = 250. / g_cog_predictor_width;
6034 dash_long[1] = dash_long[0] / 2;
6035 }
6036
6037 wxPen ppPen2(cPred, g_cog_predictor_width,
6038 (wxPenStyle)g_cog_predictor_style);
6039 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6040 ppPen2.SetDashes(2, dash_long);
6041 dc.SetPen(ppPen2);
6042 dc.StrokeLine(
6043 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6044 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6045
6046 if (g_cog_predictor_width > 1) {
6047 float line_width = g_cog_predictor_width / 3.;
6048
6049 wxDash dash_long3[2];
6050 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6051 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6052
6053 wxPen ppPen3(GetGlobalColor(_T ( "UBLCK" )), wxMax(1, line_width),
6054 (wxPenStyle)g_cog_predictor_style);
6055 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6056 ppPen3.SetDashes(2, dash_long3);
6057 dc.SetPen(ppPen3);
6058 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6059 lGPSPoint.m_y + GPSOffsetPixels.y,
6060 lPredPoint.x + GPSOffsetPixels.x,
6061 lPredPoint.y + GPSOffsetPixels.y);
6062 }
6063
6064 if (g_cog_predictor_endmarker) {
6065 // Prepare COG predictor endpoint icon
6066 double png_pred_icon_scale_factor = .4;
6067 if (g_ShipScaleFactorExp > 1.0)
6068 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6069 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6070
6071 wxPoint icon[4];
6072
6073 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6074 (float)(lPredPoint.x - lShipMidPoint.x));
6075 cog_rad += (float)PI;
6076
6077 for (int i = 0; i < 4; i++) {
6078 int j = i * 2;
6079 double pxa = (double)(s_png_pred_icon[j]);
6080 double pya = (double)(s_png_pred_icon[j + 1]);
6081
6082 pya *= png_pred_icon_scale_factor;
6083 pxa *= png_pred_icon_scale_factor;
6084
6085 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6086 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6087
6088 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6089 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6090 }
6091
6092 // Render COG endpoint icon
6093 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), g_cog_predictor_width / 2,
6094 wxPENSTYLE_SOLID);
6095 dc.SetPen(ppPen1);
6096 dc.SetBrush(wxBrush(cPred));
6097
6098 dc.StrokePolygon(4, icon);
6099 }
6100 }
6101 }
6102
6103 // HDT Predictor
6104 if (b_render_hdt) {
6105 float hdt_dash_length = ref_dim * 0.4;
6106
6107 cPred.Set(g_ownship_HDTpredictor_color);
6108 if (cPred == wxNullColour) cPred = PredColor();
6109 float hdt_width =
6110 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6111 : g_cog_predictor_width * 0.8);
6112 wxDash dash_short[2];
6113 dash_short[0] =
6114 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6115 hdt_width); // Short dash , in mm <---------+
6116 dash_short[1] =
6117 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6118 hdt_width); // Short gap |
6119
6120 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6121 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6122 ppPen2.SetDashes(2, dash_short);
6123
6124 dc.SetPen(ppPen2);
6125 dc.StrokeLine(
6126 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6127 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6128
6129 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6130 dc.SetPen(ppPen1);
6131 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "GREY2" ))));
6132
6133 if (g_ownship_HDTpredictor_endmarker) {
6134 double nominal_circle_size_pixels = wxMax(
6135 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6136
6137 // Scale the circle to ChartScaleFactor, slightly softened....
6138 if (g_ShipScaleFactorExp > 1.0)
6139 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6140
6141 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6142 lHeadPoint.y + GPSOffsetPixels.y,
6143 nominal_circle_size_pixels / 2);
6144 }
6145 }
6146
6147 // Draw radar rings if activated
6148 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6149 double factor = 1.00;
6150 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6151 factor = 1 / 1.852;
6152 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6153 if (std::isnan(gSog))
6154 factor = 0.0;
6155 else
6156 factor = gSog / 60;
6157 }
6158 factor *= g_fNavAidRadarRingsStep;
6159
6160 double tlat, tlon;
6161 wxPoint r;
6162 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6163 GetCanvasPointPix(tlat, tlon, &r);
6164
6165 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6166 pow((double)(lGPSPoint.m_y - r.y), 2));
6167 int pix_radius = (int)lpp;
6168
6169 extern wxColor GetDimColor(wxColor c);
6170 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6171
6172 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6173
6174 dc.SetPen(ppPen1);
6175 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6176
6177 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6178 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6179 }
6180}
6181
6182void ChartCanvas::ComputeShipScaleFactor(
6183 float icon_hdt, int ownShipWidth, int ownShipLength,
6184 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6185 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6186 float screenResolution = m_pix_per_mm;
6187
6188 // Calculate the true ship length in exact pixels
6189 double ship_bow_lat, ship_bow_lon;
6190 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6191 &ship_bow_lat, &ship_bow_lon);
6192 wxPoint lShipBowPoint;
6193 wxPoint2DDouble b_point =
6194 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6195 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6196
6197 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6198 powf((float)(b_point.m_y - a_point.m_y), 2));
6199
6200 // And in mm
6201 float shipLength_mm = shipLength_px / screenResolution;
6202
6203 // Set minimum ownship drawing size
6204 float ownship_min_mm = g_n_ownship_min_mm;
6205 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6206
6207 // Calculate Nautical Miles distance from midships to gps antenna
6208 float hdt_ant = icon_hdt + 180.;
6209 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6210 float dx = g_n_gps_antenna_offset_x / 1852.;
6211 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6212 {
6213 hdt_ant = icon_hdt;
6214 dy = -dy;
6215 }
6216
6217 // If the drawn ship size is going to be clamped, adjust the gps antenna
6218 // offsets
6219 if (shipLength_mm < ownship_min_mm) {
6220 dy /= shipLength_mm / ownship_min_mm;
6221 dx /= shipLength_mm / ownship_min_mm;
6222 }
6223
6224 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6225
6226 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6227 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6228 &ship_mid_lon1);
6229
6230 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6231 &lShipMidPoint);
6232
6233 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6234 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6235
6236 float scale_factor = shipLength_px / ownShipLength;
6237
6238 // Calculate a scale factor that would produce a reasonably sized icon
6239 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6240
6241 // And choose the correct one
6242 scale_factor = wxMax(scale_factor, scale_factor_min);
6243
6244 scale_factor_y = scale_factor;
6245 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6246 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6247}
6248
6249void ChartCanvas::ShipDraw(ocpnDC &dc) {
6250 if (!GetVP().IsValid()) return;
6251
6252 wxPoint GPSOffsetPixels(0, 0);
6253 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6254
6255 // COG/SOG may be undefined in NMEA data stream
6256 float pCog = std::isnan(gCog) ? 0 : gCog;
6257 float pSog = std::isnan(gSog) ? 0 : gSog;
6258
6259 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6260
6261 lShipMidPoint = lGPSPoint;
6262
6263 // Draw the icon rotated to the COG
6264 // or to the Hdt if available
6265 float icon_hdt = pCog;
6266 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6267
6268 // COG may be undefined in NMEA data stream
6269 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6270
6271 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6272 // predictor
6273 double osd_head_lat, osd_head_lon;
6274 wxPoint osd_head_point;
6275
6276 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6277 &osd_head_lon);
6278
6279 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6280
6281 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6282 (float)(osd_head_point.x - lShipMidPoint.m_x));
6283 icon_rad += (float)PI;
6284
6285 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6286
6287 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6288 // nominal size and is just barely outside the viewport ....
6289 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6290
6291 // TODO: fix to include actual size of boat that will be rendered
6292 int img_height = 0;
6293 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6294 if (GetVP().chart_scale >
6295 300000) // According to S52, this should be 50,000
6296 {
6297 ShipDrawLargeScale(dc, lShipMidPoint);
6298 img_height = 20;
6299 } else {
6300 wxImage pos_image;
6301
6302 // Substitute user ownship image if found
6303 if (m_pos_image_user)
6304 pos_image = m_pos_image_user->Copy();
6305 else if (SHIP_NORMAL == m_ownship_state)
6306 pos_image = m_pos_image_red->Copy();
6307 if (SHIP_LOWACCURACY == m_ownship_state)
6308 pos_image = m_pos_image_yellow->Copy();
6309 else if (SHIP_NORMAL != m_ownship_state)
6310 pos_image = m_pos_image_grey->Copy();
6311
6312 // Substitute user ownship image if found
6313 if (m_pos_image_user) {
6314 pos_image = m_pos_image_user->Copy();
6315
6316 if (SHIP_LOWACCURACY == m_ownship_state)
6317 pos_image = m_pos_image_user_yellow->Copy();
6318 else if (SHIP_NORMAL != m_ownship_state)
6319 pos_image = m_pos_image_user_grey->Copy();
6320 }
6321
6322 img_height = pos_image.GetHeight();
6323
6324 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6325 g_OwnShipIconType > 0) // use large ship
6326 {
6327 int ownShipWidth = 22; // Default values from s_ownship_icon
6328 int ownShipLength = 84;
6329 if (g_OwnShipIconType == 1) {
6330 ownShipWidth = pos_image.GetWidth();
6331 ownShipLength = pos_image.GetHeight();
6332 }
6333
6334 float scale_factor_x, scale_factor_y;
6335 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6336 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6337 scale_factor_x, scale_factor_y);
6338
6339 if (g_OwnShipIconType == 1) { // Scaled bitmap
6340 pos_image.Rescale(ownShipWidth * scale_factor_x,
6341 ownShipLength * scale_factor_y,
6342 wxIMAGE_QUALITY_HIGH);
6343 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6344 wxImage rot_image =
6345 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6346
6347 // Simple sharpening algorithm.....
6348 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6349 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6350 if (rot_image.GetAlpha(ip, jp) > 64)
6351 rot_image.SetAlpha(ip, jp, 255);
6352
6353 wxBitmap os_bm(rot_image);
6354
6355 int w = os_bm.GetWidth();
6356 int h = os_bm.GetHeight();
6357 img_height = h;
6358
6359 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6360 lShipMidPoint.m_y - h / 2, true);
6361
6362 // Maintain dirty box,, missing in __WXMSW__ library
6363 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6364 lShipMidPoint.m_y - h / 2);
6365 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6366 lShipMidPoint.m_y - h / 2 + h);
6367 }
6368
6369 else if (g_OwnShipIconType == 2) { // Scaled Vector
6370 wxPoint ownship_icon[10];
6371
6372 for (int i = 0; i < 10; i++) {
6373 int j = i * 2;
6374 float pxa = (float)(s_ownship_icon[j]);
6375 float pya = (float)(s_ownship_icon[j + 1]);
6376 pya *= scale_factor_y;
6377 pxa *= scale_factor_x;
6378
6379 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6380 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6381
6382 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6383 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6384 }
6385
6386 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), 1, wxPENSTYLE_SOLID);
6387 dc.SetPen(ppPen1);
6388 dc.SetBrush(wxBrush(ShipColor()));
6389
6390 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6391
6392 // draw reference point (midships) cross
6393 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6394 ownship_icon[7].y);
6395 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6396 ownship_icon[9].y);
6397 }
6398
6399 img_height = ownShipLength * scale_factor_y;
6400
6401 // Reference point, where the GPS antenna is
6402 int circle_rad = 3;
6403 if (m_pos_image_user) circle_rad = 1;
6404
6405 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6406 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6407 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6408 } else { // Fixed bitmap icon.
6409 /* non opengl, or suboptimal opengl via ocpndc: */
6410 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6411 wxImage rot_image =
6412 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6413
6414 // Simple sharpening algorithm.....
6415 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6416 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6417 if (rot_image.GetAlpha(ip, jp) > 64)
6418 rot_image.SetAlpha(ip, jp, 255);
6419
6420 wxBitmap os_bm(rot_image);
6421
6422 if (g_ShipScaleFactorExp > 1) {
6423 wxImage scaled_image = os_bm.ConvertToImage();
6424 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6425 1.0; // soften the scale factor a bit
6426 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6427 scaled_image.GetHeight() * factor,
6428 wxIMAGE_QUALITY_HIGH));
6429 }
6430 int w = os_bm.GetWidth();
6431 int h = os_bm.GetHeight();
6432 img_height = h;
6433
6434 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6435 lShipMidPoint.m_y - h / 2, true);
6436
6437 // Reference point, where the GPS antenna is
6438 int circle_rad = 3;
6439 if (m_pos_image_user) circle_rad = 1;
6440
6441 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6442 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6443 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6444
6445 // Maintain dirty box,, missing in __WXMSW__ library
6446 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6447 lShipMidPoint.m_y - h / 2);
6448 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6449 lShipMidPoint.m_y - h / 2 + h);
6450 }
6451 } // ownship draw
6452 }
6453
6454 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6455}
6456
6457/* @ChartCanvas::CalcGridSpacing
6458 **
6459 ** Calculate the major and minor spacing between the lat/lon grid
6460 **
6461 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6462 *window
6463 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6464 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6465 ** @return [void]
6466 */
6467void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6468 float &MinorSpacing) {
6469 // table for calculating the distance between the grids
6470 // [0] view_scale ppm
6471 // [1] spacing between major grid lines in degrees
6472 // [2] spacing between minor grid lines in degrees
6473 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6474 {.000001f, 45.0f, 15.0f},
6475 {.0002f, 30.0f, 10.0f},
6476 {.0003f, 10.0f, 2.0f},
6477 {.0008f, 5.0f, 1.0f},
6478 {.001f, 2.0f, 30.0f / 60.0f},
6479 {.003f, 1.0f, 20.0f / 60.0f},
6480 {.006f, 0.5f, 10.0f / 60.0f},
6481 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6482 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6483 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6484 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6485 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6486 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6487 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6488 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6489
6490 unsigned int tabi;
6491 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6492 if (view_scale_ppm < lltab[tabi][0]) break;
6493 MajorSpacing = lltab[tabi][1]; // major latitude distance
6494 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6495 return;
6496}
6497/* @ChartCanvas::CalcGridText *************************************
6498 **
6499 ** Calculates text to display at the major grid lines
6500 **
6501 ** @param [r] latlon [float] latitude or longitude of grid line
6502 ** @param [r] spacing [float] distance between two major grid lines
6503 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6504 **
6505 ** @return
6506 */
6507
6508wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6509 int deg = (int)fabs(latlon); // degrees
6510 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6511 char postfix;
6512
6513 // calculate postfix letter (NSEW)
6514 if (latlon > 0.0) {
6515 if (bPostfix) {
6516 postfix = 'N';
6517 } else {
6518 postfix = 'E';
6519 }
6520 } else if (latlon < 0.0) {
6521 if (bPostfix) {
6522 postfix = 'S';
6523 } else {
6524 postfix = 'W';
6525 }
6526 } else {
6527 postfix = ' '; // no postfix for equator and greenwich
6528 }
6529 // calculate text, display minutes only if spacing is smaller than one degree
6530
6531 wxString ret;
6532 if (spacing >= 1.0) {
6533 ret.Printf(_T("%3d%c %c"), deg, 0x00b0, postfix);
6534 } else if (spacing >= (1.0 / 60.0)) {
6535 ret.Printf(_T("%3d%c%02.0f %c"), deg, 0x00b0, min, postfix);
6536 } else {
6537 ret.Printf(_T("%3d%c%02.2f %c"), deg, 0x00b0, min, postfix);
6538 }
6539
6540 return ret;
6541}
6542
6543/* @ChartCanvas::GridDraw *****************************************
6544 **
6545 ** Draws major and minor Lat/Lon Grid on the chart
6546 ** - distance between Grid-lm ines are calculated automatic
6547 ** - major grid lines will be across the whole chart window
6548 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6549 **
6550 ** @param [w] dc [wxDC&] the wx drawing context
6551 **
6552 ** @return [void]
6553 ************************************************************************/
6554void ChartCanvas::GridDraw(ocpnDC &dc) {
6555 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6556
6557 double nlat, elon, slat, wlon;
6558 float lat, lon;
6559 float dlon;
6560 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6561 wxCoord w, h;
6562 wxPen GridPen(GetGlobalColor(_T ( "SNDG1" )), 1, wxPENSTYLE_SOLID);
6563 dc.SetPen(GridPen);
6564 if (!m_pgridFont) SetupGridFont();
6565 dc.SetFont(*m_pgridFont);
6566 dc.SetTextForeground(GetGlobalColor(_T ( "SNDG1" )));
6567
6568 w = m_canvas_width;
6569 h = m_canvas_height;
6570
6571 GetCanvasPixPoint(0, 0, nlat,
6572 wlon); // get lat/lon of upper left point of the window
6573 GetCanvasPixPoint(w, h, slat,
6574 elon); // get lat/lon of lower right point of the window
6575 dlon =
6576 elon -
6577 wlon; // calculate how many degrees of longitude are shown in the window
6578 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6579 {
6580 dlon = dlon + 360.0;
6581 }
6582 // calculate distance between latitude grid lines
6583 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6584
6585 // calculate position of first major latitude grid line
6586 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6587
6588 // Draw Major latitude grid lines and text
6589 while (lat < nlat) {
6590 wxPoint r;
6591 wxString st =
6592 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6593 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6594 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6595 dc.DrawText(st, 0, r.y); // draw text
6596 lat = lat + gridlatMajor;
6597
6598 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6599 }
6600
6601 // calculate position of first minor latitude grid line
6602 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6603
6604 // Draw minor latitude grid lines
6605 while (lat < nlat) {
6606 wxPoint r;
6607 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6608 dc.DrawLine(0, r.y, 10, r.y, false);
6609 dc.DrawLine(w - 10, r.y, w, r.y, false);
6610 lat = lat + gridlatMinor;
6611 }
6612
6613 // calculate distance between grid lines
6614 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6615
6616 // calculate position of first major latitude grid line
6617 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6618
6619 // draw major longitude grid lines
6620 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6621 wxPoint r;
6622 wxString st = CalcGridText(lon, gridlonMajor, false);
6623 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6624 dc.DrawLine(r.x, 0, r.x, h, false);
6625 dc.DrawText(st, r.x, 0);
6626 lon = lon + gridlonMajor;
6627 if (lon > 180.0) {
6628 lon = lon - 360.0;
6629 }
6630
6631 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6632 }
6633
6634 // calculate position of first minor longitude grid line
6635 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6636 // draw minor longitude grid lines
6637 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6638 wxPoint r;
6639 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6640 dc.DrawLine(r.x, 0, r.x, 10, false);
6641 dc.DrawLine(r.x, h - 10, r.x, h, false);
6642 lon = lon + gridlonMinor;
6643 if (lon > 180.0) {
6644 lon = lon - 360.0;
6645 }
6646 }
6647}
6648
6649void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6650 if (0 ) {
6651 double blat, blon, tlat, tlon;
6652 wxPoint r;
6653
6654 int x_origin = m_bDisplayGrid ? 60 : 20;
6655 int y_origin = m_canvas_height - 50;
6656
6657 float dist;
6658 int count;
6659 wxPen pen1, pen2;
6660
6661 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6662 {
6663 dist = 10.0;
6664 count = 5;
6665 pen1 = wxPen(GetGlobalColor(_T ( "SNDG2" )), 3, wxPENSTYLE_SOLID);
6666 pen2 = wxPen(GetGlobalColor(_T ( "SNDG1" )), 3, wxPENSTYLE_SOLID);
6667 } else // Draw 1 mile scale as SCALEB10
6668 {
6669 dist = 1.0;
6670 count = 10;
6671 pen1 = wxPen(GetGlobalColor(_T ( "SCLBR" )), 3, wxPENSTYLE_SOLID);
6672 pen2 = wxPen(GetGlobalColor(_T ( "CHGRD" )), 3, wxPENSTYLE_SOLID);
6673 }
6674
6675 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6676 double rotation = -VPoint.rotation;
6677
6678 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6679 GetCanvasPointPix(tlat, tlon, &r);
6680 int l1 = (y_origin - r.y) / count;
6681
6682 for (int i = 0; i < count; i++) {
6683 int y = l1 * i;
6684 if (i & 1)
6685 dc.SetPen(pen1);
6686 else
6687 dc.SetPen(pen2);
6688
6689 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6690 }
6691 } else {
6692 double blat, blon, tlat, tlon;
6693
6694 int x_origin = 5.0 * GetPixPerMM();
6695 int chartbar_height = GetChartbarHeight();
6696 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6697 // if (style->chartStatusWindowTransparent)
6698 // chartbar_height = 0;
6699 int y_origin = m_canvas_height - chartbar_height - 5;
6700#ifdef __WXOSX__
6701 if (!g_bopengl)
6702 y_origin =
6703 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6704#endif
6705
6706 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6707 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6708
6709 double d;
6710 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6711 d /= 2;
6712
6713 int unit = g_iDistanceFormat;
6714 if (d < .5 &&
6715 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6716 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6717
6718 // nice number
6719 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6720 float places = floor(logdist), rem = logdist - places;
6721 dist = pow(10, places);
6722
6723 if (rem < .2)
6724 dist /= 5;
6725 else if (rem < .5)
6726 dist /= 2;
6727
6728 wxString s = wxString::Format(_T("%g "), dist) + getUsrDistanceUnit(unit);
6729 wxPen pen1 = wxPen(GetGlobalColor(_T ( "UBLCK" )), 3, wxPENSTYLE_SOLID);
6730 double rotation = -VPoint.rotation;
6731
6732 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6733 &tlat, &tlon);
6734 wxPoint r;
6735 GetCanvasPointPix(tlat, tlon, &r);
6736 int l1 = r.x - x_origin;
6737
6738 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6739 12); // Store this for later reference
6740
6741 dc.SetPen(pen1);
6742
6743 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6744 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6745 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6746
6747 if (!m_pgridFont) SetupGridFont();
6748 dc.SetFont(*m_pgridFont);
6749 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
6750 int w, h;
6751 dc.GetTextExtent(s, &w, &h);
6752 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6753 if (g_bopengl) {
6754 w /= dpi_factor;
6755 h /= dpi_factor;
6756 }
6757 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6758 }
6759}
6760
6761void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6762 // Constants?
6763 double da_min = 2.;
6764 double da_max = 6.;
6765 double ra_min = 0.;
6766 double ra_max = 40.;
6767
6768 wxPen pen_save = dc.GetPen();
6769
6770 wxDateTime now = wxDateTime::Now();
6771
6772 dc.SetPen(pen);
6773
6774 int x0, y0, x1, y1;
6775
6776 x0 = x1 = x + radius; // Start point
6777 y0 = y1 = y;
6778 double angle = 0.;
6779 int i = 0;
6780
6781 while (angle < 360.) {
6782 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6783 angle += da;
6784
6785 if (angle > 360.) angle = 360.;
6786
6787 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6788
6789 double r;
6790 if (i & 1)
6791 r = radius + ra;
6792 else
6793 r = radius - ra;
6794
6795 x1 = (int)(x + cos(angle * PI / 180.) * r);
6796 y1 = (int)(y + sin(angle * PI / 180.) * r);
6797
6798 dc.DrawLine(x0, y0, x1, y1);
6799
6800 x0 = x1;
6801 y0 = y1;
6802
6803 i++;
6804 }
6805
6806 dc.DrawLine(x + radius, y, x1, y1); // closure
6807
6808 dc.SetPen(pen_save);
6809}
6810
6811static bool bAnchorSoundPlaying = false;
6812
6813static void onAnchorSoundFinished(void *ptr) {
6814 g_anchorwatch_sound->UnLoad();
6815 bAnchorSoundPlaying = false;
6816}
6817
6818void ChartCanvas::AlertDraw(ocpnDC &dc) {
6819 // Visual and audio alert for anchorwatch goes here
6820 bool play_sound = false;
6821 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6822 if (AnchorAlertOn1) {
6823 wxPoint TargetPoint;
6824 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
6825 &TargetPoint);
6826 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6827 TargetPoint.y, 100);
6828 play_sound = true;
6829 }
6830 } else
6831 AnchorAlertOn1 = false;
6832
6833 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6834 if (AnchorAlertOn2) {
6835 wxPoint TargetPoint;
6836 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
6837 &TargetPoint);
6838 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6839 TargetPoint.y, 100);
6840 play_sound = true;
6841 }
6842 } else
6843 AnchorAlertOn2 = false;
6844
6845 if (play_sound) {
6846 if (!bAnchorSoundPlaying) {
6847 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6848 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6849 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6850 if (g_anchorwatch_sound->IsOk()) {
6851 bAnchorSoundPlaying = true;
6852 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6853 g_anchorwatch_sound->Play();
6854 }
6855 }
6856 }
6857}
6858
6859void ChartCanvas::UpdateShips() {
6860 // Get the rectangle in the current dc which bounds the "ownship" symbol
6861
6862 wxClientDC dc(this);
6863 if (!dc.IsOk()) return;
6864
6865 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6866 if (!test_bitmap.IsOk()) return;
6867
6868 wxMemoryDC temp_dc(test_bitmap);
6869
6870 temp_dc.ResetBoundingBox();
6871 temp_dc.DestroyClippingRegion();
6872 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6873
6874 // Draw the ownship on the temp_dc
6875 ocpnDC ocpndc = ocpnDC(temp_dc);
6876 ShipDraw(ocpndc);
6877
6878 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6879 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6880 if (p) {
6881 wxPoint px;
6882 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6883 ocpndc.CalcBoundingBox(px.x, px.y);
6884 }
6885 }
6886
6887 ship_draw_rect =
6888 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6889 temp_dc.MaxY() - temp_dc.MinY());
6890
6891 wxRect own_ship_update_rect = ship_draw_rect;
6892
6893 if (!own_ship_update_rect.IsEmpty()) {
6894 // The required invalidate rectangle is the union of the last drawn
6895 // rectangle and this drawn rectangle
6896 own_ship_update_rect.Union(ship_draw_last_rect);
6897 own_ship_update_rect.Inflate(2);
6898 }
6899
6900 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6901
6902 ship_draw_last_rect = ship_draw_rect;
6903
6904 temp_dc.SelectObject(wxNullBitmap);
6905}
6906
6907void ChartCanvas::UpdateAlerts() {
6908 // Get the rectangle in the current dc which bounds the detected Alert
6909 // targets
6910
6911 // Use this dc
6912 wxClientDC dc(this);
6913
6914 // Get dc boundary
6915 int sx, sy;
6916 dc.GetSize(&sx, &sy);
6917
6918 // Need a bitmap
6919 wxBitmap test_bitmap(sx, sy, -1);
6920
6921 // Create a memory DC
6922 wxMemoryDC temp_dc;
6923 temp_dc.SelectObject(test_bitmap);
6924
6925 temp_dc.ResetBoundingBox();
6926 temp_dc.DestroyClippingRegion();
6927 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6928
6929 // Draw the Alert Targets on the temp_dc
6930 ocpnDC ocpndc = ocpnDC(temp_dc);
6931 AlertDraw(ocpndc);
6932
6933 // Retrieve the drawing extents
6934 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6935 temp_dc.MaxX() - temp_dc.MinX(),
6936 temp_dc.MaxY() - temp_dc.MinY());
6937
6938 if (!alert_rect.IsEmpty())
6939 alert_rect.Inflate(2); // clear all drawing artifacts
6940
6941 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6942 // The required invalidate rectangle is the union of the last drawn
6943 // rectangle and this drawn rectangle
6944 wxRect alert_update_rect = alert_draw_rect;
6945 alert_update_rect.Union(alert_rect);
6946
6947 // Invalidate the rectangular region
6948 RefreshRect(alert_update_rect, false);
6949 }
6950
6951 // Save this rectangle for next time
6952 alert_draw_rect = alert_rect;
6953
6954 temp_dc.SelectObject(wxNullBitmap); // clean up
6955}
6956
6957void ChartCanvas::UpdateAIS() {
6958 if (!g_pAIS) return;
6959
6960 // Get the rectangle in the current dc which bounds the detected AIS targets
6961
6962 // Use this dc
6963 wxClientDC dc(this);
6964
6965 // Get dc boundary
6966 int sx, sy;
6967 dc.GetSize(&sx, &sy);
6968
6969 wxRect ais_rect;
6970
6971 // How many targets are there?
6972
6973 // If more than "some number", it will be cheaper to refresh the entire
6974 // screen than to build update rectangles for each target.
6975 if (g_pAIS->GetTargetList().size() > 10) {
6976 ais_rect = wxRect(0, 0, sx, sy); // full screen
6977 } else {
6978 // Need a bitmap
6979 wxBitmap test_bitmap(sx, sy, -1);
6980
6981 // Create a memory DC
6982 wxMemoryDC temp_dc;
6983 temp_dc.SelectObject(test_bitmap);
6984
6985 temp_dc.ResetBoundingBox();
6986 temp_dc.DestroyClippingRegion();
6987 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6988
6989 // Draw the AIS Targets on the temp_dc
6990 ocpnDC ocpndc = ocpnDC(temp_dc);
6991 AISDraw(ocpndc, GetVP(), this);
6992 AISDrawAreaNotices(ocpndc, GetVP(), this);
6993
6994 // Retrieve the drawing extents
6995 ais_rect =
6996 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6997 temp_dc.MaxY() - temp_dc.MinY());
6998
6999 if (!ais_rect.IsEmpty())
7000 ais_rect.Inflate(2); // clear all drawing artifacts
7001
7002 temp_dc.SelectObject(wxNullBitmap); // clean up
7003 }
7004
7005 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
7006 // The required invalidate rectangle is the union of the last drawn
7007 // rectangle and this drawn rectangle
7008 wxRect ais_update_rect = ais_draw_rect;
7009 ais_update_rect.Union(ais_rect);
7010
7011 // Invalidate the rectangular region
7012 RefreshRect(ais_update_rect, false);
7013 }
7014
7015 // Save this rectangle for next time
7016 ais_draw_rect = ais_rect;
7017}
7018
7019void ChartCanvas::ToggleCPAWarn() {
7020 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
7021 wxString mess;
7022 if (g_bCPAWarn) {
7023 g_bTCPA_Max = true;
7024 mess = _("ON");
7025 } else {
7026 g_bTCPA_Max = false;
7027 mess = _("OFF");
7028 }
7029 // Print to status bar if available.
7030 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
7031 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7032 } else {
7033 if (!g_AisFirstTimeUse) {
7034 OCPNMessageBox(this,
7035 _("CPA Alarm is switched") + _T(" ") + mess.MakeLower(),
7036 _("CPA") + _T(" ") + mess, 4, 4);
7037 }
7038 }
7039}
7040
7041void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7042
7043void ChartCanvas::OnSize(wxSizeEvent &event) {
7044 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7045 // GetClientSize returns the size of the canvas area in logical pixels.
7046 GetClientSize(&m_canvas_width, &m_canvas_height);
7047
7048#ifdef __WXOSX__
7049 // Support scaled HDPI displays.
7050 m_displayScale = GetContentScaleFactor();
7051#endif
7052
7053 // Convert to physical pixels.
7054 m_canvas_width *= m_displayScale;
7055 m_canvas_height *= m_displayScale;
7056
7057 // Resize the current viewport
7058 VPoint.pix_width = m_canvas_width;
7059 VPoint.pix_height = m_canvas_height;
7060 VPoint.SetPixelScale(m_displayScale);
7061
7062 // Get some canvas metrics
7063
7064 // Rescale to current value, in order to rebuild VPoint data
7065 // structures for new canvas size
7067
7068 m_absolute_min_scale_ppm =
7069 m_canvas_width /
7070 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7071
7072 // Inform the parent Frame that I am being resized...
7073 gFrame->ProcessCanvasResize();
7074
7075 // if MUIBar is active, size the bar
7076 // if(g_useMUI && !m_muiBar){ // rebuild if
7077 // necessary
7078 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7079 // m_muiBarHOSize = m_muiBar->GetSize();
7080 // }
7081
7082 if (m_muiBar) {
7083 SetMUIBarPosition();
7084 UpdateFollowButtonState();
7085 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7086 }
7087
7088 // Set up the scroll margins
7089 xr_margin = m_canvas_width * 95 / 100;
7090 xl_margin = m_canvas_width * 5 / 100;
7091 yt_margin = m_canvas_height * 5 / 100;
7092 yb_margin = m_canvas_height * 95 / 100;
7093
7094 if (m_pQuilt)
7095 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7096
7097 // Resize the scratch BM
7098 delete pscratch_bm;
7099 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7100 m_brepaint_piano = true;
7101
7102 // Resize the Route Calculation BM
7103 m_dc_route.SelectObject(wxNullBitmap);
7104 delete proute_bm;
7105 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7106 m_dc_route.SelectObject(*proute_bm);
7107
7108 // Resize the saved Bitmap
7109 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7110
7111 // Resize the working Bitmap
7112 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7113
7114 // Rescale again, to capture all the changes for new canvas size
7116
7117#ifdef ocpnUSE_GL
7118 if (/*g_bopengl &&*/ m_glcc) {
7119 // FIXME (dave) This can go away?
7120 m_glcc->OnSize(event);
7121 }
7122#endif
7123
7124 FormatPianoKeys();
7125 // Invalidate the whole window
7126 ReloadVP();
7127}
7128
7129void ChartCanvas::ProcessNewGUIScale() {
7130 // m_muiBar->Hide();
7131 delete m_muiBar;
7132 m_muiBar = 0;
7133
7134 CreateMUIBar();
7135}
7136
7137void ChartCanvas::CreateMUIBar() {
7138 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7139
7140 // We need to update the m_bENCGroup flag, at least for the initial creation
7141 // of a MUIBar
7142 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7143
7144 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7145 m_muiBar->SetColorScheme(m_cs);
7146 m_muiBarHOSize = m_muiBar->m_size;
7147 }
7148
7149 if (m_muiBar) {
7150 SetMUIBarPosition();
7151 UpdateFollowButtonState();
7152 m_muiBar->UpdateDynamicValues();
7153 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7154 }
7155}
7156
7157void ChartCanvas::SetMUIBarPosition() {
7158 // if MUIBar is active, size the bar
7159 if (m_muiBar) {
7160 // We estimate the piano width based on the canvas width
7161 int pianoWidth = GetClientSize().x * 0.6f;
7162 // If the piano already exists, we can use its exact width
7163 // if(m_Piano)
7164 // pianoWidth = m_Piano->GetWidth();
7165
7166 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7167 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7168 delete m_muiBar;
7169 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7170 m_muiBar->SetColorScheme(m_cs);
7171 }
7172 }
7173
7174 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7175 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7176 delete m_muiBar;
7177 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7178 m_muiBar->SetColorScheme(m_cs);
7179 }
7180 }
7181
7182 m_muiBar->SetBestPosition();
7183 }
7184}
7185
7186void ChartCanvas::DestroyMuiBar() {
7187 if (m_muiBar) {
7188 delete m_muiBar;
7189 m_muiBar = NULL;
7190 }
7191}
7192
7193void ChartCanvas::ShowCompositeInfoWindow(
7194 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7195 if (n_charts > 0) {
7196 if (NULL == m_pCIWin) {
7197 m_pCIWin = new ChInfoWin(this);
7198 m_pCIWin->Hide();
7199 }
7200
7201 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7202 wxString s;
7203
7204 s = _("Composite of ");
7205
7206 wxString s1;
7207 s1.Printf("%d ", n_charts);
7208 if (n_charts > 1)
7209 s1 += _("charts");
7210 else
7211 s1 += _("chart");
7212 s += s1;
7213 s += '\n';
7214
7215 s1.Printf(_("Chart scale"));
7216 s1 += ": ";
7217 wxString s2;
7218 s2.Printf("1:%d\n", scale);
7219 s += s1;
7220 s += s2;
7221
7222 s1 = _("Zoom in for more information");
7223 s += s1;
7224 s += '\n';
7225
7226 int char_width = s1.Length();
7227 int char_height = 3;
7228
7229 if (g_bChartBarEx) {
7230 s += '\n';
7231 int j = 0;
7232 for (int i : index_vector) {
7233 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7234 wxString path = cte.GetFullSystemPath();
7235 s += path;
7236 s += '\n';
7237 char_height++;
7238 char_width = wxMax(char_width, path.Length());
7239 if (j++ >= 9) break;
7240 }
7241 if (j >= 9) {
7242 s += " .\n .\n .\n";
7243 char_height += 3;
7244 }
7245 s += '\n';
7246 char_height += 1;
7247
7248 char_width += 4; // Fluff
7249 }
7250
7251 m_pCIWin->SetString(s);
7252
7253 m_pCIWin->FitToChars(char_width, char_height);
7254
7255 wxPoint p;
7256 p.x = x / GetContentScaleFactor();
7257 if ((p.x + m_pCIWin->GetWinSize().x) >
7258 (m_canvas_width / GetContentScaleFactor()))
7259 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7260 m_pCIWin->GetWinSize().x) /
7261 2; // centered
7262
7263 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7264 4 - m_pCIWin->GetWinSize().y;
7265
7266 m_pCIWin->dbIndex = 0;
7267 m_pCIWin->chart_scale = 0;
7268 m_pCIWin->SetPosition(p);
7269 m_pCIWin->SetBitmap();
7270 m_pCIWin->Refresh();
7271 m_pCIWin->Show();
7272 }
7273 } else {
7274 HideChartInfoWindow();
7275 }
7276}
7277
7278void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7279 if (dbIndex >= 0) {
7280 if (NULL == m_pCIWin) {
7281 m_pCIWin = new ChInfoWin(this);
7282 m_pCIWin->Hide();
7283 }
7284
7285 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7286 wxString s;
7287 ChartBase *pc = NULL;
7288
7289 // TOCTOU race but worst case will reload chart.
7290 // need to lock it or the background spooler may evict charts in
7291 // OpenChartFromDBAndLock
7292 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7293 pc = ChartData->OpenChartFromDBAndLock(
7294 dbIndex, FULL_INIT); // this must come from cache
7295
7296 int char_width, char_height;
7297 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7298 if (pc) ChartData->UnLockCacheChart(dbIndex);
7299
7300 m_pCIWin->SetString(s);
7301 m_pCIWin->FitToChars(char_width, char_height);
7302
7303 wxPoint p;
7304 p.x = x / GetContentScaleFactor();
7305 if ((p.x + m_pCIWin->GetWinSize().x) >
7306 (m_canvas_width / GetContentScaleFactor()))
7307 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7308 m_pCIWin->GetWinSize().x) /
7309 2; // centered
7310
7311 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7312 4 - m_pCIWin->GetWinSize().y;
7313
7314 m_pCIWin->dbIndex = dbIndex;
7315 m_pCIWin->SetPosition(p);
7316 m_pCIWin->SetBitmap();
7317 m_pCIWin->Refresh();
7318 m_pCIWin->Show();
7319 }
7320 } else {
7321 HideChartInfoWindow();
7322 }
7323}
7324
7325void ChartCanvas::HideChartInfoWindow(void) {
7326 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7327 m_pCIWin->Hide();
7328 m_pCIWin->Destroy();
7329 m_pCIWin = NULL;
7330
7331#ifdef __ANDROID__
7332 androidForceFullRepaint();
7333#endif
7334 }
7335}
7336
7337void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7338 wxMouseEvent ev(wxEVT_MOTION);
7339 ev.m_x = mouse_x;
7340 ev.m_y = mouse_y;
7341 ev.m_leftDown = mouse_leftisdown;
7342
7343 wxEvtHandler *evthp = GetEventHandler();
7344
7345 ::wxPostEvent(evthp, ev);
7346}
7347
7348void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7349 if ((m_panx_target_final - m_panx_target_now) ||
7350 (m_pany_target_final - m_pany_target_now)) {
7351 DoTimedMovementTarget();
7352 } else
7353 DoTimedMovement();
7354}
7355
7356void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7357
7358bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7359 int delta) {
7360 if (m_disable_edge_pan) return false;
7361
7362 bool bft = false;
7363 int pan_margin = m_canvas_width * margin / 100;
7364 int pan_timer_set = 200;
7365 double pan_delta = GetVP().pix_width * delta / 100;
7366 int pan_x = 0;
7367 int pan_y = 0;
7368
7369 if (x > m_canvas_width - pan_margin) {
7370 bft = true;
7371 pan_x = pan_delta;
7372 }
7373
7374 else if (x < pan_margin) {
7375 bft = true;
7376 pan_x = -pan_delta;
7377 }
7378
7379 if (y < pan_margin) {
7380 bft = true;
7381 pan_y = -pan_delta;
7382 }
7383
7384 else if (y > m_canvas_height - pan_margin) {
7385 bft = true;
7386 pan_y = pan_delta;
7387 }
7388
7389 // Of course, if dragging, and the mouse left button is not down, we must
7390 // stop the event injection
7391 if (bdragging) {
7392 if (!g_btouch) {
7393 wxMouseState state = ::wxGetMouseState();
7394#if wxCHECK_VERSION(3, 0, 0)
7395 if (!state.LeftIsDown())
7396#else
7397 if (!state.LeftDown())
7398#endif
7399 bft = false;
7400 }
7401 }
7402 if ((bft) && !pPanTimer->IsRunning()) {
7403 PanCanvas(pan_x, pan_y);
7404 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7405 return true;
7406 }
7407
7408 // This mouse event must not be due to pan timer event injector
7409 // Mouse is out of the pan zone, so prevent any orphan event injection
7410 if ((!bft) && pPanTimer->IsRunning()) {
7411 pPanTimer->Stop();
7412 }
7413
7414 return (false);
7415}
7416
7417// Look for waypoints at the current position.
7418// Used to determine what a mouse event should act on.
7419
7420void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7421 bool setBeingEdited) {
7422 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7423 m_pRoutePointEditTarget = NULL;
7424 m_pFoundPoint = NULL;
7425
7426 SelectItem *pFind = NULL;
7427 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7428 SelectableItemList SelList = pSelect->FindSelectionList(
7429 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7430 wxSelectableItemListNode *node = SelList.GetFirst();
7431 while (node) {
7432 pFind = node->GetData();
7433
7434 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7435
7436 // Get an array of all routes using this point
7437 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7438 // TODO: delete m_pEditRouteArray after use?
7439
7440 // Use route array to determine actual visibility for the point
7441 bool brp_viz = false;
7442 if (m_pEditRouteArray) {
7443 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7444 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7445 if (pr->IsVisible()) {
7446 brp_viz = true;
7447 break;
7448 }
7449 }
7450 } else
7451 brp_viz = frp->IsVisible(); // isolated point
7452
7453 if (brp_viz) {
7454 // Use route array to rubberband all affected routes
7455 if (m_pEditRouteArray) // Editing Waypoint as part of route
7456 {
7457 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7458 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7459 pr->m_bIsBeingEdited = setBeingEdited;
7460 }
7461 m_bRouteEditing = setBeingEdited;
7462 } else // editing Mark
7463 {
7464 frp->m_bRPIsBeingEdited = setBeingEdited;
7465 m_bMarkEditing = setBeingEdited;
7466 }
7467
7468 m_pRoutePointEditTarget = frp;
7469 m_pFoundPoint = pFind;
7470 break; // out of the while(node)
7471 }
7472
7473 node = node->GetNext();
7474 } // while (node)
7475}
7476std::shared_ptr<PI_PointContext> ChartCanvas::GetCanvasContextAtPoint(int x,
7477 int y) {
7478 // General Right Click
7479 // Look for selectable objects
7480 double slat, slon;
7481 GetCanvasPixPoint(x, y, slat, slon);
7482
7483 SelectItem *pFindAIS;
7484 SelectItem *pFindRP;
7485 SelectItem *pFindRouteSeg;
7486 SelectItem *pFindTrackSeg;
7487 SelectItem *pFindCurrent = NULL;
7488 SelectItem *pFindTide = NULL;
7489
7490 // Get all the selectable things at the selected point
7491 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7492 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7493 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7494 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7495 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7496
7497 if (m_bShowCurrent)
7498 pFindCurrent =
7499 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7500
7501 if (m_bShowTide) // look for tide stations
7502 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7503
7504 int seltype = 0;
7505
7506 // Try for AIS targets first
7507 int FoundAIS_MMSI = 0;
7508 if (pFindAIS) {
7509 FoundAIS_MMSI = pFindAIS->GetUserData();
7510
7511 // Make sure the target data is available
7512 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7513 seltype |= SELTYPE_AISTARGET;
7514 }
7515
7516 // Now the various Route Parts
7517
7518 RoutePoint *FoundRoutePoint = NULL;
7519 Route *SelectedRoute = NULL;
7520
7521 if (pFindRP) {
7522 RoutePoint *pFirstVizPoint = NULL;
7523 RoutePoint *pFoundActiveRoutePoint = NULL;
7524 RoutePoint *pFoundVizRoutePoint = NULL;
7525 Route *pSelectedActiveRoute = NULL;
7526 Route *pSelectedVizRoute = NULL;
7527
7528 // There is at least one routepoint, so get the whole list
7529 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7530 SelectableItemList SelList =
7531 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7532 wxSelectableItemListNode *node = SelList.GetFirst();
7533 while (node) {
7534 SelectItem *pFindSel = node->GetData();
7535
7536 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7537
7538 // Get an array of all routes using this point
7539 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7540
7541 // Use route array (if any) to determine actual visibility for this point
7542 bool brp_viz = false;
7543 if (proute_array) {
7544 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7545 Route *pr = (Route *)proute_array->Item(ir);
7546 if (pr->IsVisible()) {
7547 brp_viz = true;
7548 break;
7549 }
7550 }
7551 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7552 // but still exists as a waypoint
7553 brp_viz = prp->IsVisible(); // so treat as isolated point
7554
7555 } else
7556 brp_viz = prp->IsVisible(); // isolated point
7557
7558 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7559
7560 // Use route array to choose the appropriate route
7561 // Give preference to any active route, otherwise select the first visible
7562 // route in the array for this point
7563 if (proute_array) {
7564 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7565 Route *pr = (Route *)proute_array->Item(ir);
7566 if (pr->m_bRtIsActive) {
7567 pSelectedActiveRoute = pr;
7568 pFoundActiveRoutePoint = prp;
7569 break;
7570 }
7571 }
7572
7573 if (NULL == pSelectedVizRoute) {
7574 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7575 Route *pr = (Route *)proute_array->Item(ir);
7576 if (pr->IsVisible()) {
7577 pSelectedVizRoute = pr;
7578 pFoundVizRoutePoint = prp;
7579 break;
7580 }
7581 }
7582 }
7583
7584 delete proute_array;
7585 }
7586
7587 node = node->GetNext();
7588 }
7589
7590 // Now choose the "best" selections
7591 if (pFoundActiveRoutePoint) {
7592 FoundRoutePoint = pFoundActiveRoutePoint;
7593 SelectedRoute = pSelectedActiveRoute;
7594 } else if (pFoundVizRoutePoint) {
7595 FoundRoutePoint = pFoundVizRoutePoint;
7596 SelectedRoute = pSelectedVizRoute;
7597 } else
7598 // default is first visible point in list
7599 FoundRoutePoint = pFirstVizPoint;
7600
7601 if (SelectedRoute) {
7602 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7603 } else if (FoundRoutePoint)
7604 seltype |= SELTYPE_MARKPOINT;
7605
7606 // Highlite the selected point, to verify the proper right click
7607 // selection
7608#if 0
7609 if (m_pFoundRoutePoint) {
7610 m_pFoundRoutePoint->m_bPtIsSelected = true;
7611 wxRect wp_rect;
7612 RoutePointGui(*m_pFoundRoutePoint)
7613 .CalculateDCRect(m_dc_route, this, &wp_rect);
7614 RefreshRect(wp_rect, true);
7615 }
7616#endif
7617 }
7618
7619 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7620 // routes But call the popup handler with identifier appropriate to the type
7621 if (pFindRouteSeg) // there is at least one select item
7622 {
7623 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7624 SelectableItemList SelList =
7625 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7626
7627 if (NULL == SelectedRoute) // the case where a segment only is selected
7628 {
7629 // Choose the first visible route containing segment in the list
7630 wxSelectableItemListNode *node = SelList.GetFirst();
7631 while (node) {
7632 SelectItem *pFindSel = node->GetData();
7633
7634 Route *pr = (Route *)pFindSel->m_pData3;
7635 if (pr->IsVisible()) {
7636 SelectedRoute = pr;
7637 break;
7638 }
7639 node = node->GetNext();
7640 }
7641 }
7642
7643 if (SelectedRoute) {
7644 if (NULL == FoundRoutePoint)
7645 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7646
7647 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7648 seltype |= SELTYPE_ROUTESEGMENT;
7649 }
7650 }
7651
7652#if 0
7653 if (pFindTrackSeg) {
7654 m_pSelectedTrack = NULL;
7655 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7656 SelectableItemList SelList =
7657 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7658
7659 // Choose the first visible track containing segment in the list
7660 wxSelectableItemListNode *node = SelList.GetFirst();
7661 while (node) {
7662 SelectItem *pFindSel = node->GetData();
7663
7664 Track *pt = (Track *)pFindSel->m_pData3;
7665 if (pt->IsVisible()) {
7666 m_pSelectedTrack = pt;
7667 break;
7668 }
7669 node = node->GetNext();
7670 }
7671
7672 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7673 }
7674#endif
7675
7676#if 0
7677 bool bseltc = false;
7678 // if(0 == seltype)
7679 {
7680 if (pFindCurrent) {
7681 // There may be multiple current entries at the same point.
7682 // For example, there often is a current substation (with directions
7683 // specified) co-located with its master. We want to select the
7684 // substation, so that the direction will be properly indicated on the
7685 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
7686 // substation)
7687 IDX_entry *pIDX_best_candidate;
7688
7689 SelectItem *pFind = NULL;
7690 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7691 SelectableItemList SelList = pSelectTC->FindSelectionList(
7692 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_CURRENTPOINT);
7693
7694 // Default is first entry
7695 wxSelectableItemListNode *node = SelList.GetFirst();
7696 pFind = node->GetData();
7697 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7698
7699 if (SelList.GetCount() > 1) {
7700 node = node->GetNext();
7701 while (node) {
7702 pFind = node->GetData();
7703 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
7704 if (pIDX_candidate->IDX_type == 'c') {
7705 pIDX_best_candidate = pIDX_candidate;
7706 break;
7707 }
7708
7709 node = node->GetNext();
7710 } // while (node)
7711 } else {
7712 wxSelectableItemListNode *node = SelList.GetFirst();
7713 pFind = node->GetData();
7714 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7715 }
7716
7717 m_pIDXCandidate = pIDX_best_candidate;
7718
7719 if (0 == seltype) {
7720 DrawTCWindow(x, y, (void *)pIDX_best_candidate);
7721 Refresh(false);
7722 bseltc = true;
7723 } else
7724 seltype |= SELTYPE_CURRENTPOINT;
7725 }
7726
7727 else if (pFindTide) {
7728 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
7729
7730 if (0 == seltype) {
7731 DrawTCWindow(x, y, (void *)pFindTide->m_pData1);
7732 Refresh(false);
7733 bseltc = true;
7734 } else
7735 seltype |= SELTYPE_TIDEPOINT;
7736 }
7737 }
7738#endif
7739
7740 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7741
7742 // Populate the return struct
7743 auto rstruct = std::make_shared<PI_PointContext>();
7744 rstruct->object_type = OBJECT_CHART;
7745 rstruct->object_ident = "";
7746
7747 if (seltype == SELTYPE_AISTARGET) {
7748 rstruct->object_type = OBJECT_AISTARGET;
7749 wxString val;
7750 val.Printf("%d", FoundAIS_MMSI);
7751 rstruct->object_ident = val.ToStdString();
7752 } else if (seltype & SELTYPE_MARKPOINT) {
7753 if (FoundRoutePoint) {
7754 rstruct->object_type = OBJECT_ROUTEPOINT;
7755 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7756 }
7757 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7758 if (SelectedRoute) {
7759 rstruct->object_type = OBJECT_ROUTESEGMENT;
7760 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7761 }
7762 }
7763
7764 return rstruct;
7765}
7766
7767void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7768 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7769 singleClickEventIsValid = false;
7770 m_DoubleClickTimer->Stop();
7771}
7772
7773bool leftIsDown;
7774
7775bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7776 if (!m_bChartDragging && !m_bDrawingRoute) {
7777 /*
7778 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7779 * mouse event coordinates are in logical pixels.
7780 */
7781 if (m_Compass && m_Compass->IsShown()) {
7782 wxRect logicalRect = m_Compass->GetLogicalRect();
7783 bool isInCompass = logicalRect.Contains(event.GetPosition());
7784 if (isInCompass) {
7785 if (m_Compass->MouseEvent(event)) {
7786 cursor_region = CENTER;
7787 if (!g_btouch) SetCanvasCursor(event);
7788 return true;
7789 }
7790 }
7791 }
7792
7793 if (m_notification_button && m_notification_button->IsShown()) {
7794 wxRect logicalRect = m_notification_button->GetLogicalRect();
7795 bool isinButton = logicalRect.Contains(event.GetPosition());
7796 if (isinButton) {
7797 SetCursor(*pCursorArrow);
7798 if (event.LeftDown()) HandleNotificationMouseClick();
7799 return true;
7800 }
7801 }
7802
7803 if (MouseEventToolbar(event)) return true;
7804
7805 if (MouseEventChartBar(event)) return true;
7806
7807 if (MouseEventMUIBar(event)) return true;
7808
7809 if (MouseEventIENCBar(event)) return true;
7810 }
7811 return false;
7812}
7813
7814void ChartCanvas::HandleNotificationMouseClick() {
7815 if (!m_NotificationsList) {
7816 m_NotificationsList = new NotificationsList(this);
7817
7818 // calculate best size for Notification list
7819 m_NotificationsList->RecalculateSize();
7820 m_NotificationsList->Hide();
7821 }
7822
7823 if (m_NotificationsList->IsShown()) {
7824 m_NotificationsList->Hide();
7825 } else {
7826 m_NotificationsList->RecalculateSize();
7827 m_NotificationsList->ReloadNotificationList();
7828 m_NotificationsList->Show();
7829 }
7830}
7831bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7832 if (!g_bShowChartBar) return false;
7833
7834 if (!m_Piano->MouseEvent(event)) return false;
7835
7836 cursor_region = CENTER;
7837 if (!g_btouch) SetCanvasCursor(event);
7838 return true;
7839}
7840
7841bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7842 if (!IsPrimaryCanvas()) return false;
7843
7844 if (g_MainToolbar) {
7845 if (!g_MainToolbar->MouseEvent(event))
7846 return false;
7847 else
7848 g_MainToolbar->RefreshToolbar();
7849 }
7850
7851 cursor_region = CENTER;
7852 if (!g_btouch) SetCanvasCursor(event);
7853 return true;
7854}
7855
7856bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7857 if (!IsPrimaryCanvas()) return false;
7858
7859 if (g_iENCToolbar) {
7860 if (!g_iENCToolbar->MouseEvent(event))
7861 return false;
7862 else {
7863 g_iENCToolbar->RefreshToolbar();
7864 return true;
7865 }
7866 }
7867 return false;
7868}
7869
7870bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7871 if (m_muiBar) {
7872 if (!m_muiBar->MouseEvent(event)) return false;
7873 }
7874
7875 cursor_region = CENTER;
7876 if (!g_btouch) SetCanvasCursor(event);
7877 if (m_muiBar)
7878 return true;
7879 else
7880 return false;
7881}
7882
7883bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7884 int x, y;
7885
7886 bool bret = false;
7887
7888 event.GetPosition(&x, &y);
7889
7890 x *= m_displayScale;
7891 y *= m_displayScale;
7892
7893 m_MouseDragging = event.Dragging();
7894
7895 // Some systems produce null drag events, where the pointer position has not
7896 // changed from the previous value. Detect this case, and abort further
7897 // processing (FS#1748)
7898#ifdef __WXMSW__
7899 if (event.Dragging()) {
7900 if ((x == mouse_x) && (y == mouse_y)) return true;
7901 }
7902#endif
7903
7904 mouse_x = x;
7905 mouse_y = y;
7906 mouse_leftisdown = event.LeftDown();
7908
7909 // Establish the event region
7910 cursor_region = CENTER;
7911
7912 int chartbar_height = GetChartbarHeight();
7913
7914 if (m_Compass && m_Compass->IsShown() &&
7915 m_Compass->GetRect().Contains(event.GetPosition())) {
7916 cursor_region = CENTER;
7917 } else if (x > xr_margin) {
7918 cursor_region = MID_RIGHT;
7919 } else if (x < xl_margin) {
7920 cursor_region = MID_LEFT;
7921 } else if (y > yb_margin - chartbar_height &&
7922 y < m_canvas_height - chartbar_height) {
7923 cursor_region = MID_TOP;
7924 } else if (y < yt_margin) {
7925 cursor_region = MID_BOT;
7926 } else {
7927 cursor_region = CENTER;
7928 }
7929
7930 if (!g_btouch) SetCanvasCursor(event);
7931
7932 // Protect from leftUp's coming from event handlers in child
7933 // windows who return focus to the canvas.
7934 leftIsDown = event.LeftDown();
7935
7936#ifndef __WXOSX__
7937 if (event.LeftDown()) {
7938 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7939 // The menu bar is temporarily visible due to alt having been pressed.
7940 // Clicking will hide it, and do nothing else.
7941 g_bTempShowMenuBar = false;
7942 parent_frame->ApplyGlobalSettings(false);
7943 return (true);
7944 }
7945 }
7946#endif
7947
7948 // Update modifiers here; some window managers never send the key event
7949 m_modkeys = 0;
7950 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7951 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7952
7953#ifdef __WXMSW__
7954 // TODO Test carefully in other platforms, remove ifdef....
7955 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7956 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7957#endif
7958
7959 event.SetEventObject(this);
7960 if (SendMouseEventToPlugins(event))
7961 return (true); // PlugIn did something, and does not want the canvas to
7962 // do anything else
7963
7964 // Capture LeftUp's and time them, unless it already came from the timer.
7965
7966 // Detect end of chart dragging
7967 if (g_btouch && m_bChartDragging && event.LeftUp()) {
7968 StartChartDragInertia();
7969 }
7970
7971 if (b_handle_dclick && event.LeftUp() && !singleClickEventIsValid) {
7972 // Ignore the second LeftUp after the DClick.
7973 if (m_DoubleClickTimer->IsRunning()) {
7974 m_DoubleClickTimer->Stop();
7975 return (true);
7976 }
7977
7978 // Save the event for later running if there is no DClick.
7979 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7980 singleClickEvent = event;
7981 singleClickEventIsValid = true;
7982 return (true);
7983 }
7984
7985 // This logic is necessary on MSW to handle the case where
7986 // a context (right-click) menu is dismissed without action
7987 // by clicking on the chart surface.
7988 // We need to avoid an unintentional pan by eating some clicks...
7989#ifdef __WXMSW__
7990 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7991 if (g_click_stop > 0) {
7992 g_click_stop--;
7993 return (true);
7994 }
7995 }
7996#endif
7997
7998 // Kick off the Rotation control timer
7999 if (GetUpMode() == COURSE_UP_MODE) {
8000 m_b_rot_hidef = false;
8001 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
8002 } else
8003 pRotDefTimer->Stop();
8004
8005 // Retrigger the route leg / AIS target popup timer
8006 bool bRoll = !g_btouch;
8007#ifdef __ANDROID__
8008 bRoll = g_bRollover;
8009#endif
8010 if (bRoll) {
8011 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
8012 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
8013 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
8014 m_RolloverPopupTimer.Start(
8015 10,
8016 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
8017 else
8018 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
8019 }
8020
8021 // Retrigger the cursor tracking timer
8022 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
8023
8024// Show cursor position on Status Bar, if present
8025// except for GTK, under which status bar updates are very slow
8026// due to Update() call.
8027// In this case, as a workaround, update the status window
8028// after an interval timer (pCurTrackTimer) pops, which will happen
8029// whenever the mouse has stopped moving for specified interval.
8030// See the method OnCursorTrackTimerEvent()
8031#if !defined(__WXGTK__) && !defined(__WXQT__)
8032 SetCursorStatus(m_cursor_lat, m_cursor_lon);
8033#endif
8034
8035 // Send the current cursor lat/lon to all PlugIns requesting it
8036 if (g_pi_manager) {
8037 // Occasionally, MSW will produce nonsense events on right click....
8038 // This results in an error in cursor geo position, so we skip this case
8039 if ((x >= 0) && (y >= 0))
8040 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
8041 }
8042
8043 if (!g_btouch) {
8044 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
8045 wxPoint p = ClientToScreen(wxPoint(x, y));
8046 }
8047 }
8048
8049 if (1 ) {
8050 // Route Creation Rubber Banding
8051 if (m_routeState >= 2) {
8052 r_rband.x = x;
8053 r_rband.y = y;
8054 m_bDrawingRoute = true;
8055
8056 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8057 Refresh(false);
8058 }
8059
8060 // Measure Tool Rubber Banding
8061 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
8062 r_rband.x = x;
8063 r_rband.y = y;
8064 m_bDrawingRoute = true;
8065
8066 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8067 Refresh(false);
8068 }
8069 }
8070 return bret;
8071}
8072
8073int ChartCanvas::PrepareContextSelections(double lat, double lon) {
8074 // On general Right Click
8075 // Look for selectable objects
8076 double slat = lat;
8077 double slon = lon;
8078
8079#if defined(__WXMAC__) || defined(__ANDROID__)
8080 wxScreenDC sdc;
8081 ocpnDC dc(sdc);
8082#else
8083 wxClientDC cdc(GetParent());
8084 ocpnDC dc(cdc);
8085#endif
8086
8087 SelectItem *pFindAIS;
8088 SelectItem *pFindRP;
8089 SelectItem *pFindRouteSeg;
8090 SelectItem *pFindTrackSeg;
8091 SelectItem *pFindCurrent = NULL;
8092 SelectItem *pFindTide = NULL;
8093
8094 // Deselect any current objects
8095 if (m_pSelectedRoute) {
8096 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
8097 m_pSelectedRoute->DeSelectRoute();
8098#ifdef ocpnUSE_GL
8099 if (g_bopengl && m_glcc) {
8100 InvalidateGL();
8101 Update();
8102 } else
8103#endif
8104 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8105 }
8106
8107 if (m_pFoundRoutePoint) {
8108 m_pFoundRoutePoint->m_bPtIsSelected = false;
8109 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8110 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8111 }
8112
8115 if (g_btouch && m_pRoutePointEditTarget) {
8116 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8117 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8118 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8119 }
8120
8121 // Get all the selectable things at the cursor
8122 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8123 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8124 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8125 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8126 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8127
8128 if (m_bShowCurrent)
8129 pFindCurrent =
8130 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8131
8132 if (m_bShowTide) // look for tide stations
8133 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8134
8135 int seltype = 0;
8136
8137 // Try for AIS targets first
8138 if (pFindAIS) {
8139 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8140
8141 // Make sure the target data is available
8142 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8143 seltype |= SELTYPE_AISTARGET;
8144 }
8145
8146 // Now examine the various Route parts
8147
8148 m_pFoundRoutePoint = NULL;
8149 if (pFindRP) {
8150 RoutePoint *pFirstVizPoint = NULL;
8151 RoutePoint *pFoundActiveRoutePoint = NULL;
8152 RoutePoint *pFoundVizRoutePoint = NULL;
8153 Route *pSelectedActiveRoute = NULL;
8154 Route *pSelectedVizRoute = NULL;
8155
8156 // There is at least one routepoint, so get the whole list
8157 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8158 SelectableItemList SelList =
8159 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8160 wxSelectableItemListNode *node = SelList.GetFirst();
8161 while (node) {
8162 SelectItem *pFindSel = node->GetData();
8163
8164 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8165
8166 // Get an array of all routes using this point
8167 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8168
8169 // Use route array (if any) to determine actual visibility for this point
8170 bool brp_viz = false;
8171 if (proute_array) {
8172 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8173 Route *pr = (Route *)proute_array->Item(ir);
8174 if (pr->IsVisible()) {
8175 brp_viz = true;
8176 break;
8177 }
8178 }
8179 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8180 // but still exists as a waypoint
8181 brp_viz = prp->IsVisible(); // so treat as isolated point
8182
8183 } else
8184 brp_viz = prp->IsVisible(); // isolated point
8185
8186 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8187
8188 // Use route array to choose the appropriate route
8189 // Give preference to any active route, otherwise select the first visible
8190 // route in the array for this point
8191 m_pSelectedRoute = NULL;
8192 if (proute_array) {
8193 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8194 Route *pr = (Route *)proute_array->Item(ir);
8195 if (pr->m_bRtIsActive) {
8196 pSelectedActiveRoute = pr;
8197 pFoundActiveRoutePoint = prp;
8198 break;
8199 }
8200 }
8201
8202 if (NULL == pSelectedVizRoute) {
8203 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8204 Route *pr = (Route *)proute_array->Item(ir);
8205 if (pr->IsVisible()) {
8206 pSelectedVizRoute = pr;
8207 pFoundVizRoutePoint = prp;
8208 break;
8209 }
8210 }
8211 }
8212
8213 delete proute_array;
8214 }
8215 node = node->GetNext();
8216 }
8217
8218 // Now choose the "best" selections
8219 if (pFoundActiveRoutePoint) {
8220 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8221 m_pSelectedRoute = pSelectedActiveRoute;
8222 } else if (pFoundVizRoutePoint) {
8223 m_pFoundRoutePoint = pFoundVizRoutePoint;
8224 m_pSelectedRoute = pSelectedVizRoute;
8225 } else
8226 // default is first visible point in list
8227 m_pFoundRoutePoint = pFirstVizPoint;
8228
8229 if (m_pSelectedRoute) {
8230 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8231 } else if (m_pFoundRoutePoint)
8232 seltype |= SELTYPE_MARKPOINT;
8233
8234 // Highlite the selected point, to verify the proper right click
8235 // selection
8236 if (m_pFoundRoutePoint) {
8237 m_pFoundRoutePoint->m_bPtIsSelected = true;
8238 wxRect wp_rect;
8239 RoutePointGui(*m_pFoundRoutePoint)
8240 .CalculateDCRect(m_dc_route, this, &wp_rect);
8241 RefreshRect(wp_rect, true);
8242 }
8243 }
8244
8245 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8246 // routes But call the popup handler with identifier appropriate to the type
8247 if (pFindRouteSeg) // there is at least one select item
8248 {
8249 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8250 SelectableItemList SelList =
8251 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8252
8253 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8254 {
8255 // Choose the first visible route containing segment in the list
8256 wxSelectableItemListNode *node = SelList.GetFirst();
8257 while (node) {
8258 SelectItem *pFindSel = node->GetData();
8259
8260 Route *pr = (Route *)pFindSel->m_pData3;
8261 if (pr->IsVisible()) {
8262 m_pSelectedRoute = pr;
8263 break;
8264 }
8265 node = node->GetNext();
8266 }
8267 }
8268
8269 if (m_pSelectedRoute) {
8270 if (NULL == m_pFoundRoutePoint)
8271 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8272
8273 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8274 if (m_pSelectedRoute->m_bRtIsSelected) {
8275#ifdef ocpnUSE_GL
8276 if (g_bopengl && m_glcc) {
8277 InvalidateGL();
8278 Update();
8279 } else
8280#endif
8281 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8282 }
8283 seltype |= SELTYPE_ROUTESEGMENT;
8284 }
8285 }
8286
8287 if (pFindTrackSeg) {
8288 m_pSelectedTrack = NULL;
8289 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8290 SelectableItemList SelList =
8291 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8292
8293 // Choose the first visible track containing segment in the list
8294 wxSelectableItemListNode *node = SelList.GetFirst();
8295 while (node) {
8296 SelectItem *pFindSel = node->GetData();
8297
8298 Track *pt = (Track *)pFindSel->m_pData3;
8299 if (pt->IsVisible()) {
8300 m_pSelectedTrack = pt;
8301 break;
8302 }
8303 node = node->GetNext();
8304 }
8305 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8306 }
8307
8308 {
8309 if (pFindCurrent) {
8310 // There may be multiple current entries at the same point.
8311 // For example, there often is a current substation (with directions
8312 // specified) co-located with its master. We want to select the
8313 // substation, so that the direction will be properly indicated on the
8314 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8315 // substation)
8316 IDX_entry *pIDX_best_candidate;
8317
8318 SelectItem *pFind = NULL;
8319 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8320 SelectableItemList SelList =
8321 pSelectTC->FindSelectionList(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8322
8323 // Default is first entry
8324 wxSelectableItemListNode *node = SelList.GetFirst();
8325 pFind = node->GetData();
8326 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8327
8328 if (SelList.GetCount() > 1) {
8329 node = node->GetNext();
8330 while (node) {
8331 pFind = node->GetData();
8332 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8333 if (pIDX_candidate->IDX_type == 'c') {
8334 pIDX_best_candidate = pIDX_candidate;
8335 break;
8336 }
8337
8338 node = node->GetNext();
8339 } // while (node)
8340 } else {
8341 wxSelectableItemListNode *node = SelList.GetFirst();
8342 pFind = node->GetData();
8343 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8344 }
8345
8346 m_pIDXCandidate = pIDX_best_candidate;
8347 seltype |= SELTYPE_CURRENTPOINT;
8348 }
8349
8350 else if (pFindTide) {
8351 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8352 seltype |= SELTYPE_TIDEPOINT;
8353 }
8354 }
8355
8356 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8357
8358 return seltype;
8359}
8360
8361void ChartCanvas::CallPopupMenu(int x, int y) {
8362 last_drag.x = x;
8363 last_drag.y = y;
8364 if (m_routeState) { // creating route?
8365 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8366 return;
8367 }
8368
8370
8371 // If tide or current point is selected, then show the TC dialog immediately
8372 // without context menu
8373 if (SELTYPE_CURRENTPOINT == seltype) {
8374 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8375 Refresh(false);
8376 return;
8377 }
8378
8379 if (SELTYPE_TIDEPOINT == seltype) {
8380 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8381 Refresh(false);
8382 return;
8383 }
8384
8385 InvokeCanvasMenu(x, y, seltype);
8386
8387 // Clean up if not deleted in InvokeCanvasMenu
8388 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8389 m_pSelectedRoute->m_bRtIsSelected = false;
8390 }
8391
8392 m_pSelectedRoute = NULL;
8393
8394 if (m_pFoundRoutePoint) {
8395 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8396 m_pFoundRoutePoint->m_bPtIsSelected = false;
8397 }
8398 m_pFoundRoutePoint = NULL;
8399
8400 Refresh(true);
8401 // Refresh(false); // needed for MSW, not GTK Why??
8402}
8403
8404bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8405 // For now just bail out completely if the point clicked is not on the chart
8406 if (std::isnan(m_cursor_lat)) return false;
8407
8408 // Mouse Clicks
8409 bool ret = false; // return true if processed
8410
8411 int x, y, mx, my;
8412 event.GetPosition(&x, &y);
8413 mx = x;
8414 my = y;
8415
8416 // Calculate meaningful SelectRadius
8417 float SelectRadius;
8418 SelectRadius = g_Platform->GetSelectRadiusPix() /
8419 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8420
8422 // We start with Double Click processing. The first left click just starts a
8423 // timer and is remembered, then we actually do something if there is a
8424 // LeftDClick. If there is, the two single clicks are ignored.
8425
8426 if (event.LeftDClick() && (cursor_region == CENTER)) {
8427 m_DoubleClickTimer->Start();
8428 singleClickEventIsValid = false;
8429
8430 double zlat, zlon;
8431 GetCanvasPixPoint(x * g_current_monitor_dip_px_ratio,
8432 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8433
8434 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8435 if (m_bShowAIS) {
8436 SelectItem *pFindAIS;
8437 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8438
8439 if (pFindAIS) {
8440 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8441 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8442 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8443 }
8444 return true;
8445 }
8446 }
8447
8448 SelectableItemList rpSelList =
8449 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8450 wxSelectableItemListNode *node = rpSelList.GetFirst();
8451 bool b_onRPtarget = false;
8452 while (node) {
8453 SelectItem *pFind = node->GetData();
8454 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8455 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8456 b_onRPtarget = true;
8457 break;
8458 }
8459 node = node->GetNext();
8460 }
8461
8462 // Double tap with selected RoutePoint or Mark
8463
8464 if (m_pRoutePointEditTarget) {
8465 if (b_onRPtarget) {
8466 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8467 return true;
8468 } else {
8469 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8470 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8471 if (g_btouch)
8472 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8473 wxRect wp_rect;
8474 RoutePointGui(*m_pRoutePointEditTarget)
8475 .CalculateDCRect(m_dc_route, this, &wp_rect);
8476 m_pRoutePointEditTarget = NULL; // cancel selection
8477 RefreshRect(wp_rect, true);
8478 return true;
8479 }
8480 } else {
8481 node = rpSelList.GetFirst();
8482 if (node) {
8483 SelectItem *pFind = node->GetData();
8484 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8485 if (frp) {
8486 wxArrayPtrVoid *proute_array =
8487 g_pRouteMan->GetRouteArrayContaining(frp);
8488
8489 // Use route array (if any) to determine actual visibility for this
8490 // point
8491 bool brp_viz = false;
8492 if (proute_array) {
8493 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8494 Route *pr = (Route *)proute_array->Item(ir);
8495 if (pr->IsVisible()) {
8496 brp_viz = true;
8497 break;
8498 }
8499 }
8500 delete proute_array;
8501 if (!brp_viz &&
8502 frp->IsShared()) // is not visible as part of route, but still
8503 // exists as a waypoint
8504 brp_viz = frp->IsVisible(); // so treat as isolated point
8505 } else
8506 brp_viz = frp->IsVisible(); // isolated point
8507
8508 if (brp_viz) {
8509 ShowMarkPropertiesDialog(frp);
8510 return true;
8511 }
8512 }
8513 }
8514 }
8515
8516 SelectItem *cursorItem;
8517 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8518
8519 if (cursorItem) {
8520 Route *pr = (Route *)cursorItem->m_pData3;
8521 if (pr->IsVisible()) {
8522 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8523 return true;
8524 }
8525 }
8526
8527 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8528
8529 if (cursorItem) {
8530 Track *pt = (Track *)cursorItem->m_pData3;
8531 if (pt->IsVisible()) {
8532 ShowTrackPropertiesDialog(pt);
8533 return true;
8534 }
8535 }
8536
8537 // Found no object to act on, so show chart info.
8538
8539 ShowObjectQueryWindow(x, y, zlat, zlon);
8540 return true;
8541 }
8542
8544 if (event.LeftDown()) {
8545 // This really should not be needed, but....
8546 // on Windows, when using wxAUIManager, sometimes the focus is lost
8547 // when clicking into another pane, e.g.the AIS target list, and then back
8548 // to this pane. Oddly, some mouse events are not lost, however. Like this
8549 // one....
8550 SetFocus();
8551
8552 last_drag.x = mx;
8553 last_drag.y = my;
8554 leftIsDown = true;
8555
8556 if (!g_btouch) {
8557 if (m_routeState) // creating route?
8558 {
8559 double rlat, rlon;
8560 bool appending = false;
8561 bool inserting = false;
8562 Route *tail = 0;
8563
8564 SetCursor(*pCursorPencil);
8565 rlat = m_cursor_lat;
8566 rlon = m_cursor_lon;
8567
8568 m_bRouteEditing = true;
8569
8570 if (m_routeState == 1) {
8571 m_pMouseRoute = new Route();
8572 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8573 pRouteList->Append(m_pMouseRoute);
8574 r_rband.x = x;
8575 r_rband.y = y;
8576 }
8577
8578 // Check to see if there is a nearby point which may be reused
8579 RoutePoint *pMousePoint = NULL;
8580
8581 // Calculate meaningful SelectRadius
8582 double nearby_radius_meters =
8583 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8584
8585 RoutePoint *pNearbyPoint =
8586 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8587 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8588 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8589 wxArrayPtrVoid *proute_array =
8590 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8591
8592 // Use route array (if any) to determine actual visibility for this
8593 // point
8594 bool brp_viz = false;
8595 if (proute_array) {
8596 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8597 Route *pr = (Route *)proute_array->Item(ir);
8598 if (pr->IsVisible()) {
8599 brp_viz = true;
8600 break;
8601 }
8602 }
8603 delete proute_array;
8604 if (!brp_viz &&
8605 pNearbyPoint->IsShared()) // is not visible as part of route,
8606 // but still exists as a waypoint
8607 brp_viz =
8608 pNearbyPoint->IsVisible(); // so treat as isolated point
8609 } else
8610 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8611
8612 if (brp_viz) {
8613 wxString msg = _("Use nearby waypoint?");
8614 // Don't add a mark without name to the route. Name it if needed
8615 const bool noname(pNearbyPoint->GetName() == "");
8616 if (noname) {
8617 msg =
8618 _("Use nearby nameless waypoint and name it M with"
8619 " a unique number?");
8620 }
8621 // Avoid route finish on focus change for message dialog
8622 m_FinishRouteOnKillFocus = false;
8623 int dlg_return =
8624 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8625 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8626 m_FinishRouteOnKillFocus = true;
8627 if (dlg_return == wxID_YES) {
8628 if (noname) {
8629 if (m_pMouseRoute) {
8630 int last_wp_num = m_pMouseRoute->GetnPoints();
8631 // AP-ECRMB will truncate to 6 characters
8632 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8633 wxString wp_name = wxString::Format(
8634 "M%002i-%s", last_wp_num + 1, guid_short);
8635 pNearbyPoint->SetName(wp_name);
8636 } else
8637 pNearbyPoint->SetName("WPXX");
8638 }
8639 pMousePoint = pNearbyPoint;
8640
8641 // Using existing waypoint, so nothing to delete for undo.
8642 if (m_routeState > 1)
8643 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8644 Undo_HasParent, NULL);
8645
8646 tail =
8647 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8648 bool procede = false;
8649 if (tail) {
8650 procede = true;
8651 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8652 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8653 procede = false;
8654 }
8655
8656 if (procede) {
8657 int dlg_return;
8658 m_FinishRouteOnKillFocus = false;
8659 if (m_routeState ==
8660 1) { // first point in new route, preceeding route to be
8661 // added? Not touch case
8662
8663 wxString dmsg =
8664 _("Insert first part of this route in the new route?");
8665 if (tail->GetIndexOf(pMousePoint) ==
8666 tail->GetnPoints()) // Starting on last point of another
8667 // route?
8668 dmsg = _("Insert this route in the new route?");
8669
8670 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8671 dlg_return = OCPNMessageBox(
8672 this, dmsg, _("OpenCPN Route Create"),
8673 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8674 m_FinishRouteOnKillFocus = true;
8675
8676 if (dlg_return == wxID_YES) {
8677 inserting = true; // part of the other route will be
8678 // preceeding the new route
8679 }
8680 }
8681 } else {
8682 wxString dmsg =
8683 _("Append last part of this route to the new route?");
8684 if (tail->GetIndexOf(pMousePoint) == 1)
8685 dmsg = _(
8686 "Append this route to the new route?"); // Picking the
8687 // first point
8688 // of another
8689 // route?
8690
8691 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8692 dlg_return = OCPNMessageBox(
8693 this, dmsg, _("OpenCPN Route Create"),
8694 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8695 m_FinishRouteOnKillFocus = true;
8696
8697 if (dlg_return == wxID_YES) {
8698 appending = true; // part of the other route will be
8699 // appended to the new route
8700 }
8701 }
8702 }
8703 }
8704
8705 // check all other routes to see if this point appears in any
8706 // other route If it appears in NO other route, then it should e
8707 // considered an isolated mark
8708 if (!FindRouteContainingWaypoint(pMousePoint))
8709 pMousePoint->SetShared(true);
8710 }
8711 }
8712 }
8713
8714 if (NULL == pMousePoint) { // need a new point
8715 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8716 _T(""), wxEmptyString);
8717 pMousePoint->SetNameShown(false);
8718
8719 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8720
8721 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8722
8723 if (m_routeState > 1)
8724 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8725 Undo_IsOrphanded, NULL);
8726 }
8727
8728 if (m_pMouseRoute) {
8729 if (m_routeState == 1) {
8730 // First point in the route.
8731 m_pMouseRoute->AddPoint(pMousePoint);
8732 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8733 } else {
8734 if (m_pMouseRoute->m_NextLegGreatCircle) {
8735 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8736 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8737 &rhumbBearing, &rhumbDist);
8738 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8739 rlat, &gcDist, &gcBearing, NULL);
8740 double gcDistNM = gcDist / 1852.0;
8741
8742 // Empirically found expression to get reasonable route segments.
8743 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8744 pow(rhumbDist - gcDistNM - 1, 0.5);
8745
8746 wxString msg;
8747 msg << _("For this leg the Great Circle route is ")
8748 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8749 << _(" shorter than rhumbline.\n\n")
8750 << _("Would you like include the Great Circle routing points "
8751 "for this leg?");
8752
8753 m_FinishRouteOnKillFocus = false;
8754 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8755 // does not fully capture mouse
8756
8757 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8758 wxYES_NO | wxNO_DEFAULT);
8759
8760 m_disable_edge_pan = false;
8761 m_FinishRouteOnKillFocus = true;
8762
8763 if (answer == wxID_YES) {
8764 RoutePoint *gcPoint;
8765 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8766 wxRealPoint gcCoord;
8767
8768 for (int i = 1; i <= segmentCount; i++) {
8769 double fraction = (double)i * (1.0 / (double)segmentCount);
8770 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8771 gcDist * fraction, gcBearing,
8772 &gcCoord.x, &gcCoord.y, NULL);
8773
8774 if (i < segmentCount) {
8775 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
8776 _T(""), wxEmptyString);
8777 gcPoint->SetNameShown(false);
8778 // pConfig->AddNewWayPoint(gcPoint, -1);
8779 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8780
8781 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8782 gcPoint);
8783 } else {
8784 gcPoint = pMousePoint; // Last point, previously exsisting!
8785 }
8786
8787 m_pMouseRoute->AddPoint(gcPoint);
8788 pSelect->AddSelectableRouteSegment(
8789 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8790 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8791 prevGcPoint = gcPoint;
8792 }
8793
8794 undo->CancelUndoableAction(true);
8795
8796 } else {
8797 m_pMouseRoute->AddPoint(pMousePoint);
8798 pSelect->AddSelectableRouteSegment(
8799 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8800 pMousePoint, m_pMouseRoute);
8801 undo->AfterUndoableAction(m_pMouseRoute);
8802 }
8803 } else {
8804 // Ordinary rhumblinesegment.
8805 m_pMouseRoute->AddPoint(pMousePoint);
8806 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8807 rlon, m_prev_pMousePoint,
8808 pMousePoint, m_pMouseRoute);
8809 undo->AfterUndoableAction(m_pMouseRoute);
8810 }
8811 }
8812 }
8813 m_prev_rlat = rlat;
8814 m_prev_rlon = rlon;
8815 m_prev_pMousePoint = pMousePoint;
8816 if (m_pMouseRoute)
8817 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8818
8819 m_routeState++;
8820
8821 if (appending ||
8822 inserting) { // Appending a route or making a new route
8823 int connect = tail->GetIndexOf(pMousePoint);
8824 if (connect == 1) {
8825 inserting = false; // there is nothing to insert
8826 appending = true; // so append
8827 }
8828 int length = tail->GetnPoints();
8829
8830 int i;
8831 int start, stop;
8832 if (appending) {
8833 start = connect + 1;
8834 stop = length;
8835 } else { // inserting
8836 start = 1;
8837 stop = connect;
8838 m_pMouseRoute->RemovePoint(
8839 m_pMouseRoute
8840 ->GetLastPoint()); // Remove the first and only point
8841 }
8842 for (i = start; i <= stop; i++) {
8843 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8844 if (m_pMouseRoute)
8845 m_pMouseRoute->m_lastMousePointIndex =
8846 m_pMouseRoute->GetnPoints();
8847 m_routeState++;
8848 gFrame->RefreshAllCanvas();
8849 ret = true;
8850 }
8851 m_prev_rlat =
8852 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8853 m_prev_rlon =
8854 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8855 m_pMouseRoute->FinalizeForRendering();
8856 }
8857 gFrame->RefreshAllCanvas();
8858 ret = true;
8859 }
8860
8861 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8862 {
8863 SetCursor(*pCursorPencil);
8864
8865 if (!m_pMeasureRoute) {
8866 m_pMeasureRoute = new Route();
8867 pRouteList->Append(m_pMeasureRoute);
8868 }
8869
8870 if (m_nMeasureState == 1) {
8871 r_rband.x = x;
8872 r_rband.y = y;
8873 }
8874
8875 RoutePoint *pMousePoint = new RoutePoint(m_cursor_lat, m_cursor_lon,
8876 wxString(_T ( "circle" )),
8877 wxEmptyString, wxEmptyString);
8878 pMousePoint->m_bShowName = false;
8879 pMousePoint->SetShowWaypointRangeRings(false);
8880
8881 m_pMeasureRoute->AddPoint(pMousePoint);
8882
8883 m_prev_rlat = m_cursor_lat;
8884 m_prev_rlon = m_cursor_lon;
8885 m_prev_pMousePoint = pMousePoint;
8886 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8887
8888 m_nMeasureState++;
8889 gFrame->RefreshAllCanvas();
8890 ret = true;
8891 }
8892
8893 else {
8894 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8895 }
8896 } // !g_btouch
8897 else { // g_btouch
8898
8899 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8900 // if near screen edge, pan with injection
8901 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8902 // return;
8903 // }
8904 }
8905 }
8906
8907 if (ret) return true;
8908 }
8909
8910 if (event.Dragging()) {
8911 // in touch screen mode ensure the finger/cursor is on the selected point's
8912 // radius to allow dragging
8913 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8914 if (g_btouch) {
8915 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8916 SelectItem *pFind = NULL;
8917 SelectableItemList SelList = pSelect->FindSelectionList(
8918 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8919 wxSelectableItemListNode *node = SelList.GetFirst();
8920 while (node) {
8921 pFind = node->GetData();
8922 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8923 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8924 node = node->GetNext();
8925 }
8926 }
8927
8928 // Check for use of dragHandle
8929 if (m_pRoutePointEditTarget &&
8930 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8931 SelectItem *pFind = NULL;
8932 SelectableItemList SelList = pSelect->FindSelectionList(
8933 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8934 wxSelectableItemListNode *node = SelList.GetFirst();
8935 while (node) {
8936 pFind = node->GetData();
8937 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8938 if (m_pRoutePointEditTarget == frp) {
8939 m_bIsInRadius = true;
8940 break;
8941 }
8942 node = node->GetNext();
8943 }
8944
8945 if (!m_dragoffsetSet) {
8946 RoutePointGui(*m_pRoutePointEditTarget)
8947 .PresetDragOffset(this, mouse_x, mouse_y);
8948 m_dragoffsetSet = true;
8949 }
8950 }
8951 }
8952
8953 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8954 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8955
8956 if (NULL == g_pMarkInfoDialog) {
8957 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8958 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8959 DraggingAllowed = false;
8960
8961 if (m_pRoutePointEditTarget &&
8962 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8963 DraggingAllowed = false;
8964
8965 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8966
8967 if (DraggingAllowed) {
8968 if (!undo->InUndoableAction()) {
8969 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8970 Undo_NeedsCopy, m_pFoundPoint);
8971 }
8972
8973 // Get the update rectangle for the union of the un-edited routes
8974 wxRect pre_rect;
8975
8976 if (!g_bopengl && m_pEditRouteArray) {
8977 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8978 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8979 // Need to validate route pointer
8980 // Route may be gone due to drgging close to ownship with
8981 // "Delete On Arrival" state set, as in the case of
8982 // navigating to an isolated waypoint on a temporary route
8983 if (g_pRouteMan->IsRouteValid(pr)) {
8984 wxRect route_rect;
8985 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8986 pre_rect.Union(route_rect);
8987 }
8988 }
8989 }
8990
8991 double new_cursor_lat = m_cursor_lat;
8992 double new_cursor_lon = m_cursor_lon;
8993
8994 if (CheckEdgePan(x, y, true, 5, 2))
8995 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8996
8997 // update the point itself
8998 if (g_btouch) {
8999 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9000 // new_cursor_lat, new_cursor_lon);
9001 RoutePointGui(*m_pRoutePointEditTarget)
9002 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9003 // update the Drag Handle entry in the pSelect list
9004 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
9005 m_pRoutePointEditTarget,
9006 SELTYPE_DRAGHANDLE);
9007 m_pFoundPoint->m_slat =
9008 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9009 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9010 } else {
9011 m_pRoutePointEditTarget->m_lat =
9012 new_cursor_lat; // update the RoutePoint entry
9013 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
9014 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9015 m_pFoundPoint->m_slat =
9016 new_cursor_lat; // update the SelectList entry
9017 m_pFoundPoint->m_slon = new_cursor_lon;
9018 }
9019
9020 // Update the MarkProperties Dialog, if currently shown
9021 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9022 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9023 g_pMarkInfoDialog->UpdateProperties(true);
9024 }
9025
9026 if (g_bopengl) {
9027 // InvalidateGL();
9028 Refresh(false);
9029 } else {
9030 // Get the update rectangle for the edited route
9031 wxRect post_rect;
9032
9033 if (m_pEditRouteArray) {
9034 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9035 ir++) {
9036 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9037 if (g_pRouteMan->IsRouteValid(pr)) {
9038 wxRect route_rect;
9039 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9040 post_rect.Union(route_rect);
9041 }
9042 }
9043 }
9044
9045 // Invalidate the union region
9046 pre_rect.Union(post_rect);
9047 RefreshRect(pre_rect, false);
9048 }
9049 gFrame->RefreshCanvasOther(this);
9050 m_bRoutePoinDragging = true;
9051 }
9052 ret = true;
9053 } // if Route Editing
9054
9055 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
9056 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9057
9058 if (NULL == g_pMarkInfoDialog) {
9059 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9060 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9061 DraggingAllowed = false;
9062
9063 if (m_pRoutePointEditTarget &&
9064 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
9065 DraggingAllowed = false;
9066
9067 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9068
9069 if (DraggingAllowed) {
9070 if (!undo->InUndoableAction()) {
9071 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9072 Undo_NeedsCopy, m_pFoundPoint);
9073 }
9074
9075 // The mark may be an anchorwatch
9076 double lpp1 = 0.;
9077 double lpp2 = 0.;
9078 double lppmax;
9079
9080 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
9081 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
9082 }
9083 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9084 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9085 }
9086 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9087
9088 // Get the update rectangle for the un-edited mark
9089 wxRect pre_rect;
9090 if (!g_bopengl) {
9091 RoutePointGui(*m_pRoutePointEditTarget)
9092 .CalculateDCRect(m_dc_route, this, &pre_rect);
9093 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9094 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9095 (int)(lppmax - (pre_rect.height / 2)));
9096 }
9097
9098 // update the point itself
9099 if (g_btouch) {
9100 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9101 // m_cursor_lat, m_cursor_lon);
9102 RoutePointGui(*m_pRoutePointEditTarget)
9103 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9104 // update the Drag Handle entry in the pSelect list
9105 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9106 m_pRoutePointEditTarget,
9107 SELTYPE_DRAGHANDLE);
9108 m_pFoundPoint->m_slat =
9109 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9110 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9111 } else {
9112 m_pRoutePointEditTarget->m_lat =
9113 m_cursor_lat; // update the RoutePoint entry
9114 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9115 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9116 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9117 m_pFoundPoint->m_slon = m_cursor_lon;
9118 }
9119
9120 // Update the MarkProperties Dialog, if currently shown
9121 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9122 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9123 g_pMarkInfoDialog->UpdateProperties(true);
9124 }
9125
9126 // Invalidate the union region
9127 if (g_bopengl) {
9128 if (!g_btouch) InvalidateGL();
9129 Refresh(false);
9130 } else {
9131 // Get the update rectangle for the edited mark
9132 wxRect post_rect;
9133 RoutePointGui(*m_pRoutePointEditTarget)
9134 .CalculateDCRect(m_dc_route, this, &post_rect);
9135 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9136 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9137 (int)(lppmax - (post_rect.height / 2)));
9138
9139 // Invalidate the union region
9140 pre_rect.Union(post_rect);
9141 RefreshRect(pre_rect, false);
9142 }
9143 gFrame->RefreshCanvasOther(this);
9144 m_bRoutePoinDragging = true;
9145 }
9146 ret = true;
9147 }
9148
9149 if (ret) return true;
9150 } // dragging
9151
9152 if (event.LeftUp()) {
9153 bool b_startedit_route = false;
9154 m_dragoffsetSet = false;
9155
9156 if (g_btouch) {
9157 m_bChartDragging = false;
9158 m_bIsInRadius = false;
9159
9160 if (m_routeState) // creating route?
9161 {
9162 if (m_bedge_pan) {
9163 m_bedge_pan = false;
9164 return false;
9165 }
9166
9167 double rlat, rlon;
9168 bool appending = false;
9169 bool inserting = false;
9170 Route *tail = 0;
9171
9172 rlat = m_cursor_lat;
9173 rlon = m_cursor_lon;
9174
9175 if (m_pRoutePointEditTarget) {
9176 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9177 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9178 if (!g_bopengl) {
9179 wxRect wp_rect;
9180 RoutePointGui(*m_pRoutePointEditTarget)
9181 .CalculateDCRect(m_dc_route, this, &wp_rect);
9182 RefreshRect(wp_rect, true);
9183 }
9184 m_pRoutePointEditTarget = NULL;
9185 }
9186 m_bRouteEditing = true;
9187
9188 if (m_routeState == 1) {
9189 m_pMouseRoute = new Route();
9190 m_pMouseRoute->SetHiLite(50);
9191 pRouteList->Append(m_pMouseRoute);
9192 r_rband.x = x;
9193 r_rband.y = y;
9194 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9195 }
9196
9197 // Check to see if there is a nearby point which may be reused
9198 RoutePoint *pMousePoint = NULL;
9199
9200 // Calculate meaningful SelectRadius
9201 double nearby_radius_meters =
9202 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9203
9204 RoutePoint *pNearbyPoint =
9205 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9206 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9207 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9208 int dlg_return;
9209#ifndef __WXOSX__
9210 m_FinishRouteOnKillFocus =
9211 false; // Avoid route finish on focus change for message dialog
9212 dlg_return = OCPNMessageBox(
9213 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9214 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9215 m_FinishRouteOnKillFocus = true;
9216#else
9217 dlg_return = wxID_YES;
9218#endif
9219 if (dlg_return == wxID_YES) {
9220 pMousePoint = pNearbyPoint;
9221
9222 // Using existing waypoint, so nothing to delete for undo.
9223 if (m_routeState > 1)
9224 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9225 Undo_HasParent, NULL);
9226 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9227
9228 bool procede = false;
9229 if (tail) {
9230 procede = true;
9231 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9232 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9233 procede = false;
9234 }
9235
9236 if (procede) {
9237 int dlg_return;
9238 m_FinishRouteOnKillFocus = false;
9239 if (m_routeState == 1) { // first point in new route, preceeding
9240 // route to be added? touch case
9241
9242 wxString dmsg =
9243 _("Insert first part of this route in the new route?");
9244 if (tail->GetIndexOf(pMousePoint) ==
9245 tail->GetnPoints()) // Starting on last point of another
9246 // route?
9247 dmsg = _("Insert this route in the new route?");
9248
9249 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9250 dlg_return =
9251 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9252 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9253 m_FinishRouteOnKillFocus = true;
9254
9255 if (dlg_return == wxID_YES) {
9256 inserting = true; // part of the other route will be
9257 // preceeding the new route
9258 }
9259 }
9260 } else {
9261 wxString dmsg =
9262 _("Append last part of this route to the new route?");
9263 if (tail->GetIndexOf(pMousePoint) == 1)
9264 dmsg = _(
9265 "Append this route to the new route?"); // Picking the
9266 // first point of
9267 // another route?
9268
9269 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9270 dlg_return =
9271 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9272 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9273 m_FinishRouteOnKillFocus = true;
9274
9275 if (dlg_return == wxID_YES) {
9276 appending = true; // part of the other route will be
9277 // appended to the new route
9278 }
9279 }
9280 }
9281 }
9282
9283 // check all other routes to see if this point appears in any other
9284 // route If it appears in NO other route, then it should e
9285 // considered an isolated mark
9286 if (!FindRouteContainingWaypoint(pMousePoint))
9287 pMousePoint->SetShared(true);
9288 }
9289 }
9290
9291 if (NULL == pMousePoint) { // need a new point
9292 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9293 _T(""), wxEmptyString);
9294 pMousePoint->SetNameShown(false);
9295
9296 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9297
9298 if (m_routeState > 1)
9299 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9300 Undo_IsOrphanded, NULL);
9301 }
9302
9303 if (m_routeState == 1) {
9304 // First point in the route.
9305 m_pMouseRoute->AddPoint(pMousePoint);
9306 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9307
9308 } else {
9309 if (m_pMouseRoute->m_NextLegGreatCircle) {
9310 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9311 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9312 &rhumbBearing, &rhumbDist);
9313 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9314 &gcDist, &gcBearing, NULL);
9315 double gcDistNM = gcDist / 1852.0;
9316
9317 // Empirically found expression to get reasonable route segments.
9318 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9319 pow(rhumbDist - gcDistNM - 1, 0.5);
9320
9321 wxString msg;
9322 msg << _("For this leg the Great Circle route is ")
9323 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9324 << _(" shorter than rhumbline.\n\n")
9325 << _("Would you like include the Great Circle routing points "
9326 "for this leg?");
9327
9328#ifndef __WXOSX__
9329 m_FinishRouteOnKillFocus = false;
9330 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9331 wxYES_NO | wxNO_DEFAULT);
9332 m_FinishRouteOnKillFocus = true;
9333#else
9334 int answer = wxID_NO;
9335#endif
9336
9337 if (answer == wxID_YES) {
9338 RoutePoint *gcPoint;
9339 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9340 wxRealPoint gcCoord;
9341
9342 for (int i = 1; i <= segmentCount; i++) {
9343 double fraction = (double)i * (1.0 / (double)segmentCount);
9344 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9345 gcDist * fraction, gcBearing,
9346 &gcCoord.x, &gcCoord.y, NULL);
9347
9348 if (i < segmentCount) {
9349 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
9350 _T(""), wxEmptyString);
9351 gcPoint->SetNameShown(false);
9352 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9353 gcPoint);
9354 } else {
9355 gcPoint = pMousePoint; // Last point, previously exsisting!
9356 }
9357
9358 m_pMouseRoute->AddPoint(gcPoint);
9359 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9360
9361 pSelect->AddSelectableRouteSegment(
9362 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9363 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9364 prevGcPoint = gcPoint;
9365 }
9366
9367 undo->CancelUndoableAction(true);
9368
9369 } else {
9370 m_pMouseRoute->AddPoint(pMousePoint);
9371 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9372 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9373 rlon, m_prev_pMousePoint,
9374 pMousePoint, m_pMouseRoute);
9375 undo->AfterUndoableAction(m_pMouseRoute);
9376 }
9377 } else {
9378 // Ordinary rhumblinesegment.
9379 m_pMouseRoute->AddPoint(pMousePoint);
9380 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9381
9382 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9383 rlon, m_prev_pMousePoint,
9384 pMousePoint, m_pMouseRoute);
9385 undo->AfterUndoableAction(m_pMouseRoute);
9386 }
9387 }
9388
9389 m_prev_rlat = rlat;
9390 m_prev_rlon = rlon;
9391 m_prev_pMousePoint = pMousePoint;
9392 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9393
9394 m_routeState++;
9395
9396 if (appending ||
9397 inserting) { // Appending a route or making a new route
9398 int connect = tail->GetIndexOf(pMousePoint);
9399 if (connect == 1) {
9400 inserting = false; // there is nothing to insert
9401 appending = true; // so append
9402 }
9403 int length = tail->GetnPoints();
9404
9405 int i;
9406 int start, stop;
9407 if (appending) {
9408 start = connect + 1;
9409 stop = length;
9410 } else { // inserting
9411 start = 1;
9412 stop = connect;
9413 m_pMouseRoute->RemovePoint(
9414 m_pMouseRoute
9415 ->GetLastPoint()); // Remove the first and only point
9416 }
9417 for (i = start; i <= stop; i++) {
9418 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9419 if (m_pMouseRoute)
9420 m_pMouseRoute->m_lastMousePointIndex =
9421 m_pMouseRoute->GetnPoints();
9422 m_routeState++;
9423 gFrame->RefreshAllCanvas();
9424 ret = true;
9425 }
9426 m_prev_rlat =
9427 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9428 m_prev_rlon =
9429 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9430 m_pMouseRoute->FinalizeForRendering();
9431 }
9432
9433 Refresh(true);
9434 ret = true;
9435 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9436 {
9437 if (m_bedge_pan) {
9438 m_bedge_pan = false;
9439 return false;
9440 }
9441
9442 if (m_nMeasureState == 1) {
9443 m_pMeasureRoute = new Route();
9444 pRouteList->Append(m_pMeasureRoute);
9445 r_rband.x = x;
9446 r_rband.y = y;
9447 }
9448
9449 if (m_pMeasureRoute) {
9450 RoutePoint *pMousePoint = new RoutePoint(
9451 m_cursor_lat, m_cursor_lon, wxString(_T ( "circle" )),
9452 wxEmptyString, wxEmptyString);
9453 pMousePoint->m_bShowName = false;
9454
9455 m_pMeasureRoute->AddPoint(pMousePoint);
9456
9457 m_prev_rlat = m_cursor_lat;
9458 m_prev_rlon = m_cursor_lon;
9459 m_prev_pMousePoint = pMousePoint;
9460 m_pMeasureRoute->m_lastMousePointIndex =
9461 m_pMeasureRoute->GetnPoints();
9462
9463 m_nMeasureState++;
9464 } else {
9465 CancelMeasureRoute();
9466 }
9467
9468 Refresh(true);
9469 ret = true;
9470 } else {
9471 bool bSelectAllowed = true;
9472 if (NULL == g_pMarkInfoDialog) {
9473 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9474 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9475 bSelectAllowed = false;
9476
9477 /*if this left up happens at the end of a route point dragging and if
9478 the cursor/thumb is on the draghandle icon, not on the point iself a new
9479 selection will select nothing and the drag will never be ended, so the
9480 legs around this point never selectable. At this step we don't need a
9481 new selection, just keep the previoulsly selected and dragged point */
9482 if (m_bRoutePoinDragging) bSelectAllowed = false;
9483
9484 if (bSelectAllowed) {
9485 bool b_was_editing_mark = m_bMarkEditing;
9486 bool b_was_editing_route = m_bRouteEditing;
9487 FindRoutePointsAtCursor(SelectRadius,
9488 true); // Possibly selecting a point in a
9489 // route for later dragging
9490
9491 /*route and a mark points in layer can't be dragged so should't be
9492 * selected and no draghandle icon*/
9493 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9494 m_pRoutePointEditTarget = NULL;
9495
9496 if (!b_was_editing_route) {
9497 if (m_pEditRouteArray) {
9498 b_startedit_route = true;
9499
9500 // Hide the track and route rollover during route point edit, not
9501 // needed, and may be confusing
9502 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9503 m_pTrackRolloverWin->IsActive(false);
9504 }
9505 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9506 m_pRouteRolloverWin->IsActive(false);
9507 }
9508
9509 wxRect pre_rect;
9510 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9511 ir++) {
9512 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9513 // Need to validate route pointer
9514 // Route may be gone due to drgging close to ownship with
9515 // "Delete On Arrival" state set, as in the case of
9516 // navigating to an isolated waypoint on a temporary route
9517 if (g_pRouteMan->IsRouteValid(pr)) {
9518 // pr->SetHiLite(50);
9519 wxRect route_rect;
9520 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9521 pre_rect.Union(route_rect);
9522 }
9523 }
9524 RefreshRect(pre_rect, true);
9525 }
9526 } else {
9527 b_startedit_route = false;
9528 }
9529
9530 // Mark editing
9531 if (m_pRoutePointEditTarget) {
9532 if (b_was_editing_mark ||
9533 b_was_editing_route) { // kill previous hilight
9534 if (m_lastRoutePointEditTarget) {
9535 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9536 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9537 RoutePointGui(*m_lastRoutePointEditTarget)
9538 .EnableDragHandle(false);
9539 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9540 SELTYPE_DRAGHANDLE);
9541 }
9542 }
9543
9544 if (m_pRoutePointEditTarget) {
9545 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9546 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9547 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9548 wxPoint2DDouble dragHandlePoint =
9549 RoutePointGui(*m_pRoutePointEditTarget)
9550 .GetDragHandlePoint(this);
9551 pSelect->AddSelectablePoint(
9552 dragHandlePoint.m_y, dragHandlePoint.m_x,
9553 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9554 }
9555 } else { // Deselect everything
9556 if (m_lastRoutePointEditTarget) {
9557 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9558 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9559 RoutePointGui(*m_lastRoutePointEditTarget)
9560 .EnableDragHandle(false);
9561 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9562 SELTYPE_DRAGHANDLE);
9563
9564 // Clear any routes being edited, probably orphans
9565 wxArrayPtrVoid *lastEditRouteArray =
9566 g_pRouteMan->GetRouteArrayContaining(
9567 m_lastRoutePointEditTarget);
9568 if (lastEditRouteArray) {
9569 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9570 ir++) {
9571 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9572 if (g_pRouteMan->IsRouteValid(pr)) {
9573 pr->m_bIsBeingEdited = false;
9574 }
9575 }
9576 delete lastEditRouteArray;
9577 }
9578 }
9579 }
9580
9581 // Do the refresh
9582
9583 if (g_bopengl) {
9584 InvalidateGL();
9585 Refresh(false);
9586 } else {
9587 if (m_lastRoutePointEditTarget) {
9588 wxRect wp_rect;
9589 RoutePointGui(*m_lastRoutePointEditTarget)
9590 .CalculateDCRect(m_dc_route, this, &wp_rect);
9591 RefreshRect(wp_rect, true);
9592 }
9593
9594 if (m_pRoutePointEditTarget) {
9595 wxRect wp_rect;
9596 RoutePointGui(*m_pRoutePointEditTarget)
9597 .CalculateDCRect(m_dc_route, this, &wp_rect);
9598 RefreshRect(wp_rect, true);
9599 }
9600 }
9601 }
9602 } // bSelectAllowed
9603
9604 // Check to see if there is a route or AIS target under the cursor
9605 // If so, start the rollover timer which creates the popup
9606 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9607 bool b_start_rollover = false;
9608 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9609 SelectItem *pFind = pSelectAIS->FindSelection(
9610 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9611 if (pFind) b_start_rollover = true;
9612 }
9613
9614 if (!b_start_rollover && !b_startedit_route) {
9615 SelectableItemList SelList = pSelect->FindSelectionList(
9616 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9617 wxSelectableItemListNode *node = SelList.GetFirst();
9618 while (node) {
9619 SelectItem *pFindSel = node->GetData();
9620
9621 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9622
9623 if (pr && pr->IsVisible()) {
9624 b_start_rollover = true;
9625 break;
9626 }
9627 node = node->GetNext();
9628 } // while
9629 }
9630
9631 if (!b_start_rollover && !b_startedit_route) {
9632 SelectableItemList SelList = pSelect->FindSelectionList(
9633 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9634 wxSelectableItemListNode *node = SelList.GetFirst();
9635 while (node) {
9636 SelectItem *pFindSel = node->GetData();
9637
9638 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9639
9640 if (tr && tr->IsVisible()) {
9641 b_start_rollover = true;
9642 break;
9643 }
9644 node = node->GetNext();
9645 } // while
9646 }
9647
9648 if (b_start_rollover)
9649 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9650 wxTIMER_ONE_SHOT);
9651 Route *tail = 0;
9652 Route *current = 0;
9653 bool appending = false;
9654 bool inserting = false;
9655 int connect = 0;
9656 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9657 // drag
9658 if (m_pRoutePointEditTarget) {
9659 // Check to see if there is a nearby point which may replace the
9660 // dragged one
9661 RoutePoint *pMousePoint = NULL;
9662
9663 int index_last;
9664 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9665 double nearby_radius_meters =
9666 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9667 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9668 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9669 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9670 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9671 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9672 bool duplicate =
9673 false; // ensure we won't create duplicate point in routes
9674 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9675 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9676 ir++) {
9677 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9678 if (pr && pr->pRoutePointList) {
9679 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9680 wxNOT_FOUND) {
9681 duplicate = true;
9682 break;
9683 }
9684 }
9685 }
9686 }
9687
9688 // Special case:
9689 // Allow "re-use" of a route's waypoints iff it is a simple
9690 // isolated route. This allows, for instance, creation of a closed
9691 // polygon route
9692 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9693
9694 if (!duplicate) {
9695 int dlg_return;
9696 dlg_return =
9697 OCPNMessageBox(this,
9698 _("Replace this RoutePoint by the nearby "
9699 "Waypoint?"),
9700 _("OpenCPN RoutePoint change"),
9701 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9702 if (dlg_return == wxID_YES) {
9703 /*double confirmation if the dragged point has been manually
9704 * created which can be important and could be deleted
9705 * unintentionally*/
9706
9707 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9708 pNearbyPoint);
9709 current =
9710 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9711
9712 if (tail && current && (tail != current)) {
9713 int dlg_return1;
9714 connect = tail->GetIndexOf(pNearbyPoint);
9715 int index_current_route =
9716 current->GetIndexOf(m_pRoutePointEditTarget);
9717 index_last = current->GetIndexOf(current->GetLastPoint());
9718 dlg_return1 = wxID_NO;
9719 if (index_last ==
9720 index_current_route) { // we are dragging the last
9721 // point of the route
9722 if (connect != tail->GetnPoints()) { // anything to do?
9723
9724 wxString dmsg(
9725 _("Last part of route to be appended to dragged "
9726 "route?"));
9727 if (connect == 1)
9728 dmsg =
9729 _("Full route to be appended to dragged route?");
9730
9731 dlg_return1 = OCPNMessageBox(
9732 this, dmsg, _("OpenCPN Route Create"),
9733 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9734 if (dlg_return1 == wxID_YES) {
9735 appending = true;
9736 }
9737 }
9738 } else if (index_current_route ==
9739 1) { // dragging the first point of the route
9740 if (connect != 1) { // anything to do?
9741
9742 wxString dmsg(
9743 _("First part of route to be inserted into dragged "
9744 "route?"));
9745 if (connect == tail->GetnPoints())
9746 dmsg = _(
9747 "Full route to be inserted into dragged route?");
9748
9749 dlg_return1 = OCPNMessageBox(
9750 this, dmsg, _("OpenCPN Route Create"),
9751 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9752 if (dlg_return1 == wxID_YES) {
9753 inserting = true;
9754 }
9755 }
9756 }
9757 }
9758
9759 if (m_pRoutePointEditTarget->IsShared()) {
9760 // dlg_return = wxID_NO;
9761 dlg_return = OCPNMessageBox(
9762 this,
9763 _("Do you really want to delete and replace this "
9764 "WayPoint") +
9765 _T("\n") + _("which has been created manually?"),
9766 ("OpenCPN RoutePoint warning"),
9767 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9768 }
9769 }
9770 if (dlg_return == wxID_YES) {
9771 pMousePoint = pNearbyPoint;
9772 if (pMousePoint->m_bIsolatedMark) {
9773 pMousePoint->SetShared(true);
9774 }
9775 pMousePoint->m_bIsolatedMark =
9776 false; // definitely no longer isolated
9777 pMousePoint->m_bIsInRoute = true;
9778 }
9779 }
9780 }
9781 }
9782 if (!pMousePoint)
9783 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9784
9785 if (m_pEditRouteArray) {
9786 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9787 ir++) {
9788 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9789 if (g_pRouteMan->IsRouteValid(pr)) {
9790 if (pMousePoint) { // remove the dragged point and insert the
9791 // nearby
9792 int nRP =
9793 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9794
9795 pSelect->DeleteAllSelectableRoutePoints(pr);
9796 pSelect->DeleteAllSelectableRouteSegments(pr);
9797
9798 pr->pRoutePointList->Insert(nRP, pMousePoint);
9799 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9800
9801 pSelect->AddAllSelectableRouteSegments(pr);
9802 pSelect->AddAllSelectableRoutePoints(pr);
9803 }
9804 pr->FinalizeForRendering();
9805 pr->UpdateSegmentDistances();
9806 if (m_bRoutePoinDragging) {
9807 // pConfig->UpdateRoute(pr);
9808 NavObj_dB::GetInstance().UpdateRoute(pr);
9809 }
9810 }
9811 }
9812 }
9813
9814 // Update the RouteProperties Dialog, if currently shown
9815 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9816 if (m_pEditRouteArray) {
9817 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9818 ir++) {
9819 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9820 if (g_pRouteMan->IsRouteValid(pr)) {
9821 if (pRoutePropDialog->GetRoute() == pr) {
9822 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9823 }
9824 /* cannot edit track points anyway
9825 else if ( ( NULL !=
9826 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9827 pTrackPropDialog->m_pTrack == pr ) {
9828 pTrackPropDialog->SetTrackAndUpdate(
9829 pr );
9830 }
9831 */
9832 }
9833 }
9834 }
9835 }
9836 if (pMousePoint) { // clear all about the dragged point
9837 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9838 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9839 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9840 // Hide mark properties dialog if open on the replaced point
9841 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9842 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9843 g_pMarkInfoDialog->Hide();
9844
9845 delete m_pRoutePointEditTarget;
9846 m_lastRoutePointEditTarget = NULL;
9847 m_pRoutePointEditTarget = NULL;
9848 undo->AfterUndoableAction(pMousePoint);
9849 undo->InvalidateUndo();
9850 }
9851 }
9852 }
9853
9854 else if (m_bMarkEditing) { // End of way point drag
9855 if (m_pRoutePointEditTarget)
9856 if (m_bRoutePoinDragging) {
9857 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9858 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9859 }
9860 }
9861
9862 if (m_pRoutePointEditTarget)
9863 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9864
9865 if (!m_pRoutePointEditTarget) {
9866 delete m_pEditRouteArray;
9867 m_pEditRouteArray = NULL;
9868 m_bRouteEditing = false;
9869 }
9870 m_bRoutePoinDragging = false;
9871
9872 if (appending) { // Appending to the route of which the last point is
9873 // dragged onto another route
9874
9875 // copy tail from connect until length to end of current after dragging
9876
9877 int length = tail->GetnPoints();
9878 for (int i = connect + 1; i <= length; i++) {
9879 current->AddPointAndSegment(tail->GetPoint(i), false);
9880 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9881 m_routeState++;
9882 gFrame->RefreshAllCanvas();
9883 ret = true;
9884 }
9885 current->FinalizeForRendering();
9886 current->m_bIsBeingEdited = false;
9887 FinishRoute();
9888 g_pRouteMan->DeleteRoute(tail);
9889 }
9890 if (inserting) {
9891 pSelect->DeleteAllSelectableRoutePoints(current);
9892 pSelect->DeleteAllSelectableRouteSegments(current);
9893 for (int i = 1; i < connect; i++) { // numbering in the tail route
9894 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9895 }
9896 pSelect->AddAllSelectableRouteSegments(current);
9897 pSelect->AddAllSelectableRoutePoints(current);
9898 current->FinalizeForRendering();
9899 current->m_bIsBeingEdited = false;
9900 g_pRouteMan->DeleteRoute(tail);
9901 }
9902
9903 // Update the RouteProperties Dialog, if currently shown
9904 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9905 if (m_pEditRouteArray) {
9906 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9907 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9908 if (g_pRouteMan->IsRouteValid(pr)) {
9909 if (pRoutePropDialog->GetRoute() == pr) {
9910 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9911 }
9912 }
9913 }
9914 }
9915 }
9916
9917 } // g_btouch
9918
9919 else { // !g_btouch
9920 if (m_bRouteEditing) { // End of RoutePoint drag
9921 Route *tail = 0;
9922 Route *current = 0;
9923 bool appending = false;
9924 bool inserting = false;
9925 int connect = 0;
9926 int index_last;
9927 if (m_pRoutePointEditTarget) {
9928 m_pRoutePointEditTarget->m_bBlink = false;
9929 // Check to see if there is a nearby point which may replace the
9930 // dragged one
9931 RoutePoint *pMousePoint = NULL;
9932 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9933 double nearby_radius_meters =
9934 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9935 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9936 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9937 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9938 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9939 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9940 bool duplicate = false; // don't create duplicate point in routes
9941 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9942 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9943 ir++) {
9944 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9945 if (pr && pr->pRoutePointList) {
9946 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9947 wxNOT_FOUND) {
9948 duplicate = true;
9949 break;
9950 }
9951 }
9952 }
9953 }
9954
9955 // Special case:
9956 // Allow "re-use" of a route's waypoints iff it is a simple
9957 // isolated route. This allows, for instance, creation of a closed
9958 // polygon route
9959 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9960
9961 if (!duplicate) {
9962 int dlg_return;
9963 dlg_return =
9964 OCPNMessageBox(this,
9965 _("Replace this RoutePoint by the nearby "
9966 "Waypoint?"),
9967 _("OpenCPN RoutePoint change"),
9968 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9969 if (dlg_return == wxID_YES) {
9970 /*double confirmation if the dragged point has been manually
9971 * created which can be important and could be deleted
9972 * unintentionally*/
9973 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9974 pNearbyPoint);
9975 current =
9976 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9977
9978 if (tail && current && (tail != current)) {
9979 int dlg_return1;
9980 connect = tail->GetIndexOf(pNearbyPoint);
9981 int index_current_route =
9982 current->GetIndexOf(m_pRoutePointEditTarget);
9983 index_last = current->GetIndexOf(current->GetLastPoint());
9984 dlg_return1 = wxID_NO;
9985 if (index_last ==
9986 index_current_route) { // we are dragging the last
9987 // point of the route
9988 if (connect != tail->GetnPoints()) { // anything to do?
9989
9990 wxString dmsg(
9991 _("Last part of route to be appended to dragged "
9992 "route?"));
9993 if (connect == 1)
9994 dmsg =
9995 _("Full route to be appended to dragged route?");
9996
9997 dlg_return1 = OCPNMessageBox(
9998 this, dmsg, _("OpenCPN Route Create"),
9999 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10000 if (dlg_return1 == wxID_YES) {
10001 appending = true;
10002 }
10003 }
10004 } else if (index_current_route ==
10005 1) { // dragging the first point of the route
10006 if (connect != 1) { // anything to do?
10007
10008 wxString dmsg(
10009 _("First part of route to be inserted into dragged "
10010 "route?"));
10011 if (connect == tail->GetnPoints())
10012 dmsg = _(
10013 "Full route to be inserted into dragged route?");
10014
10015 dlg_return1 = OCPNMessageBox(
10016 this, dmsg, _("OpenCPN Route Create"),
10017 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10018 if (dlg_return1 == wxID_YES) {
10019 inserting = true;
10020 }
10021 }
10022 }
10023 }
10024
10025 if (m_pRoutePointEditTarget->IsShared()) {
10026 dlg_return = wxID_NO;
10027 dlg_return = OCPNMessageBox(
10028 this,
10029 _("Do you really want to delete and replace this "
10030 "WayPoint") +
10031 _T("\n") + _("which has been created manually?"),
10032 ("OpenCPN RoutePoint warning"),
10033 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10034 }
10035 }
10036 if (dlg_return == wxID_YES) {
10037 pMousePoint = pNearbyPoint;
10038 if (pMousePoint->m_bIsolatedMark) {
10039 pMousePoint->SetShared(true);
10040 }
10041 pMousePoint->m_bIsolatedMark =
10042 false; // definitely no longer isolated
10043 pMousePoint->m_bIsInRoute = true;
10044 }
10045 }
10046 }
10047 }
10048 if (!pMousePoint)
10049 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
10050
10051 if (m_pEditRouteArray) {
10052 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10053 ir++) {
10054 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10055 if (g_pRouteMan->IsRouteValid(pr)) {
10056 if (pMousePoint) { // replace dragged point by nearby one
10057 int nRP =
10058 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
10059
10060 pSelect->DeleteAllSelectableRoutePoints(pr);
10061 pSelect->DeleteAllSelectableRouteSegments(pr);
10062
10063 pr->pRoutePointList->Insert(nRP, pMousePoint);
10064 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
10065
10066 pSelect->AddAllSelectableRouteSegments(pr);
10067 pSelect->AddAllSelectableRoutePoints(pr);
10068 }
10069 pr->FinalizeForRendering();
10070 pr->UpdateSegmentDistances();
10071 pr->m_bIsBeingEdited = false;
10072
10073 if (m_bRoutePoinDragging) {
10074 // Special case optimization.
10075 // Dragging a single point of a route
10076 // without any point additions or re-ordering
10077 if (!pMousePoint)
10078 NavObj_dB::GetInstance().UpdateRoutePoint(
10079 m_pRoutePointEditTarget);
10080 else
10081 NavObj_dB::GetInstance().UpdateRoute(pr);
10082 }
10083 pr->SetHiLite(0);
10084 }
10085 }
10086 Refresh(false);
10087 }
10088
10089 if (appending) {
10090 // copy tail from connect until length to end of current after
10091 // dragging
10092
10093 int length = tail->GetnPoints();
10094 for (int i = connect + 1; i <= length; i++) {
10095 current->AddPointAndSegment(tail->GetPoint(i), false);
10096 if (current)
10097 current->m_lastMousePointIndex = current->GetnPoints();
10098 m_routeState++;
10099 gFrame->RefreshAllCanvas();
10100 ret = true;
10101 }
10102 current->FinalizeForRendering();
10103 current->m_bIsBeingEdited = false;
10104 FinishRoute();
10105 g_pRouteMan->DeleteRoute(tail);
10106 }
10107 if (inserting) {
10108 pSelect->DeleteAllSelectableRoutePoints(current);
10109 pSelect->DeleteAllSelectableRouteSegments(current);
10110 for (int i = 1; i < connect; i++) { // numbering in the tail route
10111 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10112 }
10113 pSelect->AddAllSelectableRouteSegments(current);
10114 pSelect->AddAllSelectableRoutePoints(current);
10115 current->FinalizeForRendering();
10116 current->m_bIsBeingEdited = false;
10117 g_pRouteMan->DeleteRoute(tail);
10118 }
10119
10120 // Update the RouteProperties Dialog, if currently shown
10121 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10122 if (m_pEditRouteArray) {
10123 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10124 ir++) {
10125 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10126 if (g_pRouteMan->IsRouteValid(pr)) {
10127 if (pRoutePropDialog->GetRoute() == pr) {
10128 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10129 }
10130 }
10131 }
10132 }
10133 }
10134
10135 if (pMousePoint) {
10136 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10137 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10138 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10139 // Hide mark properties dialog if open on the replaced point
10140 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10141 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10142 g_pMarkInfoDialog->Hide();
10143
10144 delete m_pRoutePointEditTarget;
10145 m_lastRoutePointEditTarget = NULL;
10146 undo->AfterUndoableAction(pMousePoint);
10147 undo->InvalidateUndo();
10148 } else {
10149 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10150 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10151
10152 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10153 }
10154
10155 delete m_pEditRouteArray;
10156 m_pEditRouteArray = NULL;
10157 }
10158
10159 InvalidateGL();
10160 m_bRouteEditing = false;
10161 m_pRoutePointEditTarget = NULL;
10162
10163 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10164 ret = true;
10165 }
10166
10167 else if (m_bMarkEditing) { // end of Waypoint drag
10168 if (m_pRoutePointEditTarget) {
10169 if (m_bRoutePoinDragging) {
10170 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10171 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10172 }
10173 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10174 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10175 if (!g_bopengl) {
10176 wxRect wp_rect;
10177 RoutePointGui(*m_pRoutePointEditTarget)
10178 .CalculateDCRect(m_dc_route, this, &wp_rect);
10179 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10180 RefreshRect(wp_rect, true);
10181 }
10182 }
10183 m_pRoutePointEditTarget = NULL;
10184 m_bMarkEditing = false;
10185 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10186 ret = true;
10187 }
10188
10189 else if (leftIsDown) { // left click for chart center
10190 leftIsDown = false;
10191 ret = false;
10192
10193 if (!g_btouch) {
10194 if (!m_bChartDragging && !m_bMeasure_Active) {
10195 } else {
10196 m_bChartDragging = false;
10197 }
10198 }
10199 }
10200 m_bRoutePoinDragging = false;
10201 } // !btouch
10202
10203 if (ret) return true;
10204 } // left up
10205
10206 if (event.RightDown()) {
10207 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10208 last_drag.x = mx;
10209 last_drag.y = my;
10210
10211 if (g_btouch) {
10212 // if( m_pRoutePointEditTarget )
10213 // return false;
10214 }
10215
10216 ret = true;
10217 m_FinishRouteOnKillFocus = false;
10218 CallPopupMenu(mx, my);
10219 m_FinishRouteOnKillFocus = true;
10220 } // Right down
10221
10222 return ret;
10223}
10224
10225bool panleftIsDown;
10226
10227bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10228 // Skip all mouse processing if shift is held.
10229 // This allows plugins to implement shift+drag behaviors.
10230 if (event.ShiftDown()) {
10231 return false;
10232 }
10233 int x, y;
10234 event.GetPosition(&x, &y);
10235
10236 x *= m_displayScale;
10237 y *= m_displayScale;
10238
10239 // Check for wheel rotation
10240 // ideally, should be just longer than the time between
10241 // processing accumulated mouse events from the event queue
10242 // as would happen during screen redraws.
10243 int wheel_dir = event.GetWheelRotation();
10244
10245 if (wheel_dir) {
10246 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10247 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10248
10249 double factor = g_mouse_zoom_sensitivity;
10250 if (wheel_dir < 0) factor = 1 / factor;
10251
10252 if (g_bsmoothpanzoom) {
10253 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10254 if (wheel_dir == m_last_wheel_dir) {
10255 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10256 // m_zoom_target /= factor;
10257 } else
10258 StopMovement();
10259 } else {
10260 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10261 m_wheelstopwatch.Start(0);
10262 // m_zoom_target = VPoint.chart_scale / factor;
10263 }
10264 }
10265
10266 m_last_wheel_dir = wheel_dir;
10267
10268 ZoomCanvas(factor, true, false);
10269 }
10270
10271 if (event.LeftDown()) {
10272 // Skip the first left click if it will cause a canvas focus shift
10273 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10274 // printf("focus shift\n");
10275 return false;
10276 }
10277
10278 last_drag.x = x, last_drag.y = y;
10279 panleftIsDown = true;
10280 }
10281
10282 if (event.LeftUp()) {
10283 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10284 // seen here.
10285 panleftIsDown = false;
10286
10287 if (!g_btouch) {
10288 if (!m_bChartDragging && !m_bMeasure_Active) {
10289 switch (cursor_region) {
10290 case MID_RIGHT: {
10291 PanCanvas(100, 0);
10292 break;
10293 }
10294
10295 case MID_LEFT: {
10296 PanCanvas(-100, 0);
10297 break;
10298 }
10299
10300 case MID_TOP: {
10301 PanCanvas(0, 100);
10302 break;
10303 }
10304
10305 case MID_BOT: {
10306 PanCanvas(0, -100);
10307 break;
10308 }
10309
10310 case CENTER: {
10311 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10312 break;
10313 }
10314 }
10315 } else {
10316 m_bChartDragging = false;
10317 }
10318 }
10319 }
10320 }
10321
10322 if (event.Dragging() && event.LeftIsDown()) {
10323 /*
10324 * fixed dragging.
10325 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10326 * before the drag event. Hence, as there is no mouse down event, last_drag
10327 * is not reset before the drag. And that results in one single drag
10328 * session, meaning you cannot drag the map a few miles north, lift your
10329 * finger, and the go even further north. Instead, the map resets itself
10330 * always to the very first drag start (since there is not reset of
10331 * last_drag).
10332 *
10333 * Besides, should not left down and dragging be enough of a situation to
10334 * start a drag procedure?
10335 *
10336 * Anyways, guarded it to be active in touch situations only.
10337 */
10338
10339 if (g_btouch) {
10340 struct timespec now;
10341 clock_gettime(CLOCK_MONOTONIC, &now);
10342 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10343
10344 if (false == m_bChartDragging) {
10345 // Reset drag calculation members
10346 last_drag.x = x, last_drag.y = y;
10347 m_bChartDragging = true;
10348 m_chart_drag_total_time = 0;
10349 m_chart_drag_total_x = 0;
10350 m_chart_drag_total_y = 0;
10351 m_inertia_last_drag_x = x;
10352 m_inertia_last_drag_y = y;
10353 m_drag_vec_x.clear();
10354 m_drag_vec_y.clear();
10355 m_drag_vec_t.clear();
10356 m_last_drag_time = tnow;
10357 }
10358
10359 // Calculate and store drag dynamics.
10360 uint64_t delta_t = tnow - m_last_drag_time;
10361 double delta_tf = delta_t / 1e9;
10362
10363 m_chart_drag_total_time += delta_tf;
10364 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10365 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10366
10367 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10368 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10369 m_drag_vec_t.push_back(delta_tf);
10370
10371 m_inertia_last_drag_x = x;
10372 m_inertia_last_drag_y = y;
10373 m_last_drag_time = tnow;
10374
10375 if ((last_drag.x != x) || (last_drag.y != y)) {
10376 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10377 // dragging on route create.
10378 // github #2994
10379 m_bChartDragging = true;
10380 StartTimedMovement();
10381 m_pan_drag.x += last_drag.x - x;
10382 m_pan_drag.y += last_drag.y - y;
10383 last_drag.x = x, last_drag.y = y;
10384 }
10385 }
10386 } else {
10387 if ((last_drag.x != x) || (last_drag.y != y)) {
10388 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10389 // dragging on route create.
10390 // github #2994
10391 m_bChartDragging = true;
10392 StartTimedMovement();
10393 m_pan_drag.x += last_drag.x - x;
10394 m_pan_drag.y += last_drag.y - y;
10395 last_drag.x = x, last_drag.y = y;
10396 }
10397 }
10398 }
10399
10400 // Handle some special cases
10401 if (g_btouch) {
10402 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10403 // deactivate next LeftUp to ovoid creating an unexpected point
10404 m_DoubleClickTimer->Start();
10405 singleClickEventIsValid = false;
10406 }
10407 }
10408 }
10409
10410 return true;
10411}
10412
10413void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10414 if (MouseEventOverlayWindows(event)) return;
10415
10416 if (MouseEventSetup(event)) return; // handled, no further action required
10417
10418 if (!MouseEventProcessObjects(event)) MouseEventProcessCanvas(event);
10419}
10420
10421void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10422 // Switch to the appropriate cursor on mouse movement
10423
10424 wxCursor *ptarget_cursor = pCursorArrow;
10425 if (!pPlugIn_Cursor) {
10426 ptarget_cursor = pCursorArrow;
10427 if ((!m_routeState) &&
10428 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10429 if (cursor_region == MID_RIGHT) {
10430 ptarget_cursor = pCursorRight;
10431 } else if (cursor_region == MID_LEFT) {
10432 ptarget_cursor = pCursorLeft;
10433 } else if (cursor_region == MID_TOP) {
10434 ptarget_cursor = pCursorDown;
10435 } else if (cursor_region == MID_BOT) {
10436 ptarget_cursor = pCursorUp;
10437 } else {
10438 ptarget_cursor = pCursorArrow;
10439 }
10440 } else if (m_bMeasure_Active ||
10441 m_routeState) // If Measure tool use Pencil Cursor
10442 ptarget_cursor = pCursorPencil;
10443 } else {
10444 ptarget_cursor = pPlugIn_Cursor;
10445 }
10446
10447 SetCursor(*ptarget_cursor);
10448}
10449
10450void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10451 SetCursor(*pCursorArrow);
10452}
10453
10454void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10455 ChartPlugInWrapper *target_plugin_chart = NULL;
10456 s57chart *Chs57 = NULL;
10457 wxFileName file;
10458 wxArrayString files;
10459
10460 ChartBase *target_chart = GetChartAtCursor();
10461 if (target_chart) {
10462 file.Assign(target_chart->GetFullPath());
10463 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10464 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10465 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10466 else
10467 Chs57 = dynamic_cast<s57chart *>(target_chart);
10468 } else { // target_chart = null, might be mbtiles
10469 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10470 unsigned int im = stackIndexArray.size();
10471 int scale = 2147483647; // max 32b integer
10472 if (VPoint.b_quilt && im > 0) {
10473 for (unsigned int is = 0; is < im; is++) {
10474 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10475 CHART_TYPE_MBTILES) {
10476 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10477 double lat, lon;
10478 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10479 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10480 .GetBBox()
10481 .Contains(lat, lon)) {
10482 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10483 scale) {
10484 scale =
10485 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10486 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10487 }
10488 }
10489 }
10490 }
10491 }
10492 }
10493
10494 std::vector<Ais8_001_22 *> area_notices;
10495
10496 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10497 float vp_scale = GetVPScale();
10498
10499 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10500 auto target_data = target.second;
10501 if (!target_data->area_notices.empty()) {
10502 for (auto &ani : target_data->area_notices) {
10503 Ais8_001_22 &area_notice = ani.second;
10504
10505 BoundingBox bbox;
10506
10507 for (Ais8_001_22_SubAreaList::iterator sa =
10508 area_notice.sub_areas.begin();
10509 sa != area_notice.sub_areas.end(); ++sa) {
10510 switch (sa->shape) {
10511 case AIS8_001_22_SHAPE_CIRCLE: {
10512 wxPoint target_point;
10513 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10514 bbox.Expand(target_point);
10515 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10516 break;
10517 }
10518 case AIS8_001_22_SHAPE_RECT: {
10519 wxPoint target_point;
10520 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10521 bbox.Expand(target_point);
10522 if (sa->e_dim_m > sa->n_dim_m)
10523 bbox.EnLarge(sa->e_dim_m * vp_scale);
10524 else
10525 bbox.EnLarge(sa->n_dim_m * vp_scale);
10526 break;
10527 }
10528 case AIS8_001_22_SHAPE_POLYGON:
10529 case AIS8_001_22_SHAPE_POLYLINE: {
10530 for (int i = 0; i < 4; ++i) {
10531 double lat = sa->latitude;
10532 double lon = sa->longitude;
10533 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10534 &lat, &lon);
10535 wxPoint target_point;
10536 GetCanvasPointPix(lat, lon, &target_point);
10537 bbox.Expand(target_point);
10538 }
10539 break;
10540 }
10541 case AIS8_001_22_SHAPE_SECTOR: {
10542 double lat1 = sa->latitude;
10543 double lon1 = sa->longitude;
10544 double lat, lon;
10545 wxPoint target_point;
10546 GetCanvasPointPix(lat1, lon1, &target_point);
10547 bbox.Expand(target_point);
10548 for (int i = 0; i < 18; ++i) {
10549 ll_gc_ll(
10550 lat1, lon1,
10551 sa->left_bound_deg +
10552 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10553 sa->radius_m / 1852.0, &lat, &lon);
10554 GetCanvasPointPix(lat, lon, &target_point);
10555 bbox.Expand(target_point);
10556 }
10557 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10558 &lat, &lon);
10559 GetCanvasPointPix(lat, lon, &target_point);
10560 bbox.Expand(target_point);
10561 break;
10562 }
10563 }
10564 }
10565
10566 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10567 area_notices.push_back(&area_notice);
10568 }
10569 }
10570 }
10571 }
10572 }
10573
10574 if (target_chart || !area_notices.empty() || file.HasName()) {
10575 // Go get the array of all objects at the cursor lat/lon
10576 int sel_rad_pix = 5;
10577 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10578
10579 // Make sure we always get the lights from an object, even if we are
10580 // currently not displaying lights on the chart.
10581
10582 SetCursor(wxCURSOR_WAIT);
10583 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10584 if (!lightsVis) SetShowENCLights(true);
10585 ;
10586
10587 ListOfObjRazRules *rule_list = NULL;
10588 ListOfPI_S57Obj *pi_rule_list = NULL;
10589 if (Chs57)
10590 rule_list =
10591 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10592 else if (target_plugin_chart)
10593 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10594 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10595
10596 ListOfObjRazRules *overlay_rule_list = NULL;
10597 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10598 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10599
10600 if (CHs57_Overlay) {
10601 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10602 zlat, zlon, SelectRadius, &GetVP());
10603 }
10604
10605 if (!lightsVis) SetShowENCLights(false);
10606
10607 wxString objText;
10608 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10609 wxString face = dFont->GetFaceName();
10610
10611 if (NULL == g_pObjectQueryDialog) {
10612 g_pObjectQueryDialog =
10613 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10614 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10615 }
10616
10617 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10618 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10619
10620#ifdef __WXOSX__
10621 // Auto Adjustment for dark mode
10622 fg = g_pObjectQueryDialog->GetForegroundColour();
10623#endif
10624
10625 objText.Printf(
10626 _T("<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>"),
10627 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10628
10629#ifdef __WXOSX__
10630 int points = dFont->GetPointSize();
10631#else
10632 int points = dFont->GetPointSize() + 1;
10633#endif
10634
10635 int sizes[7];
10636 for (int i = -2; i < 5; i++) {
10637 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10638 }
10639 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10640
10641 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += _T("<i>");
10642
10643 if (overlay_rule_list && CHs57_Overlay) {
10644 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10645 objText << _T("<hr noshade>");
10646 }
10647
10648 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10649 an != area_notices.end(); ++an) {
10650 objText << _T( "<b>AIS Area Notice:</b> " );
10651 objText << ais8_001_22_notice_names[(*an)->notice_type];
10652 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10653 (*an)->sub_areas.begin();
10654 sa != (*an)->sub_areas.end(); ++sa)
10655 if (!sa->text.empty()) objText << sa->text;
10656 objText << _T( "<br>expires: " ) << (*an)->expiry_time.Format();
10657 objText << _T( "<hr noshade>" );
10658 }
10659
10660 if (Chs57)
10661 objText << Chs57->CreateObjDescriptions(rule_list);
10662 else if (target_plugin_chart)
10663 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10664 pi_rule_list);
10665
10666 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << _T("</i>");
10667
10668 // Add the additional info files
10669 wxString AddFiles, filenameOK;
10670 int filecount = 0;
10671 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10672 // plugin
10673
10674 AddFiles = wxString::Format(
10675 _T("<hr noshade><br><b>Additional info files attached to: </b> ")
10676 _T("<font ")
10677 _T("size=-2>%s</font><br><table border=0 cellspacing=0 ")
10678 _T("cellpadding=3>"),
10679 file.GetFullName());
10680 file.Normalize();
10681 file.Assign(file.GetPath(), wxT(""));
10682 wxDir dir(file.GetFullPath());
10683 wxString filename;
10684 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10685 while (cont) {
10686 file.Assign(dir.GetNameWithSep().append(filename));
10687 wxString FormatString =
10688 _T("<td valign=top><font size=-2><a ")
10689 _T("href=\"%s\">%s</a></font></td>");
10690 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10691 filenameOK = file.GetFullPath(); // remember last valid name
10692 // we are making a 3 columns table. New row only every third file
10693 if (3 * ((int)filecount / 3) == filecount)
10694 FormatString.Prepend(_T("<tr>")); // new row
10695 else
10696 FormatString.Prepend(
10697 _T("<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>")); // an empty
10698 // spacer column
10699
10700 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10701 file.GetFullName());
10702 filecount++;
10703 }
10704 cont = dir.GetNext(&filename);
10705 }
10706 objText << AddFiles << _T("</table>");
10707 }
10708 objText << _T("</font>");
10709 objText << _T("</body></html>");
10710
10711 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10712 g_pObjectQueryDialog->SetHTMLPage(objText);
10713 g_pObjectQueryDialog->Show();
10714 }
10715 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10716 // generate an event to avoid double code
10717 wxHtmlLinkInfo hli(filenameOK);
10718 wxHtmlLinkEvent hle(1, hli);
10719 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10720 }
10721
10722 if (rule_list) rule_list->Clear();
10723 delete rule_list;
10724
10725 if (overlay_rule_list) overlay_rule_list->Clear();
10726 delete overlay_rule_list;
10727
10728 if (pi_rule_list) pi_rule_list->Clear();
10729 delete pi_rule_list;
10730
10731 SetCursor(wxCURSOR_ARROW);
10732 }
10733}
10734
10735void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10736 bool bNew = false;
10737 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10738 // Dialog
10739 g_pMarkInfoDialog = new MarkInfoDlg(this);
10740 bNew = true;
10741 }
10742
10743 if (1 /*g_bresponsive*/) {
10744 wxSize canvas_size = GetSize();
10745
10746 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10747 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10748
10749 g_pMarkInfoDialog->Layout();
10750
10751 wxPoint canvas_pos = GetPosition();
10752 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10753
10754 bool newFit = false;
10755 if (canvas_size.x < fitted_size.x) {
10756 fitted_size.x = canvas_size.x - 40;
10757 if (canvas_size.y < fitted_size.y)
10758 fitted_size.y -= 40; // scrollbar added
10759 }
10760 if (canvas_size.y < fitted_size.y) {
10761 fitted_size.y = canvas_size.y - 40;
10762 if (canvas_size.x < fitted_size.x)
10763 fitted_size.x -= 40; // scrollbar added
10764 }
10765
10766 if (newFit) {
10767 g_pMarkInfoDialog->SetSize(fitted_size);
10768 g_pMarkInfoDialog->Centre();
10769 }
10770 }
10771
10772 markPoint->m_bRPIsBeingEdited = false;
10773
10774 wxString title_base = _("Mark Properties");
10775 if (markPoint->m_bIsInRoute) {
10776 title_base = _("Waypoint Properties");
10777 }
10778 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10779 g_pMarkInfoDialog->UpdateProperties();
10780 if (markPoint->m_bIsInLayer) {
10781 wxString caption(wxString::Format(_T("%s, %s: %s"), title_base, _("Layer"),
10782 GetLayerName(markPoint->m_LayerID)));
10783 g_pMarkInfoDialog->SetDialogTitle(caption);
10784 } else
10785 g_pMarkInfoDialog->SetDialogTitle(title_base);
10786
10787 g_pMarkInfoDialog->Show();
10788 g_pMarkInfoDialog->Raise();
10789 g_pMarkInfoDialog->InitialFocus();
10790 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10791}
10792
10793void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10794 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10795 pRoutePropDialog->SetRouteAndUpdate(selected);
10796 // pNew->UpdateProperties();
10797 pRoutePropDialog->Show();
10798 pRoutePropDialog->Raise();
10799 return;
10800 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10801 this); // There is one global instance of the RouteProp Dialog
10802
10803 if (g_bresponsive) {
10804 wxSize canvas_size = GetSize();
10805 wxPoint canvas_pos = GetPosition();
10806 wxSize fitted_size = pRoutePropDialog->GetSize();
10807 ;
10808
10809 if (canvas_size.x < fitted_size.x) {
10810 fitted_size.x = canvas_size.x;
10811 if (canvas_size.y < fitted_size.y)
10812 fitted_size.y -= 20; // scrollbar added
10813 }
10814 if (canvas_size.y < fitted_size.y) {
10815 fitted_size.y = canvas_size.y;
10816 if (canvas_size.x < fitted_size.x)
10817 fitted_size.x -= 20; // scrollbar added
10818 }
10819
10820 pRoutePropDialog->SetSize(fitted_size);
10821 pRoutePropDialog->Centre();
10822
10823 // int xp = (canvas_size.x - fitted_size.x)/2;
10824 // int yp = (canvas_size.y - fitted_size.y)/2;
10825
10826 wxPoint xxp = ClientToScreen(canvas_pos);
10827 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10828 }
10829
10830 pRoutePropDialog->SetRouteAndUpdate(selected);
10831
10832 pRoutePropDialog->Show();
10833
10834 Refresh(false);
10835}
10836
10837void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10838 pTrackPropDialog = TrackPropDlg::getInstance(
10839 this); // There is one global instance of the RouteProp Dialog
10840
10841 pTrackPropDialog->SetTrackAndUpdate(selected);
10842 pTrackPropDialog->UpdateProperties();
10843
10844 pTrackPropDialog->Show();
10845
10846 Refresh(false);
10847}
10848
10849void pupHandler_PasteWaypoint() {
10850 Kml kml;
10851
10852 int pasteBuffer = kml.ParsePasteBuffer();
10853 RoutePoint *pasted = kml.GetParsedRoutePoint();
10854 if (!pasted) return;
10855
10856 double nearby_radius_meters =
10857 g_Platform->GetSelectRadiusPix() /
10858 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10859
10860 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10861 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10862
10863 int answer = wxID_NO;
10864 if (nearPoint && !nearPoint->m_bIsInLayer) {
10865 wxString msg;
10866 msg << _(
10867 "There is an existing waypoint at the same location as the one you are "
10868 "pasting. Would you like to merge the pasted data with it?\n\n");
10869 msg << _("Answering 'No' will create a new waypoint at the same location.");
10870 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10871 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10872 }
10873
10874 if (answer == wxID_YES) {
10875 nearPoint->SetName(pasted->GetName());
10876 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10877 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10878 pRouteManagerDialog->UpdateWptListCtrl();
10879 }
10880
10881 if (answer == wxID_NO) {
10882 RoutePoint *newPoint = new RoutePoint(pasted);
10883 newPoint->m_bIsolatedMark = true;
10884 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10885 newPoint);
10886 // pConfig->AddNewWayPoint(newPoint, -1);
10887 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10888
10889 pWayPointMan->AddRoutePoint(newPoint);
10890 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10891 pRouteManagerDialog->UpdateWptListCtrl();
10892 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10893 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10894 }
10895
10896 gFrame->InvalidateAllGL();
10897 gFrame->RefreshAllCanvas(false);
10898}
10899
10900void pupHandler_PasteRoute() {
10901 Kml kml;
10902
10903 int pasteBuffer = kml.ParsePasteBuffer();
10904 Route *pasted = kml.GetParsedRoute();
10905 if (!pasted) return;
10906
10907 double nearby_radius_meters =
10908 g_Platform->GetSelectRadiusPix() /
10909 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10910
10911 RoutePoint *curPoint;
10912 RoutePoint *nearPoint;
10913 RoutePoint *prevPoint = NULL;
10914
10915 bool mergepoints = false;
10916 bool createNewRoute = true;
10917 int existingWaypointCounter = 0;
10918
10919 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10920 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10921 nearPoint = pWayPointMan->GetNearbyWaypoint(
10922 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10923 if (nearPoint) {
10924 mergepoints = true;
10925 existingWaypointCounter++;
10926 // Small hack here to avoid both extending RoutePoint and repeating all
10927 // the GetNearbyWaypoint calculations. Use existin data field in
10928 // RoutePoint as temporary storage.
10929 curPoint->m_bPtIsSelected = true;
10930 }
10931 }
10932
10933 int answer = wxID_NO;
10934 if (mergepoints) {
10935 wxString msg;
10936 msg << _(
10937 "There are existing waypoints at the same location as some of the ones "
10938 "you are pasting. Would you like to just merge the pasted data into "
10939 "them?\n\n");
10940 msg << _("Answering 'No' will create all new waypoints for this route.");
10941 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10942 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10943
10944 if (answer == wxID_CANCEL) {
10945 return;
10946 }
10947 }
10948
10949 // If all waypoints exist since before, and a route with the same name, we
10950 // don't create a new route.
10951 if (mergepoints && answer == wxID_YES &&
10952 existingWaypointCounter == pasted->GetnPoints()) {
10953 wxRouteListNode *route_node = pRouteList->GetFirst();
10954 while (route_node) {
10955 Route *proute = route_node->GetData();
10956
10957 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10958 createNewRoute = false;
10959 break;
10960 }
10961 route_node = route_node->GetNext();
10962 }
10963 }
10964
10965 Route *newRoute = 0;
10966 RoutePoint *newPoint = 0;
10967
10968 if (createNewRoute) {
10969 newRoute = new Route();
10970 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10971 }
10972
10973 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10974 curPoint = pasted->GetPoint(i);
10975 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10976 curPoint->m_bPtIsSelected = false;
10977 newPoint = pWayPointMan->GetNearbyWaypoint(
10978 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10979 newPoint->SetName(curPoint->GetName());
10980 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10981
10982 if (createNewRoute) newRoute->AddPoint(newPoint);
10983 } else {
10984 curPoint->m_bPtIsSelected = false;
10985
10986 newPoint = new RoutePoint(curPoint);
10987 newPoint->m_bIsolatedMark = false;
10988 newPoint->SetIconName(_T("circle"));
10989 newPoint->m_bIsVisible = true;
10990 newPoint->m_bShowName = false;
10991 newPoint->SetShared(false);
10992
10993 newRoute->AddPoint(newPoint);
10994 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10995 newPoint);
10996 // pConfig->AddNewWayPoint(newPoint, -1);
10997 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10998 pWayPointMan->AddRoutePoint(newPoint);
10999 }
11000 if (i > 1 && createNewRoute)
11001 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
11002 curPoint->m_lat, curPoint->m_lon,
11003 prevPoint, newPoint, newRoute);
11004 prevPoint = newPoint;
11005 }
11006
11007 if (createNewRoute) {
11008 pRouteList->Append(newRoute);
11009 // pConfig->AddNewRoute(newRoute); // use auto next num
11010 NavObj_dB::GetInstance().InsertRoute(newRoute);
11011
11012 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
11013 pRoutePropDialog->SetRouteAndUpdate(newRoute);
11014 }
11015
11016 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
11017 pRouteManagerDialog->UpdateRouteListCtrl();
11018 pRouteManagerDialog->UpdateWptListCtrl();
11019 }
11020 gFrame->InvalidateAllGL();
11021 gFrame->RefreshAllCanvas(false);
11022 }
11023 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11024 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11025}
11026
11027void pupHandler_PasteTrack() {
11028 Kml kml;
11029
11030 int pasteBuffer = kml.ParsePasteBuffer();
11031 Track *pasted = kml.GetParsedTrack();
11032 if (!pasted) return;
11033
11034 TrackPoint *curPoint;
11035
11036 Track *newTrack = new Track();
11037 TrackPoint *newPoint;
11038 TrackPoint *prevPoint = NULL;
11039
11040 newTrack->SetName(pasted->GetName());
11041
11042 for (int i = 0; i < pasted->GetnPoints(); i++) {
11043 curPoint = pasted->GetPoint(i);
11044
11045 newPoint = new TrackPoint(curPoint);
11046
11047 wxDateTime now = wxDateTime::Now();
11048 newPoint->SetCreateTime(curPoint->GetCreateTime());
11049
11050 newTrack->AddPoint(newPoint);
11051
11052 if (prevPoint)
11053 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
11054 newPoint->m_lat, newPoint->m_lon,
11055 prevPoint, newPoint, newTrack);
11056
11057 prevPoint = newPoint;
11058 }
11059
11060 g_TrackList.push_back(newTrack);
11061 // pConfig->AddNewTrack(newTrack);
11062 NavObj_dB::GetInstance().InsertTrack(newTrack);
11063
11064 gFrame->InvalidateAllGL();
11065 gFrame->RefreshAllCanvas(false);
11066}
11067
11068bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11069 wxJSONValue v;
11070 v[_T("CanvasIndex")] = GetCanvasIndexUnderMouse();
11071 v[_T("CursorPosition_x")] = x;
11072 v[_T("CursorPosition_y")] = y;
11073 // Send a limited set of selection types depending on what is
11074 // found under the mouse point.
11075 if (seltype & SELTYPE_UNKNOWN) v[_T("SelectionType")] = wxT("Canvas");
11076 if (seltype & SELTYPE_ROUTEPOINT) v[_T("SelectionType")] = wxT("RoutePoint");
11077 if (seltype & SELTYPE_AISTARGET) v[_T("SelectionType")] = wxT("AISTarget");
11078
11079 wxJSONWriter w;
11080 wxString out;
11081 w.Write(v, out);
11082 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11083
11084 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11085
11086#if 0
11087#define SELTYPE_UNKNOWN 0x0001
11088#define SELTYPE_ROUTEPOINT 0x0002
11089#define SELTYPE_ROUTESEGMENT 0x0004
11090#define SELTYPE_TIDEPOINT 0x0008
11091#define SELTYPE_CURRENTPOINT 0x0010
11092#define SELTYPE_ROUTECREATE 0x0020
11093#define SELTYPE_AISTARGET 0x0040
11094#define SELTYPE_MARKPOINT 0x0080
11095#define SELTYPE_TRACKSEGMENT 0x0100
11096#define SELTYPE_DRAGHANDLE 0x0200
11097#endif
11098
11099 if (g_bhide_context_menus) return true;
11100 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11101 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11102 m_pIDXCandidate, m_nmea_log);
11103
11104 Connect(
11105 wxEVT_COMMAND_MENU_SELECTED,
11106 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11107
11108 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11109
11110 Disconnect(
11111 wxEVT_COMMAND_MENU_SELECTED,
11112 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11113
11114 delete m_canvasMenu;
11115 m_canvasMenu = NULL;
11116
11117#ifdef __WXQT__
11118 // gFrame->SurfaceToolbar();
11119 // g_MainToolbar->Raise();
11120#endif
11121
11122 return true;
11123}
11124
11125void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11126 // Pass menu events from the canvas to the menu handler
11127 // This is necessarily in ChartCanvas since that is the menu's parent.
11128 if (m_canvasMenu) {
11129 m_canvasMenu->PopupMenuHandler(event);
11130 }
11131 return;
11132}
11133
11134void ChartCanvas::StartRoute(void) {
11135 // Do not allow more than one canvas to create a route at one time.
11136 if (g_brouteCreating) return;
11137
11138 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11139
11140 g_brouteCreating = true;
11141 m_routeState = 1;
11142 m_bDrawingRoute = false;
11143 SetCursor(*pCursorPencil);
11144 // SetCanvasToolbarItemState(ID_ROUTE, true);
11145 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11146
11147 HideGlobalToolbar();
11148
11149#ifdef __ANDROID__
11150 androidSetRouteAnnunciator(true);
11151#endif
11152}
11153
11154wxString ChartCanvas::FinishRoute(void) {
11155 m_routeState = 0;
11156 m_prev_pMousePoint = NULL;
11157 m_bDrawingRoute = false;
11158 wxString rv = "";
11159 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11160
11161 // SetCanvasToolbarItemState(ID_ROUTE, false);
11162 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11163#ifdef __ANDROID__
11164 androidSetRouteAnnunciator(false);
11165#endif
11166
11167 SetCursor(*pCursorArrow);
11168
11169 if (m_pMouseRoute) {
11170 if (m_bAppendingRoute) {
11171 // pConfig->UpdateRoute(m_pMouseRoute);
11172 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11173 } else {
11174 if (m_pMouseRoute->GetnPoints() > 1) {
11175 // pConfig->AddNewRoute(m_pMouseRoute);
11176 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11177 } else {
11178 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11179 m_pMouseRoute = NULL;
11180 }
11181 }
11182 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11183
11184 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11185 (pRoutePropDialog->IsShown())) {
11186 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11187 }
11188
11189 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11190 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11191 pRouteManagerDialog->UpdateRouteListCtrl();
11192 }
11193 }
11194 m_bAppendingRoute = false;
11195 m_pMouseRoute = NULL;
11196
11197 m_pSelectedRoute = NULL;
11198
11199 undo->InvalidateUndo();
11200 gFrame->RefreshAllCanvas(true);
11201
11202 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11203
11204 ShowGlobalToolbar();
11205
11206 g_brouteCreating = false;
11207
11208 return rv;
11209}
11210
11211void ChartCanvas::HideGlobalToolbar() {
11212 if (m_canvasIndex == 0) {
11213 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
11214 }
11215}
11216
11217void ChartCanvas::ShowGlobalToolbar() {
11218 if (m_canvasIndex == 0) {
11219 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
11220 }
11221}
11222
11223void ChartCanvas::ShowAISTargetList(void) {
11224 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11225 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11226 }
11227
11228 g_pAISTargetList->UpdateAISTargetList();
11229}
11230
11231void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11232 if (!m_bShowOutlines) return;
11233
11234 if (!ChartData) return;
11235
11236 int nEntry = ChartData->GetChartTableEntries();
11237
11238 for (int i = 0; i < nEntry; i++) {
11239 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11240
11241 // Check to see if the candidate chart is in the currently active group
11242 bool b_group_draw = false;
11243 if (m_groupIndex > 0) {
11244 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11245 int index = pt->GetGroupArray()[ig];
11246 if (m_groupIndex == index) {
11247 b_group_draw = true;
11248 break;
11249 }
11250 }
11251 } else
11252 b_group_draw = true;
11253
11254 if (b_group_draw) RenderChartOutline(dc, i, vp);
11255 }
11256
11257 // On CM93 Composite Charts, draw the outlines of the next smaller
11258 // scale cell
11259 cm93compchart *pcm93 = NULL;
11260 if (VPoint.b_quilt) {
11261 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11262 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11263 pcm93 = (cm93compchart *)pch;
11264 break;
11265 }
11266 } else if (m_singleChart &&
11267 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11268 pcm93 = (cm93compchart *)m_singleChart;
11269
11270 if (pcm93) {
11271 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11272 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11273
11274 if (zoom_factor > 8.0) {
11275 wxPen mPen(GetGlobalColor(_T("UINFM")), 2, wxPENSTYLE_SHORT_DASH);
11276 dc.SetPen(mPen);
11277 } else {
11278 wxPen mPen(GetGlobalColor(_T("UINFM")), 1, wxPENSTYLE_SOLID);
11279 dc.SetPen(mPen);
11280 }
11281
11282 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11283 }
11284}
11285
11286void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11287#ifdef ocpnUSE_GL
11288 if (g_bopengl && m_glcc) {
11289 /* opengl version specially optimized */
11290 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11291 return;
11292 }
11293#endif
11294
11295 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11296 if (!ChartData->IsChartAvailable(dbIndex)) return;
11297 }
11298
11299 float plylat, plylon;
11300 float plylat1, plylon1;
11301
11302 int pixx, pixy, pixx1, pixy1;
11303
11304 LLBBox box;
11305 ChartData->GetDBBoundingBox(dbIndex, box);
11306
11307 // Don't draw an outline in the case where the chart covers the entire world
11308 // */
11309 if (box.GetLonRange() == 360) return;
11310
11311 double lon_bias = 0;
11312 // chart is outside of viewport lat/lon bounding box
11313 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11314
11315 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11316
11317 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11318 dc.SetPen(wxPen(GetGlobalColor(_T ( "YELO1" )), 1, wxPENSTYLE_SOLID));
11319
11320 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11321 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFG" )), 1, wxPENSTYLE_SOLID));
11322
11323 else
11324 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFR" )), 1, wxPENSTYLE_SOLID));
11325
11326 // Are there any aux ply entries?
11327 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11328 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11329 {
11330 wxPoint r, r1;
11331
11332 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11333 plylon += lon_bias;
11334
11335 GetCanvasPointPix(plylat, plylon, &r);
11336 pixx = r.x;
11337 pixy = r.y;
11338
11339 for (int i = 0; i < nPly - 1; i++) {
11340 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11341 plylon1 += lon_bias;
11342
11343 GetCanvasPointPix(plylat1, plylon1, &r1);
11344 pixx1 = r1.x;
11345 pixy1 = r1.y;
11346
11347 int pixxs1 = pixx1;
11348 int pixys1 = pixy1;
11349
11350 bool b_skip = false;
11351
11352 if (vp.chart_scale > 5e7) {
11353 // calculate projected distance between these two points in meters
11354 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11355 pow((double)(pixy1 - pixy), 2)) /
11356 vp.view_scale_ppm;
11357
11358 if (dist > 0.0) {
11359 // calculate GC distance between these two points in meters
11360 double distgc =
11361 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11362
11363 // If the distances are nonsense, it means that the scale is very
11364 // small and the segment wrapped the world So skip it....
11365 // TODO improve this to draw two segments
11366 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11367 b_skip = true;
11368 } else
11369 b_skip = true;
11370 }
11371
11372 ClipResult res = cohen_sutherland_line_clip_i(
11373 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11374 if (res != Invisible && !b_skip)
11375 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11376
11377 plylat = plylat1;
11378 plylon = plylon1;
11379 pixx = pixxs1;
11380 pixy = pixys1;
11381 }
11382
11383 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11384 plylon1 += lon_bias;
11385
11386 GetCanvasPointPix(plylat1, plylon1, &r1);
11387 pixx1 = r1.x;
11388 pixy1 = r1.y;
11389
11390 ClipResult res = cohen_sutherland_line_clip_i(
11391 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11392 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11393 }
11394
11395 else // Use Aux PlyPoints
11396 {
11397 wxPoint r, r1;
11398
11399 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11400 for (int j = 0; j < nAuxPlyEntries; j++) {
11401 int nAuxPly =
11402 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11403 GetCanvasPointPix(plylat, plylon, &r);
11404 pixx = r.x;
11405 pixy = r.y;
11406
11407 for (int i = 0; i < nAuxPly - 1; i++) {
11408 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11409
11410 GetCanvasPointPix(plylat1, plylon1, &r1);
11411 pixx1 = r1.x;
11412 pixy1 = r1.y;
11413
11414 int pixxs1 = pixx1;
11415 int pixys1 = pixy1;
11416
11417 bool b_skip = false;
11418
11419 if (vp.chart_scale > 5e7) {
11420 // calculate projected distance between these two points in meters
11421 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11422 ((pixy1 - pixy) * (pixy1 - pixy))) /
11423 vp.view_scale_ppm;
11424 if (dist > 0.0) {
11425 // calculate GC distance between these two points in meters
11426 double distgc =
11427 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11428
11429 // If the distances are nonsense, it means that the scale is very
11430 // small and the segment wrapped the world So skip it....
11431 // TODO improve this to draw two segments
11432 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11433 b_skip = true;
11434 } else
11435 b_skip = true;
11436 }
11437
11438 ClipResult res = cohen_sutherland_line_clip_i(
11439 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11440 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11441
11442 plylat = plylat1;
11443 plylon = plylon1;
11444 pixx = pixxs1;
11445 pixy = pixys1;
11446 }
11447
11448 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11449 GetCanvasPointPix(plylat1, plylon1, &r1);
11450 pixx1 = r1.x;
11451 pixy1 = r1.y;
11452
11453 ClipResult res = cohen_sutherland_line_clip_i(
11454 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11455 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11456 }
11457 }
11458}
11459
11460static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
11461 const wxString &second) {
11462 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11463
11464 int pointsize = dFont->GetPointSize();
11465 pointsize /= OCPN_GetWinDIPScaleFactor();
11466
11467 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11468 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11469 false, dFont->GetFaceName());
11470
11471 dc.SetFont(*psRLI_font);
11472
11473 int w1, h1;
11474 int w2 = 0;
11475 int h2 = 0;
11476 int h, w;
11477
11478 int xp, yp;
11479 int hilite_offset = 3;
11480#ifdef __WXMAC__
11481 wxScreenDC sdc;
11482 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
11483 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
11484#else
11485 dc.GetTextExtent(first, &w1, &h1);
11486 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
11487#endif
11488
11489 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11490 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11491
11492 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
11493 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11494
11495 h = h1 + h2;
11496
11497 xp = ref_point.x - w;
11498 yp = ref_point.y;
11499 yp += hilite_offset;
11500
11501 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor(_T ( "YELO1" )), 172);
11502
11503 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" ))));
11504 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
11505
11506 dc.DrawText(first, xp, yp);
11507 if (second.Len()) dc.DrawText(second, xp, yp + h1);
11508}
11509
11510void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11511 if (!g_bAllowShipToActive) return;
11512
11513 Route *rt = g_pRouteMan->GetpActiveRoute();
11514 if (!rt) return;
11515
11516 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11517 wxPoint2DDouble pa, pb;
11518 GetDoubleCanvasPointPix(gLat, gLon, &pa);
11519 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11520
11521 // set pen
11522 int width =
11523 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11524 if (rt->m_width != wxPENSTYLE_INVALID)
11525 width = rt->m_width; // set route pen style if any
11526 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11527 g_shipToActiveStyle, 5)]; // get setting pen style
11528 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11529 wxColour color =
11530 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11531 : // set setting route pen color
11532 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11533 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11534
11535 dc.SetPen(*mypen);
11536 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11537
11538 if (!Use_Opengl)
11539 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11540 (int)pb.m_y, GetVP(), true);
11541
11542#ifdef ocpnUSE_GL
11543 else {
11544#ifdef USE_ANDROID_GLES2
11545 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11546#else
11547 if (style != wxPENSTYLE_SOLID) {
11548 if (glChartCanvas::dash_map.find(style) !=
11549 glChartCanvas::dash_map.end()) {
11550 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11551 dc.SetPen(*mypen);
11552 }
11553 }
11554 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11555#endif
11556
11557 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11558 (int)pb.m_x, (int)pb.m_y, GetVP());
11559 }
11560#endif
11561 }
11562}
11563
11564void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11565 Route *route = 0;
11566 if (m_routeState >= 2) route = m_pMouseRoute;
11567 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11568 route = m_pMeasureRoute;
11569
11570 if (!route) return;
11571
11572 // Validate route pointer
11573 if (!g_pRouteMan->IsRouteValid(route)) return;
11574
11575 double render_lat = m_cursor_lat;
11576 double render_lon = m_cursor_lon;
11577
11578 int np = route->GetnPoints();
11579 if (np) {
11580 if (g_btouch && (np > 1)) np--;
11581 RoutePoint rp = route->GetPoint(np);
11582 render_lat = rp.m_lat;
11583 render_lon = rp.m_lon;
11584 }
11585
11586 double rhumbBearing, rhumbDist;
11587 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11588 &rhumbBearing, &rhumbDist);
11589 double brg = rhumbBearing;
11590 double dist = rhumbDist;
11591
11592 // Skip GreatCircle rubberbanding on touch devices.
11593 if (!g_btouch) {
11594 double gcBearing, gcBearing2, gcDist;
11595 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11596 m_cursor_lat, &gcDist, &gcBearing,
11597 &gcBearing2);
11598 double gcDistm = gcDist / 1852.0;
11599
11600 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11601 rhumbBearing = 90.;
11602
11603 wxPoint destPoint, lastPoint;
11604
11605 route->m_NextLegGreatCircle = false;
11606 int milesDiff = rhumbDist - gcDistm;
11607 if (milesDiff > 1) {
11608 brg = gcBearing;
11609 dist = gcDistm;
11610 route->m_NextLegGreatCircle = true;
11611 }
11612
11613 // FIXME (MacOS, the first segment is rendered wrong)
11614 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11615 &lastPoint);
11616
11617 if (route->m_NextLegGreatCircle) {
11618 for (int i = 1; i <= milesDiff; i++) {
11619 double p = (double)i * (1.0 / (double)milesDiff);
11620 double pLat, pLon;
11621 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11622 &pLon, &pLat, &gcBearing2);
11623 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11624 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11625 false);
11626 lastPoint = destPoint;
11627 }
11628 } else {
11629 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11630 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11631 false);
11632 if (m_bMeasure_DistCircle) {
11633 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11634 powf((float)(r_rband.y - lastPoint.y), 2));
11635
11636 dc.SetPen(*g_pRouteMan->GetRoutePen());
11637 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11638 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11639 }
11640 }
11641 }
11642 }
11643
11644 wxString routeInfo;
11645 double varBrg = 0;
11646 if (g_bShowTrue)
11647 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11648 0x00B0);
11649
11650 if (g_bShowMag) {
11651 double latAverage = (m_cursor_lat + render_lat) / 2;
11652 double lonAverage = (m_cursor_lon + render_lon) / 2;
11653 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11654
11655 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11656 (int)varBrg, 0x00B0);
11657 }
11658 routeInfo << _T(" ") << FormatDistanceAdaptive(dist);
11659
11660 // To make it easier to use a route as a bearing on a charted object add for
11661 // the first leg also the reverse bearing.
11662 if (np == 1) {
11663 routeInfo << "\nReverse: ";
11664 if (g_bShowTrue)
11665 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11666 (int)(brg + 180.) % 360, 0x00B0);
11667 if (g_bShowMag)
11668 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11669 (int)(varBrg + 180.) % 360, 0x00B0);
11670 }
11671
11672 wxString s0;
11673 if (!route->m_bIsInLayer)
11674 s0.Append(_("Route") + _T(": "));
11675 else
11676 s0.Append(_("Layer Route: "));
11677
11678 double disp_length = route->m_route_length;
11679 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11680 s0 += FormatDistanceAdaptive(disp_length);
11681
11682 RouteLegInfo(dc, r_rband, routeInfo, s0);
11683
11684 m_brepaint_piano = true;
11685}
11686
11687void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11688 if (!m_bShowVisibleSectors) return;
11689
11690 if (g_bDeferredInitDone) {
11691 // need to re-evaluate sectors?
11692 double rhumbBearing, rhumbDist;
11693 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11694 &rhumbBearing, &rhumbDist);
11695
11696 if (rhumbDist > 0.05) // miles
11697 {
11698 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11699 m_sectorlegsVisible);
11700 m_sector_glat = gLat;
11701 m_sector_glon = gLon;
11702 }
11703 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11704 }
11705}
11706
11707void ChartCanvas::WarpPointerDeferred(int x, int y) {
11708 warp_x = x;
11709 warp_y = y;
11710 warp_flag = true;
11711}
11712
11713int s_msg;
11714
11715void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11716 if (!ps52plib) return;
11717
11718 if (VPoint.b_quilt) { // quilted
11719 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11720
11721 if (m_pQuilt->IsQuiltVector()) {
11722 if (ps52plib->GetStateHash() != m_s52StateHash) {
11723 UpdateS52State();
11724 m_s52StateHash = ps52plib->GetStateHash();
11725 }
11726 }
11727 } else {
11728 if (ps52plib->GetStateHash() != m_s52StateHash) {
11729 UpdateS52State();
11730 m_s52StateHash = ps52plib->GetStateHash();
11731 }
11732 }
11733
11734 // Plugin charts
11735 bool bSendPlibState = true;
11736 if (VPoint.b_quilt) { // quilted
11737 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11738 }
11739
11740 if (bSendPlibState) {
11741 wxJSONValue v;
11742 v[_T("OpenCPN Version Major")] = VERSION_MAJOR;
11743 v[_T("OpenCPN Version Minor")] = VERSION_MINOR;
11744 v[_T("OpenCPN Version Patch")] = VERSION_PATCH;
11745 v[_T("OpenCPN Version Date")] = VERSION_DATE;
11746 v[_T("OpenCPN Version Full")] = VERSION_FULL;
11747
11748 // S52PLIB state
11749 v[_T("OpenCPN S52PLIB ShowText")] = GetShowENCText();
11750 v[_T("OpenCPN S52PLIB ShowSoundings")] = GetShowENCDepth();
11751 v[_T("OpenCPN S52PLIB ShowLights")] = GetShowENCLights();
11752 v[_T("OpenCPN S52PLIB ShowAnchorConditions")] = m_encShowAnchor;
11753 v[_T("OpenCPN S52PLIB ShowQualityOfData")] = GetShowENCDataQual();
11754 v[_T("OpenCPN S52PLIB ShowATONLabel")] = GetShowENCBuoyLabels();
11755 v[_T("OpenCPN S52PLIB ShowLightDescription")] = GetShowENCLightDesc();
11756
11757 v[_T("OpenCPN S52PLIB DisplayCategory")] = GetENCDisplayCategory();
11758
11759 v[_T("OpenCPN S52PLIB SoundingsFactor")] = g_ENCSoundingScaleFactor;
11760 v[_T("OpenCPN S52PLIB TextFactor")] = g_ENCTextScaleFactor;
11761
11762 // Global S52 options
11763
11764 v[_T("OpenCPN S52PLIB MetaDisplay")] = ps52plib->m_bShowMeta;
11765 v[_T("OpenCPN S52PLIB DeclutterText")] = ps52plib->m_bDeClutterText;
11766 v[_T("OpenCPN S52PLIB ShowNationalText")] = ps52plib->m_bShowNationalTexts;
11767 v[_T("OpenCPN S52PLIB ShowImportantTextOnly")] =
11768 ps52plib->m_bShowS57ImportantTextOnly;
11769 v[_T("OpenCPN S52PLIB UseSCAMIN")] = ps52plib->m_bUseSCAMIN;
11770 v[_T("OpenCPN S52PLIB UseSUPER_SCAMIN")] = ps52plib->m_bUseSUPER_SCAMIN;
11771 v[_T("OpenCPN S52PLIB SymbolStyle")] = ps52plib->m_nSymbolStyle;
11772 v[_T("OpenCPN S52PLIB BoundaryStyle")] = ps52plib->m_nBoundaryStyle;
11773 v[_T("OpenCPN S52PLIB ColorShades")] =
11774 S52_getMarinerParam(S52_MAR_TWO_SHADES);
11775
11776 // Some global GUI parameters, for completeness
11777 v[_T("OpenCPN Zoom Mod Vector")] = g_chart_zoom_modifier_vector;
11778 v[_T("OpenCPN Zoom Mod Other")] = g_chart_zoom_modifier_raster;
11779 v[_T("OpenCPN Scale Factor Exp")] =
11780 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11781 v[_T("OpenCPN Display Width")] = (int)g_display_size_mm;
11782
11783 wxJSONWriter w;
11784 wxString out;
11785 w.Write(v, out);
11786
11787 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11788 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11789 g_lastS52PLIBPluginMessage = out;
11790 }
11791 }
11792}
11793int spaint;
11794int s_in_update;
11795void ChartCanvas::OnPaint(wxPaintEvent &event) {
11796 wxPaintDC dc(this);
11797
11798 // GetToolbar()->Show( m_bToolbarEnable );
11799
11800 // Paint updates may have been externally disabled (temporarily, to avoid
11801 // Yield() recursion performance loss) It is important that the wxPaintDC is
11802 // built, even if we elect to not process this paint message. Otherwise, the
11803 // paint message may not be removed from the message queue, esp on Windows.
11804 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11805
11806 if (!m_b_paint_enable) {
11807 return;
11808 }
11809
11810 // If necessary, reconfigure the S52 PLIB
11811 UpdateCanvasS52PLIBConfig();
11812
11813#ifdef ocpnUSE_GL
11814 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11815
11816 if (m_glcc && g_bopengl) {
11817 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11818 s_in_update++;
11819 m_glcc->Update();
11820 s_in_update--;
11821 }
11822
11823 return;
11824 }
11825#endif
11826
11827 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11828
11829 wxRegion ru = GetUpdateRegion();
11830
11831 int rx, ry, rwidth, rheight;
11832 ru.GetBox(rx, ry, rwidth, rheight);
11833 // printf("%d Onpaint update region box: %d %d %d %d\n", spaint++, rx, ry,
11834 // rwidth, rheight);
11835
11836#ifdef ocpnUSE_DIBSECTION
11837 ocpnMemDC temp_dc;
11838#else
11839 wxMemoryDC temp_dc;
11840#endif
11841
11842 long height = GetVP().pix_height;
11843
11844#ifdef __WXMAC__
11845 // On OS X we have to explicitly extend the region for the piano area
11846 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11847 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11848 height += m_Piano->GetHeight();
11849#endif // __WXMAC__
11850 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11851
11852 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11853 if (pthumbwin) {
11854 int thumbx, thumby, thumbsx, thumbsy;
11855 pthumbwin->GetPosition(&thumbx, &thumby);
11856 pthumbwin->GetSize(&thumbsx, &thumbsy);
11857 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11858
11859 if (pthumbwin->IsShown()) {
11860 rgn_chart.Subtract(rgn_thumbwin);
11861 ru.Subtract(rgn_thumbwin);
11862 }
11863 }
11864
11865 // subtract the chart bar if it isn't transparent, and determine if we need to
11866 // paint it
11867 wxRegion rgn_blit = ru;
11868 if (g_bShowChartBar) {
11869 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11870 GetClientSize().x, m_Piano->GetHeight());
11871
11872 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11873 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11874 if (style->chartStatusWindowTransparent)
11875 m_brepaint_piano = true;
11876 else
11877 ru.Subtract(chart_bar_rect);
11878 }
11879 }
11880
11881 if (m_Compass && m_Compass->IsShown()) {
11882 wxRect compassRect = m_Compass->GetRect();
11883 if (ru.Contains(compassRect) != wxOutRegion) {
11884 ru.Subtract(compassRect);
11885 }
11886 }
11887
11888 wxRect noteRect = m_notification_button->GetRect();
11889 if (ru.Contains(noteRect) != wxOutRegion) {
11890 ru.Subtract(noteRect);
11891 }
11892
11893 // Is this viewpoint the same as the previously painted one?
11894 bool b_newview = true;
11895
11896 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11897 (m_cache_vp.rotation == VPoint.rotation) &&
11898 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11899 m_cache_vp.IsValid()) {
11900 b_newview = false;
11901 }
11902
11903 // If the ViewPort is skewed or rotated, we may be able to use the cached
11904 // rotated bitmap.
11905 bool b_rcache_ok = false;
11906 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11907 b_rcache_ok = !b_newview;
11908
11909 // Make a special VP
11910 if (VPoint.b_MercatorProjectionOverride)
11911 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11912 ViewPort svp = VPoint;
11913
11914 svp.pix_width = svp.rv_rect.width;
11915 svp.pix_height = svp.rv_rect.height;
11916
11917 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11918 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11919 // VPoint.rv_rect.height);
11920
11921 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11922
11923 // If we are going to use the cached rotated image, there is no need to fetch
11924 // any chart data and this will do it...
11925 if (b_rcache_ok) chart_get_region.Clear();
11926
11927 // Blit pan acceleration
11928 if (VPoint.b_quilt) // quilted
11929 {
11930 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11931
11932 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11933
11934 bool busy = false;
11935 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11936 m_cache_vp.rotation != VPoint.rotation)) {
11937 AbstractPlatform::ShowBusySpinner();
11938 busy = true;
11939 }
11940
11941 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11942 (m_working_bm.GetHeight() != svp.pix_height))
11943 m_working_bm.Create(svp.pix_width, svp.pix_height,
11944 -1); // make sure the target is big enoug
11945
11946 if (fabs(VPoint.rotation) < 0.01) {
11947 bool b_save = true;
11948
11949 if (g_SencThreadManager) {
11950 if (g_SencThreadManager->GetJobCount()) {
11951 b_save = false;
11952 m_cache_vp.Invalidate();
11953 }
11954 }
11955
11956 // If the saved wxBitmap from last OnPaint is useable
11957 // calculate the blit parameters
11958
11959 // We can only do screen blit painting if subsequent ViewPorts differ by
11960 // whole pixels So, in small scale bFollow mode, force the full screen
11961 // render. This seems a hack....There may be better logic here.....
11962
11963 // if(m_bFollow)
11964 // b_save = false;
11965
11966 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11967 if (b_newview) {
11968 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11969 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11970
11971 int dy = c_new.y - c_old.y;
11972 int dx = c_new.x - c_old.x;
11973
11974 // printf("In OnPaint Trying Blit dx: %d
11975 // dy:%d\n\n", dx, dy);
11976
11977 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11978 if (dx || dy) {
11979 // Blit the reuseable portion of the cached wxBitmap to a working
11980 // bitmap
11981 temp_dc.SelectObject(m_working_bm);
11982
11983 wxMemoryDC cache_dc;
11984 cache_dc.SelectObject(m_cached_chart_bm);
11985
11986 if (dy > 0) {
11987 if (dx > 0) {
11988 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11989 VPoint.pix_height - dy, &cache_dc, dx, dy);
11990 } else {
11991 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11992 VPoint.pix_height - dy, &cache_dc, 0, dy);
11993 }
11994
11995 } else {
11996 if (dx > 0) {
11997 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11998 VPoint.pix_height + dy, &cache_dc, dx, 0);
11999 } else {
12000 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
12001 VPoint.pix_height + dy, &cache_dc, 0, 0);
12002 }
12003 }
12004
12005 OCPNRegion update_region;
12006 if (dy) {
12007 if (dy > 0)
12008 update_region.Union(
12009 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
12010 else
12011 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
12012 }
12013
12014 if (dx) {
12015 if (dx > 0)
12016 update_region.Union(
12017 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
12018 else
12019 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
12020 }
12021
12022 // Render the new region
12023 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12024 update_region);
12025 cache_dc.SelectObject(wxNullBitmap);
12026 } else {
12027 // No sensible (dx, dy) change in the view, so use the cached
12028 // member bitmap
12029 temp_dc.SelectObject(m_cached_chart_bm);
12030 b_save = false;
12031 }
12032 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
12033
12034 } else // not blitable
12035 {
12036 temp_dc.SelectObject(m_working_bm);
12037 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12038 chart_get_region);
12039 }
12040 } else {
12041 // No change in the view, so use the cached member bitmap2
12042 temp_dc.SelectObject(m_cached_chart_bm);
12043 b_save = false;
12044 }
12045 } else // cached bitmap is not yet valid
12046 {
12047 temp_dc.SelectObject(m_working_bm);
12048 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12049 chart_get_region);
12050 }
12051
12052 // Save the fully rendered quilt image as a wxBitmap member of this class
12053 if (b_save) {
12054 // if((m_cached_chart_bm.GetWidth() !=
12055 // svp.pix_width) ||
12056 // (m_cached_chart_bm.GetHeight() !=
12057 // svp.pix_height))
12058 // m_cached_chart_bm.Create(svp.pix_width,
12059 // svp.pix_height, -1); // target wxBitmap
12060 // is big enough
12061 wxMemoryDC scratch_dc_0;
12062 scratch_dc_0.SelectObject(m_cached_chart_bm);
12063 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12064
12065 scratch_dc_0.SelectObject(wxNullBitmap);
12066
12067 m_bm_cache_vp =
12068 VPoint; // save the ViewPort associated with the cached wxBitmap
12069 }
12070 }
12071
12072 else // quilted, rotated
12073 {
12074 temp_dc.SelectObject(m_working_bm);
12075 OCPNRegion chart_get_all_region(
12076 wxRect(0, 0, svp.pix_width, svp.pix_height));
12077 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12078 chart_get_all_region);
12079 }
12080
12081 AbstractPlatform::HideBusySpinner();
12082
12083 }
12084
12085 else // not quilted
12086 {
12087 if (!m_singleChart) {
12088 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12089 dc.Clear();
12090 return;
12091 }
12092
12093 if (!chart_get_region.IsEmpty()) {
12094 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12095 }
12096 }
12097
12098 if (temp_dc.IsOk()) {
12099 // Arrange to render the World Chart vector data behind the rendered
12100 // current chart so that uncovered canvas areas show at least the world
12101 // chart.
12102 OCPNRegion chartValidRegion;
12103 if (!VPoint.b_quilt) {
12104 // Make a region covering the current chart on the canvas
12105
12106 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12107 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12108 else {
12109 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12110 // require that the viewport passed here have pix_width and pix_height
12111 // set to the actual display, not the virtual (rv_rect) sizes
12112 // (the vector calculations require the virtual sizes in svp)
12113
12114 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12115 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12116 }
12117 } else
12118 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12119
12120 temp_dc.DestroyClippingRegion();
12121
12122 // Copy current chart region
12123 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12124
12125 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12126
12127 if (!backgroundRegion.IsEmpty()) {
12128 // Draw the Background Chart only in the areas NOT covered by the
12129 // current chart view
12130
12131 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12132 clipping regions with more than 1 rectangle so... */
12133 wxColour water = pWorldBackgroundChart->water;
12134 if (water.IsOk()) {
12135 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12136 temp_dc.SetBrush(wxBrush(water));
12137 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12138 while (upd.HaveRects()) {
12139 wxRect rect = upd.GetRect();
12140 temp_dc.DrawRectangle(rect);
12141 upd.NextRect();
12142 }
12143 }
12144 // Associate with temp_dc
12145 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12146 temp_dc.SetDeviceClippingRegion(*clip_region);
12147 delete clip_region;
12148
12149 ocpnDC bgdc(temp_dc);
12150 double r = VPoint.rotation;
12151 SetVPRotation(VPoint.skew);
12152
12153 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12154 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12155
12156 SetVPRotation(r);
12157 }
12158 } // temp_dc.IsOk();
12159
12160 wxMemoryDC *pChartDC = &temp_dc;
12161 wxMemoryDC rotd_dc;
12162
12163 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12164 // Can we use the current rotated image cache?
12165 if (!b_rcache_ok) {
12166#ifdef __WXMSW__
12167 wxMemoryDC tbase_dc;
12168 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12169 tbase_dc.SelectObject(bm_base);
12170 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12171 tbase_dc.SelectObject(wxNullBitmap);
12172#else
12173 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12174#endif
12175
12176 wxImage base_image;
12177 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12178
12179 // Use a local static image rotator to improve wxWidgets code profile
12180 // Especially, on GTK the wxRound and wxRealPoint functions are very
12181 // expensive.....
12182
12183 double angle = GetVP().skew - GetVP().rotation;
12184 wxImage ri;
12185 bool b_rot_ok = false;
12186 if (base_image.IsOk()) {
12187 ViewPort rot_vp = GetVP();
12188
12189 m_b_rot_hidef = false;
12190
12191 ri = Image_Rotate(
12192 base_image, angle,
12193 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12194 m_b_rot_hidef, &m_roffset);
12195
12196 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12197 (rot_vp.rotation == VPoint.rotation) &&
12198 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12199 rot_vp.IsValid() && (ri.IsOk())) {
12200 b_rot_ok = true;
12201 }
12202 }
12203
12204 if (b_rot_ok) {
12205 delete m_prot_bm;
12206 m_prot_bm = new wxBitmap(ri);
12207 }
12208
12209 m_roffset.x += VPoint.rv_rect.x;
12210 m_roffset.y += VPoint.rv_rect.y;
12211 }
12212
12213 if (m_prot_bm && m_prot_bm->IsOk()) {
12214 rotd_dc.SelectObject(*m_prot_bm);
12215 pChartDC = &rotd_dc;
12216 } else {
12217 pChartDC = &temp_dc;
12218 m_roffset = wxPoint(0, 0);
12219 }
12220 } else { // unrotated
12221 pChartDC = &temp_dc;
12222 m_roffset = wxPoint(0, 0);
12223 }
12224
12225 wxPoint offset = m_roffset;
12226
12227 // Save the PixelCache viewpoint for next time
12228 m_cache_vp = VPoint;
12229
12230 // Set up a scratch DC for overlay objects
12231 wxMemoryDC mscratch_dc;
12232 mscratch_dc.SelectObject(*pscratch_bm);
12233
12234 mscratch_dc.ResetBoundingBox();
12235 mscratch_dc.DestroyClippingRegion();
12236 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12237
12238 // Blit the externally invalidated areas of the chart onto the scratch dc
12239 wxRegionIterator upd(rgn_blit); // get the update rect list
12240 while (upd) {
12241 wxRect rect = upd.GetRect();
12242
12243 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12244 rect.x - offset.x, rect.y - offset.y);
12245 upd++;
12246 }
12247
12248 // If multi-canvas, indicate which canvas has keyboard focus
12249 // by drawing a simple blue bar at the top.
12250 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12251 if (this == wxWindow::FindFocus()) {
12252 g_focusCanvas = this;
12253
12254 wxColour colour = GetGlobalColor(_T("BLUE4"));
12255 mscratch_dc.SetPen(wxPen(colour));
12256 mscratch_dc.SetBrush(wxBrush(colour));
12257
12258 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12259 mscratch_dc.DrawRectangle(activeRect);
12260 }
12261 }
12262
12263 // Any MBtiles?
12264 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12265 unsigned int im = stackIndexArray.size();
12266 if (VPoint.b_quilt && im > 0) {
12267 std::vector<int> tiles_to_show;
12268 for (unsigned int is = 0; is < im; is++) {
12269 const ChartTableEntry &cte =
12270 ChartData->GetChartTableEntry(stackIndexArray[is]);
12271 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12272 continue;
12273 }
12274 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12275 tiles_to_show.push_back(stackIndexArray[is]);
12276 }
12277 }
12278
12279 if (tiles_to_show.size())
12280 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12281 }
12282
12283 // May get an unexpected OnPaint call while switching display modes
12284 // Guard for that.
12285 if (!g_bopengl) {
12286 ocpnDC scratch_dc(mscratch_dc);
12287 RenderAlertMessage(mscratch_dc, GetVP());
12288 }
12289
12290#if 0
12291 // quiting?
12292 if (g_bquiting) {
12293#ifdef ocpnUSE_DIBSECTION
12294 ocpnMemDC q_dc;
12295#else
12296 wxMemoryDC q_dc;
12297#endif
12298 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12299 q_dc.SelectObject(qbm);
12300
12301 // Get a copy of the screen
12302 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12303
12304 // Draw a rectangle over the screen with a stipple brush
12305 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12306 q_dc.SetBrush(qbr);
12307 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12308
12309 // Blit back into source
12310 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12311 wxCOPY);
12312
12313 q_dc.SelectObject(wxNullBitmap);
12314 }
12315#endif
12316
12317#if 0
12318 // It is possible that this two-step method may be reuired for some platforms.
12319 // So, retain in the code base to aid recovery if necessary
12320
12321 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12322 if( VPoint.b_quilt ) {
12323 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12324 ChartBase *chart = m_pQuilt->GetRefChart();
12325 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12326
12327 // Clear the text Global declutter list
12328 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12329 if(ChPI)
12330 ChPI->ClearPLIBTextList();
12331 else{
12332 if(ps52plib)
12333 ps52plib->ClearTextList();
12334 }
12335
12336 wxMemoryDC t_dc;
12337 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12338
12339 wxColor maskBackground = wxColour(1,0,0);
12340 t_dc.SelectObject( qbm );
12341 t_dc.SetBackground(wxBrush(maskBackground));
12342 t_dc.Clear();
12343
12344 // Copy the scratch DC into the new bitmap
12345 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12346
12347 // Render the text to the new bitmap
12348 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12349 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12350
12351 // Copy the new bitmap back to the scratch dc
12352 wxRegionIterator upd_final( ru );
12353 while( upd_final ) {
12354 wxRect rect = upd_final.GetRect();
12355 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12356 upd_final++;
12357 }
12358
12359 t_dc.SelectObject( wxNullBitmap );
12360 }
12361 }
12362 }
12363#endif
12364 // Direct rendering model...
12365 if (VPoint.b_quilt) {
12366 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12367 ChartBase *chart = m_pQuilt->GetRefChart();
12368 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12369 // Clear the text Global declutter list
12370 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12371 if (ChPI)
12372 ChPI->ClearPLIBTextList();
12373 else {
12374 if (ps52plib) ps52plib->ClearTextList();
12375 }
12376
12377 // Render the text directly to the scratch bitmap
12378 OCPNRegion chart_all_text_region(
12379 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12380
12381 if (g_bShowChartBar && m_Piano) {
12382 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12383 GetVP().pix_width, m_Piano->GetHeight());
12384
12385 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12386 if (!style->chartStatusWindowTransparent)
12387 chart_all_text_region.Subtract(chart_bar_rect);
12388 }
12389
12390 if (m_Compass && m_Compass->IsShown()) {
12391 wxRect compassRect = m_Compass->GetRect();
12392 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12393 chart_all_text_region.Subtract(compassRect);
12394 }
12395 }
12396
12397 mscratch_dc.DestroyClippingRegion();
12398
12399 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12400 chart_all_text_region);
12401 }
12402 }
12403 }
12404
12405 // Now that charts are fully rendered, apply the overlay objects as decals.
12406 ocpnDC scratch_dc(mscratch_dc);
12407 DrawOverlayObjects(scratch_dc, ru);
12408
12409 // And finally, blit the scratch dc onto the physical dc
12410 wxRegionIterator upd_final(rgn_blit);
12411 while (upd_final) {
12412 wxRect rect = upd_final.GetRect();
12413 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12414 rect.y);
12415 upd_final++;
12416 }
12417
12418 // Deselect the chart bitmap from the temp_dc, so that it will not be
12419 // destroyed in the temp_dc dtor
12420 temp_dc.SelectObject(wxNullBitmap);
12421 // And for the scratch bitmap
12422 mscratch_dc.SelectObject(wxNullBitmap);
12423
12424 dc.DestroyClippingRegion();
12425
12426 PaintCleanup();
12427}
12428
12429void ChartCanvas::PaintCleanup() {
12430 // Handle the current graphic window, if present
12431
12432 if (pCwin) {
12433 pCwin->Show();
12434 if (m_bTCupdate) {
12435 pCwin->Refresh();
12436 pCwin->Update();
12437 }
12438 }
12439
12440 // And set flags for next time
12441 m_bTCupdate = false;
12442
12443 // Handle deferred WarpPointer
12444 if (warp_flag) {
12445 WarpPointer(warp_x, warp_y);
12446 warp_flag = false;
12447 }
12448
12449 // Start movement timers, this runs nearly immediately.
12450 // the reason we cannot simply call it directly is the
12451 // refresh events it emits may be blocked from this paint event
12452 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12453 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12454}
12455
12456#if 0
12457wxColour GetErrorGraphicColor(double val)
12458{
12459 /*
12460 double valm = wxMin(val_max, val);
12461
12462 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12463 unsigned char red = (unsigned char)(255 * (valm/val_max));
12464
12465 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12466
12467 hv.saturation = 1.0;
12468 hv.value = 1.0;
12469
12470 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12471 return wxColour(rv.red, rv.green, rv.blue);
12472 */
12473
12474 // HTML colors taken from NOAA WW3 Web representation
12475 wxColour c;
12476 if((val > 0) && (val < 1)) c.Set(_T("#002ad9"));
12477 else if((val >= 1) && (val < 2)) c.Set(_T("#006ed9"));
12478 else if((val >= 2) && (val < 3)) c.Set(_T("#00b2d9"));
12479 else if((val >= 3) && (val < 4)) c.Set(_T("#00d4d4"));
12480 else if((val >= 4) && (val < 5)) c.Set(_T("#00d9a6"));
12481 else if((val >= 5) && (val < 7)) c.Set(_T("#00d900"));
12482 else if((val >= 7) && (val < 9)) c.Set(_T("#95d900"));
12483 else if((val >= 9) && (val < 12)) c.Set(_T("#d9d900"));
12484 else if((val >= 12) && (val < 15)) c.Set(_T("#d9ae00"));
12485 else if((val >= 15) && (val < 18)) c.Set(_T("#d98300"));
12486 else if((val >= 18) && (val < 21)) c.Set(_T("#d95700"));
12487 else if((val >= 21) && (val < 24)) c.Set(_T("#d90000"));
12488 else if((val >= 24) && (val < 27)) c.Set(_T("#ae0000"));
12489 else if((val >= 27) && (val < 30)) c.Set(_T("#8c0000"));
12490 else if((val >= 30) && (val < 36)) c.Set(_T("#870000"));
12491 else if((val >= 36) && (val < 42)) c.Set(_T("#690000"));
12492 else if((val >= 42) && (val < 48)) c.Set(_T("#550000"));
12493 else if( val >= 48) c.Set(_T("#410000"));
12494
12495 return c;
12496}
12497
12498void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12499{
12500 wxImage gr_image(vp->pix_width, vp->pix_height);
12501 gr_image.InitAlpha();
12502
12503 double maxval = -10000;
12504 double minval = 10000;
12505
12506 double rlat, rlon;
12507 double glat, glon;
12508
12509 GetCanvasPixPoint(0, 0, rlat, rlon);
12510
12511 for(int i=1; i < vp->pix_height-1; i++)
12512 {
12513 for(int j=0; j < vp->pix_width; j++)
12514 {
12515 // Reference mercator value
12516// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12517
12518 // Georef value
12519 GetCanvasPixPoint(j, i, glat, glon);
12520
12521 maxval = wxMax(maxval, (glat - rlat));
12522 minval = wxMin(minval, (glat - rlat));
12523
12524 }
12525 rlat = glat;
12526 }
12527
12528 GetCanvasPixPoint(0, 0, rlat, rlon);
12529 for(int i=1; i < vp->pix_height-1; i++)
12530 {
12531 for(int j=0; j < vp->pix_width; j++)
12532 {
12533 // Reference mercator value
12534// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12535
12536 // Georef value
12537 GetCanvasPixPoint(j, i, glat, glon);
12538
12539 double f = ((glat - rlat)-minval)/(maxval - minval);
12540
12541 double dy = (f * 40);
12542
12543 wxColour c = GetErrorGraphicColor(dy);
12544 unsigned char r = c.Red();
12545 unsigned char g = c.Green();
12546 unsigned char b = c.Blue();
12547
12548 gr_image.SetRGB(j, i, r,g,b);
12549 if((glat - rlat )!= 0)
12550 gr_image.SetAlpha(j, i, 128);
12551 else
12552 gr_image.SetAlpha(j, i, 255);
12553
12554 }
12555 rlat = glat;
12556 }
12557
12558 // Create a Bitmap
12559 wxBitmap *pbm = new wxBitmap(gr_image);
12560 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12561 pbm->SetMask(gr_mask);
12562
12563 pmdc->DrawBitmap(*pbm, 0,0);
12564
12565 delete pbm;
12566
12567}
12568
12569#endif
12570
12571void ChartCanvas::CancelMouseRoute() {
12572 m_routeState = 0;
12573 m_pMouseRoute = NULL;
12574 m_bDrawingRoute = false;
12575}
12576
12577int ChartCanvas::GetNextContextMenuId() {
12578 return CanvasMenuHandler::GetNextContextMenuId();
12579}
12580
12581bool ChartCanvas::SetCursor(const wxCursor &c) {
12582#ifdef ocpnUSE_GL
12583 if (g_bopengl && m_glcc)
12584 return m_glcc->SetCursor(c);
12585 else
12586#endif
12587 return wxWindow::SetCursor(c);
12588}
12589
12590void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12591 if (g_bquiting) return;
12592 // Keep the mouse position members up to date
12593 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12594
12595 // Retrigger the route leg popup timer
12596 // This handles the case when the chart is moving in auto-follow mode,
12597 // but no user mouse input is made. The timer handler may Hide() the
12598 // popup if the chart moved enough n.b. We use slightly longer oneshot
12599 // value to allow this method's Refresh() to complete before potentially
12600 // getting another Refresh() in the popup timer handler.
12601 if (!m_RolloverPopupTimer.IsRunning() &&
12602 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12603 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12604 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12605 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12606
12607#ifdef ocpnUSE_GL
12608 if (m_glcc && g_bopengl) {
12609 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12610 // overlay objects.
12611 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12612
12613 m_glcc->Refresh(eraseBackground,
12614 NULL); // We always are going to render the entire screen
12615 // anyway, so make
12616 // sure that the window managers understand the invalid area
12617 // is actually the entire client area.
12618
12619 // We need to selectively Refresh some child windows, if they are visible.
12620 // Note that some children are refreshed elsewhere on timer ticks, so don't
12621 // need attention here.
12622
12623 // Thumbnail chart
12624 if (pthumbwin && pthumbwin->IsShown()) {
12625 pthumbwin->Raise();
12626 pthumbwin->Refresh(false);
12627 }
12628
12629 // ChartInfo window
12630 if (m_pCIWin && m_pCIWin->IsShown()) {
12631 m_pCIWin->Raise();
12632 m_pCIWin->Refresh(false);
12633 }
12634
12635 // if(g_MainToolbar)
12636 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12637
12638 } else
12639#endif
12640 wxWindow::Refresh(eraseBackground, rect);
12641}
12642
12643void ChartCanvas::Update() {
12644 if (m_glcc && g_bopengl) {
12645#ifdef ocpnUSE_GL
12646 m_glcc->Update();
12647#endif
12648 } else
12649 wxWindow::Update();
12650}
12651
12652void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12653 if (!pemboss) return;
12654 int x = pemboss->x, y = pemboss->y;
12655 const double factor = 200;
12656
12657 wxASSERT_MSG(dc.GetDC(), wxT("DrawEmboss has no dc (opengl?)"));
12658 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12659 wxASSERT_MSG(pmdc, wxT("dc to EmbossCanvas not a memory dc"));
12660
12661 // Grab a snipped image out of the chart
12662 wxMemoryDC snip_dc;
12663 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12664 snip_dc.SelectObject(snip_bmp);
12665
12666 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12667 snip_dc.SelectObject(wxNullBitmap);
12668
12669 wxImage snip_img = snip_bmp.ConvertToImage();
12670
12671 // Apply Emboss map to the snip image
12672 unsigned char *pdata = snip_img.GetData();
12673 if (pdata) {
12674 for (int y = 0; y < pemboss->height; y++) {
12675 int map_index = (y * pemboss->width);
12676 for (int x = 0; x < pemboss->width; x++) {
12677 double val = (pemboss->pmap[map_index] * factor) / 256.;
12678
12679 int nred = (int)((*pdata) + val);
12680 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12681 *pdata++ = (unsigned char)nred;
12682
12683 int ngreen = (int)((*pdata) + val);
12684 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12685 *pdata++ = (unsigned char)ngreen;
12686
12687 int nblue = (int)((*pdata) + val);
12688 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12689 *pdata++ = (unsigned char)nblue;
12690
12691 map_index++;
12692 }
12693 }
12694 }
12695
12696 // Convert embossed snip to a bitmap
12697 wxBitmap emb_bmp(snip_img);
12698
12699 // Map to another memoryDC
12700 wxMemoryDC result_dc;
12701 result_dc.SelectObject(emb_bmp);
12702
12703 // Blit to target
12704 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12705
12706 result_dc.SelectObject(wxNullBitmap);
12707}
12708
12709emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12710 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12711
12712 if (GetQuiltMode()) {
12713 // disable Overzoom indicator for MBTiles
12714 int refIndex = GetQuiltRefChartdbIndex();
12715 if (refIndex >= 0) {
12716 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12717 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12718 if (current_type == CHART_TYPE_MBTILES) {
12719 ChartBase *pChart = m_pQuilt->GetRefChart();
12720 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12721 if (ptc) {
12722 zoom_factor = ptc->GetZoomFactor();
12723 }
12724 }
12725 }
12726
12727 if (zoom_factor <= 3.9) return NULL;
12728 } else {
12729 if (m_singleChart) {
12730 if (zoom_factor <= 3.9) return NULL;
12731 } else
12732 return NULL;
12733 }
12734
12735 if (m_pEM_OverZoom) {
12736 m_pEM_OverZoom->x = 4;
12737 m_pEM_OverZoom->y = 0;
12738 if (g_MainToolbar && IsPrimaryCanvas()) {
12739 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12740 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12741 }
12742 }
12743 return m_pEM_OverZoom;
12744}
12745
12746void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12747 GridDraw(dc);
12748
12749 // bool pluginOverlayRender = true;
12750 //
12751 // if(g_canvasConfig > 0){ // Multi canvas
12752 // if(IsPrimaryCanvas())
12753 // pluginOverlayRender = false;
12754 // }
12755
12756 g_overlayCanvas = this;
12757
12758 if (g_pi_manager) {
12759 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12760 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12762 }
12763
12764 AISDrawAreaNotices(dc, GetVP(), this);
12765
12766 wxDC *pdc = dc.GetDC();
12767 if (pdc) {
12768 pdc->DestroyClippingRegion();
12769 wxDCClipper(*pdc, ru);
12770 }
12771
12772 if (m_bShowNavobjects) {
12773 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12774 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12775 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12776 DrawAnchorWatchPoints(dc);
12777 } else {
12778 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12779 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12780 }
12781
12782 AISDraw(dc, GetVP(), this);
12783 ShipDraw(dc);
12784 AlertDraw(dc);
12785
12786 RenderVisibleSectorLights(dc);
12787
12788 RenderAllChartOutlines(dc, GetVP());
12789 RenderRouteLegs(dc);
12790 RenderShipToActive(dc, false);
12791 ScaleBarDraw(dc);
12792 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12793 if (g_pi_manager) {
12794 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12796 }
12797
12798 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12799 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12800
12801 if (g_pi_manager) {
12802 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12804 }
12805
12806 if (m_bShowTide) {
12807 RebuildTideSelectList(GetVP().GetBBox());
12808 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12809 }
12810
12811 if (m_bShowCurrent) {
12812 RebuildCurrentSelectList(GetVP().GetBBox());
12813 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12814 }
12815
12816 if (!g_PrintingInProgress) {
12817 if (IsPrimaryCanvas()) {
12818 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12819 }
12820
12821 if (IsPrimaryCanvas()) {
12822 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12823 }
12824
12825 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12826
12827 if (m_pTrackRolloverWin) {
12828 m_pTrackRolloverWin->Draw(dc);
12829 m_brepaint_piano = true;
12830 }
12831
12832 if (m_pRouteRolloverWin) {
12833 m_pRouteRolloverWin->Draw(dc);
12834 m_brepaint_piano = true;
12835 }
12836
12837 if (m_pAISRolloverWin) {
12838 m_pAISRolloverWin->Draw(dc);
12839 m_brepaint_piano = true;
12840 }
12841 if (m_brepaint_piano && g_bShowChartBar) {
12842 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12843 }
12844
12845 if (m_Compass) m_Compass->Paint(dc);
12846
12847 if (!g_CanvasHideNotificationIcon) {
12848 auto &noteman = NotificationManager::GetInstance();
12849 if (noteman.GetNotificationCount()) {
12850 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12851 if (m_notification_button->UpdateStatus()) Refresh();
12852 m_notification_button->Show(true);
12853 m_notification_button->Paint(dc);
12854 } else {
12855 m_notification_button->Show(false);
12856 }
12857 }
12858 }
12859 if (g_pi_manager) {
12860 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12862 }
12863}
12864
12865emboss_data *ChartCanvas::EmbossDepthScale() {
12866 if (!m_bShowDepthUnits) return NULL;
12867
12868 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12869
12870 if (GetQuiltMode()) {
12871 wxString s = m_pQuilt->GetQuiltDepthUnit();
12872 s.MakeUpper();
12873 if (s == _T("FEET"))
12874 depth_unit_type = DEPTH_UNIT_FEET;
12875 else if (s.StartsWith(_T("FATHOMS")))
12876 depth_unit_type = DEPTH_UNIT_FATHOMS;
12877 else if (s.StartsWith(_T("METERS")))
12878 depth_unit_type = DEPTH_UNIT_METERS;
12879 else if (s.StartsWith(_T("METRES")))
12880 depth_unit_type = DEPTH_UNIT_METERS;
12881 else if (s.StartsWith(_T("METRIC")))
12882 depth_unit_type = DEPTH_UNIT_METERS;
12883 else if (s.StartsWith(_T("METER")))
12884 depth_unit_type = DEPTH_UNIT_METERS;
12885
12886 } else {
12887 if (m_singleChart) {
12888 depth_unit_type = m_singleChart->GetDepthUnitType();
12889 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12890 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12891 }
12892 }
12893
12894 emboss_data *ped = NULL;
12895 switch (depth_unit_type) {
12896 case DEPTH_UNIT_FEET:
12897 ped = m_pEM_Feet;
12898 break;
12899 case DEPTH_UNIT_METERS:
12900 ped = m_pEM_Meters;
12901 break;
12902 case DEPTH_UNIT_FATHOMS:
12903 ped = m_pEM_Fathoms;
12904 break;
12905 default:
12906 return NULL;
12907 }
12908
12909 ped->x = (GetVP().pix_width - ped->width);
12910
12911 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12912 wxRect r = m_Compass->GetRect();
12913 ped->y = r.y + r.height;
12914 } else {
12915 ped->y = 40;
12916 }
12917 return ped;
12918}
12919
12920void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12921 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12922 wxFont font;
12923 if (style->embossFont == wxEmptyString) {
12924 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12925 font = *dFont;
12926 font.SetPointSize(60);
12927 font.SetWeight(wxFONTWEIGHT_BOLD);
12928 } else
12929 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12930 wxFONTWEIGHT_BOLD, false, style->embossFont);
12931
12932 int emboss_width = 500;
12933 int emboss_height = 200;
12934
12935 // Free any existing emboss maps
12936 delete m_pEM_Feet;
12937 delete m_pEM_Meters;
12938 delete m_pEM_Fathoms;
12939
12940 // Create the 3 DepthUnit emboss map structures
12941 m_pEM_Feet =
12942 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12943 m_pEM_Meters =
12944 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12945 m_pEM_Fathoms =
12946 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12947}
12948
12949#define OVERZOOM_TEXT _("OverZoom")
12950
12951void ChartCanvas::SetOverzoomFont() {
12952 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12953 int w, h;
12954
12955 wxFont font;
12956 if (style->embossFont == wxEmptyString) {
12957 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12958 font = *dFont;
12959 font.SetPointSize(40);
12960 font.SetWeight(wxFONTWEIGHT_BOLD);
12961 } else
12962 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12963 wxFONTWEIGHT_BOLD, false, style->embossFont);
12964
12965 wxClientDC dc(this);
12966 dc.SetFont(font);
12967 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12968
12969 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12970 font.SetPointSize(font.GetPointSize() - 1);
12971 dc.SetFont(font);
12972 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12973 }
12974 m_overzoomFont = font;
12975 m_overzoomTextWidth = w;
12976 m_overzoomTextHeight = h;
12977}
12978
12979void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12980 delete m_pEM_OverZoom;
12981
12982 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12983 m_pEM_OverZoom =
12984 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12985 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12986}
12987
12988emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12989 int height, const wxString &str,
12990 ColorScheme cs) {
12991 int *pmap;
12992
12993 // Create a temporary bitmap
12994 wxBitmap bmp(width, height, -1);
12995
12996 // Create a memory DC
12997 wxMemoryDC temp_dc;
12998 temp_dc.SelectObject(bmp);
12999
13000 // Paint on it
13001 temp_dc.SetBackground(*wxWHITE_BRUSH);
13002 temp_dc.SetTextBackground(*wxWHITE);
13003 temp_dc.SetTextForeground(*wxBLACK);
13004
13005 temp_dc.Clear();
13006
13007 temp_dc.SetFont(font);
13008
13009 int str_w, str_h;
13010 temp_dc.GetTextExtent(str, &str_w, &str_h);
13011 // temp_dc.DrawText( str, width - str_w - 10, 10 );
13012 temp_dc.DrawText(str, 1, 1);
13013
13014 // Deselect the bitmap
13015 temp_dc.SelectObject(wxNullBitmap);
13016
13017 // Convert bitmap the wxImage for manipulation
13018 wxImage img = bmp.ConvertToImage();
13019
13020 int image_width = str_w * 105 / 100;
13021 int image_height = str_h * 105 / 100;
13022 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
13023 wxMin(image_height, img.GetHeight()));
13024 wxImage imgs = img.GetSubImage(r);
13025
13026 double val_factor;
13027 switch (cs) {
13028 case GLOBAL_COLOR_SCHEME_DAY:
13029 default:
13030 val_factor = 1;
13031 break;
13032 case GLOBAL_COLOR_SCHEME_DUSK:
13033 val_factor = .5;
13034 break;
13035 case GLOBAL_COLOR_SCHEME_NIGHT:
13036 val_factor = .25;
13037 break;
13038 }
13039
13040 int val;
13041 int index;
13042 const int w = imgs.GetWidth();
13043 const int h = imgs.GetHeight();
13044 pmap = (int *)calloc(w * h * sizeof(int), 1);
13045 // Create emboss map by differentiating the emboss image
13046 // and storing integer results in pmap
13047 // n.b. since the image is B/W, it is sufficient to check
13048 // one channel (i.e. red) only
13049 for (int y = 1; y < h - 1; y++) {
13050 for (int x = 1; x < w - 1; x++) {
13051 val =
13052 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13053 val = (int)(val * val_factor);
13054 index = (y * w) + x;
13055 pmap[index] = val;
13056 }
13057 }
13058
13059 emboss_data *pret = new emboss_data;
13060 pret->pmap = pmap;
13061 pret->width = w;
13062 pret->height = h;
13063
13064 return pret;
13065}
13066
13067void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13068 Track *active_track = NULL;
13069 for (Track *pTrackDraw : g_TrackList) {
13070 if (g_pActiveTrack == pTrackDraw) {
13071 active_track = pTrackDraw;
13072 continue;
13073 }
13074
13075 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13076 }
13077
13078 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13079}
13080
13081void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13082 Track *active_track = NULL;
13083 for (Track *pTrackDraw : g_TrackList) {
13084 if (g_pActiveTrack == pTrackDraw) {
13085 active_track = pTrackDraw;
13086 break;
13087 }
13088 }
13089 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13090}
13091
13092void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13093 Route *active_route = NULL;
13094
13095 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
13096 node = node->GetNext()) {
13097 Route *pRouteDraw = node->GetData();
13098 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13099 active_route = pRouteDraw;
13100 continue;
13101 }
13102
13103 // if(m_canvasIndex == 1)
13104 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13105 }
13106
13107 // Draw any active or selected route (or track) last, so that is is always on
13108 // top
13109 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13110}
13111
13112void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13113 Route *active_route = NULL;
13114
13115 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
13116 node = node->GetNext()) {
13117 Route *pRouteDraw = node->GetData();
13118 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13119 active_route = pRouteDraw;
13120 break;
13121 }
13122 }
13123 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13124}
13125
13126void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13127 if (!pWayPointMan) return;
13128
13129 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
13130
13131 while (node) {
13132 RoutePoint *pWP = node->GetData();
13133 if (pWP) {
13134 if (pWP->m_bIsInRoute) {
13135 node = node->GetNext();
13136 continue;
13137 }
13138
13139 /* technically incorrect... waypoint has bounding box */
13140 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13141 RoutePointGui(*pWP).Draw(dc, this, NULL);
13142 else {
13143 // Are Range Rings enabled?
13144 if (pWP->GetShowWaypointRangeRings() &&
13145 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13146 double factor = 1.00;
13147 if (pWP->GetWaypointRangeRingsStepUnits() ==
13148 1) // convert kilometers to NMi
13149 factor = 1 / 1.852;
13150
13151 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13152 pWP->GetWaypointRangeRingsStep() / 60.;
13153 radius *= 2; // Fudge factor
13154
13155 LLBBox radar_box;
13156 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13157 pWP->m_lat + radius, pWP->m_lon + radius);
13158 if (!BltBBox.IntersectOut(radar_box)) {
13159 RoutePointGui(*pWP).Draw(dc, this, NULL);
13160 }
13161 }
13162 }
13163 }
13164
13165 node = node->GetNext();
13166 }
13167}
13168
13169void ChartCanvas::DrawBlinkObjects(void) {
13170 // All RoutePoints
13171 wxRect update_rect;
13172
13173 if (!pWayPointMan) return;
13174
13175 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
13176
13177 while (node) {
13178 RoutePoint *pWP = node->GetData();
13179 if (pWP) {
13180 if (pWP->m_bBlink) {
13181 update_rect.Union(pWP->CurrentRect_in_DC);
13182 }
13183 }
13184
13185 node = node->GetNext();
13186 }
13187 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13188}
13189
13190void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13191 // draw anchor watch rings, if activated
13192
13193 if (pAnchorWatchPoint1 || pAnchorWatchPoint2) {
13194 wxPoint r1, r2;
13195 wxPoint lAnchorPoint1, lAnchorPoint2;
13196 double lpp1 = 0.0;
13197 double lpp2 = 0.0;
13198 if (pAnchorWatchPoint1) {
13199 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13200 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
13201 &lAnchorPoint1);
13202 }
13203 if (pAnchorWatchPoint2) {
13204 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13205 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
13206 &lAnchorPoint2);
13207 }
13208
13209 wxPen ppPeng(GetGlobalColor(_T ( "UGREN" )), 2);
13210 wxPen ppPenr(GetGlobalColor(_T ( "URED" )), 2);
13211
13212 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13213 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13214 dc.SetBrush(*ppBrush);
13215
13216 if (lpp1 > 0) {
13217 dc.SetPen(ppPeng);
13218 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13219 }
13220
13221 if (lpp2 > 0) {
13222 dc.SetPen(ppPeng);
13223 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13224 }
13225
13226 if (lpp1 < 0) {
13227 dc.SetPen(ppPenr);
13228 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13229 }
13230
13231 if (lpp2 < 0) {
13232 dc.SetPen(ppPenr);
13233 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13234 }
13235 }
13236}
13237
13238double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13239 double lpp = 0.;
13240 wxPoint r1;
13241 wxPoint lAnchorPoint;
13242 double d1 = 0.0;
13243 double dabs;
13244 double tlat1, tlon1;
13245
13246 if (pAnchorWatchPoint) {
13247 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13248 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13249 dabs = fabs(d1 / 1852.);
13250 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13251 &tlat1, &tlon1);
13252 GetCanvasPointPix(tlat1, tlon1, &r1);
13253 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13254 &lAnchorPoint);
13255 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13256 pow((double)(lAnchorPoint.y - r1.y), 2));
13257
13258 // This is an entry watch
13259 if (d1 < 0) lpp = -lpp;
13260 }
13261 return lpp;
13262}
13263
13264//------------------------------------------------------------------------------------------
13265// Tides Support
13266//------------------------------------------------------------------------------------------
13267void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13268 if (!ptcmgr) return;
13269
13270 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13271
13272 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13273 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13274 double lon = pIDX->IDX_lon;
13275 double lat = pIDX->IDX_lat;
13276
13277 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13278 if ((type == 't') || (type == 'T')) {
13279 if (BBox.Contains(lat, lon)) {
13280 // Manage the point selection list
13281 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13282 }
13283 }
13284 }
13285}
13286
13287extern wxDateTime gTimeSource;
13288
13289void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13290 if (!ptcmgr) return;
13291
13292 wxDateTime this_now = gTimeSource;
13293 bool cur_time = !gTimeSource.IsValid();
13294 if (cur_time) this_now = wxDateTime::Now();
13295 time_t t_this_now = this_now.GetTicks();
13296
13297 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
13298 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
13299 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13300 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )), 1,
13301 wxPENSTYLE_SOLID);
13302 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13303 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )), 1,
13304 wxPENSTYLE_SOLID);
13305
13306 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13307 GetGlobalColor(_T ( "GREEN1" )), wxBRUSHSTYLE_SOLID);
13308 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13309 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )),
13310 wxBRUSHSTYLE_SOLID);
13311 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13312 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )),
13313 wxBRUSHSTYLE_SOLID);
13314
13315 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13316 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13317 int font_size = wxMax(10, dFont->GetPointSize());
13318 font_size /= g_Platform->GetDisplayDIPMult(this);
13319 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13320 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13321 false, dFont->GetFaceName());
13322
13323 dc.SetPen(*pblack_pen);
13324 dc.SetBrush(*pgreen_brush);
13325
13326 wxBitmap bm;
13327 switch (m_cs) {
13328 case GLOBAL_COLOR_SCHEME_DAY:
13329 bm = m_bmTideDay;
13330 break;
13331 case GLOBAL_COLOR_SCHEME_DUSK:
13332 bm = m_bmTideDusk;
13333 break;
13334 case GLOBAL_COLOR_SCHEME_NIGHT:
13335 bm = m_bmTideNight;
13336 break;
13337 default:
13338 bm = m_bmTideDay;
13339 break;
13340 }
13341
13342 int bmw = bm.GetWidth();
13343 int bmh = bm.GetHeight();
13344
13345 float scale_factor = 1.0;
13346
13347 // Set the onscreen size of the symbol
13348 // Compensate for various display resolutions
13349 float icon_pixelRefDim = 45;
13350
13351 // Tidal report graphic is scaled by the text size of the label in use
13352 wxScreenDC sdc;
13353 int height;
13354 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13355 height *= g_Platform->GetDisplayDIPMult(this);
13356 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13357
13358 scale_factor *= pix_factor;
13359
13360 float user_scale_factor = g_ChartScaleFactorExp;
13361 if (g_ChartScaleFactorExp > 1.0)
13362 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13363 1.2; // soften the scale factor a bit
13364
13365 scale_factor *= user_scale_factor;
13366 scale_factor *= GetContentScaleFactor();
13367
13368 {
13369 double marge = 0.05;
13370 std::vector<LLBBox> drawn_boxes;
13371 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13372 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13373
13374 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13375 if ((type == 't') || (type == 'T')) // only Tides
13376 {
13377 double lon = pIDX->IDX_lon;
13378 double lat = pIDX->IDX_lat;
13379
13380 if (BBox.ContainsMarge(lat, lon, marge)) {
13381 // Avoid drawing detailed graphic for duplicate tide stations
13382 if (GetVP().chart_scale < 500000) {
13383 bool bdrawn = false;
13384 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13385 if (drawn_boxes[i].Contains(lat, lon)) {
13386 bdrawn = true;
13387 break;
13388 }
13389 }
13390 if (bdrawn) continue; // the station loop
13391
13392 LLBBox this_box;
13393 this_box.Set(lat, lon, lat, lon);
13394 this_box.EnLarge(.005);
13395 drawn_boxes.push_back(this_box);
13396 }
13397
13398 wxPoint r;
13399 GetCanvasPointPix(lat, lon, &r);
13400 // draw standard icons
13401 if (GetVP().chart_scale > 500000) {
13402 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13403 }
13404 // draw "extended" icons
13405 else {
13406 dc.SetFont(*plabelFont);
13407 {
13408 {
13409 float val, nowlev;
13410 float ltleve = 0.;
13411 float htleve = 0.;
13412 time_t tctime;
13413 time_t lttime = 0;
13414 time_t httime = 0;
13415 bool wt;
13416 // define if flood or ebb in the last ten minutes and verify if
13417 // data are useable
13418 if (ptcmgr->GetTideFlowSens(
13419 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13420 pIDX->IDX_rec_num, nowlev, val, wt)) {
13421 // search forward the first HW or LW near "now" ( starting at
13422 // "now" - ten minutes )
13423 ptcmgr->GetHightOrLowTide(
13424 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13425 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13426 wt, pIDX->IDX_rec_num, val, tctime);
13427 if (wt) {
13428 httime = tctime;
13429 htleve = val;
13430 } else {
13431 lttime = tctime;
13432 ltleve = val;
13433 }
13434 wt = !wt;
13435
13436 // then search opposite tide near "now"
13437 if (tctime > t_this_now) // search backward
13438 ptcmgr->GetHightOrLowTide(
13439 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13440 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13441 pIDX->IDX_rec_num, val, tctime);
13442 else
13443 // or search forward
13444 ptcmgr->GetHightOrLowTide(
13445 t_this_now, FORWARD_TEN_MINUTES_STEP,
13446 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13447 val, tctime);
13448 if (wt) {
13449 httime = tctime;
13450 htleve = val;
13451 } else {
13452 lttime = tctime;
13453 ltleve = val;
13454 }
13455
13456 // draw the tide rectangle:
13457
13458 // tide icon rectangle has default pre-scaled width = 12 ,
13459 // height = 45
13460 int width = (int)(12 * scale_factor + 0.5);
13461 int height = (int)(45 * scale_factor + 0.5);
13462 int linew = wxMax(1, (int)(scale_factor));
13463 int xDraw = r.x - (width / 2);
13464 int yDraw = r.y - (height / 2);
13465
13466 // process tide state ( %height and flow sens )
13467 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13468 int hs = (httime > lttime) ? -4 : 4;
13469 hs *= (int)(scale_factor + 0.5);
13470 if (ts > 0.995 || ts < 0.005) hs = 0;
13471 int ht_y = (int)(height * ts);
13472
13473 // draw yellow tide rectangle outlined in black
13474 pblack_pen->SetWidth(linew);
13475 dc.SetPen(*pblack_pen);
13476 dc.SetBrush(*pyelo_brush);
13477 dc.DrawRectangle(xDraw, yDraw, width, height);
13478
13479 // draw blue rectangle as water height, smaller in width than
13480 // yellow rectangle
13481 dc.SetPen(*pblue_pen);
13482 dc.SetBrush(*pblue_brush);
13483 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13484 (width - (4 * linew)), height - ht_y);
13485
13486 // draw sens arrows (ensure they are not "under-drawn" by top
13487 // line of blue rectangle )
13488 int hl;
13489 wxPoint arrow[3];
13490 arrow[0].x = xDraw + 2 * linew;
13491 arrow[1].x = xDraw + width / 2;
13492 arrow[2].x = xDraw + width - 2 * linew;
13493 pyelo_pen->SetWidth(linew);
13494 pblue_pen->SetWidth(linew);
13495 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13496 {
13497 hl = (int)(height * 0.25) + yDraw;
13498 arrow[0].y = hl;
13499 arrow[1].y = hl + hs;
13500 arrow[2].y = hl;
13501 if (ts < 0.15)
13502 dc.SetPen(*pyelo_pen);
13503 else
13504 dc.SetPen(*pblue_pen);
13505 dc.DrawLines(3, arrow);
13506 }
13507 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13508 {
13509 hl = (int)(height * 0.5) + yDraw;
13510 arrow[0].y = hl;
13511 arrow[1].y = hl + hs;
13512 arrow[2].y = hl;
13513 if (ts < 0.40)
13514 dc.SetPen(*pyelo_pen);
13515 else
13516 dc.SetPen(*pblue_pen);
13517 dc.DrawLines(3, arrow);
13518 }
13519 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13520 {
13521 hl = (int)(height * 0.75) + yDraw;
13522 arrow[0].y = hl;
13523 arrow[1].y = hl + hs;
13524 arrow[2].y = hl;
13525 if (ts < 0.65)
13526 dc.SetPen(*pyelo_pen);
13527 else
13528 dc.SetPen(*pblue_pen);
13529 dc.DrawLines(3, arrow);
13530 }
13531 // draw tide level text
13532 wxString s;
13533 s.Printf(_T("%3.1f"), nowlev);
13534 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13535 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13536 int wx1;
13537 dc.GetTextExtent(s, &wx1, NULL);
13538 wx1 *= g_Platform->GetDisplayDIPMult(this);
13539 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13540 }
13541 }
13542 }
13543 }
13544 }
13545 }
13546 }
13547 }
13548}
13549
13550//------------------------------------------------------------------------------------------
13551// Currents Support
13552//------------------------------------------------------------------------------------------
13553
13554void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13555 if (!ptcmgr) return;
13556
13557 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13558
13559 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13560 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13561 double lon = pIDX->IDX_lon;
13562 double lat = pIDX->IDX_lat;
13563
13564 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13565 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13566 if ((BBox.Contains(lat, lon))) {
13567 // Manage the point selection list
13568 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13569 }
13570 }
13571 }
13572}
13573
13574void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13575 if (!ptcmgr) return;
13576
13577 float tcvalue, dir;
13578 bool bnew_val;
13579 char sbuf[20];
13580 wxFont *pTCFont;
13581 double lon_last = 0.;
13582 double lat_last = 0.;
13583 // arrow size for Raz Blanchard : 12 knots north
13584 double marge = 0.2;
13585 bool cur_time = !gTimeSource.IsValid();
13586
13587 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13588 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13589
13590 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
13591 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
13592 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13593 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )), 1,
13594 wxPENSTYLE_SOLID);
13595 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13596 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )),
13597 wxBRUSHSTYLE_SOLID);
13598 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13599 GetGlobalColor(_T ( "UIBDR" )), wxBRUSHSTYLE_SOLID);
13600 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13601 GetGlobalColor(_T ( "UINFD" )), wxBRUSHSTYLE_SOLID);
13602
13603 double skew_angle = GetVPRotation();
13604
13605 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13606 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13607 int font_size = wxMax(10, dFont->GetPointSize());
13608 font_size /= g_Platform->GetDisplayDIPMult(this);
13609 pTCFont = FontMgr::Get().FindOrCreateFont(
13610 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13611 false, dFont->GetFaceName());
13612
13613 float scale_factor = 1.0;
13614
13615 // Set the onscreen size of the symbol
13616 // Current report graphic is scaled by the text size of the label in use
13617 wxScreenDC sdc;
13618 int height;
13619 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13620 height *= g_Platform->GetDisplayDIPMult(this);
13621 float nominal_icon_size_pixels = 15;
13622 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13623
13624 scale_factor *= pix_factor;
13625
13626 float user_scale_factor = g_ChartScaleFactorExp;
13627 if (g_ChartScaleFactorExp > 1.0)
13628 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13629 1.2; // soften the scale factor a bit
13630
13631 scale_factor *= user_scale_factor;
13632
13633 scale_factor *= GetContentScaleFactor();
13634
13635 {
13636 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13637 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13638 double lon = pIDX->IDX_lon;
13639 double lat = pIDX->IDX_lat;
13640
13641 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13642 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13643 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13644 wxPoint r;
13645 GetCanvasPointPix(lat, lon, &r);
13646
13647 wxPoint d[4]; // points of a diamond at the current station location
13648 int dd = (int)(5.0 * scale_factor + 0.5);
13649 d[0].x = r.x;
13650 d[0].y = r.y + dd;
13651 d[1].x = r.x + dd;
13652 d[1].y = r.y;
13653 d[2].x = r.x;
13654 d[2].y = r.y - dd;
13655 d[3].x = r.x - dd;
13656 d[3].y = r.y;
13657
13658 if (1) {
13659 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13660 dc.SetPen(*pblack_pen);
13661 dc.SetBrush(*porange_brush);
13662 dc.DrawPolygon(4, d);
13663
13664 if (type == 'C') {
13665 dc.SetBrush(*pblack_brush);
13666 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13667 }
13668
13669 if (GetVP().chart_scale < 1000000) {
13670 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13671 continue;
13672 } else
13673 continue;
13674
13675 if (1 /*type == 'c'*/) {
13676 {
13677 // Get the display pixel location of the current station
13678 int pixxc, pixyc;
13679 pixxc = r.x;
13680 pixyc = r.y;
13681
13682 // Adjust drawing size using logarithmic scale. tcvalue is
13683 // current in knots
13684 double a1 = fabs(tcvalue) * 10.;
13685 // Current values <= 0.1 knot will have no arrow
13686 a1 = wxMax(1.0, a1);
13687 double a2 = log10(a1);
13688
13689 float cscale = scale_factor * a2 * 0.3;
13690
13691 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13692 dc.SetPen(*porange_pen);
13693 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13694 cscale);
13695 // Draw text, if enabled
13696
13697 if (bDrawCurrentValues) {
13698 dc.SetFont(*pTCFont);
13699 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13700 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13701 }
13702 }
13703 } // scale
13704 }
13705 /* This is useful for debugging the TC database
13706 else
13707 {
13708 dc.SetPen ( *porange_pen );
13709 dc.SetBrush ( *pgray_brush );
13710 dc.DrawPolygon ( 4, d );
13711 }
13712 */
13713 }
13714 lon_last = lon;
13715 lat_last = lat;
13716 }
13717 }
13718 }
13719}
13720
13721void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13722 pCwin = new TCWin(this, x, y, pvIDX);
13723}
13724
13725#define NUM_CURRENT_ARROW_POINTS 9
13726static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13727 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13728 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13729 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13730
13731void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13732 double scale) {
13733 if (scale > 1e-2) {
13734 float sin_rot = sin(rot_angle * PI / 180.);
13735 float cos_rot = cos(rot_angle * PI / 180.);
13736
13737 // Move to the first point
13738
13739 float xt = CurrentArrowArray[0].x;
13740 float yt = CurrentArrowArray[0].y;
13741
13742 float xp = (xt * cos_rot) - (yt * sin_rot);
13743 float yp = (xt * sin_rot) + (yt * cos_rot);
13744 int x1 = (int)(xp * scale);
13745 int y1 = (int)(yp * scale);
13746
13747 // Walk thru the point list
13748 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13749 xt = CurrentArrowArray[ip].x;
13750 yt = CurrentArrowArray[ip].y;
13751
13752 float xp = (xt * cos_rot) - (yt * sin_rot);
13753 float yp = (xt * sin_rot) + (yt * cos_rot);
13754 int x2 = (int)(xp * scale);
13755 int y2 = (int)(yp * scale);
13756
13757 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13758
13759 x1 = x2;
13760 y1 = y2;
13761 }
13762 }
13763}
13764
13765wxString ChartCanvas::FindValidUploadPort() {
13766 wxString port;
13767 // Try to use the saved persistent upload port first
13768 if (!g_uploadConnection.IsEmpty() &&
13769 g_uploadConnection.StartsWith(_T("Serial"))) {
13770 port = g_uploadConnection;
13771 }
13772
13773 else {
13774 // If there is no persistent upload port recorded (yet)
13775 // then use the first available serial connection which has output defined.
13776 for (auto *cp : TheConnectionParams()) {
13777 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13778 port << _T("Serial:") << cp->Port;
13779 }
13780 }
13781 return port;
13782}
13783
13784void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13785 if (!win) return;
13786
13787 if (NULL == g_pais_query_dialog_active) {
13788 int pos_x = g_ais_query_dialog_x;
13789 int pos_y = g_ais_query_dialog_y;
13790
13791 if (g_pais_query_dialog_active) {
13792 g_pais_query_dialog_active->Destroy();
13793 g_pais_query_dialog_active = new AISTargetQueryDialog();
13794 } else {
13795 g_pais_query_dialog_active = new AISTargetQueryDialog();
13796 }
13797
13798 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13799 wxPoint(pos_x, pos_y));
13800
13801 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13802 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13803 g_pais_query_dialog_active->SetMMSI(mmsi);
13804 g_pais_query_dialog_active->UpdateText();
13805 wxSize sz = g_pais_query_dialog_active->GetSize();
13806
13807 bool b_reset_pos = false;
13808#ifdef __WXMSW__
13809 // Support MultiMonitor setups which an allow negative window positions.
13810 // If the requested window title bar does not intersect any installed
13811 // monitor, then default to simple primary monitor positioning.
13812 RECT frame_title_rect;
13813 frame_title_rect.left = pos_x;
13814 frame_title_rect.top = pos_y;
13815 frame_title_rect.right = pos_x + sz.x;
13816 frame_title_rect.bottom = pos_y + 30;
13817
13818 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13819 b_reset_pos = true;
13820#else
13821
13822 // Make sure drag bar (title bar) of window intersects wxClient Area of
13823 // screen, with a little slop...
13824 wxRect window_title_rect; // conservative estimate
13825 window_title_rect.x = pos_x;
13826 window_title_rect.y = pos_y;
13827 window_title_rect.width = sz.x;
13828 window_title_rect.height = 30;
13829
13830 wxRect ClientRect = wxGetClientDisplayRect();
13831 ClientRect.Deflate(
13832 60, 60); // Prevent the new window from being too close to the edge
13833 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13834
13835#endif
13836
13837 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13838
13839 } else {
13840 g_pais_query_dialog_active->SetMMSI(mmsi);
13841 g_pais_query_dialog_active->UpdateText();
13842 }
13843
13844 g_pais_query_dialog_active->Show();
13845}
13846
13847void ChartCanvas::ToggleCanvasQuiltMode(void) {
13848 bool cur_mode = GetQuiltMode();
13849
13850 if (!GetQuiltMode())
13851 SetQuiltMode(true);
13852 else if (GetQuiltMode()) {
13853 SetQuiltMode(false);
13854 g_sticky_chart = GetQuiltReferenceChartIndex();
13855 }
13856
13857 if (cur_mode != GetQuiltMode()) {
13858 SetupCanvasQuiltMode();
13859 DoCanvasUpdate();
13860 InvalidateGL();
13861 Refresh();
13862 }
13863 // TODO What to do about this?
13864 // g_bQuiltEnable = GetQuiltMode();
13865
13866 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13867 if (ps52plib) ps52plib->GenerateStateHash();
13868
13869 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13870 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13871}
13872
13873void ChartCanvas::DoCanvasStackDelta(int direction) {
13874 if (!GetQuiltMode()) {
13875 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13876 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13877 if ((current_stack_index + direction) < 0) return;
13878
13879 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13880 int new_dbIndex =
13881 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13882
13883 if (IsChartQuiltableRef(new_dbIndex)) {
13884 ToggleCanvasQuiltMode();
13885 SelectQuiltRefdbChart(new_dbIndex);
13886 m_bpersistent_quilt = false;
13887 }
13888 } else {
13889 SelectChartFromStack(current_stack_index + direction);
13890 }
13891 } else {
13892 std::vector<int> piano_chart_index_array =
13893 GetQuiltExtendedStackdbIndexArray();
13894 int refdb = GetQuiltRefChartdbIndex();
13895
13896 // Find the ref chart in the stack
13897 int current_index = -1;
13898 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13899 if (refdb == piano_chart_index_array[i]) {
13900 current_index = i;
13901 break;
13902 }
13903 }
13904 if (current_index == -1) return;
13905
13906 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13907 int target_family = ctet.GetChartFamily();
13908
13909 int new_index = -1;
13910 int check_index = current_index + direction;
13911 bool found = false;
13912 int check_dbIndex = -1;
13913 int new_dbIndex = -1;
13914
13915 // When quilted. switch within the same chart family
13916 while (!found &&
13917 (unsigned int)check_index < piano_chart_index_array.size() &&
13918 (check_index >= 0)) {
13919 check_dbIndex = piano_chart_index_array[check_index];
13920 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13921 if (target_family == cte.GetChartFamily()) {
13922 found = true;
13923 new_index = check_index;
13924 new_dbIndex = check_dbIndex;
13925 break;
13926 }
13927
13928 check_index += direction;
13929 }
13930
13931 if (!found) return;
13932
13933 if (!IsChartQuiltableRef(new_dbIndex)) {
13934 ToggleCanvasQuiltMode();
13935 SelectdbChart(new_dbIndex);
13936 m_bpersistent_quilt = true;
13937 } else {
13938 SelectQuiltRefChart(new_index);
13939 }
13940 }
13941
13942 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13943 // (checkmarks etc)
13944 SetQuiltChartHiLiteIndex(-1);
13945
13946 ReloadVP();
13947}
13948
13949//--------------------------------------------------------------------------------------------------------
13950//
13951// Toolbar support
13952//
13953//--------------------------------------------------------------------------------------------------------
13954
13955void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13956 // Handle the per-canvas toolbar clicks here
13957
13958 switch (event.GetId()) {
13959 case ID_ZOOMIN: {
13960 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13961 break;
13962 }
13963
13964 case ID_ZOOMOUT: {
13965 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13966 break;
13967 }
13968
13969 case ID_STKUP:
13970 DoCanvasStackDelta(1);
13971 DoCanvasUpdate();
13972 break;
13973
13974 case ID_STKDN:
13975 DoCanvasStackDelta(-1);
13976 DoCanvasUpdate();
13977 break;
13978
13979 case ID_FOLLOW: {
13980 TogglebFollow();
13981 break;
13982 }
13983
13984 case ID_CURRENT: {
13985 ShowCurrents(!GetbShowCurrent());
13986 ReloadVP();
13987 Refresh(false);
13988 break;
13989 }
13990
13991 case ID_TIDE: {
13992 ShowTides(!GetbShowTide());
13993 ReloadVP();
13994 Refresh(false);
13995 break;
13996 }
13997
13998 case ID_ROUTE: {
13999 if (0 == m_routeState) {
14000 StartRoute();
14001 } else {
14002 FinishRoute();
14003 }
14004
14005#ifdef __ANDROID__
14006 androidSetRouteAnnunciator(m_routeState == 1);
14007#endif
14008 break;
14009 }
14010
14011 case ID_AIS: {
14012 SetAISCanvasDisplayStyle(-1);
14013 break;
14014 }
14015
14016 default:
14017 break;
14018 }
14019
14020 // And then let gFrame handle the rest....
14021 event.Skip();
14022}
14023
14024void ChartCanvas::SetShowAIS(bool show) {
14025 m_bShowAIS = show;
14026 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14027 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14028}
14029
14030void ChartCanvas::SetAttenAIS(bool show) {
14031 m_bShowAISScaled = show;
14032 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14033 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14034}
14035
14036void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14037 // make some arrays to hold the dfferences between cycle steps
14038 // show all, scaled, hide all
14039 bool bShowAIS_Array[3] = {true, true, false};
14040 bool bShowScaled_Array[3] = {false, true, true};
14041 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14042 _("Attenuate less critical AIS targets"),
14043 _("Hide AIS Targets")};
14044 wxString iconName_Array[3] = {_T("AIS"), _T("AIS_Suppressed"),
14045 _T("AIS_Disabled")};
14046 int ArraySize = 3;
14047 int AIS_Toolbar_Switch = 0;
14048 if (StyleIndx == -1) { // -1 means coming from toolbar button
14049 // find current state of switch
14050 for (int i = 1; i < ArraySize; i++) {
14051 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14052 (bShowScaled_Array[i] == m_bShowAISScaled))
14053 AIS_Toolbar_Switch = i;
14054 }
14055 AIS_Toolbar_Switch++; // we did click so continu with next item
14056 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14057 AIS_Toolbar_Switch++;
14058
14059 } else { // coming from menu bar.
14060 AIS_Toolbar_Switch = StyleIndx;
14061 }
14062 // make sure we are not above array
14063 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14064
14065 int AIS_Toolbar_Switch_Next =
14066 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14067 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14068 AIS_Toolbar_Switch_Next++;
14069 if (AIS_Toolbar_Switch_Next >= ArraySize)
14070 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14071
14072 // Set found values to global and member variables
14073 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14074 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14075}
14076
14077void ChartCanvas::TouchAISToolActive(void) {}
14078
14079void ChartCanvas::UpdateAISTBTool(void) {}
14080
14081//---------------------------------------------------------------------------------
14082//
14083// Compass/GPS status icon support
14084//
14085//---------------------------------------------------------------------------------
14086
14087void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14088 // Look for change in overlap or positions
14089 bool b_update = false;
14090 int cc1_edge_comp = 2;
14091 wxRect rect = m_Compass->GetRect();
14092 wxSize parent_size = GetSize();
14093
14094 parent_size *= m_displayScale;
14095
14096 // check to see if it would overlap if it was in its home position (upper
14097 // right)
14098 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14099 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14100 wxRect compass_rect(compass_pt, rect.GetSize());
14101
14102 m_Compass->Move(compass_pt);
14103
14104 if (m_Compass && m_Compass->IsShown())
14105 m_Compass->UpdateStatus(b_force_new | b_update);
14106
14107 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14108 scaler = wxMax(scaler, 1.0);
14109 wxPoint note_point = wxPoint(
14110 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14111 m_notification_button->Move(note_point);
14112 m_notification_button->UpdateStatus();
14113
14114 if (b_force_new | b_update) Refresh();
14115}
14116
14117void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14118 ChartTypeEnum New_Type,
14119 ChartFamilyEnum New_Family) {
14120 if (!GetpCurrentStack()) return;
14121 if (!ChartData) return;
14122
14123 if (index < GetpCurrentStack()->nEntry) {
14124 // Open the new chart
14125 ChartBase *pTentative_Chart;
14126 pTentative_Chart = ChartData->OpenStackChartConditional(
14127 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14128
14129 if (pTentative_Chart) {
14130 if (m_singleChart) m_singleChart->Deactivate();
14131
14132 m_singleChart = pTentative_Chart;
14133 m_singleChart->Activate();
14134
14135 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14136 GetpCurrentStack(), m_singleChart->GetFullPath());
14137 }
14138
14139 // Setup the view
14140 double zLat, zLon;
14141 if (m_bFollow) {
14142 zLat = gLat;
14143 zLon = gLon;
14144 } else {
14145 zLat = m_vLat;
14146 zLon = m_vLon;
14147 }
14148
14149 double best_scale_ppm = GetBestVPScale(m_singleChart);
14150 double rotation = GetVPRotation();
14151 double oldskew = GetVPSkew();
14152 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14153
14154 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14155 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14156 if (fabs(newskew) > 0.0001) rotation = newskew;
14157 }
14158
14159 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14160
14161 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14162 }
14163
14164 // refresh Piano
14165 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14166 if (idx < 0) return;
14167
14168 std::vector<int> piano_active_chart_index_array;
14169 piano_active_chart_index_array.push_back(
14170 GetpCurrentStack()->GetCurrentEntrydbIndex());
14171 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14172}
14173
14174void ChartCanvas::SelectdbChart(int dbindex) {
14175 if (!GetpCurrentStack()) return;
14176 if (!ChartData) return;
14177
14178 if (dbindex >= 0) {
14179 // Open the new chart
14180 ChartBase *pTentative_Chart;
14181 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14182
14183 if (pTentative_Chart) {
14184 if (m_singleChart) m_singleChart->Deactivate();
14185
14186 m_singleChart = pTentative_Chart;
14187 m_singleChart->Activate();
14188
14189 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14190 GetpCurrentStack(), m_singleChart->GetFullPath());
14191 }
14192
14193 // Setup the view
14194 double zLat, zLon;
14195 if (m_bFollow) {
14196 zLat = gLat;
14197 zLon = gLon;
14198 } else {
14199 zLat = m_vLat;
14200 zLon = m_vLon;
14201 }
14202
14203 double best_scale_ppm = GetBestVPScale(m_singleChart);
14204
14205 if (m_singleChart)
14206 SetViewPoint(zLat, zLon, best_scale_ppm,
14207 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14208
14209 // SetChartUpdatePeriod( );
14210
14211 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14212 }
14213
14214 // TODO refresh_Piano();
14215}
14216
14217void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14218 double target_scale = GetVP().view_scale_ppm;
14219
14220 if (!GetQuiltMode()) {
14221 if (GetpCurrentStack()) {
14222 int stack_index = -1;
14223 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14224 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14225 if (check_dbIndex < 0) continue;
14226 const ChartTableEntry &cte =
14227 ChartData->GetChartTableEntry(check_dbIndex);
14228 if (type == cte.GetChartType()) {
14229 stack_index = i;
14230 break;
14231 } else if (family == cte.GetChartFamily()) {
14232 stack_index = i;
14233 break;
14234 }
14235 }
14236
14237 if (stack_index >= 0) {
14238 SelectChartFromStack(stack_index);
14239 }
14240 }
14241 } else {
14242 int sel_dbIndex = -1;
14243 std::vector<int> piano_chart_index_array =
14244 GetQuiltExtendedStackdbIndexArray();
14245 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14246 int check_dbIndex = piano_chart_index_array[i];
14247 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14248 if (type == cte.GetChartType()) {
14249 if (IsChartQuiltableRef(check_dbIndex)) {
14250 sel_dbIndex = check_dbIndex;
14251 break;
14252 }
14253 } else if (family == cte.GetChartFamily()) {
14254 if (IsChartQuiltableRef(check_dbIndex)) {
14255 sel_dbIndex = check_dbIndex;
14256 break;
14257 }
14258 }
14259 }
14260
14261 if (sel_dbIndex >= 0) {
14262 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14263 // Re-qualify the quilt reference chart selection
14264 AdjustQuiltRefChart();
14265 }
14266
14267 // Now reset the scale to the target...
14268 SetVPScale(target_scale);
14269 }
14270
14271 SetQuiltChartHiLiteIndex(-1);
14272
14273 ReloadVP();
14274}
14275
14276bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14277 return std::find(m_tile_yesshow_index_array.begin(),
14278 m_tile_yesshow_index_array.end(),
14279 index) != m_tile_yesshow_index_array.end();
14280}
14281
14282bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14283 return std::find(m_tile_noshow_index_array.begin(),
14284 m_tile_noshow_index_array.end(),
14285 index) != m_tile_noshow_index_array.end();
14286}
14287
14288void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14289 if (std::find(m_tile_noshow_index_array.begin(),
14290 m_tile_noshow_index_array.end(),
14291 index) == m_tile_noshow_index_array.end()) {
14292 m_tile_noshow_index_array.push_back(index);
14293 }
14294}
14295
14296//-------------------------------------------------------------------------------------------------------
14297//
14298// Piano support
14299//
14300//-------------------------------------------------------------------------------------------------------
14301
14302void ChartCanvas::HandlePianoClick(
14303 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14304 if (g_options && g_options->IsShown())
14305 return; // Piano might be invalid due to chartset updates.
14306 if (!m_pCurrentStack) return;
14307 if (!ChartData) return;
14308
14309 // stop movement or on slow computer we may get something like :
14310 // zoom out with the wheel (timer is set)
14311 // quickly click and display a chart, which may zoom in
14312 // but the delayed timer fires first and it zooms out again!
14313 StopMovement();
14314
14315 // When switching by piano key click, we may appoint the new target chart to
14316 // be any chart in the composite array.
14317 // As an improvement to UX, find the chart that is "closest" to the current
14318 // vp,
14319 // and select that chart. This will cause a jump to the centroid of that
14320 // chart
14321
14322 double distance = 25000; // RTW
14323 int closest_index = -1;
14324 for (int chart_index : selected_dbIndex_array) {
14325 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14326 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14327 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14328
14329 // measure distance as Manhattan style
14330 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14331 if (test_distance < distance) {
14332 distance = test_distance;
14333 closest_index = chart_index;
14334 }
14335 }
14336
14337 int selected_dbIndex = selected_dbIndex_array[0];
14338 if (closest_index >= 0) selected_dbIndex = closest_index;
14339
14340 if (!GetQuiltMode()) {
14341 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14342 if (IsChartQuiltableRef(selected_dbIndex)) {
14343 ToggleCanvasQuiltMode();
14344 SelectQuiltRefdbChart(selected_dbIndex);
14345 m_bpersistent_quilt = false;
14346 } else {
14347 SelectChartFromStack(selected_index);
14348 }
14349 } else {
14350 SelectChartFromStack(selected_index);
14351 g_sticky_chart = selected_dbIndex;
14352 }
14353
14354 if (m_singleChart)
14355 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14356 } else {
14357 // Handle MBTiles overlays first
14358 // Left click simply toggles the noshow array index entry
14359 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14360 bool bfound = false;
14361 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14362 if (m_tile_noshow_index_array[i] ==
14363 selected_dbIndex) { // chart is in the noshow list
14364 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14365 i); // erase it
14366 bfound = true;
14367 break;
14368 }
14369 }
14370 if (!bfound) {
14371 m_tile_noshow_index_array.push_back(selected_dbIndex);
14372 }
14373
14374 // If not already present, add this tileset to the "yes_show" array.
14375 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14376 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14377 }
14378
14379 else {
14380 if (IsChartQuiltableRef(selected_dbIndex)) {
14381 // if( ChartData ) ChartData->PurgeCache();
14382
14383 // If the chart is a vector chart, and of very large scale,
14384 // then we had better set the new scale directly to avoid excessive
14385 // underzoom on, eg, Inland ENCs
14386 bool set_scale = false;
14387 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14388 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14389 set_scale = true;
14390 }
14391 }
14392
14393 if (!set_scale) {
14394 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14395 } else {
14396 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14397
14398 // Adjust scale so that the selected chart is underzoomed/overzoomed
14399 // by a controlled amount
14400 ChartBase *pc =
14401 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14402 if (pc) {
14403 double proposed_scale_onscreen =
14405
14406 if (g_bPreserveScaleOnX) {
14407 proposed_scale_onscreen =
14408 wxMin(proposed_scale_onscreen,
14409 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14410 GetCanvasWidth()));
14411 } else {
14412 proposed_scale_onscreen =
14413 wxMin(proposed_scale_onscreen,
14414 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14415 GetCanvasWidth()));
14416
14417 proposed_scale_onscreen =
14418 wxMax(proposed_scale_onscreen,
14419 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14420 g_b_overzoom_x));
14421 }
14422
14423 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14424 }
14425 }
14426 } else {
14427 ToggleCanvasQuiltMode();
14428 SelectdbChart(selected_dbIndex);
14429 m_bpersistent_quilt = true;
14430 }
14431 }
14432 }
14433
14434 SetQuiltChartHiLiteIndex(-1);
14435 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14436 // (checkmarks etc)
14437 HideChartInfoWindow();
14438 DoCanvasUpdate();
14439 ReloadVP(); // Pick up the new selections
14440}
14441
14442void ChartCanvas::HandlePianoRClick(
14443 int x, int y, int selected_index,
14444 const std::vector<int> &selected_dbIndex_array) {
14445 if (g_options && g_options->IsShown())
14446 return; // Piano might be invalid due to chartset updates.
14447 if (!GetpCurrentStack()) return;
14448
14449 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14450 UpdateCanvasControlBar();
14451
14452 SetQuiltChartHiLiteIndex(-1);
14453}
14454
14455void ChartCanvas::HandlePianoRollover(
14456 int selected_index, const std::vector<int> &selected_dbIndex_array,
14457 int n_charts, int scale) {
14458 if (g_options && g_options->IsShown())
14459 return; // Piano might be invalid due to chartset updates.
14460 if (!GetpCurrentStack()) return;
14461 if (!ChartData) return;
14462
14463 if (ChartData->IsBusy()) return;
14464
14465 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14466
14467 if (!GetQuiltMode()) {
14468 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14469 } else {
14470 // Select the correct vector
14471 std::vector<int> piano_chart_index_array;
14472 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14473 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14474 if ((GetpCurrentStack()->nEntry > 1) ||
14475 (piano_chart_index_array.size() >= 1)) {
14476 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14477
14478 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14479 ReloadVP(false); // no VP adjustment allowed
14480 } else if (GetpCurrentStack()->nEntry == 1) {
14481 const ChartTableEntry &cte =
14482 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14483 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14484 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14485 ReloadVP(false);
14486 } else if ((-1 == selected_index) &&
14487 (0 == selected_dbIndex_array.size())) {
14488 ShowChartInfoWindow(key_location.x, -1);
14489 }
14490 }
14491 } else {
14492 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14493
14494 if ((GetpCurrentStack()->nEntry > 1) ||
14495 (piano_chart_index_array.size() >= 1)) {
14496 if (n_charts > 1)
14497 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14498 selected_dbIndex_array);
14499 else if (n_charts == 1)
14500 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14501
14502 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14503 ReloadVP(false); // no VP adjustment allowed
14504 }
14505 }
14506 }
14507}
14508
14509void ChartCanvas::ClearPianoRollover() {
14510 ClearQuiltChartHiLiteIndexArray();
14511 ShowChartInfoWindow(0, -1);
14512 std::vector<int> vec;
14513 ShowCompositeInfoWindow(0, 0, 0, vec);
14514 ReloadVP(false);
14515}
14516
14517void ChartCanvas::UpdateCanvasControlBar(void) {
14518 if (m_pianoFrozen) return;
14519
14520 if (!GetpCurrentStack()) return;
14521 if (!ChartData) return;
14522 if (!g_bShowChartBar) return;
14523
14524 int sel_type = -1;
14525 int sel_family = -1;
14526
14527 std::vector<int> piano_chart_index_array;
14528 std::vector<int> empty_piano_chart_index_array;
14529
14530 wxString old_hash = m_Piano->GetStoredHash();
14531
14532 if (GetQuiltMode()) {
14533 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14534 GetQuiltFullScreendbIndexArray());
14535
14536 std::vector<int> piano_active_chart_index_array =
14537 GetQuiltCandidatedbIndexArray();
14538 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14539
14540 std::vector<int> piano_eclipsed_chart_index_array =
14541 GetQuiltEclipsedStackdbIndexArray();
14542 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14543
14544 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14545 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14546
14547 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14548 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14549 } else {
14550 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14551 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14552 // TODO refresh_Piano();
14553
14554 if (m_singleChart) {
14555 sel_type = m_singleChart->GetChartType();
14556 sel_family = m_singleChart->GetChartFamily();
14557 }
14558 }
14559
14560 // Set up the TMerc and Skew arrays
14561 std::vector<int> piano_skew_chart_index_array;
14562 std::vector<int> piano_tmerc_chart_index_array;
14563 std::vector<int> piano_poly_chart_index_array;
14564
14565 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14566 const ChartTableEntry &ctei =
14567 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14568 double skew_norm = ctei.GetChartSkew();
14569 if (skew_norm > 180.) skew_norm -= 360.;
14570
14571 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14572 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14573
14574 // Polyconic skewed charts should show as skewed
14575 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14576 if (fabs(skew_norm) > 1.)
14577 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14578 else
14579 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14580 } else if (fabs(skew_norm) > 1.)
14581 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14582 }
14583 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14584 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14585 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14586
14587 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14588 if (new_hash != old_hash) {
14589 m_Piano->FormatKeys();
14590 HideChartInfoWindow();
14591 m_Piano->ResetRollover();
14592 SetQuiltChartHiLiteIndex(-1);
14593 m_brepaint_piano = true;
14594 }
14595
14596 // Create a bitmask int that describes what Family/Type of charts are shown in
14597 // the bar, and notify the platform.
14598 int mask = 0;
14599 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14600 const ChartTableEntry &ctei =
14601 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14602 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14603 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14604 if (e == CHART_FAMILY_RASTER) mask |= 1;
14605 if (e == CHART_FAMILY_VECTOR) {
14606 if (t == CHART_TYPE_CM93COMP)
14607 mask |= 4;
14608 else
14609 mask |= 2;
14610 }
14611 }
14612
14613 wxString s_indicated;
14614 if (sel_type == CHART_TYPE_CM93COMP)
14615 s_indicated = _T("cm93");
14616 else {
14617 if (sel_family == CHART_FAMILY_RASTER)
14618 s_indicated = _T("raster");
14619 else if (sel_family == CHART_FAMILY_VECTOR)
14620 s_indicated = _T("vector");
14621 }
14622
14623 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14624}
14625
14626void ChartCanvas::FormatPianoKeys(void) { m_Piano->FormatKeys(); }
14627
14628void ChartCanvas::PianoPopupMenu(
14629 int x, int y, int selected_index,
14630 const std::vector<int> &selected_dbIndex_array) {
14631 if (!GetpCurrentStack()) return;
14632
14633 // No context menu if quilting is disabled
14634 if (!GetQuiltMode()) return;
14635
14636 m_piano_ctx_menu = new wxMenu();
14637
14638 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14639 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14640 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14641 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14642 } else {
14643 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14644 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14645 // wxEVT_COMMAND_MENU_SELECTED,
14646 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14647
14648 menu_selected_dbIndex = selected_dbIndex_array[0];
14649 menu_selected_index = selected_index;
14650
14651 // Search the no-show array
14652 bool b_is_in_noshow = false;
14653 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14654 if (m_quilt_noshow_index_array[i] ==
14655 menu_selected_dbIndex) // chart is in the noshow list
14656 {
14657 b_is_in_noshow = true;
14658 break;
14659 }
14660 }
14661
14662 if (b_is_in_noshow) {
14663 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14664 _("Show This Chart"));
14665 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14666 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14667 } else if (GetpCurrentStack()->nEntry > 1) {
14668 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14669 _("Hide This Chart"));
14670 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14671 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14672 }
14673 }
14674
14675 wxPoint pos = wxPoint(x, y - 30);
14676
14677 // Invoke the drop-down menu
14678 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14679 PopupMenu(m_piano_ctx_menu, pos);
14680
14681 delete m_piano_ctx_menu;
14682 m_piano_ctx_menu = NULL;
14683
14684 HideChartInfoWindow();
14685 m_Piano->ResetRollover();
14686
14687 SetQuiltChartHiLiteIndex(-1);
14688 ClearQuiltChartHiLiteIndexArray();
14689
14690 ReloadVP();
14691}
14692
14693void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14694 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14695 if (m_quilt_noshow_index_array[i] ==
14696 menu_selected_dbIndex) // chart is in the noshow list
14697 {
14698 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14699 break;
14700 }
14701 }
14702}
14703
14704void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14705 if (!GetpCurrentStack()) return;
14706 if (!ChartData) return;
14707
14708 RemoveChartFromQuilt(menu_selected_dbIndex);
14709
14710 // It could happen that the chart being disabled is the reference
14711 // chart....
14712 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14713 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14714
14715 int i = menu_selected_index + 1; // select next smaller scale chart
14716 bool b_success = false;
14717 while (i < GetpCurrentStack()->nEntry - 1) {
14718 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14719 if (type == ChartData->GetDBChartType(dbIndex)) {
14720 SelectQuiltRefChart(i);
14721 b_success = true;
14722 break;
14723 }
14724 i++;
14725 }
14726
14727 // If that did not work, try to select the next larger scale compatible
14728 // chart
14729 if (!b_success) {
14730 i = menu_selected_index - 1;
14731 while (i > 0) {
14732 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14733 if (type == ChartData->GetDBChartType(dbIndex)) {
14734 SelectQuiltRefChart(i);
14735 b_success = true;
14736 break;
14737 }
14738 i--;
14739 }
14740 }
14741 }
14742}
14743
14744void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14745 // Remove the item from the list (if it appears) to avoid multiple addition
14746 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14747 if (m_quilt_noshow_index_array[i] ==
14748 dbIndex) // chart is already in the noshow list
14749 {
14750 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14751 break;
14752 }
14753 }
14754
14755 m_quilt_noshow_index_array.push_back(dbIndex);
14756}
14757
14758bool ChartCanvas::UpdateS52State() {
14759 bool retval = false;
14760 // printf(" update %d\n", IsPrimaryCanvas());
14761
14762 if (ps52plib) {
14763 ps52plib->SetShowS57Text(m_encShowText);
14764 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14765 ps52plib->m_bShowSoundg = m_encShowDepth;
14766 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14767 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14768
14769 // Lights
14770 if (!m_encShowLights) // On, going off
14771 ps52plib->AddObjNoshow("LIGHTS");
14772 else // Off, going on
14773 ps52plib->RemoveObjNoshow("LIGHTS");
14774 ps52plib->SetLightsOff(!m_encShowLights);
14775 ps52plib->m_bExtendLightSectors = true;
14776
14777 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14778 ps52plib->SetAnchorOn(m_encShowAnchor);
14779 ps52plib->SetQualityOfData(m_encShowDataQual);
14780 }
14781
14782 return retval;
14783}
14784
14785void ChartCanvas::SetShowENCDataQual(bool show) {
14786 m_encShowDataQual = show;
14787 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14788 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14789
14790 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14791}
14792
14793void ChartCanvas::SetShowENCText(bool show) {
14794 m_encShowText = show;
14795 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14796 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14797
14798 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14799}
14800
14801void ChartCanvas::SetENCDisplayCategory(int category) {
14802 m_encDisplayCategory = category;
14803 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14804}
14805
14806void ChartCanvas::SetShowENCDepth(bool show) {
14807 m_encShowDepth = show;
14808 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14809 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14810
14811 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14812}
14813
14814void ChartCanvas::SetShowENCLightDesc(bool show) {
14815 m_encShowLightDesc = show;
14816 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14817 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14818
14819 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14820}
14821
14822void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14823 m_encShowBuoyLabels = show;
14824 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14825}
14826
14827void ChartCanvas::SetShowENCLights(bool show) {
14828 m_encShowLights = show;
14829 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14830 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14831
14832 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14833}
14834
14835void ChartCanvas::SetShowENCAnchor(bool show) {
14836 m_encShowAnchor = show;
14837 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14838 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14839
14840 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14841}
14842
14843wxRect ChartCanvas::GetMUIBarRect() {
14844 wxRect rv;
14845 if (m_muiBar) {
14846 rv = m_muiBar->GetRect();
14847 }
14848
14849 return rv;
14850}
14851
14852void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14853 if (!GetAlertString().IsEmpty()) {
14854 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14855 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14856
14857 dc.SetFont(*pfont);
14858 dc.SetPen(*wxTRANSPARENT_PEN);
14859
14860 dc.SetBrush(wxColour(243, 229, 47));
14861 int w, h;
14862 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14863 h += 2;
14864 // int yp = vp.pix_height - 20 - h;
14865
14866 wxRect sbr = GetScaleBarRect();
14867 int xp = sbr.x + sbr.width + 10;
14868 int yp = (sbr.y + sbr.height) - h;
14869
14870 int wdraw = w + 10;
14871 dc.DrawRectangle(xp, yp, wdraw, h);
14872 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14873 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14874 }
14875}
14876
14877//--------------------------------------------------------------------------------------------------------
14878// Screen Brightness Control Support Routines
14879//
14880//--------------------------------------------------------------------------------------------------------
14881
14882#ifdef __UNIX__
14883#define BRIGHT_XCALIB
14884#define __OPCPN_USEICC__
14885#endif
14886
14887#ifdef __OPCPN_USEICC__
14888int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14889 double co_green, double co_blue);
14890
14891wxString temp_file_name;
14892#endif
14893
14894#if 0
14895class ocpnCurtain: public wxDialog
14896{
14897 DECLARE_CLASS( ocpnCurtain )
14898 DECLARE_EVENT_TABLE()
14899
14900public:
14901 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14902 ~ocpnCurtain( );
14903 bool ProcessEvent(wxEvent& event);
14904
14905};
14906
14907IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14908
14909BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14910END_EVENT_TABLE()
14911
14912ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14913{
14914 wxDialog::Create( parent, -1, _T("ocpnCurtain"), position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14915}
14916
14917ocpnCurtain::~ocpnCurtain()
14918{
14919}
14920
14921bool ocpnCurtain::ProcessEvent(wxEvent& event)
14922{
14923 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14924 return GetParent()->GetEventHandler()->ProcessEvent(event);
14925}
14926#endif
14927
14928#ifdef _WIN32
14929#include <windows.h>
14930
14931HMODULE hGDI32DLL;
14932typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14933typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14934SetDeviceGammaRamp_ptr_type
14935 g_pSetDeviceGammaRamp; // the API entry points in the dll
14936GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14937
14938WORD *g_pSavedGammaMap;
14939
14940#endif
14941
14942int InitScreenBrightness(void) {
14943#ifdef _WIN32
14944#ifdef ocpnUSE_GL
14945 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14946 HDC hDC;
14947 BOOL bbr;
14948
14949 if (NULL == hGDI32DLL) {
14950 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14951
14952 if (NULL != hGDI32DLL) {
14953 // Get the entry points of the required functions
14954 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14955 hGDI32DLL, "SetDeviceGammaRamp");
14956 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14957 hGDI32DLL, "GetDeviceGammaRamp");
14958
14959 // If the functions are not found, unload the DLL and return false
14960 if ((NULL == g_pSetDeviceGammaRamp) ||
14961 (NULL == g_pGetDeviceGammaRamp)) {
14962 FreeLibrary(hGDI32DLL);
14963 hGDI32DLL = NULL;
14964 return 0;
14965 }
14966 }
14967 }
14968
14969 // Interface is ready, so....
14970 // Get some storage
14971 if (!g_pSavedGammaMap) {
14972 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14973
14974 hDC = GetDC(NULL); // Get the full screen DC
14975 bbr = g_pGetDeviceGammaRamp(
14976 hDC, g_pSavedGammaMap); // Get the existing ramp table
14977 ReleaseDC(NULL, hDC); // Release the DC
14978 }
14979
14980 // On Windows hosts, try to adjust the registry to allow full range
14981 // setting of Gamma table This is an undocumented Windows hack.....
14982 wxRegKey *pRegKey = new wxRegKey(
14983 _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows ")
14984 _T("NT\\CurrentVersion\\ICM"));
14985 if (!pRegKey->Exists()) pRegKey->Create();
14986 pRegKey->SetValue(_T("GdiIcmGammaRange"), 256);
14987
14988 g_brightness_init = true;
14989 return 1;
14990 }
14991#endif
14992
14993 {
14994 if (NULL == g_pcurtain) {
14995 if (gFrame->CanSetTransparent()) {
14996 // Build the curtain window
14997 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, _T(""),
14998 wxPoint(0, 0), ::wxGetDisplaySize(),
14999 wxNO_BORDER | wxTRANSPARENT_WINDOW |
15000 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15001
15002 // g_pcurtain = new ocpnCurtain(gFrame,
15003 // wxPoint(0,0),::wxGetDisplaySize(),
15004 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15005 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15006
15007 g_pcurtain->Hide();
15008
15009 HWND hWnd = GetHwndOf(g_pcurtain);
15010 SetWindowLong(hWnd, GWL_EXSTYLE,
15011 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15012 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15013 g_pcurtain->SetTransparent(0);
15014
15015 g_pcurtain->Maximize();
15016 g_pcurtain->Show();
15017
15018 // All of this is obtuse, but necessary for Windows...
15019 g_pcurtain->Enable();
15020 g_pcurtain->Disable();
15021
15022 gFrame->Disable();
15023 gFrame->Enable();
15024 // SetFocus();
15025 }
15026 }
15027 g_brightness_init = true;
15028
15029 return 1;
15030 }
15031#else
15032 // Look for "xcalib" application
15033 wxString cmd(_T ( "xcalib -version" ));
15034
15035 wxArrayString output;
15036 long r = wxExecute(cmd, output);
15037 if (0 != r)
15038 wxLogMessage(
15039 _T(" External application \"xcalib\" not found. Screen brightness ")
15040 _T("not changed."));
15041
15042 g_brightness_init = true;
15043 return 0;
15044#endif
15045}
15046
15047int RestoreScreenBrightness(void) {
15048#ifdef _WIN32
15049
15050 if (g_pSavedGammaMap) {
15051 HDC hDC = GetDC(NULL); // Get the full screen DC
15052 g_pSetDeviceGammaRamp(hDC,
15053 g_pSavedGammaMap); // Restore the saved ramp table
15054 ReleaseDC(NULL, hDC); // Release the DC
15055
15056 free(g_pSavedGammaMap);
15057 g_pSavedGammaMap = NULL;
15058 }
15059
15060 if (g_pcurtain) {
15061 g_pcurtain->Close();
15062 g_pcurtain->Destroy();
15063 g_pcurtain = NULL;
15064 }
15065
15066 g_brightness_init = false;
15067 return 1;
15068
15069#endif
15070
15071#ifdef BRIGHT_XCALIB
15072 if (g_brightness_init) {
15073 wxString cmd;
15074 cmd = _T("xcalib -clear");
15075 wxExecute(cmd, wxEXEC_ASYNC);
15076 g_brightness_init = false;
15077 }
15078
15079 return 1;
15080#endif
15081
15082 return 0;
15083}
15084
15085// Set brightness. [0..100]
15086int SetScreenBrightness(int brightness) {
15087#ifdef _WIN32
15088
15089 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15090 // some (most modern?) versions of gdi32.dll Load the required library dll,
15091 // if not already in place
15092#ifdef ocpnUSE_GL
15093 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
15094 if (g_pcurtain) {
15095 g_pcurtain->Close();
15096 g_pcurtain->Destroy();
15097 g_pcurtain = NULL;
15098 }
15099
15100 InitScreenBrightness();
15101
15102 if (NULL == hGDI32DLL) {
15103 // Unicode stuff.....
15104 wchar_t wdll_name[80];
15105 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15106 LPCWSTR cstr = wdll_name;
15107
15108 hGDI32DLL = LoadLibrary(cstr);
15109
15110 if (NULL != hGDI32DLL) {
15111 // Get the entry points of the required functions
15112 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15113 hGDI32DLL, "SetDeviceGammaRamp");
15114 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15115 hGDI32DLL, "GetDeviceGammaRamp");
15116
15117 // If the functions are not found, unload the DLL and return false
15118 if ((NULL == g_pSetDeviceGammaRamp) ||
15119 (NULL == g_pGetDeviceGammaRamp)) {
15120 FreeLibrary(hGDI32DLL);
15121 hGDI32DLL = NULL;
15122 return 0;
15123 }
15124 }
15125 }
15126
15127 HDC hDC = GetDC(NULL); // Get the full screen DC
15128
15129 /*
15130 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15131 if (cmcap != CM_GAMMA_RAMP)
15132 {
15133 wxLogMessage(_T(" Video hardware does not support brightness control by
15134 gamma ramp adjustment.")); return false;
15135 }
15136 */
15137
15138 int increment = brightness * 256 / 100;
15139
15140 // Build the Gamma Ramp table
15141 WORD GammaTable[3][256];
15142
15143 int table_val = 0;
15144 for (int i = 0; i < 256; i++) {
15145 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15146 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15147 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15148
15149 table_val += increment;
15150
15151 if (table_val > 65535) table_val = 65535;
15152 }
15153
15154 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15155 ReleaseDC(NULL, hDC); // Release the DC
15156
15157 return 1;
15158 }
15159#endif
15160
15161 {
15162 if (g_pSavedGammaMap) {
15163 HDC hDC = GetDC(NULL); // Get the full screen DC
15164 g_pSetDeviceGammaRamp(hDC,
15165 g_pSavedGammaMap); // Restore the saved ramp table
15166 ReleaseDC(NULL, hDC); // Release the DC
15167 }
15168
15169 if (brightness < 100) {
15170 if (NULL == g_pcurtain) InitScreenBrightness();
15171
15172 if (g_pcurtain) {
15173 int sbrite = wxMax(1, brightness);
15174 sbrite = wxMin(100, sbrite);
15175
15176 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15177 }
15178 } else {
15179 if (g_pcurtain) {
15180 g_pcurtain->Close();
15181 g_pcurtain->Destroy();
15182 g_pcurtain = NULL;
15183 }
15184 }
15185
15186 return 1;
15187 }
15188
15189#endif
15190
15191#ifdef BRIGHT_XCALIB
15192
15193 if (!g_brightness_init) {
15194 last_brightness = 100;
15195 g_brightness_init = true;
15196 temp_file_name = wxFileName::CreateTempFileName(_T(""));
15197 InitScreenBrightness();
15198 }
15199
15200#ifdef __OPCPN_USEICC__
15201 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15202 // desired, and then activate this temporary profile using xcalib <filename>
15203 if (!CreateSimpleICCProfileFile(
15204 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15205 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15206 wxString cmd(_T ( "xcalib " ));
15207 cmd += temp_file_name;
15208
15209 wxExecute(cmd, wxEXEC_ASYNC);
15210 }
15211
15212#else
15213 // Or, use "xcalib -co" to set overall contrast value
15214 // This is not as nice, since the -co parameter wants to be a fraction of
15215 // the current contrast, and values greater than 100 are not allowed. As a
15216 // result, increases of contrast must do a "-clear" step first, which
15217 // produces objectionable flashing.
15218 if (brightness > last_brightness) {
15219 wxString cmd;
15220 cmd = _T("xcalib -clear");
15221 wxExecute(cmd, wxEXEC_ASYNC);
15222
15223 ::wxMilliSleep(10);
15224
15225 int brite_adj = wxMax(1, brightness);
15226 cmd.Printf(_T("xcalib -co %2d -a"), brite_adj);
15227 wxExecute(cmd, wxEXEC_ASYNC);
15228 } else {
15229 int brite_adj = wxMax(1, brightness);
15230 int factor = (brite_adj * 100) / last_brightness;
15231 factor = wxMax(1, factor);
15232 wxString cmd;
15233 cmd.Printf(_T("xcalib -co %2d -a"), factor);
15234 wxExecute(cmd, wxEXEC_ASYNC);
15235 }
15236
15237#endif
15238
15239 last_brightness = brightness;
15240
15241#endif
15242
15243 return 0;
15244}
15245
15246#ifdef __OPCPN_USEICC__
15247
15248#define MLUT_TAG 0x6d4c5554L
15249#define VCGT_TAG 0x76636774L
15250
15251int GetIntEndian(unsigned char *s) {
15252 int ret;
15253 unsigned char *p;
15254 int i;
15255
15256 p = (unsigned char *)&ret;
15257
15258 if (1)
15259 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15260 else
15261 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15262
15263 return ret;
15264}
15265
15266unsigned short GetShortEndian(unsigned char *s) {
15267 unsigned short ret;
15268 unsigned char *p;
15269 int i;
15270
15271 p = (unsigned char *)&ret;
15272
15273 if (1)
15274 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15275 else
15276 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15277
15278 return ret;
15279}
15280
15281// Create a very simple Gamma correction file readable by xcalib
15282int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15283 double co_green, double co_blue) {
15284 FILE *fp;
15285
15286 if (file_name) {
15287 fp = fopen(file_name, "wb");
15288 if (!fp) return -1; /* file can not be created */
15289 } else
15290 return -1; /* filename char pointer not valid */
15291
15292 // Write header
15293 char header[128];
15294 for (int i = 0; i < 128; i++) header[i] = 0;
15295
15296 fwrite(header, 128, 1, fp);
15297
15298 // Num tags
15299 int numTags0 = 1;
15300 int numTags = GetIntEndian((unsigned char *)&numTags0);
15301 fwrite(&numTags, 1, 4, fp);
15302
15303 int tagName0 = VCGT_TAG;
15304 int tagName = GetIntEndian((unsigned char *)&tagName0);
15305 fwrite(&tagName, 1, 4, fp);
15306
15307 int tagOffset0 = 128 + 4 * sizeof(int);
15308 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15309 fwrite(&tagOffset, 1, 4, fp);
15310
15311 int tagSize0 = 1;
15312 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15313 fwrite(&tagSize, 1, 4, fp);
15314
15315 fwrite(&tagName, 1, 4, fp); // another copy of tag
15316
15317 fwrite(&tagName, 1, 4, fp); // dummy
15318
15319 // Table type
15320
15321 /* VideoCardGammaTable (The simplest type) */
15322 int gammatype0 = 0;
15323 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15324 fwrite(&gammatype, 1, 4, fp);
15325
15326 int numChannels0 = 3;
15327 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15328 fwrite(&numChannels, 1, 2, fp);
15329
15330 int numEntries0 = 256;
15331 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15332 fwrite(&numEntries, 1, 2, fp);
15333
15334 int entrySize0 = 1;
15335 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15336 fwrite(&entrySize, 1, 2, fp);
15337
15338 unsigned char ramp[256];
15339
15340 // Red ramp
15341 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15342 fwrite(ramp, 256, 1, fp);
15343
15344 // Green ramp
15345 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15346 fwrite(ramp, 256, 1, fp);
15347
15348 // Blue ramp
15349 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15350 fwrite(ramp, 256, 1, fp);
15351
15352 fclose(fp);
15353
15354 return 0;
15355}
15356#endif // __OPCPN_USEICC__
bool g_bresponsive
Flag to control adaptive UI scaling.
Definition ocpn_app.cpp:666
Class AisDecoder and helpers.
Global state for AIS decoder.
Dialog for displaying a list of AIS targets.
Dialog for querying detailed information about an AIS target.
bool Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &caption=_("Object Query"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=AIS_TARGET_QUERY_STYLE)
Creation.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
Represents an active track that is currently being recorded.
Definition track.h:221
Handles context menu events for the chart canvas.
Definition canvasMenu.h:82
A custom panel for displaying chart information.
Definition ChInfoWin.h:36
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:131
Base class for all chart types.
Definition chartbase.h:119
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:153
bool GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates rounded to nearest integer using specified vie...
Definition chcanv.cpp:4574
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4570
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3669
void GetDoubleCanvasPointPixVP(ViewPort &vp, double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision,...
Definition chcanv.cpp:4520
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:749
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:479
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:510
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2406
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:8073
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7883
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5098
float GetVPScale()
Return the ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:468
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:840
double GetCanvasTrueScale()
Return the physical pixels per meter at chart center, accounting for latitude distortion.
Definition chcanv.h:484
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4651
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5369
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:733
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4595
void ZoomCanvas(double factor, bool can_zoom_to_cursor=true, bool stoptimer=true)
Perform a smooth zoom operation on the chart canvas by the specified factor.
Definition chcanv.cpp:4657
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4515
bool SetViewPoint(double lat, double lon)
Set the viewport center point.
Definition chcanv.cpp:5388
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10227
Manages the chart database and provides access to chart data.
Definition chartdb.h:95
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:466
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:392
void Notify() override
Notify all listeners, no data supplied.
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition FontMgr.cpp:467
wxColour GetFontColor(const wxString &TextElement) const
Gets the text color for a UI element.
Definition FontMgr.cpp:117
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Gets a font object for a UI element.
Definition FontMgr.cpp:203
Modal dialog displaying hotkeys.
Definition hotkeys_dlg.h:29
Represents an index entry for tidal and current data.
Definition IDX_entry.h:49
char IDX_type
Entry type identifier "TCtcIUu".
Definition IDX_entry.h:61
double IDX_lat
Latitude of the station (in degrees, +North)
Definition IDX_entry.h:65
double IDX_lon
Longitude of the station (in degrees, +East)
Definition IDX_entry.h:64
bool b_skipTooDeep
Flag to skip processing if depth exceeds limit.
Definition IDX_entry.h:110
Station_Data * pref_sta_data
Pointer to the reference station data.
Definition IDX_entry.h:97
int IDX_rec_num
Record number for multiple entries with same name.
Definition IDX_entry.h:60
Definition kml.h:54
Modern User Interface Control Bar for OpenCPN.
Definition MUIBar.h:63
Dialog for displaying and editing waypoint properties.
Definition MarkInfo.h:212
Main application frame.
Definition ocpn_frame.h:136
wxRect GetLogicalRect(void) const
Return the coordinates of the widget, in logical pixels.
wxRect GetRect(void) const
Return the coordinates of the widget, in physical pixels relative to the canvas window.
Provides platform-specific support utilities for OpenCPN.
wxSize getDisplaySize()
Get the display size in logical pixels.
An iterator class for OCPNRegion.
Definition OCPNRegion.h:156
A wrapper class for wxRegion with additional functionality.
Definition OCPNRegion.h:56
Definition piano.h:65
PluginLoader is a backend module without any direct GUI functionality.
A popup frame containing a detail slider for chart display.
Definition Quilt.h:83
bool Compose(const ViewPort &vp)
Definition Quilt.cpp:1713
Represents a waypoint or mark within the navigation system.
Definition route_point.h:70
wxString m_MarkDescription
Description text for the waypoint.
LLBBox m_wpBBox
Bounding box for the waypoint.
bool m_bRPIsBeingEdited
Flag indicating if this waypoint is currently being edited.
wxRect CurrentRect_in_DC
Current rectangle occupied by the waypoint in the display.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bIsActive
Flag indicating if this waypoint is active for navigation.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
double m_seg_len
Length of the leg from previous waypoint to this waypoint in nautical miles.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
bool m_bBlink
Flag indicating if the waypoint should blink when displayed.
bool m_bPtIsSelected
Flag indicating if this waypoint is currently selected.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
Represents a navigational route in the navigation system.
Definition route.h:98
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:202
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:335
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:236
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:207
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:287
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:246
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:272
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:277
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:297
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:223
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:314
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:194
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:419
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:834
Dialog for displaying query results of S57 objects.
Manager for S57 chart SENC creation threads.
Manages a set of ShapeBaseChart objects at different resolutions.
Definition tcmgr.h:88
Definition TCWin.h:46
Window for displaying chart thumbnails.
Definition thumbwin.h:56
Represents a single point in a track.
Definition track.h:53
wxDateTime GetCreateTime(void)
Retrieves the creation timestamp of a track point as a wxDateTime object.
Definition track.cpp:140
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:146
Class TrackPropDlg.
bool UpdateProperties()
Represents a track, which is a series of connected track points.
Definition track.h:111
Definition undo.h:60
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:81
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:229
double ref_scale
The nominal scale of the "reference chart" for this view.
Definition viewport.h:246
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:258
void SetBoxes(void)
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp:823
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:239
void SetPixelScale(double scale)
Set the physical to logical pixel ratio for the display.
Definition viewport.cpp:133
int pix_width
Width of the viewport in physical pixels.
Definition viewport.h:256
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:145
double tilt
Tilt angle for perspective view in radians.
Definition viewport.h:241
double skew
Angular distortion (shear transform) applied to the viewport in radians.
Definition viewport.h:237
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:105
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:224
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:222
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:136
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:244
bool AddRoutePoint(RoutePoint *prp)
Add a point to list which owns it.
bool RemoveRoutePoint(RoutePoint *prp)
Remove a routepoint from list if present, deallocate it all cases.
Encapsulates persistent canvas configuration.
double iLat
Latitude of the center of the chart, in degrees.
bool bShowOutlines
Display chart outlines.
bool bShowDepthUnits
Display depth unit indicators.
double iLon
Longitude of the center of the chart, in degrees.
double iRotation
Initial rotation angle in radians.
bool bCourseUp
Orient display to course up.
bool bQuilt
Enable chart quilting.
bool bFollow
Enable vessel following mode.
double iScale
Initial chart scale factor.
bool bShowENCText
Display ENC text elements.
bool bShowAIS
Display AIS targets.
bool bShowGrid
Display coordinate grid.
bool bShowCurrents
Display current information.
bool bShowTides
Display tide information.
bool bLookahead
Enable lookahead mode.
bool bHeadUp
Orient display to heading up.
bool bAttenAIS
Enable AIS target attenuation.
Represents a composite CM93 chart covering multiple scales.
Definition cm93.h:424
Stores emboss effect data for textures.
Definition emboss_data.h:35
OpenGL chart rendering canvas.
Floating toolbar for iENC (International Electronic Navigational Chart) functionality.
Definition iENCToolbar.h:43
Represents a compass display in the OpenCPN navigation system.
Definition compass.h:37
wxRect GetRect(void) const
Return the coordinates of the compass widget, in physical pixels relative to the canvas window.
Definition compass.h:61
wxRect GetLogicalRect(void) const
Return the coordinates of the compass widget, in logical pixels.
Definition compass.cpp:201
Device context class that can use either wxDC or OpenGL for drawing.
Definition ocpndc.h:64
void DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2, bool b_hiqual=true)
Draw a line between two points using either wxDC or OpenGL.
Definition ocpndc.cpp:476
Floating toolbar dialog for OpenCPN.
Definition toolbar.h:386
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:120
The JSON value class implementation.
Definition jsonval.h:84
The JSON document writer.
Definition jsonwriter.h:50
void Write(const wxJSONValue &value, wxString &str)
Write the JSONvalue object to a JSON text.
Global variables reflecting command line options and arguments.
Hooks into gui available in model.
Multiplexer class and helpers.
Class NavObj_dB.
Class NotificationManager.
#define OVERLAY_LEGACY
Overlay rendering priorities determine the layering order of plugin graphics.
#define OVERLAY_OVER_UI
Highest priority for overlays above all UI elements.
#define OVERLAY_OVER_EMBOSS
Priority for overlays above embossed chart features.
#define OVERLAY_OVER_SHIPS
Priority for overlays that should appear above ship symbols.
int GetChartbarHeight(void)
Gets height of chart bar in pixels.
int GetCanvasCount()
Gets total number of chart canvases.
PI_DisCat GetENCDisplayCategory(int CanvasIndex)
Gets current ENC display category.
int GetCanvasIndexUnderMouse(void)
Gets index of chart canvas under mouse cursor.
double OCPN_GetWinDIPScaleFactor()
Gets Windows-specific DPI scaling factor.
Tools to send data to plugins.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Route validators for dialog validation.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:181
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.