OpenCPN Partial API docs
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"
42 #include "model/ais_state_vars.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/idents.h"
49 #include "model/multiplexer.h"
50 #include "model/nav_object_database.h"
51 #include "model/navutil_base.h"
52 #include "model/own_ship.h"
53 #include "model/route.h"
54 #include "model/routeman.h"
55 #include "model/select.h"
56 #include "model/select_item.h"
57 #include "model/track.h"
58 #include "model/wx28compat.h"
59 
60 #include "ais.h"
61 #include "AISTargetAlertDialog.h"
62 #include "CanvasConfig.h"
63 #include "canvasMenu.h"
64 #include "CanvasOptions.h"
65 #include "chartdb.h"
66 #include "chartimg.h"
67 #include "chcanv.h"
68 #include "ChInfoWin.h"
69 #include "cm93.h" // for chart outline draw
70 #include "compass.h"
71 #include "concanv.h"
72 #include "displays.h"
73 #include "FontMgr.h"
74 #include "glTextureDescriptor.h"
75 #include "gshhs.h"
76 #include "iENCToolbar.h"
77 #include "kml.h"
78 #include "line_clip.h"
79 #include "MarkInfo.h"
80 #include "mbtiles.h"
81 #include "MUIBar.h"
82 #include "navutil.h"
83 #include "OCPN_AUIManager.h"
84 #include "ocpndc.h"
85 #include "ocpn_frame.h"
86 #include "ocpn_pixel.h"
87 #include "OCPNRegion.h"
88 #include "piano.h"
89 #include "pluginmanager.h"
90 #include "Quilt.h"
91 #include "route_gui.h"
92 #include "routemanagerdialog.h"
93 #include "route_point_gui.h"
94 #include "RoutePropDlgImpl.h"
95 #include "s52plib.h"
96 #include "s52utils.h"
97 #include "s57chart.h" // for ArrayOfS57Obj
98 #include "SendToGpsDlg.h"
99 #include "shapefile_basemap.h"
100 #include "styles.h"
101 #include "SystemCmdSound.h"
102 #include "tcmgr.h"
103 #include "TCWin.h"
104 #include "thumbwin.h"
105 #include "tide_time.h"
106 #include "timers.h"
107 #include "toolbar.h"
108 #include "track_gui.h"
109 #include "TrackPropDlg.h"
110 #include "undo.h"
111 
112 #ifdef __ANDROID__
113 #include "androidUTIL.h"
114 #endif
115 
116 #ifdef ocpnUSE_GL
117 #include "glChartCanvas.h"
118 #endif
119 
120 #ifdef __MSVC__
121 #define _CRTDBG_MAP_ALLOC
122 #include <stdlib.h>
123 #include <crtdbg.h>
124 #define DEBUG_NEW new (_NORMAL_BLOCK, __FILE__, __LINE__)
125 #define new DEBUG_NEW
126 #endif
127 
128 #ifndef __WXMSW__
129 #include <signal.h>
130 #include <setjmp.h>
131 
132 #endif
133 
134 extern float g_ShipScaleFactorExp;
135 extern double g_mouse_zoom_sensitivity;
136 
137 #include <vector>
138 
139 #if defined(__MSVC__) && (_MSC_VER < 1700)
140 #define trunc(d) ((d > 0) ? floor(d) : ceil(d))
141 #endif
142 
143 // Define to enable the invocation of a temporary menubar by pressing the Alt
144 // key. Not implemented for Windows XP, as it interferes with Alt-Tab
145 // processing.
146 #define OCPN_ALT_MENUBAR 1
147 
148 // Profiling support
149 //#include "/usr/include/valgrind/callgrind.h"
150 
151 // ----------------------------------------------------------------------------
152 // Useful Prototypes
153 // ----------------------------------------------------------------------------
154 extern bool G_FloatPtInPolygon(MyFlPoint *rgpts, int wnumpts, float x, float y);
155 extern void catch_signals(int signo);
156 
157 extern void AlphaBlending(ocpnDC &dc, int x, int y, int size_x, int size_y,
158  float radius, wxColour color,
159  unsigned char transparency);
160 
161 extern double g_ChartNotRenderScaleFactor;
162 extern ChartDB *ChartData;
163 extern bool bDBUpdateInProgress;
164 extern ColorScheme global_color_scheme;
165 extern int g_nbrightness;
166 
167 extern ConsoleCanvas *console;
168 extern OCPNPlatform *g_Platform;
169 
170 extern RouteList *pRouteList;
171 extern std::vector<Track*> g_TrackList;
172 extern MyConfig *pConfig;
173 extern Routeman *g_pRouteMan;
174 extern ThumbWin *pthumbwin;
175 extern TCMgr *ptcmgr;
176 extern Select *pSelectTC;
177 extern MarkInfoDlg *g_pMarkInfoDialog;
178 extern RoutePropDlgImpl *pRoutePropDialog;
179 extern TrackPropDlg *pTrackPropDialog;
180 extern ActiveTrack *g_pActiveTrack;
181 
182 extern double AnchorPointMinDist;
183 extern bool AnchorAlertOn1;
184 extern bool AnchorAlertOn2;
185 extern int g_nAWMax;
186 
187 extern RouteManagerDialog *pRouteManagerDialog;
188 extern GoToPositionDialog *pGoToPositionDialog;
189 extern wxString GetLayerName(int id);
190 extern wxString g_uploadConnection;
191 extern bool g_bsimplifiedScalebar;
192 
193 extern bool bDrawCurrentValues;
194 
195 extern s52plib *ps52plib;
196 
197 extern bool g_bTempShowMenuBar;
198 extern bool g_bShowMenuBar;
199 extern bool g_bShowCompassWin;
200 
201 extern MyFrame *gFrame;
202 
203 extern int g_iNavAidRadarRingsNumberVisible;
204 extern bool g_bNavAidRadarRingsShown;
205 extern float g_fNavAidRadarRingsStep;
206 extern int g_pNavAidRadarRingsStepUnits;
207 extern bool g_bWayPointPreventDragging;
208 extern bool g_bEnableZoomToCursor;
209 extern bool g_bShowChartBar;
210 extern int g_ENCSoundingScaleFactor;
211 extern int g_ENCTextScaleFactor;
212 extern int g_maxzoomin;
213 
214 bool g_bShowShipToActive;
215 int g_shipToActiveStyle;
216 int g_shipToActiveColor;
217 
218 extern AISTargetQueryDialog *g_pais_query_dialog_active;
219 
220 extern int g_S57_dialog_sx, g_S57_dialog_sy;
221 
222 extern PopUpDSlide *pPopupDetailSlider;
223 extern int g_detailslider_dialog_x, g_detailslider_dialog_y;
224 
225 extern bool g_b_overzoom_x; // Allow high overzoom
226 extern double g_plus_minus_zoom_factor;
227 
228 extern int g_OwnShipIconType;
229 extern double g_n_ownship_length_meters;
230 extern double g_n_ownship_beam_meters;
231 extern double g_n_gps_antenna_offset_y;
232 extern double g_n_gps_antenna_offset_x;
233 extern int g_n_ownship_min_mm;
234 
235 extern double g_COGAvg; // only needed for debug....
236 
237 extern int g_click_stop;
238 
239 extern double g_ownship_predictor_minutes;
240 extern int g_cog_predictor_style;
241 extern wxString g_cog_predictor_color;
242 extern int g_cog_predictor_endmarker;
243 extern int g_ownship_HDTpredictor_style;
244 extern wxString g_ownship_HDTpredictor_color;
245 extern int g_ownship_HDTpredictor_endmarker;
246 extern int g_ownship_HDTpredictor_width;
247 extern double g_ownship_HDTpredictor_miles;
248 
249 extern bool g_bquiting;
250 extern AISTargetListDialog *g_pAISTargetList;
251 
252 extern PlugInManager *g_pi_manager;
253 
254 extern OCPN_AUIManager *g_pauimgr;
255 
256 extern bool g_bopengl;
257 
258 extern bool g_bFullScreenQuilt;
259 
260 extern bool g_bsmoothpanzoom;
261 
262 bool g_bDebugOGL;
263 
264 extern bool g_b_assume_azerty;
265 
266 extern ChartGroupArray *g_pGroupArray;
267 
268 extern S57QueryDialog *g_pObjectQueryDialog;
269 extern ocpnStyle::StyleManager *g_StyleManager;
270 
271 extern OcpnSound *g_anchorwatch_sound;
272 
273 extern bool g_bresponsive;
274 extern int g_chart_zoom_modifier_raster;
275 extern int g_chart_zoom_modifier_vector;
276 extern int g_ChartScaleFactor;
277 
278 
279 #ifdef ocpnUSE_GL
280 #endif
281 
282 extern double g_gl_ms_per_frame;
283 extern bool g_benable_rotate;
284 extern bool g_bRollover;
285 
286 extern bool g_bSpaceDropMark;
287 extern bool g_bAutoHideToolbar;
288 extern int g_nAutoHideToolbar;
289 extern bool g_bDeferredInitDone;
290 
291 extern wxString g_CmdSoundString;
292 extern bool g_boptionsactive;
293 ShapeBaseChartSet gShapeBasemap;
294 
295 
296 // TODO why are these static?
297 static int mouse_x;
298 static int mouse_y;
299 static bool mouse_leftisdown;
300 
301 bool g_brouteCreating;
302 
303 bool g_bShowTrackPointTime;
304 
305 int r_gamma_mult;
306 int g_gamma_mult;
307 int b_gamma_mult;
308 int gamma_state;
309 bool g_brightness_init;
310 int last_brightness;
311 
312 int g_cog_predictor_width;
313 extern double g_display_size_mm;
314 
315 extern ocpnFloatingToolbarDialog *g_MainToolbar;
316 extern iENCToolbar *g_iENCToolbar;
317 extern wxColour g_colourOwnshipRangeRingsColour;
318 
319 // LIVE ETA OPTION
320 bool g_bShowLiveETA;
321 extern double g_defaultBoatSpeed;
322 double g_defaultBoatSpeedUserUnit;
323 
324 extern int g_nAIS_activity_timer;
325 extern bool g_bskew_comp;
326 extern float g_compass_scalefactor;
327 extern int g_COGAvgSec; // COG average period (sec.) for Course Up Mode
328 
329 wxGLContext *g_pGLcontext; // shared common context
330 
331 extern bool g_useMUI;
332 extern unsigned int g_canvasConfig;
333 extern wxString g_lastPluginMessage;
334 
335 extern ChartCanvas *g_focusCanvas;
336 extern ChartCanvas *g_overlayCanvas;
337 
338 extern float g_toolbar_scalefactor;
339 extern SENCThreadManager *g_SencThreadManager;
340 
341 wxString g_ObjQFileExt;
342 
343 // "Curtain" mode parameters
344 wxDialog *g_pcurtain;
345 
346 extern int g_GUIScaleFactor;
347 // Win DPI scale factor
348 double g_scaler;
349 wxString g_lastS52PLIBPluginMessage;
350 extern bool g_bChartBarEx;
351 bool g_PrintingInProgress;
352 
353 #define MIN_BRIGHT 10
354 #define MAX_BRIGHT 100
355 
356 //------------------------------------------------------------------------------
357 // ChartCanvas Implementation
358 //------------------------------------------------------------------------------
359 BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
360 EVT_PAINT(ChartCanvas::OnPaint)
361 EVT_ACTIVATE(ChartCanvas::OnActivate)
362 EVT_SIZE(ChartCanvas::OnSize)
363 #ifndef HAVE_WX_GESTURE_EVENTS
364 EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
365 #endif
366 EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
367 EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
368 EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
369 EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
370 EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
371 EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
372 EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
373 EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
374 EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
375 EVT_KEY_UP(ChartCanvas::OnKeyUp)
376 EVT_CHAR(ChartCanvas::OnKeyChar)
377 EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
378 EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
379 EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
380 EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
381 EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
382 
383 END_EVENT_TABLE()
384 
385 // Define a constructor for my canvas
386 ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex)
387  : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER) {
388  parent_frame = (MyFrame *)frame; // save a pointer to parent
389  m_canvasIndex = canvasIndex;
390 
391  pscratch_bm = NULL;
392 
393  SetBackgroundColour(wxColour(0, 0, 0));
394  SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
395  // color scheme change
396 
397  m_groupIndex = 0;
398  m_bDrawingRoute = false;
399  m_bRouteEditing = false;
400  m_bMarkEditing = false;
401  m_bRoutePoinDragging = false;
402  m_bIsInRadius = false;
403  m_bMayToggleMenuBar = true;
404 
405  m_bFollow = false;
406  m_bShowNavobjects = true;
407  m_bTCupdate = false;
408  m_bAppendingRoute = false; // was true in MSW, why??
409  pThumbDIBShow = NULL;
410  m_bShowCurrent = false;
411  m_bShowTide = false;
412  bShowingCurrent = false;
413  pCwin = NULL;
414  warp_flag = false;
415  m_bzooming = false;
416  m_b_paint_enable = true;
417  m_routeState = 0;
418 
419  pss_overlay_bmp = NULL;
420  pss_overlay_mask = NULL;
421  m_bChartDragging = false;
422  m_bMeasure_Active = false;
423  m_bMeasure_DistCircle = false;
424  m_pMeasureRoute = NULL;
425  m_pTrackRolloverWin = NULL;
426  m_pRouteRolloverWin = NULL;
427  m_pAISRolloverWin = NULL;
428  m_bedge_pan = false;
429  m_disable_edge_pan = false;
430  m_dragoffsetSet = false;
431  m_bautofind = false;
432  m_bFirstAuto = true;
433  m_groupIndex = 0;
434  m_singleChart = NULL;
435  m_upMode = NORTH_UP_MODE;
436  m_bShowAIS = true;
437  m_bShowAISScaled = false;
438 
439  m_vLat = 0.;
440  m_vLon = 0.;
441 
442  m_pCIWin = NULL;
443 
444  m_pSelectedRoute = NULL;
445  m_pSelectedTrack = NULL;
446  m_pRoutePointEditTarget = NULL;
447  m_pFoundPoint = NULL;
448  m_pMouseRoute = NULL;
449  m_prev_pMousePoint = NULL;
450  m_pEditRouteArray = NULL;
451  m_pFoundRoutePoint = NULL;
452  m_FinishRouteOnKillFocus = true;
453 
454  m_pRolloverRouteSeg = NULL;
455  m_pRolloverTrackSeg = NULL;
456  m_bsectors_shown = false;
457 
458  m_bbrightdir = false;
459  r_gamma_mult = 1;
460  g_gamma_mult = 1;
461  b_gamma_mult = 1;
462 
463  m_pos_image_user_day = NULL;
464  m_pos_image_user_dusk = NULL;
465  m_pos_image_user_night = NULL;
466  m_pos_image_user_grey_day = NULL;
467  m_pos_image_user_grey_dusk = NULL;
468  m_pos_image_user_grey_night = NULL;
469 
470  m_zoom_factor = 1;
471  m_rotation_speed = 0;
472  m_mustmove = 0;
473 
474  m_OSoffsetx = 0.;
475  m_OSoffsety = 0.;
476 
477  m_pos_image_user_yellow_day = NULL;
478  m_pos_image_user_yellow_dusk = NULL;
479  m_pos_image_user_yellow_night = NULL;
480 
481  SetOwnShipState(SHIP_INVALID);
482 
483  undo = new Undo(this);
484 
485  VPoint.Invalidate();
486 
487  m_glcc = NULL;
488 
489  m_focus_indicator_pix = 1;
490 
491  m_pCurrentStack = NULL;
492  m_bpersistent_quilt = false;
493  m_piano_ctx_menu = NULL;
494  m_Compass = NULL;
495 
496  g_ChartNotRenderScaleFactor = 2.0;
497  m_bShowScaleInStatusBar = true;
498 
499  m_muiBar = NULL;
500  m_bShowScaleInStatusBar = false;
501 
502  m_bShowOutlines = false;
503  m_bDisplayGrid = false;
504  m_bShowDepthUnits = true;
505  m_encDisplayCategory = (int)STANDARD;
506 
507  m_encShowLights = true;
508  m_encShowAnchor = true;
509  m_encShowDataQual = false;
510  m_bShowGPS = true;
511  m_pQuilt = new Quilt(this);
512  SetQuiltMode(true);
513  SetAlertString(_T(""));
514  m_sector_glat = 0;
515  m_sector_glon = 0;
516  g_PrintingInProgress = false;
517 
518 #ifdef HAVE_WX_GESTURE_EVENTS
519  m_oldVPSScale = -1.0;
520  m_popupWanted = false;
521  m_leftdown = false;
522 #endif /* HAVE_WX_GESTURE_EVENTS */
523 
524  SetupGlCanvas();
525 
526  singleClickEventIsValid = false;
527 
528  // Build the cursors
529 
530  pCursorLeft = NULL;
531  pCursorRight = NULL;
532  pCursorUp = NULL;
533  pCursorDown = NULL;
534  pCursorArrow = NULL;
535  pCursorPencil = NULL;
536  pCursorCross = NULL;
537 
538  RebuildCursors();
539 
540  SetCursor(*pCursorArrow);
541 
542  pPanTimer = new wxTimer(this, m_MouseDragging);
543  pPanTimer->Stop();
544 
545  pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
546  pMovementTimer->Stop();
547 
548  pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
549  pMovementStopTimer->Stop();
550 
551  pRotDefTimer = new wxTimer(this, ROT_TIMER);
552  pRotDefTimer->Stop();
553 
554  m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
555  m_DoubleClickTimer->Stop();
556 
557  m_panx = m_pany = 0;
558  m_panspeed = 0;
559 
560  pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
561  pCurTrackTimer->Stop();
562  m_curtrack_timer_msec = 10;
563 
564  m_wheelzoom_stop_oneshot = 0;
565  m_last_wheel_dir = 0;
566 
567  m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
568 
569  m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
570 
571  m_rollover_popup_timer_msec = 20;
572 
573  m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
574 
575  m_b_rot_hidef = true;
576 
577  proute_bm = NULL;
578  m_prot_bm = NULL;
579 
580  m_upMode = NORTH_UP_MODE;
581  m_bLookAhead = false;
582  m_VPRotate = 0;
583 
584  // Set some benign initial values
585 
586  m_cs = GLOBAL_COLOR_SCHEME_DAY;
587  VPoint.clat = 0;
588  VPoint.clon = 0;
589  VPoint.view_scale_ppm = 1;
590  VPoint.Invalidate();
591  m_nMeasureState = 0;
592 
593  m_canvas_scale_factor = 1.;
594 
595  m_canvas_width = 1000;
596 
597  m_overzoomTextWidth = 0;
598  m_overzoomTextHeight = 0;
599 
600  // Create the default world chart
601  pWorldBackgroundChart = new GSHHSChart;
602  gShapeBasemap.Reset();
603 
604  // Create the default depth unit emboss maps
605  m_pEM_Feet = NULL;
606  m_pEM_Meters = NULL;
607  m_pEM_Fathoms = NULL;
608 
609  CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
610 
611  m_pEM_OverZoom = NULL;
612  SetOverzoomFont();
613  CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
614 
615  // Build icons for tide/current points
616  ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
617  m_bmTideDay = style->GetIconScaled(_T("tidesml"), 1. / g_Platform->GetDisplayDIPMult(this));
618 
619  // Dusk
620  m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
621 
622  // Night
623  m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
624 
625  // Build Dusk/Night ownship icons
626  double factor_dusk = 0.5;
627  double factor_night = 0.25;
628 
629  // Red
630  m_os_image_red_day = style->GetIcon(_T("ship-red")).ConvertToImage();
631 
632  int rimg_width = m_os_image_red_day.GetWidth();
633  int rimg_height = m_os_image_red_day.GetHeight();
634 
635  m_os_image_red_dusk = m_os_image_red_day.Copy();
636  m_os_image_red_night = m_os_image_red_day.Copy();
637 
638  for (int iy = 0; iy < rimg_height; iy++) {
639  for (int ix = 0; ix < rimg_width; ix++) {
640  if (!m_os_image_red_day.IsTransparent(ix, iy)) {
641  wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
642  m_os_image_red_day.GetGreen(ix, iy),
643  m_os_image_red_day.GetBlue(ix, iy));
644  wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
645  hsv.value = hsv.value * factor_dusk;
646  wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
647  m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
648 
649  hsv = wxImage::RGBtoHSV(rgb);
650  hsv.value = hsv.value * factor_night;
651  nrgb = wxImage::HSVtoRGB(hsv);
652  m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
653  }
654  }
655  }
656 
657  // Grey
658  m_os_image_grey_day =
659  style->GetIcon(_T("ship-red")).ConvertToImage().ConvertToGreyscale();
660 
661  int gimg_width = m_os_image_grey_day.GetWidth();
662  int gimg_height = m_os_image_grey_day.GetHeight();
663 
664  m_os_image_grey_dusk = m_os_image_grey_day.Copy();
665  m_os_image_grey_night = m_os_image_grey_day.Copy();
666 
667  for (int iy = 0; iy < gimg_height; iy++) {
668  for (int ix = 0; ix < gimg_width; ix++) {
669  if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
670  wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
671  m_os_image_grey_day.GetGreen(ix, iy),
672  m_os_image_grey_day.GetBlue(ix, iy));
673  wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
674  hsv.value = hsv.value * factor_dusk;
675  wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
676  m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
677 
678  hsv = wxImage::RGBtoHSV(rgb);
679  hsv.value = hsv.value * factor_night;
680  nrgb = wxImage::HSVtoRGB(hsv);
681  m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
682  }
683  }
684  }
685 
686  // Yellow
687  m_os_image_yellow_day = m_os_image_red_day.Copy();
688 
689  gimg_width = m_os_image_yellow_day.GetWidth();
690  gimg_height = m_os_image_yellow_day.GetHeight();
691 
692  m_os_image_yellow_dusk = m_os_image_red_day.Copy();
693  m_os_image_yellow_night = m_os_image_red_day.Copy();
694 
695  for (int iy = 0; iy < gimg_height; iy++) {
696  for (int ix = 0; ix < gimg_width; ix++) {
697  if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
698  wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
699  m_os_image_yellow_day.GetGreen(ix, iy),
700  m_os_image_yellow_day.GetBlue(ix, iy));
701  wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
702  hsv.hue += 60. / 360.; // shift to yellow
703  wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
704  m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
705 
706  hsv = wxImage::RGBtoHSV(rgb);
707  hsv.value = hsv.value * factor_dusk;
708  hsv.hue += 60. / 360.; // shift to yellow
709  nrgb = wxImage::HSVtoRGB(hsv);
710  m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
711 
712  hsv = wxImage::RGBtoHSV(rgb);
713  hsv.hue += 60. / 360.; // shift to yellow
714  hsv.value = hsv.value * factor_night;
715  nrgb = wxImage::HSVtoRGB(hsv);
716  m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
717  }
718  }
719  }
720 
721  // Set initial pointers to ownship images
722  m_pos_image_red = &m_os_image_red_day;
723  m_pos_image_yellow = &m_os_image_yellow_day;
724  m_pos_image_grey = &m_os_image_grey_day;
725 
726  SetUserOwnship();
727 
728  m_pBrightPopup = NULL;
729 
730 #ifdef ocpnUSE_GL
731  if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
732 #endif
733 
734  int gridFontSize = 8;
735 #if defined(__WXOSX__) || defined(__WXGTK3__)
736  // Support scaled HDPI displays.
737  gridFontSize *= GetContentScaleFactor();
738 #endif
739 
740  m_pgridFont = FontMgr::Get().FindOrCreateFont(
741  gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, FALSE,
742  wxString(_T ( "Arial" )));
743 
744  m_Piano = new Piano(this);
745 
746  m_bShowCompassWin = g_bShowCompassWin;
747 
748  m_Compass = new ocpnCompass(this);
749  m_Compass->SetScaleFactor(g_compass_scalefactor);
750  m_Compass->Show(m_bShowCompassWin);
751 
752  m_pianoFrozen = false;
753 
754  SetMinSize(wxSize(200, 200));
755 
756  m_displayScale = 1.0;
757 #if defined(__WXOSX__) || defined(__WXGTK3__)
758  // Support scaled HDPI displays.
759  m_displayScale = GetContentScaleFactor();
760 #endif
761 
762 
763 #ifdef HAVE_WX_GESTURE_EVENTS
764  if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE |
765  wxTOUCH_PRESS_GESTURES)) {
766  wxLogError("Failed to enable touch events");
767  }
768 
769  Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
770 
771  Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
772  Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
773 
774  Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
775  Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
776 
777  Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
778  Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
779 
780  Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
781  Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
782 #endif
783 }
784 
785 ChartCanvas::~ChartCanvas() {
786  delete pThumbDIBShow;
787 
788  // Delete Cursors
789  delete pCursorLeft;
790  delete pCursorRight;
791  delete pCursorUp;
792  delete pCursorDown;
793  delete pCursorArrow;
794  delete pCursorPencil;
795  delete pCursorCross;
796 
797  delete pPanTimer;
798  delete pMovementTimer;
799  delete pMovementStopTimer;
800  delete pCurTrackTimer;
801  delete pRotDefTimer;
802  delete m_DoubleClickTimer;
803 
804  delete m_pTrackRolloverWin;
805  delete m_pRouteRolloverWin;
806  delete m_pAISRolloverWin;
807  delete m_pBrightPopup;
808 
809  delete m_pCIWin;
810 
811  delete pscratch_bm;
812 
813  m_dc_route.SelectObject(wxNullBitmap);
814  delete proute_bm;
815 
816  delete pWorldBackgroundChart;
817  delete pss_overlay_bmp;
818 
819  delete m_pEM_Feet;
820  delete m_pEM_Meters;
821  delete m_pEM_Fathoms;
822 
823  delete m_pEM_OverZoom;
824  // delete m_pEM_CM93Offset;
825 
826  delete m_prot_bm;
827 
828  delete m_pos_image_user_day;
829  delete m_pos_image_user_dusk;
830  delete m_pos_image_user_night;
831  delete m_pos_image_user_grey_day;
832  delete m_pos_image_user_grey_dusk;
833  delete m_pos_image_user_grey_night;
834  delete m_pos_image_user_yellow_day;
835  delete m_pos_image_user_yellow_dusk;
836  delete m_pos_image_user_yellow_night;
837 
838  delete undo;
839 #ifdef ocpnUSE_GL
840  if (!g_bdisable_opengl) {
841  delete m_glcc;
842 
843 #if wxCHECK_VERSION(2, 9, 0)
844  if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
845 #endif
846  }
847 #endif
848 
849  // Delete the MUI bar, but make sure there is no pointer to it during destroy.
850  // wx tries to deliver events to this canvas during destroy.
851  MUIBar *muiBar = m_muiBar;
852  m_muiBar = 0;
853  delete muiBar;
854  delete m_pQuilt;
855  delete m_pCurrentStack;
856  delete m_Compass;
857  delete m_Piano;
858 }
859 
860 void ChartCanvas::RebuildCursors() {
861  delete pCursorLeft;
862  delete pCursorRight;
863  delete pCursorUp;
864  delete pCursorDown;
865  delete pCursorArrow;
866  delete pCursorPencil;
867  delete pCursorCross;
868 
869  ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
870  double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
871 
872  double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
873 
874  wxImage ICursorLeft = style->GetIcon(_T("left")).ConvertToImage();
875  wxImage ICursorRight = style->GetIcon(_T("right")).ConvertToImage();
876  wxImage ICursorUp = style->GetIcon(_T("up")).ConvertToImage();
877  wxImage ICursorDown = style->GetIcon(_T("down")).ConvertToImage();
878  wxImage ICursorPencil = style->GetIconScaled(_T("pencil"), pencilScale).ConvertToImage();
879  wxImage ICursorCross = style->GetIcon(_T("cross")).ConvertToImage();
880 
881 #if !defined(__WXMSW__) && !defined(__WXQT__)
882  ICursorLeft.ConvertAlphaToMask(128);
883  ICursorRight.ConvertAlphaToMask(128);
884  ICursorUp.ConvertAlphaToMask(128);
885  ICursorDown.ConvertAlphaToMask(128);
886  ICursorPencil.ConvertAlphaToMask(10);
887  ICursorCross.ConvertAlphaToMask(10);
888 #endif
889 
890  if (ICursorLeft.Ok()) {
891  ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
892  ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
893  pCursorLeft = new wxCursor(ICursorLeft);
894  } else
895  pCursorLeft = new wxCursor(wxCURSOR_ARROW);
896 
897  if (ICursorRight.Ok()) {
898  ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
899  ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
900  pCursorRight = new wxCursor(ICursorRight);
901  } else
902  pCursorRight = new wxCursor(wxCURSOR_ARROW);
903 
904  if (ICursorUp.Ok()) {
905  ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
906  ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
907  pCursorUp = new wxCursor(ICursorUp);
908  } else
909  pCursorUp = new wxCursor(wxCURSOR_ARROW);
910 
911  if (ICursorDown.Ok()) {
912  ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
913  ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
914  pCursorDown = new wxCursor(ICursorDown);
915  } else
916  pCursorDown = new wxCursor(wxCURSOR_ARROW);
917 
918  if (ICursorPencil.Ok()) {
919  ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
920  ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
921  pCursorPencil = new wxCursor(ICursorPencil);
922  } else
923  pCursorPencil = new wxCursor(wxCURSOR_ARROW);
924 
925  if (ICursorCross.Ok()) {
926  ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
927  ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
928  pCursorCross = new wxCursor(ICursorCross);
929  } else
930  pCursorCross = new wxCursor(wxCURSOR_ARROW);
931 
932  pCursorArrow = new wxCursor(wxCURSOR_ARROW);
933  pPlugIn_Cursor = NULL;
934 }
935 
936 void ChartCanvas::CanvasApplyLocale() {
937  CreateDepthUnitEmbossMaps(m_cs);
938  CreateOZEmbossMapData(m_cs);
939 }
940 
941 void ChartCanvas::SetupGlCanvas() {
942 #ifndef __ANDROID__
943 #ifdef ocpnUSE_GL
944  if (!g_bdisable_opengl) {
945  if (g_bopengl) {
946  wxLogMessage(_T("Creating glChartCanvas"));
947  m_glcc = new glChartCanvas(this);
948 
949  // We use one context for all GL windows, so that textures etc will be
950  // automatically shared
951  if (IsPrimaryCanvas()) {
952  // qDebug() << "Creating Primary Context";
953 
954  // wxGLContextAttrs ctxAttr;
955  // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
956  // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
957  // NULL, &ctxAttr);
958  wxGLContext *pctx = new wxGLContext(m_glcc);
959  m_glcc->SetContext(pctx);
960  g_pGLcontext = pctx; // Save a copy of the common context
961  } else {
962 #ifdef __WXOSX__
963  m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
964 #else
965  m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
966  // saved common context
967 #endif
968  }
969  }
970  }
971 #endif
972 #endif
973 
974 #ifdef __ANDROID__ // ocpnUSE_GL
975  if (!g_bdisable_opengl) {
976  if (g_bopengl) {
977  // qDebug() << "SetupGlCanvas";
978  wxLogMessage(_T("Creating glChartCanvas"));
979 
980  // We use one context for all GL windows, so that textures etc will be
981  // automatically shared
982  if (IsPrimaryCanvas()) {
983  qDebug() << "Creating Primary glChartCanvas";
984 
985  // wxGLContextAttrs ctxAttr;
986  // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
987  // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
988  // NULL, &ctxAttr);
989  m_glcc = new glChartCanvas(this);
990 
991  wxGLContext *pctx = new wxGLContext(m_glcc);
992  m_glcc->SetContext(pctx);
993  g_pGLcontext = pctx; // Save a copy of the common context
994  m_glcc->m_pParentCanvas = this;
995  // m_glcc->Reparent(this);
996  } else {
997  qDebug() << "Creating Secondary glChartCanvas";
998  // QGLContext *pctx =
999  // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
1000  // << "pctx: " << pctx;
1001 
1002  m_glcc = new glChartCanvas(
1003  gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
1004  // m_glcc = new glChartCanvas(this, pctx); //Shared
1005  // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
1006  wxGLContext *pwxctx = new wxGLContext(m_glcc);
1007  m_glcc->SetContext(pwxctx);
1008  m_glcc->m_pParentCanvas = this;
1009  // m_glcc->Reparent(this);
1010  }
1011  }
1012  }
1013 #endif
1014 }
1015 
1016 void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
1017  RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1018 
1019  // On Android, we get a KillFocus on just about every keystroke.
1020  // Why?
1021 #ifdef __ANDROID__
1022  return;
1023 #endif
1024 
1025  // Special logic:
1026  // On OSX in GL mode, each mouse click causes a kill and immediate regain of
1027  // canvas focus. Why??? Who knows... So, we provide for this case by
1028  // starting a timer if required to actually Finish() a route on a legitimate
1029  // focus change, but not if the focus is quickly regained ( <20 msec.) on
1030  // this canvas.
1031 #ifdef __WXOSX__
1032  if (m_routeState && m_FinishRouteOnKillFocus)
1033  m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
1034 #else
1035  if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1036 #endif
1037 }
1038 
1039 void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
1040  m_routeFinishTimer.Stop();
1041 
1042  // Try to keep the global top-line menubar selections up to date with the
1043  // current "focus" canvas
1044  gFrame->UpdateGlobalMenuItems(this);
1045 
1046  RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1047 }
1048 
1049 void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
1050  if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1051 }
1052 
1053 #ifdef HAVE_WX_GESTURE_EVENTS
1054 void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
1055  /* we defer the popup menu call upon the leftup event
1056  else the menu disappears immediately,
1057  (see
1058  http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
1059  */
1060  m_popupWanted = true;
1061 }
1062 
1063 void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
1064  // not implemented yet
1065 }
1066 
1067 void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1068 
1069 void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1070 
1071 void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1072  wxPoint pos = event.GetPosition();
1073 
1074  m_leftdown = false;
1075 
1076  if (!m_popupWanted) {
1077  wxMouseEvent ev(wxEVT_LEFT_UP);
1078  ev.m_x = pos.x;
1079  ev.m_y = pos.y;
1080  MouseEvent(ev);
1081  return;
1082  }
1083 
1084  m_popupWanted = false;
1085 
1086  wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1087  ev.m_x = pos.x;
1088  ev.m_y = pos.y;
1089 
1090  MouseEvent(ev);
1091 }
1092 
1093 void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1094  m_leftdown = true;
1095 
1096  wxPoint pos = event.GetPosition();
1097  MouseEvent(event);
1098 }
1099 
1100 void ChartCanvas::OnMotion(wxMouseEvent &event) {
1101  /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1102  dragging, upon simple click, and without the OnLeftDown event before Thus,
1103  this consists in skiping it, and setting the leftdown bit according to a
1104  status that we trust */
1105  event.m_leftDown = m_leftdown;
1106  MouseEvent(event);
1107 }
1108 
1109 void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1110  /* there are spurious end zoom events upon right-click */
1111  if (event.IsGestureEnd()) return;
1112 
1113  double factor = event.GetZoomFactor();
1114 
1115  if (event.IsGestureStart() || m_oldVPSScale < 0) {
1116  m_oldVPSScale = GetVPScale();
1117  }
1118 
1119  double current_vps = GetVPScale();
1120  double wanted_factor = m_oldVPSScale / current_vps * factor;
1121 
1122  ZoomCanvas(wanted_factor, true, false);
1123 
1124  // Allow combined zoom/pan operation
1125  if (event.IsGestureStart()) {
1126  m_zoomStartPoint = event.GetPosition();
1127  } else {
1128  wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1129  PanCanvas(-delta.x, -delta.y);
1130  m_zoomStartPoint = event.GetPosition();
1131  }
1132 }
1133 
1134 void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1135 
1136 void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1137  DoRotateCanvas(0.0);
1138 }
1139 #endif /* HAVE_WX_GESTURE_EVENTS */
1140 
1141 void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1142  SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1143  m_vLat = pcc->iLat;
1144  m_vLon = pcc->iLon;
1145 
1146  m_restore_dbindex = pcc->DBindex;
1147  m_bFollow = pcc->bFollow;
1148  if (pcc->GroupID < 0) pcc->GroupID = 0;
1149 
1150  if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1151  m_groupIndex = 0;
1152  else
1153  m_groupIndex = pcc->GroupID;
1154 
1155  if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1156 
1157  ShowTides(pcc->bShowTides);
1158  ShowCurrents(pcc->bShowCurrents);
1159 
1160  SetShowDepthUnits(pcc->bShowDepthUnits);
1161  SetShowGrid(pcc->bShowGrid);
1162  SetShowOutlines(pcc->bShowOutlines);
1163 
1164  SetShowAIS(pcc->bShowAIS);
1165  SetAttenAIS(pcc->bAttenAIS);
1166 
1167  // ENC options
1168  SetShowENCText(pcc->bShowENCText);
1169  m_encDisplayCategory = pcc->nENCDisplayCategory;
1170  m_encShowDepth = pcc->bShowENCDepths;
1171  m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1172  m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1173  m_encShowLights = pcc->bShowENCLights;
1174  m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1175  m_encShowAnchor = pcc->bShowENCAnchorInfo;
1176  m_encShowDataQual = pcc->bShowENCDataQuality;
1177 
1178  bool courseUp = pcc->bCourseUp;
1179  bool headUp = pcc->bHeadUp;
1180  m_upMode = NORTH_UP_MODE;
1181  if (courseUp)
1182  m_upMode = COURSE_UP_MODE;
1183  else if (headUp)
1184  m_upMode = HEAD_UP_MODE;
1185 
1186  m_bLookAhead = pcc->bLookahead;
1187 
1188  m_singleChart = NULL;
1189 }
1190 
1191 void ChartCanvas::ApplyGlobalSettings() {
1192  // GPS compas window
1193  m_bShowCompassWin = g_bShowCompassWin;
1194  if (m_Compass) {
1195  m_Compass->Show(m_bShowCompassWin);
1196  if (m_bShowCompassWin) m_Compass->UpdateStatus();
1197  }
1198 }
1199 
1200 void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1201  bool groupOK = CheckGroup(m_groupIndex);
1202 
1203  if (!groupOK) {
1204  SetGroupIndex(m_groupIndex, true);
1205  }
1206 }
1207 
1208 void ChartCanvas::SetShowGPS(bool bshow) {
1209  if (m_bShowGPS != bshow) {
1210  delete m_Compass;
1211  m_Compass = new ocpnCompass(this, bshow);
1212  m_Compass->SetScaleFactor(g_compass_scalefactor);
1213  m_Compass->Show(m_bShowCompassWin);
1214  }
1215  m_bShowGPS = bshow;
1216 }
1217 
1218 void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1219  if (m_Compass) {
1220  m_Compass->Show(m_bShowCompassWin);
1221  if (m_bShowCompassWin) m_Compass->UpdateStatus();
1222  }
1223 }
1224 
1225 int ChartCanvas::GetPianoHeight() {
1226  int height = 0;
1227  if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1228 
1229  return height;
1230 }
1231 
1232 void ChartCanvas::ConfigureChartBar() {
1233  ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1234 
1235  m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
1236  m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
1237 
1238  if (GetQuiltMode()) {
1239  m_Piano->SetRoundedRectangles(true);
1240  }
1241  m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
1242  m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon(_T("polyprj"))));
1243  m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
1244 }
1245 
1246 void ChartCanvas::ShowTides(bool bShow) {
1247  gFrame->LoadHarmonics();
1248 
1249  if (ptcmgr->IsReady()) {
1250  SetbShowTide(bShow);
1251 
1252  parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1253  } else {
1254  wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1255  SetbShowTide(false);
1256  parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1257  }
1258 
1259  if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1260  GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1261 
1262  // TODO
1263  // if( GetbShowTide() ) {
1264  // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1265  // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1266  // update
1267  // } else
1268  // FrameTCTimer.Stop();
1269 }
1270 
1271 void ChartCanvas::ShowCurrents(bool bShow) {
1272  gFrame->LoadHarmonics();
1273 
1274  if (ptcmgr->IsReady()) {
1275  SetbShowCurrent(bShow);
1276  parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1277  } else {
1278  wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1279  SetbShowCurrent(false);
1280  parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1281  }
1282 
1283  if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1284  GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1285 
1286  // TODO
1287  // if( GetbShowCurrent() ) {
1288  // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1289  // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1290  // update
1291  // } else
1292  // FrameTCTimer.Stop();
1293 }
1294 
1295 // TODO
1296 extern bool g_bPreserveScaleOnX;
1297 extern ChartDummy *pDummyChart;
1298 extern int g_sticky_chart;
1299 
1300 void ChartCanvas::canvasRefreshGroupIndex(void) { SetGroupIndex(m_groupIndex); }
1301 
1302 void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1303  SetAlertString(_T(""));
1304 
1305  int new_index = index;
1306  if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1307 
1308  bool bgroup_override = false;
1309  int old_group_index = new_index;
1310 
1311  if (!CheckGroup(new_index)) {
1312  new_index = 0;
1313  bgroup_override = true;
1314  }
1315 
1316  if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1317  new_index = index;
1318 
1319  // Get the currently displayed chart native scale, and the current ViewPort
1320  int current_chart_native_scale = GetCanvasChartNativeScale();
1321  ViewPort vp = GetVP();
1322 
1323  m_groupIndex = new_index;
1324 
1325  // Are there ENCs in this group
1326  if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1327 
1328  // Update the MUIBar for ENC availability
1329  if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1330 
1331  // Allow the chart database to pre-calculate the MBTile inclusion test
1332  // boolean...
1333  ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1334 
1335  // Invalidate the "sticky" chart on group change, since it might not be in
1336  // the new group
1337  g_sticky_chart = -1;
1338 
1339  // We need a chartstack and quilt to figure out which chart to open in the
1340  // new group
1341  UpdateCanvasOnGroupChange();
1342 
1343  int dbi_now = -1;
1344  if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1345 
1346  int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1347 
1348  // If a new reference chart is indicated, set a good scale for it.
1349  if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1350  double best_scale = GetBestStartScale(dbi_hint, vp);
1351  SetVPScale(best_scale);
1352  }
1353 
1354  if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1355 
1356  // Refresh the canvas, selecting the "best" chart,
1357  // applying the prior ViewPort exactly
1358  canvasChartsRefresh(dbi_hint);
1359 
1360  UpdateCanvasControlBar();
1361 
1362  if (!autoSwitch && bgroup_override) {
1363  // show a short timed message box
1364  wxString msg(_("Group \""));
1365 
1366  ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1367  msg += pGroup->m_group_name;
1368 
1369  msg += _("\" is empty.");
1370 
1371  OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1372 
1373  return;
1374  }
1375 
1376  // Message box is deferred so that canvas refresh occurs properly before
1377  // dialog
1378  if (bgroup_override) {
1379  wxString msg(_("Group \""));
1380 
1381  ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1382  msg += pGroup->m_group_name;
1383 
1384  msg += _("\" is empty, switching to \"All Active Charts\" group.");
1385 
1386  OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1387  }
1388 }
1389 
1390 bool ChartCanvas::CheckGroup(int igroup) {
1391  if (!ChartData) return true; // Not known yet...
1392 
1393  if (igroup == 0) return true; // "all charts" is always OK
1394 
1395  if (igroup < 0) // negative group is an error
1396  return false;
1397 
1398  ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1399 
1400  if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1401  // and auto-shift to group 0
1402  return false;
1403 
1404  for (const auto &elem : pGroup->m_element_array) {
1405  for (unsigned int ic = 0;
1406  ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1407  ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1408  wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1409 
1410  if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1411  }
1412  }
1413 
1414  // If necessary, check for GSHHS
1415  for (const auto &elem : pGroup->m_element_array) {
1416  const wxString &element_root = elem.m_element_name;
1417  wxString test_string = _T("GSHH");
1418  if (element_root.Upper().Contains(test_string)) return true;
1419  }
1420 
1421  return false;
1422 }
1423 
1424 void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1425  if (!ChartData) return;
1426 
1427  AbstractPlatform::ShowBusySpinner();
1428 
1429  double old_scale = GetVPScale();
1430  InvalidateQuilt();
1431  SetQuiltRefChart(-1);
1432 
1433  m_singleChart = NULL;
1434 
1435  // delete m_pCurrentStack;
1436  // m_pCurrentStack = NULL;
1437 
1438  // Build a new ChartStack
1439  if (!m_pCurrentStack) {
1440  m_pCurrentStack = new ChartStack;
1441  ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1442  }
1443 
1444  if (-1 != dbi_hint) {
1445  if (GetQuiltMode()) {
1446  GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1447  SetQuiltRefChart(dbi_hint);
1448  } else {
1449  // Open the saved chart
1450  ChartBase *pTentative_Chart;
1451  pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1452 
1453  if (pTentative_Chart) {
1454  /* m_singleChart is always NULL here, (set above) should this go before
1455  * that? */
1456  if (m_singleChart) m_singleChart->Deactivate();
1457 
1458  m_singleChart = pTentative_Chart;
1459  m_singleChart->Activate();
1460 
1461  GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1462  GetpCurrentStack(), m_singleChart->GetFullPath());
1463  }
1464  }
1465 
1466  // refresh_Piano();
1467  } else {
1468  // Select reference chart from the stack, as though clicked by user
1469  // Make it the smallest scale chart on the stack
1470  GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1471  int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1472  SetQuiltRefChart(selected_index);
1473  }
1474 
1475  // Validate the correct single chart, or set the quilt mode as appropriate
1476  SetupCanvasQuiltMode();
1477  if (!GetQuiltMode() && m_singleChart == 0) {
1478  // use a dummy like in DoChartUpdate
1479  if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1480  m_singleChart = pDummyChart;
1481  SetVPScale(old_scale);
1482  }
1483 
1484  ReloadVP();
1485 
1486  UpdateCanvasControlBar();
1487  UpdateGPSCompassStatusBox(true);
1488 
1489  SetCursor(wxCURSOR_ARROW);
1490 
1491  AbstractPlatform::HideBusySpinner();
1492 }
1493 
1494 bool ChartCanvas::DoCanvasUpdate(void) {
1495  double tLat, tLon; // Chart Stack location
1496  double vpLat, vpLon; // ViewPort location
1497 
1498  bool bNewChart = false;
1499  bool bNewView = false;
1500  bool bCanvasChartAutoOpen = true; // debugging
1501 
1502  bool bNewPiano = false;
1503  bool bOpenSpecified;
1504  ChartStack LastStack;
1505  ChartBase *pLast_Ch;
1506 
1507  ChartStack WorkStack;
1508 
1509  if (bDBUpdateInProgress) return false;
1510  if (!ChartData) return false;
1511 
1512  if (ChartData->IsBusy()) return false;
1513 
1514  // Startup case:
1515  // Quilting is enabled, but the last chart seen was not quiltable
1516  // In this case, drop to single chart mode, set persistence flag,
1517  // And open the specified chart
1518  // TODO implement this
1519  // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1520  // if( GetQuiltMode() ) {
1521  // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1522  // gFrame->ToggleQuiltMode();
1523  // m_bpersistent_quilt = true;
1524  // m_singleChart = NULL;
1525  // }
1526  // }
1527  // }
1528 
1529  // If in auto-follow mode, use the current glat,glon to build chart
1530  // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1531  // other means
1532 
1533  if (m_bFollow) {
1534  tLat = gLat;
1535  tLon = gLon;
1536 
1537  // Set the ViewPort center based on the OWNSHIP offset
1538  double dx = m_OSoffsetx;
1539  double dy = m_OSoffsety;
1540  double d_east = dx / GetVP().view_scale_ppm;
1541  double d_north = dy / GetVP().view_scale_ppm;
1542 
1543  if (GetUpMode() == NORTH_UP_MODE) {
1544  fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1545  } else {
1546  double offset_angle = atan2(d_north, d_east);
1547  double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1548  double chart_angle = GetVPRotation();
1549  double target_angle = chart_angle + offset_angle;
1550  double d_east_mod = offset_distance * cos(target_angle);
1551  double d_north_mod = offset_distance * sin(target_angle);
1552  fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1553  }
1554 
1555  // on lookahead mode, adjust the vp center point
1556  if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1557  double angle = g_COGAvg + (GetVPRotation() * 180. / PI);
1558 
1559  double pixel_deltay =
1560  fabs(cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1561  double pixel_deltax = fabs(sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1562 
1563  double pixel_delta_tent =
1564  sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1565 
1566  double pixel_delta = 0;
1567 
1568  // The idea here is to cancel the effect of LookAhead for slow gSog, to
1569  // avoid jumping of the vp center point during slow maneuvering, or at
1570  // anchor....
1571  if (!std::isnan(gSog)) {
1572  if (gSog < 1.0)
1573  pixel_delta = 0.;
1574  else if (gSog >= 3.0)
1575  pixel_delta = pixel_delta_tent;
1576  else
1577  pixel_delta = pixel_delta_tent * (gSog - 1.0) / 2.0;
1578  }
1579 
1580  double meters_to_shift =
1581  cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1582 
1583  double dir_to_shift = g_COGAvg;
1584 
1585  ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1586  &vpLon);
1587  } else if (m_bLookAhead && !bGPSValid) {
1588  m_OSoffsetx = 0; // center ownship on loss of GPS
1589  m_OSoffsety = 0;
1590  vpLat = gLat;
1591  vpLon = gLon;
1592  }
1593 
1594  } else {
1595  tLat = m_vLat;
1596  tLon = m_vLon;
1597  vpLat = m_vLat;
1598  vpLon = m_vLon;
1599  }
1600 
1601  if (GetQuiltMode()) {
1602  int current_db_index = -1;
1603  if (m_pCurrentStack)
1604  current_db_index =
1605  m_pCurrentStack
1606  ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1607  // chart dbIndex
1608  else
1609  m_pCurrentStack = new ChartStack;
1610 
1611  // This logic added to enable opening a chart when there is no
1612  // previous chart indication, either from inital startup, or from adding
1613  // new chart directory
1614  if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1615  m_pCurrentStack) {
1616  if (m_pCurrentStack->nEntry) {
1617  int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1618  1); // smallest scale
1619  SelectQuiltRefdbChart(new_dbIndex, true);
1620  m_bautofind = false;
1621  }
1622  }
1623 
1624  ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1625  m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1626 
1627  if (m_bFirstAuto) {
1628  // Allow the chart database to pre-calculate the MBTile inclusion test
1629  // boolean...
1630  ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1631 
1632  double proposed_scale_onscreen =
1633  GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1634 
1635  int initial_db_index = m_restore_dbindex;
1636  if (initial_db_index < 0) {
1637  if (m_pCurrentStack->nEntry) {
1638  initial_db_index =
1639  m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1640  } else
1641  m_bautofind = true; // initial_db_index = 0;
1642  }
1643 
1644  if (m_pCurrentStack->nEntry) {
1645  int initial_type = ChartData->GetDBChartType(initial_db_index);
1646 
1647  // Check to see if the target new chart is quiltable as a reference
1648  // chart
1649 
1650  if (!IsChartQuiltableRef(initial_db_index)) {
1651  // If it is not quiltable, then walk the stack up looking for a
1652  // satisfactory chart i.e. one that is quiltable and of the same type
1653  // XXX if there's none?
1654  int stack_index = 0;
1655 
1656  if (stack_index >= 0) {
1657  while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1658  int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1659  if (IsChartQuiltableRef(test_db_index) &&
1660  (initial_type ==
1661  ChartData->GetDBChartType(initial_db_index))) {
1662  initial_db_index = test_db_index;
1663  break;
1664  }
1665  stack_index++;
1666  }
1667  }
1668  }
1669 
1670  ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1671  if (pc) {
1672  SetQuiltRefChart(initial_db_index);
1673  m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1674  }
1675  }
1676 
1677  bNewView |= SetViewPoint(vpLat, vpLon,
1678  GetCanvasScaleFactor() / proposed_scale_onscreen,
1679  0, GetVPRotation());
1680  }
1681  // else
1682  bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1683 
1684  goto update_finish;
1685  }
1686 
1687  // Single Chart Mode from here....
1688  pLast_Ch = m_singleChart;
1689  ChartTypeEnum new_open_type;
1690  ChartFamilyEnum new_open_family;
1691  if (pLast_Ch) {
1692  new_open_type = pLast_Ch->GetChartType();
1693  new_open_family = pLast_Ch->GetChartFamily();
1694  } else {
1695  new_open_type = CHART_TYPE_KAP;
1696  new_open_family = CHART_FAMILY_RASTER;
1697  }
1698 
1699  bOpenSpecified = m_bFirstAuto;
1700 
1701  // Make sure the target stack is valid
1702  if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1703 
1704  // Build a chart stack based on tLat, tLon
1705  if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1706  m_groupIndex)) { // Bogus Lat, Lon?
1707  if (NULL == pDummyChart) {
1708  pDummyChart = new ChartDummy;
1709  bNewChart = true;
1710  }
1711 
1712  if (m_singleChart)
1713  if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1714 
1715  m_singleChart = pDummyChart;
1716 
1717  // If the current viewpoint is invalid, set the default scale to
1718  // something reasonable.
1719  double set_scale = GetVPScale();
1720  if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1721 
1722  bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1723 
1724  // If the chart stack has just changed, there is new status
1725  if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1726  if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1727  bNewPiano = true;
1728  bNewChart = true;
1729  }
1730  }
1731 
1732  // Copy the new (by definition empty) stack into the target stack
1733  ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1734 
1735  goto update_finish;
1736  }
1737 
1738  // Check to see if Chart Stack has changed
1739  if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1740  // New chart stack, so...
1741  bNewPiano = true;
1742 
1743  // Save a copy of the current stack
1744  ChartData->CopyStack(&LastStack, m_pCurrentStack);
1745 
1746  // Copy the new stack into the target stack
1747  ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1748 
1749  // Is Current Chart in new stack?
1750 
1751  int tEntry = -1;
1752  if (NULL != m_singleChart) // this handles startup case
1753  tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1754  m_singleChart->GetFullPath());
1755 
1756  if (tEntry != -1) { // m_singleChart is in the new stack
1757  m_pCurrentStack->CurrentStackEntry = tEntry;
1758  bNewChart = false;
1759  }
1760 
1761  else // m_singleChart is NOT in new stack
1762  { // So, need to open a new chart
1763  // Find the largest scale raster chart that opens OK
1764 
1765  ChartBase *pProposed = NULL;
1766 
1767  if (bCanvasChartAutoOpen) {
1768  bool search_direction =
1769  false; // default is to search from lowest to highest
1770  int start_index = 0;
1771 
1772  // A special case: If panning at high scale, open largest scale
1773  // chart first
1774  if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1775  (LastStack.nEntry == 0)) {
1776  search_direction = true;
1777  start_index = m_pCurrentStack->nEntry - 1;
1778  }
1779 
1780  // Another special case, open specified index on program start
1781  if (bOpenSpecified) {
1782  search_direction = false;
1783  start_index = 0;
1784  if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1785  start_index = 0;
1786 
1787  new_open_type = CHART_TYPE_DONTCARE;
1788  }
1789 
1790  pProposed = ChartData->OpenStackChartConditional(
1791  m_pCurrentStack, start_index, search_direction, new_open_type,
1792  new_open_family);
1793 
1794  // Try to open other types/families of chart in some priority
1795  if (NULL == pProposed)
1796  pProposed = ChartData->OpenStackChartConditional(
1797  m_pCurrentStack, start_index, search_direction,
1798  CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1799 
1800  if (NULL == pProposed)
1801  pProposed = ChartData->OpenStackChartConditional(
1802  m_pCurrentStack, start_index, search_direction,
1803  CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1804 
1805  bNewChart = true;
1806 
1807  } // bCanvasChartAutoOpen
1808 
1809  else
1810  pProposed = NULL;
1811 
1812  // If no go, then
1813  // Open a Dummy Chart
1814  if (NULL == pProposed) {
1815  if (NULL == pDummyChart) {
1816  pDummyChart = new ChartDummy;
1817  bNewChart = true;
1818  }
1819 
1820  if (pLast_Ch)
1821  if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1822 
1823  pProposed = pDummyChart;
1824  }
1825 
1826  // Arriving here, pProposed points to an opened chart, or NULL.
1827  if (m_singleChart) m_singleChart->Deactivate();
1828  m_singleChart = pProposed;
1829 
1830  if (m_singleChart) {
1831  m_singleChart->Activate();
1832  m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1833  m_pCurrentStack, m_singleChart->GetFullPath());
1834  }
1835  } // need new chart
1836 
1837  // Arriving here, m_singleChart is opened and OK, or NULL
1838  if (NULL != m_singleChart) {
1839  // Setup the view using the current scale
1840  double set_scale = GetVPScale();
1841 
1842  // If the current viewpoint is invalid, set the default scale to
1843  // something reasonable.
1844  if (!GetVP().IsValid())
1845  set_scale = 1. / 20000.;
1846  else { // otherwise, match scale if elected.
1847  double proposed_scale_onscreen;
1848 
1849  if (m_bFollow) { // autoset the scale only if in autofollow
1850  double new_scale_ppm =
1851  m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1852  proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1853  } else
1854  proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1855 
1856  // This logic will bring a new chart onscreen at roughly twice the true
1857  // paper scale equivalent. Note that first chart opened on application
1858  // startup (bOpenSpecified = true) will open at the config saved scale
1859  if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1860  proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1861  double equivalent_vp_scale =
1862  GetCanvasScaleFactor() / proposed_scale_onscreen;
1863  double new_scale_ppm =
1864  m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1865  proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1866  }
1867 
1868  if (m_bFollow) { // bounds-check the scale only if in autofollow
1869  proposed_scale_onscreen =
1870  wxMin(proposed_scale_onscreen,
1871  m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1872  GetCanvasWidth()));
1873  proposed_scale_onscreen =
1874  wxMax(proposed_scale_onscreen,
1875  m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1876  g_b_overzoom_x));
1877  }
1878 
1879  set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1880  }
1881 
1882  bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1883  m_singleChart->GetChartSkew() * PI / 180.,
1884  GetVPRotation());
1885  }
1886  } // new stack
1887 
1888  else // No change in Chart Stack
1889  {
1890  if ((m_bFollow) && m_singleChart)
1891  bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1892  m_singleChart->GetChartSkew() * PI / 180.,
1893  GetVPRotation());
1894  }
1895 
1896 update_finish:
1897 
1898  // TODO
1899  // if( bNewPiano ) UpdateControlBar();
1900 
1901  // Update the ownship position on thumbnail chart, if shown
1902  if (pthumbwin && pthumbwin->IsShown()) {
1903  if (pthumbwin->pThumbChart) {
1904  if (pthumbwin->pThumbChart->UpdateThumbData(gLat, gLon))
1905  pthumbwin->Refresh(TRUE);
1906  }
1907  }
1908 
1909  m_bFirstAuto = false; // Auto open on program start
1910 
1911  // If we need a Refresh(), do it here...
1912  // But don't duplicate a Refresh() done by SetViewPoint()
1913  if (bNewChart && !bNewView) Refresh(false);
1914 
1915 #ifdef ocpnUSE_GL
1916  // If a new chart, need to invalidate gl viewport for refresh
1917  // so the fbo gets flushed
1918  if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1919 #endif
1920 
1921  return bNewChart | bNewView;
1922 }
1923 
1924 void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1925  if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1926 
1927  SetQuiltRefChart(db_index);
1928  if (ChartData) {
1929  ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1930  if (pc) {
1931  if (b_autoscale) {
1932  double best_scale_ppm = GetBestVPScale(pc);
1933  SetVPScale(best_scale_ppm);
1934  }
1935  } else
1936  SetQuiltRefChart(-1);
1937  } else
1938  SetQuiltRefChart(-1);
1939 }
1940 
1941 void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1942  std::vector<int> piano_chart_index_array =
1943  GetQuiltExtendedStackdbIndexArray();
1944  int current_db_index = piano_chart_index_array[selected_index];
1945 
1946  SelectQuiltRefdbChart(current_db_index);
1947 }
1948 
1949 double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1950  if (pchart) {
1951  double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1952 
1953  if ((g_bPreserveScaleOnX) ||
1954  (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
1955  double new_scale_ppm = GetVPScale();
1956  proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1957  } else {
1958  // This logic will bring the new chart onscreen at roughly twice the true
1959  // paper scale equivalent.
1960  proposed_scale_onscreen = pchart->GetNativeScale() / 2;
1961  double equivalent_vp_scale =
1962  GetCanvasScaleFactor() / proposed_scale_onscreen;
1963  double new_scale_ppm =
1964  pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1965  proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1966  }
1967 
1968  // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
1969  // set. Otherwise, we get severe performance problems on all platforms
1970 
1971  double max_underzoom_multiplier = 2.0;
1972  if (GetVP().b_quilt) {
1973  double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
1974  pchart->GetChartType(),
1975  pchart->GetChartFamily());
1976  max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
1977  }
1978 
1979  proposed_scale_onscreen = wxMin(
1980  proposed_scale_onscreen,
1981  pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
1982  max_underzoom_multiplier);
1983 
1984  // And, do not allow excessive overzoom either
1985  proposed_scale_onscreen =
1986  wxMax(proposed_scale_onscreen,
1987  pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
1988 
1989  return GetCanvasScaleFactor() / proposed_scale_onscreen;
1990  } else
1991  return 1.0;
1992 }
1993 
1994 void ChartCanvas::SetupCanvasQuiltMode(void) {
1995  if (GetQuiltMode()) // going to quilt mode
1996  {
1997  ChartData->LockCache();
1998 
1999  m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2000 
2001  ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2002 
2003  m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2004  m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2005  m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2006  m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2007 
2008  m_Piano->SetRoundedRectangles(true);
2009 
2010  // Select the proper Ref chart
2011  int target_new_dbindex = -1;
2012  if (m_pCurrentStack) {
2013  target_new_dbindex =
2014  GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2015 
2016  if (-1 != target_new_dbindex) {
2017  if (!IsChartQuiltableRef(target_new_dbindex)) {
2018  int proj = ChartData->GetDBChartProj(target_new_dbindex);
2019  int type = ChartData->GetDBChartType(target_new_dbindex);
2020 
2021  // walk the stack up looking for a satisfactory chart
2022  int stack_index = m_pCurrentStack->CurrentStackEntry;
2023 
2024  while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2025  (stack_index >= 0)) {
2026  int proj_tent = ChartData->GetDBChartProj(
2027  m_pCurrentStack->GetDBIndex(stack_index));
2028  int type_tent = ChartData->GetDBChartType(
2029  m_pCurrentStack->GetDBIndex(stack_index));
2030 
2031  if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2032  if ((proj == proj_tent) && (type_tent == type)) {
2033  target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2034  break;
2035  }
2036  }
2037  stack_index++;
2038  }
2039  }
2040  }
2041  }
2042 
2043  if (IsChartQuiltableRef(target_new_dbindex))
2044  SelectQuiltRefdbChart(target_new_dbindex,
2045  false); // Try not to allow a scale change
2046  else
2047  SelectQuiltRefdbChart(-1, false);
2048 
2049  m_singleChart = NULL; // Bye....
2050 
2051  // Re-qualify the quilt reference chart selection
2052  AdjustQuiltRefChart();
2053 
2054  // Restore projection type saved on last quilt mode toggle
2055  // TODO
2056  // if(g_sticky_projection != -1)
2057  // GetVP().SetProjectionType(g_sticky_projection);
2058  // else
2059  // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2060  GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2061 
2062  } else // going to SC Mode
2063  {
2064  std::vector<int> empty_array;
2065  m_Piano->SetActiveKeyArray(empty_array);
2066  m_Piano->SetNoshowIndexArray(empty_array);
2067  m_Piano->SetEclipsedIndexArray(empty_array);
2068 
2069  ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2070  m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2071  m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2072  m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2073  m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2074 
2075  m_Piano->SetRoundedRectangles(false);
2076  // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2077  }
2078 
2079  // When shifting from quilt to single chart mode, select the "best" single
2080  // chart to show
2081  if (!GetQuiltMode()) {
2082  if (ChartData && ChartData->IsValid()) {
2083  UnlockQuilt();
2084 
2085  double tLat, tLon;
2086  if (m_bFollow == true) {
2087  tLat = gLat;
2088  tLon = gLon;
2089  } else {
2090  tLat = m_vLat;
2091  tLon = m_vLon;
2092  }
2093 
2094  if (!m_singleChart) {
2095  // Build a temporary chart stack based on tLat, tLon
2096  ChartStack TempStack;
2097  ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2098  m_groupIndex);
2099 
2100  // Iterate over the quilt charts actually shown, looking for the
2101  // largest scale chart that will be in the new chartstack.... This
2102  // will (almost?) always be the reference chart....
2103 
2104  ChartBase *Candidate_Chart = NULL;
2105  int cur_max_scale = (int)1e8;
2106 
2107  ChartBase *pChart = GetFirstQuiltChart();
2108  while (pChart) {
2109  // Is this pChart in new stack?
2110  int tEntry =
2111  ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2112  if (tEntry != -1) {
2113  if (pChart->GetNativeScale() < cur_max_scale) {
2114  Candidate_Chart = pChart;
2115  cur_max_scale = pChart->GetNativeScale();
2116  }
2117  }
2118  pChart = GetNextQuiltChart();
2119  }
2120 
2121  m_singleChart = Candidate_Chart;
2122 
2123  // If the quilt is empty, there is no "best" chart.
2124  // So, open the smallest scale chart in the current stack
2125  if (NULL == m_singleChart) {
2126  m_singleChart = ChartData->OpenStackChartConditional(
2127  &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2128  CHART_FAMILY_DONTCARE);
2129  }
2130  }
2131 
2132  // Invalidate all the charts in the quilt,
2133  // as any cached data may be region based and not have fullscreen coverage
2134  InvalidateAllQuiltPatchs();
2135 
2136  if (m_singleChart) {
2137  int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2138  std::vector<int> one_array;
2139  one_array.push_back(dbi);
2140  m_Piano->SetActiveKeyArray(one_array);
2141  }
2142 
2143  if (m_singleChart) {
2144  GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2145  }
2146  }
2147  // Invalidate the current stack so that it will be rebuilt on next tick
2148  if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2149  }
2150 }
2151 
2152 bool ChartCanvas::IsTempMenuBarEnabled() {
2153 #ifdef __WXMSW__
2154  int major;
2155  wxGetOsVersion(&major);
2156  return (major >
2157  5); // For Windows, function is only available on Vista and above
2158 #else
2159  return true;
2160 #endif
2161 }
2162 
2163 double ChartCanvas::GetCanvasRangeMeters() {
2164  int width, height;
2165  GetSize(&width, &height);
2166  int minDimension = wxMin(width, height);
2167 
2168  double range = (minDimension / GetVP().view_scale_ppm) / 2;
2169  range *= cos(GetVP().clat * PI / 180.);
2170  return range;
2171 }
2172 
2173 void ChartCanvas::SetCanvasRangeMeters(double range) {
2174  int width, height;
2175  GetSize(&width, &height);
2176  int minDimension = wxMin(width, height);
2177 
2178  double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2179  SetVPScale(scale_ppm / 2);
2180 }
2181 
2182 bool ChartCanvas::SetUserOwnship() {
2183  // Look for user defined ownship image
2184  // This may be found in the shared data location along with other user
2185  // defined icons. and will be called "ownship.xpm" or "ownship.png"
2186  if (pWayPointMan && pWayPointMan->DoesIconExist(_T("ownship"))) {
2187  double factor_dusk = 0.5;
2188  double factor_night = 0.25;
2189 
2190  wxBitmap *pbmp = pWayPointMan->GetIconBitmap(_T("ownship"));
2191  m_pos_image_user_day = new wxImage;
2192  *m_pos_image_user_day = pbmp->ConvertToImage();
2193  if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2194 
2195  int gimg_width = m_pos_image_user_day->GetWidth();
2196  int gimg_height = m_pos_image_user_day->GetHeight();
2197 
2198  // Make dusk and night images
2199  m_pos_image_user_dusk = new wxImage;
2200  m_pos_image_user_night = new wxImage;
2201 
2202  *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2203  *m_pos_image_user_night = m_pos_image_user_day->Copy();
2204 
2205  for (int iy = 0; iy < gimg_height; iy++) {
2206  for (int ix = 0; ix < gimg_width; ix++) {
2207  if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2208  wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2209  m_pos_image_user_day->GetGreen(ix, iy),
2210  m_pos_image_user_day->GetBlue(ix, iy));
2211  wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2212  hsv.value = hsv.value * factor_dusk;
2213  wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2214  m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2215  nrgb.blue);
2216 
2217  hsv = wxImage::RGBtoHSV(rgb);
2218  hsv.value = hsv.value * factor_night;
2219  nrgb = wxImage::HSVtoRGB(hsv);
2220  m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2221  nrgb.blue);
2222  }
2223  }
2224  }
2225 
2226  // Make some alternate greyed out day/dusk/night images
2227  m_pos_image_user_grey_day = new wxImage;
2228  *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2229 
2230  m_pos_image_user_grey_dusk = new wxImage;
2231  m_pos_image_user_grey_night = new wxImage;
2232 
2233  *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2234  *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2235 
2236  for (int iy = 0; iy < gimg_height; iy++) {
2237  for (int ix = 0; ix < gimg_width; ix++) {
2238  if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2239  wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2240  m_pos_image_user_grey_day->GetGreen(ix, iy),
2241  m_pos_image_user_grey_day->GetBlue(ix, iy));
2242  wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2243  hsv.value = hsv.value * factor_dusk;
2244  wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2245  m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2246  nrgb.blue);
2247 
2248  hsv = wxImage::RGBtoHSV(rgb);
2249  hsv.value = hsv.value * factor_night;
2250  nrgb = wxImage::HSVtoRGB(hsv);
2251  m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2252  nrgb.blue);
2253  }
2254  }
2255  }
2256 
2257  // Make a yellow image for rendering under low accuracy chart conditions
2258  m_pos_image_user_yellow_day = new wxImage;
2259  m_pos_image_user_yellow_dusk = new wxImage;
2260  m_pos_image_user_yellow_night = new wxImage;
2261 
2262  *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2263  *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2264  *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2265 
2266  for (int iy = 0; iy < gimg_height; iy++) {
2267  for (int ix = 0; ix < gimg_width; ix++) {
2268  if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2269  wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2270  m_pos_image_user_grey_day->GetGreen(ix, iy),
2271  m_pos_image_user_grey_day->GetBlue(ix, iy));
2272 
2273  // Simply remove all "blue" from the greyscaled image...
2274  // so, what is not black becomes yellow.
2275  wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2276  wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2277  m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2278 
2279  hsv = wxImage::RGBtoHSV(rgb);
2280  hsv.value = hsv.value * factor_dusk;
2281  nrgb = wxImage::HSVtoRGB(hsv);
2282  m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2283 
2284  hsv = wxImage::RGBtoHSV(rgb);
2285  hsv.value = hsv.value * factor_night;
2286  nrgb = wxImage::HSVtoRGB(hsv);
2287  m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2288  0);
2289  }
2290  }
2291  }
2292 
2293  return true;
2294  } else
2295  return false;
2296 }
2297 
2298 void ChartCanvas::SetDisplaySizeMM(double size) {
2299  m_display_size_mm = size;
2300 
2301  // int sx, sy;
2302  // wxDisplaySize( &sx, &sy );
2303 
2304  // Calculate pixels per mm for later reference
2305  wxSize sd = g_Platform->getDisplaySize();
2306  double horizontal = sd.x;
2307  // Set DPI (Win) scale factor
2308  g_scaler = g_Platform->GetDisplayDIPMult(this);
2309 
2310  m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2311  m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2312 
2313  if (ps52plib) {
2314  ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2315  ps52plib->SetPPMM(m_pix_per_mm);
2316  }
2317 
2318  wxString msg;
2319  msg.Printf(
2320  _T("Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): ")
2321  _T("%d:%d "),
2322  m_display_size_mm, sd.x, sd.y);
2323  wxLogMessage(msg);
2324 
2325  int ssx, ssy;
2326  ssx = g_monitor_info[g_current_monitor].width;
2327  ssy = g_monitor_info[g_current_monitor].height;
2328  msg.Printf(_T("monitor size: %d %d"), ssx, ssy);
2329  wxLogMessage(msg);
2330 
2331  m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2332 }
2333 #if 0
2334 void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2335 {
2336  wxString msg(event.m_string.c_str(), wxConvUTF8);
2337  // if cpus are removed between runs
2338  if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2339  compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2340  }
2341 
2342  if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2343  {
2344  compress_msg_array.RemoveAt(event.thread);
2345  compress_msg_array.Insert( msg, event.thread);
2346  }
2347  else
2348  compress_msg_array.Add(msg);
2349 
2350 
2351  wxString combined_msg;
2352  for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2353  combined_msg += compress_msg_array[i];
2354  combined_msg += _T("\n");
2355  }
2356 
2357  bool skip = false;
2358  pprog->Update(pprog_count, combined_msg, &skip );
2359  pprog->SetSize(pprog_size);
2360  if(skip)
2361  b_skipout = skip;
2362 }
2363 #endif
2364 void ChartCanvas::InvalidateGL() {
2365  if (!m_glcc) return;
2366 #ifdef ocpnUSE_GL
2367  if (g_bopengl) m_glcc->Invalidate();
2368 #endif
2369  if (m_Compass) m_Compass->UpdateStatus(true);
2370 }
2371 
2372 int ChartCanvas::GetCanvasChartNativeScale() {
2373  int ret = 1;
2374  if (!VPoint.b_quilt) {
2375  if (m_singleChart) ret = m_singleChart->GetNativeScale();
2376  } else
2377  ret = (int)m_pQuilt->GetRefNativeScale();
2378 
2379  return ret;
2380 }
2381 
2382 ChartBase *ChartCanvas::GetChartAtCursor() {
2383  ChartBase *target_chart;
2384  if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2385  target_chart = m_singleChart;
2386  else if (VPoint.b_quilt)
2387  target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2388  else
2389  target_chart = NULL;
2390  return target_chart;
2391 }
2392 
2393 ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2394  ChartBase *target_chart;
2395  if (VPoint.b_quilt)
2396  target_chart =
2397  m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2398  else
2399  target_chart = NULL;
2400  return target_chart;
2401 }
2402 
2403 int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2404  int new_dbIndex = -1;
2405  if (!VPoint.b_quilt) {
2406  if (m_pCurrentStack) {
2407  for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2408  int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2409  if (sc >= scale) {
2410  new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2411  break;
2412  }
2413  }
2414  }
2415  } else {
2416  // Using the current quilt, select a useable reference chart
2417  // Said chart will be in the extended (possibly full-screen) stack,
2418  // And will have a scale equal to or just greater than the stipulated
2419  // value
2420  unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2421  if (im > 0) {
2422  for (unsigned int is = 0; is < im; is++) {
2423  const ChartTableEntry &m = ChartData->GetChartTableEntry(
2424  m_pQuilt->GetExtendedStackIndexArray()[is]);
2425  if ((m.Scale_ge(
2426  scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2427  new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2428  break;
2429  }
2430  }
2431  }
2432  }
2433 
2434  return new_dbIndex;
2435 }
2436 
2437 void ChartCanvas::EnablePaint(bool b_enable) {
2438  m_b_paint_enable = b_enable;
2439 #ifdef ocpnUSE_GL
2440  if (m_glcc) m_glcc->EnablePaint(b_enable);
2441 #endif
2442 }
2443 
2444 bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2445 
2446 void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2447 
2448 std::vector<int> ChartCanvas::GetQuiltIndexArray(void) {
2449  return m_pQuilt->GetQuiltIndexArray();
2450  ;
2451 }
2452 
2453 void ChartCanvas::SetQuiltMode(bool b_quilt) {
2454  VPoint.b_quilt = b_quilt;
2455  VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2456 }
2457 
2458 bool ChartCanvas::GetQuiltMode(void) { return VPoint.b_quilt; }
2459 
2460 int ChartCanvas::GetQuiltReferenceChartIndex(void) {
2461  return m_pQuilt->GetRefChartdbIndex();
2462 }
2463 
2464 void ChartCanvas::InvalidateAllQuiltPatchs(void) {
2465  m_pQuilt->InvalidateAllQuiltPatchs();
2466 }
2467 
2468 ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2469  return m_pQuilt->GetLargestScaleChart();
2470 }
2471 
2472 ChartBase *ChartCanvas::GetFirstQuiltChart() {
2473  return m_pQuilt->GetFirstChart();
2474 }
2475 
2476 ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2477 
2478 int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2479 
2480 void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2481  m_pQuilt->SetHiliteIndex(dbIndex);
2482 }
2483 
2484 void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2485  m_pQuilt->SetHiliteIndexArray(hilite_array);
2486 }
2487 
2488 void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2489  m_pQuilt->ClearHiliteIndexArray();
2490 }
2491 
2492 std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2493  bool flag2) {
2494  return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2495 }
2496 
2497 int ChartCanvas::GetQuiltRefChartdbIndex(void) {
2498  return m_pQuilt->GetRefChartdbIndex();
2499 }
2500 
2501 std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2502  return m_pQuilt->GetExtendedStackIndexArray();
2503 }
2504 
2505 std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2506  return m_pQuilt->GetFullscreenIndexArray();
2507 }
2508 
2509 std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2510  return m_pQuilt->GetEclipsedStackIndexArray();
2511 }
2512 
2513 void ChartCanvas::InvalidateQuilt(void) { return m_pQuilt->Invalidate(); }
2514 
2515 double ChartCanvas::GetQuiltMaxErrorFactor() {
2516  return m_pQuilt->GetMaxErrorFactor();
2517 }
2518 
2519 bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2520  return m_pQuilt->IsChartQuiltableRef(db_index);
2521 }
2522 
2523 bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2524  double chartMaxScale =
2525  chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2526  return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2527 }
2528 
2529 void ChartCanvas::StartMeasureRoute() {
2530  if (!m_routeState) { // no measure tool if currently creating route
2531  if (m_bMeasure_Active) {
2532  g_pRouteMan->DeleteRoute(m_pMeasureRoute,
2533  NavObjectChanges::getInstance());
2534  m_pMeasureRoute = NULL;
2535  }
2536 
2537  m_bMeasure_Active = true;
2538  m_nMeasureState = 1;
2539  m_bDrawingRoute = false;
2540 
2541  SetCursor(*pCursorPencil);
2542  Refresh();
2543  }
2544 }
2545 
2546 void ChartCanvas::CancelMeasureRoute() {
2547  m_bMeasure_Active = false;
2548  m_nMeasureState = 0;
2549  m_bDrawingRoute = false;
2550 
2551  g_pRouteMan->DeleteRoute(m_pMeasureRoute, NavObjectChanges::getInstance());
2552  m_pMeasureRoute = NULL;
2553 
2554  SetCursor(*pCursorArrow);
2555 }
2556 
2557 ViewPort &ChartCanvas::GetVP() { return VPoint; }
2558 
2559 void ChartCanvas::SetVP(ViewPort &vp) { VPoint = vp; }
2560 
2561 // void ChartCanvas::SetFocus()
2562 // {
2563 // printf("set %d\n", m_canvasIndex);
2564 // //wxWindow:SetFocus();
2565 // }
2566 
2567 void ChartCanvas::TriggerDeferredFocus() {
2568  //#if defined(__WXGTK__) || defined(__WXOSX__)
2569 
2570  m_deferredFocusTimer.Start(20, true);
2571 
2572 #if defined(__WXGTK__) || defined(__WXOSX__)
2573  gFrame->Raise();
2574 #endif
2575 
2576  // gFrame->Raise();
2577  //#else
2578  // SetFocus();
2579  // Refresh(true);
2580  //#endif
2581 }
2582 
2583 void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2584  SetFocus();
2585  Refresh(true);
2586 }
2587 
2588 void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2589  if (g_pi_manager)
2590  if (g_pi_manager->SendKeyEventToPlugins(event))
2591  return; // PlugIn did something, and does not want the canvas to do
2592  // anything else
2593 
2594  int key_char = event.GetKeyCode();
2595 
2596  if (g_benable_rotate) {
2597  switch (key_char) {
2598  case ']':
2599  RotateCanvas(1);
2600  Refresh();
2601  break;
2602 
2603  case '[':
2604  RotateCanvas(-1);
2605  Refresh();
2606  break;
2607 
2608  case '\\':
2609  DoRotateCanvas(0);
2610  break;
2611  }
2612  }
2613 
2614  event.Skip();
2615 }
2616 
2617 void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2618  if (g_pi_manager)
2619  if (g_pi_manager->SendKeyEventToPlugins(event))
2620  return; // PlugIn did something, and does not want the canvas to do
2621  // anything else
2622 
2623  bool b_handled = false;
2624 
2625  m_modkeys = event.GetModifiers();
2626 
2627  int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2628 
2629 #ifdef OCPN_ALT_MENUBAR
2630 #ifndef __WXOSX__
2631  // If the permanent menubar is disabled, we show it temporarily when Alt is
2632  // pressed or when Alt + a letter is presssed (for the top-menu-level
2633  // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2634  // some special cases.
2635  if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2636  // If Alt + a letter is pressed, and the menubar is hidden, show it now
2637  if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2638  if (!g_bTempShowMenuBar) {
2639  g_bTempShowMenuBar = true;
2640  parent_frame->ApplyGlobalSettings(false);
2641  }
2642  m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2643  event.Skip();
2644  return;
2645  }
2646  // If another key is pressed while Alt is down, do NOT toggle the menus when
2647  // Alt is released
2648  if (event.GetKeyCode() != WXK_ALT) {
2649  m_bMayToggleMenuBar = false;
2650  }
2651  }
2652 #endif
2653 #endif
2654 
2655  // HOTKEYS
2656  switch (event.GetKeyCode()) {
2657  case WXK_TAB:
2658  // parent_frame->SwitchKBFocus( this );
2659  break;
2660 
2661  case WXK_MENU:
2662  int x, y;
2663  event.GetPosition(&x, &y);
2664  m_FinishRouteOnKillFocus = false;
2665  CallPopupMenu(x, y);
2666  m_FinishRouteOnKillFocus = true;
2667  break;
2668 
2669  case WXK_ALT:
2670  m_modkeys |= wxMOD_ALT;
2671  break;
2672 
2673  case WXK_CONTROL:
2674  m_modkeys |= wxMOD_CONTROL;
2675  break;
2676 
2677 #ifdef __WXOSX__
2678  // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2679  case WXK_RAW_CONTROL:
2680  m_modkeys |= wxMOD_RAW_CONTROL;
2681  break;
2682 #endif
2683 
2684  case WXK_LEFT:
2685  if (m_modkeys == wxMOD_CONTROL)
2686  parent_frame->DoStackDown(this);
2687  else if (g_bsmoothpanzoom) {
2688  StartTimedMovement();
2689  m_panx = -1;
2690  } else {
2691  PanCanvas(-panspeed, 0);
2692  }
2693  b_handled = true;
2694  break;
2695 
2696  case WXK_UP:
2697  if (g_bsmoothpanzoom) {
2698  StartTimedMovement();
2699  m_pany = -1;
2700  } else
2701  PanCanvas(0, -panspeed);
2702  b_handled = true;
2703  break;
2704 
2705  case WXK_RIGHT:
2706  if (m_modkeys == wxMOD_CONTROL)
2707  parent_frame->DoStackUp(this);
2708  else if (g_bsmoothpanzoom) {
2709  StartTimedMovement();
2710  m_panx = 1;
2711  } else
2712  PanCanvas(panspeed, 0);
2713  b_handled = true;
2714 
2715  break;
2716 
2717  case WXK_DOWN:
2718  if (g_bsmoothpanzoom) {
2719  StartTimedMovement();
2720  m_pany = 1;
2721  } else
2722  PanCanvas(0, panspeed);
2723  b_handled = true;
2724  break;
2725 
2726  case WXK_F2:
2727  TogglebFollow();
2728  break;
2729 
2730  case WXK_F3: {
2731  SetShowENCText(!GetShowENCText());
2732  Refresh(true);
2733  InvalidateGL();
2734  break;
2735  }
2736  case WXK_F4:
2737  if (!m_bMeasure_Active) {
2738  if (event.ShiftDown())
2739  m_bMeasure_DistCircle = true;
2740  else
2741  m_bMeasure_DistCircle = false;
2742 
2743  StartMeasureRoute();
2744  } else {
2745  CancelMeasureRoute();
2746 
2747  SetCursor(*pCursorArrow);
2748 
2749  //SurfaceToolbar();
2750  InvalidateGL();
2751  Refresh(false);
2752  }
2753 
2754  break;
2755 
2756  case WXK_F5:
2757  parent_frame->ToggleColorScheme();
2758  gFrame->Raise();
2759  TriggerDeferredFocus();
2760  break;
2761 
2762  case WXK_F6: {
2763  int mod = m_modkeys & wxMOD_SHIFT;
2764  if (mod != m_brightmod) {
2765  m_brightmod = mod;
2766  m_bbrightdir = !m_bbrightdir;
2767  }
2768 
2769  if (!m_bbrightdir) {
2770  g_nbrightness -= 10;
2771  if (g_nbrightness <= MIN_BRIGHT) {
2772  g_nbrightness = MIN_BRIGHT;
2773  m_bbrightdir = true;
2774  }
2775  } else {
2776  g_nbrightness += 10;
2777  if (g_nbrightness >= MAX_BRIGHT) {
2778  g_nbrightness = MAX_BRIGHT;
2779  m_bbrightdir = false;
2780  }
2781  }
2782 
2783  SetScreenBrightness(g_nbrightness);
2784  ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2785 
2786  SetFocus(); // just in case the external program steals it....
2787  gFrame->Raise(); // And reactivate the application main
2788 
2789  break;
2790  }
2791 
2792  case WXK_F7:
2793  parent_frame->DoStackDown(this);
2794  break;
2795 
2796  case WXK_F8:
2797  parent_frame->DoStackUp(this);
2798  break;
2799 
2800 #ifndef __WXOSX__
2801  case WXK_F9:
2802  ToggleCanvasQuiltMode();
2803  break;
2804 #endif
2805 
2806  case WXK_F11:
2807  parent_frame->ToggleFullScreen();
2808  b_handled = true;
2809  break;
2810 
2811  case WXK_F12: {
2812  if (m_modkeys == wxMOD_ALT)
2813  m_nMeasureState = *(volatile int *)(0); // generate a fault for testing
2814 
2815  ToggleChartOutlines();
2816  break;
2817  }
2818 
2819  case WXK_PAUSE: // Drop MOB
2820  parent_frame->ActivateMOB();
2821  break;
2822 
2823  // NUMERIC PAD
2824  case WXK_NUMPAD_ADD: // '+' on NUM PAD
2825  case WXK_PAGEUP: {
2826  ZoomCanvas(g_plus_minus_zoom_factor, false);
2827  break;
2828  }
2829  case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2830  case WXK_PAGEDOWN: {
2831  ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2832  break;
2833  }
2834  case WXK_DELETE:
2835  case WXK_BACK:
2836  if (m_bMeasure_Active) {
2837  if (m_nMeasureState > 2) {
2838  m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2839  m_pMeasureRoute->m_lastMousePointIndex =
2840  m_pMeasureRoute->GetnPoints();
2841  m_nMeasureState--;
2842  gFrame->RefreshAllCanvas();
2843  } else {
2844  CancelMeasureRoute();
2845  StartMeasureRoute();
2846  }
2847  }
2848  break;
2849  default:
2850  break;
2851  }
2852 
2853  if (event.GetKeyCode() < 128) // ascii
2854  {
2855  int key_char = event.GetKeyCode();
2856 
2857  // Handle both QWERTY and AZERTY keyboard separately for a few control
2858  // codes
2859  if (!g_b_assume_azerty) {
2860  switch (key_char) {
2861  case '+':
2862  case '=':
2863  ZoomCanvas(g_plus_minus_zoom_factor, false);
2864  break;
2865 
2866  case '-':
2867  case '_':
2868  ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2869  break;
2870  }
2871 
2872 #ifdef __WXMAC__
2873  if (g_benable_rotate) {
2874  switch (key_char) {
2875  // On other platforms these are handled in OnKeyChar, which
2876  // (apparently) works better in some locales. On OS X it is better
2877  // to handle them here, since pressing Alt (which should change the
2878  // rotation speed) changes the key char and so prevents the keys
2879  // from working.
2880  case ']':
2881  RotateCanvas(1);
2882  break;
2883 
2884  case '[':
2885  RotateCanvas(-1);
2886  break;
2887 
2888  case '\\':
2889  DoRotateCanvas(0);
2890  break;
2891  }
2892  }
2893 #endif
2894  } else { // AZERTY
2895  switch (key_char) {
2896  case 43:
2897  ZoomCanvas(g_plus_minus_zoom_factor, false);
2898  break;
2899 
2900  case 54: // '-' alpha/num pad
2901  // case 56: // '_' alpha/num pad
2902  ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2903  break;
2904  }
2905  }
2906 
2907 #ifdef __WXOSX__
2908  // Ctrl+Cmd+F toggles fullscreen on macOS
2909  if (key_char == 'F' && m_modkeys & wxMOD_CONTROL && m_modkeys & wxMOD_RAW_CONTROL) {
2910  parent_frame->ToggleFullScreen();
2911  return;
2912  }
2913 #endif
2914 
2915  if (event.ControlDown()) key_char -= 64;
2916 
2917  if (key_char >= '0' && key_char <= '9')
2918  SetGroupIndex(key_char - '0');
2919  else
2920 
2921  switch (key_char) {
2922  case 'A':
2923  SetShowENCAnchor(!GetShowENCAnchor());
2924  ReloadVP();
2925 
2926  break;
2927 
2928  case 'C':
2929  parent_frame->ToggleColorScheme();
2930  break;
2931 
2932  case 'D': {
2933  int x, y;
2934  event.GetPosition(&x, &y);
2935  ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
2936  ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
2937  // First find out what kind of chart is being used
2938  if (!pPopupDetailSlider) {
2939  if (VPoint.b_quilt) {
2940  if (m_pQuilt) {
2941  if (m_pQuilt->GetChartAtPix(
2942  VPoint,
2943  wxPoint(
2944  x, y))) // = null if no chart loaded for this point
2945  {
2946  ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2947  ->GetChartType();
2948  ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2949  ->GetChartFamily();
2950  }
2951  }
2952  } else {
2953  if (m_singleChart) {
2954  ChartType = m_singleChart->GetChartType();
2955  ChartFam = m_singleChart->GetChartFamily();
2956  }
2957  }
2958  // If a charttype is found show the popupslider
2959  if ((ChartType != CHART_TYPE_UNKNOWN) ||
2960  (ChartFam != CHART_FAMILY_UNKNOWN)) {
2961  pPopupDetailSlider = new PopUpDSlide(
2962  this, -1, ChartType, ChartFam,
2963  wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
2964  wxDefaultSize, wxSIMPLE_BORDER, _T(""));
2965  if (pPopupDetailSlider) pPopupDetailSlider->Show();
2966  }
2967  } else //( !pPopupDetailSlider ) close popupslider
2968  {
2969  if (pPopupDetailSlider) pPopupDetailSlider->Close();
2970  pPopupDetailSlider = NULL;
2971  }
2972  break;
2973  }
2974 
2975  case 'L':
2976  SetShowENCLights(!GetShowENCLights());
2977  ReloadVP();
2978 
2979  break;
2980 
2981  case 'M':
2982  if (event.ShiftDown())
2983  m_bMeasure_DistCircle = true;
2984  else
2985  m_bMeasure_DistCircle = false;
2986 
2987  StartMeasureRoute();
2988  break;
2989 
2990  case 'N':
2991  if (g_bInlandEcdis && ps52plib) {
2992  SetENCDisplayCategory((_DisCat)STANDARD);
2993  }
2994  break;
2995 
2996  case 'O':
2997  ToggleChartOutlines();
2998  break;
2999 
3000  case 'Q':
3001  ToggleCanvasQuiltMode();
3002  break;
3003 
3004  case 'P':
3005  parent_frame->ToggleTestPause();
3006  break;
3007  case 'R':
3008  g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3009  if (g_bNavAidRadarRingsShown &&
3010  g_iNavAidRadarRingsNumberVisible == 0)
3011  g_iNavAidRadarRingsNumberVisible = 1;
3012  else if (!g_bNavAidRadarRingsShown &&
3013  g_iNavAidRadarRingsNumberVisible == 1)
3014  g_iNavAidRadarRingsNumberVisible = 0;
3015  break;
3016  case 'S':
3017  SetShowENCDepth(!m_encShowDepth);
3018  ReloadVP();
3019  break;
3020 
3021  case 'T':
3022  SetShowENCText(!GetShowENCText());
3023  ReloadVP();
3024  break;
3025 
3026  case 'U':
3027  SetShowENCDataQual(!GetShowENCDataQual());
3028  ReloadVP();
3029  break;
3030 
3031  case 'V':
3032  m_bShowNavobjects = !m_bShowNavobjects;
3033  Refresh(true);
3034  break;
3035 
3036  case 'W': // W Toggle CPA alarm
3037  ToggleCPAWarn();
3038 
3039  break;
3040 
3041  case 1: // Ctrl A
3042  TogglebFollow();
3043 
3044  break;
3045 
3046  case 2: // Ctrl B
3047  if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3048  break;
3049 
3050  case 13: // Ctrl M // Drop Marker at cursor
3051  {
3052  if (event.ControlDown()) gFrame->DropMarker(false);
3053  break;
3054  }
3055 
3056  case 14: // Ctrl N - Activate next waypoint in a route
3057  {
3058  if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3059  int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3060  if ((indexActive + 1) <= r->GetnPoints()) {
3061  g_pRouteMan->ActivateNextPoint(r, true);
3062  InvalidateGL();
3063  Refresh(false);
3064  }
3065  }
3066  break;
3067  }
3068 
3069  case 15: // Ctrl O - Drop Marker at boat's position
3070  {
3071  if (!g_bShowMenuBar) gFrame->DropMarker(true);
3072  break;
3073  }
3074 
3075  case 32: // Special needs use space bar
3076  {
3077  if (g_bSpaceDropMark) gFrame->DropMarker(true);
3078  break;
3079  }
3080 
3081  case -32: // Ctrl Space // Drop MOB
3082  {
3083  if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3084 
3085  break;
3086  }
3087 
3088  case -20: // Ctrl ,
3089  {
3090  parent_frame->DoSettings();
3091  break;
3092  }
3093  case 17: // Ctrl Q
3094  parent_frame->Close();
3095  return;
3096 
3097  case 18: // Ctrl R
3098  StartRoute();
3099  return;
3100 
3101  case 20: // Ctrl T
3102  if (NULL == pGoToPositionDialog) // There is one global instance of
3103  // the Go To Position Dialog
3104  pGoToPositionDialog = new GoToPositionDialog(this);
3105  pGoToPositionDialog->SetCanvas(this);
3106  pGoToPositionDialog->Show();
3107  break;
3108 
3109  case 25: // Ctrl Y
3110  if (undo->AnythingToRedo()) {
3111  undo->RedoNextAction();
3112  InvalidateGL();
3113  Refresh(false);
3114  }
3115  break;
3116 
3117  case 26:
3118  if (event.ShiftDown()) { // Shift-Ctrl-Z
3119  if (undo->AnythingToRedo()) {
3120  undo->RedoNextAction();
3121  InvalidateGL();
3122  Refresh(false);
3123  }
3124  } else { // Ctrl Z
3125  if (undo->AnythingToUndo()) {
3126  undo->UndoLastAction();
3127  InvalidateGL();
3128  Refresh(false);
3129  }
3130  }
3131  break;
3132 
3133  case 27:
3134  // Generic break
3135  if (m_bMeasure_Active) {
3136  CancelMeasureRoute();
3137 
3138  SetCursor(*pCursorArrow);
3139 
3140  //SurfaceToolbar();
3141  gFrame->RefreshAllCanvas();
3142  }
3143 
3144  if (m_routeState) // creating route?
3145  {
3146  FinishRoute();
3147  //SurfaceToolbar();
3148  InvalidateGL();
3149  Refresh(false);
3150  }
3151 
3152  break;
3153 
3154  case 7: // Ctrl G
3155  switch (gamma_state) {
3156  case (0):
3157  r_gamma_mult = 0;
3158  g_gamma_mult = 1;
3159  b_gamma_mult = 0;
3160  gamma_state = 1;
3161  break;
3162  case (1):
3163  r_gamma_mult = 1;
3164  g_gamma_mult = 0;
3165  b_gamma_mult = 0;
3166  gamma_state = 2;
3167  break;
3168  case (2):
3169  r_gamma_mult = 1;
3170  g_gamma_mult = 1;
3171  b_gamma_mult = 1;
3172  gamma_state = 0;
3173  break;
3174  }
3175  SetScreenBrightness(g_nbrightness);
3176 
3177  break;
3178 
3179  case 9: // Ctrl I
3180  if (event.ControlDown()) {
3181  m_bShowCompassWin = !m_bShowCompassWin;
3182  SetShowGPSCompassWindow(m_bShowCompassWin);
3183  Refresh(false);
3184  }
3185  break;
3186 
3187  default:
3188  break;
3189 
3190  } // switch
3191  }
3192 
3193 #ifndef __WXMAC__
3194  // Allow OnKeyChar to catch the key events too.
3195  // On OS X this is unnecessary since we handle all key events here.
3196  if (!b_handled) event.Skip();
3197 #endif
3198 }
3199 
3200 void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3201  if (g_pi_manager)
3202  if (g_pi_manager->SendKeyEventToPlugins(event))
3203  return; // PlugIn did something, and does not want the canvas to do
3204  // anything else
3205 
3206  switch (event.GetKeyCode()) {
3207  case WXK_TAB:
3208  parent_frame->SwitchKBFocus(this);
3209  break;
3210 
3211  case WXK_LEFT:
3212  case WXK_RIGHT:
3213  m_panx = 0;
3214  if (!m_pany) m_panspeed = 0;
3215  break;
3216 
3217  case WXK_UP:
3218  case WXK_DOWN:
3219  m_pany = 0;
3220  if (!m_panx) m_panspeed = 0;
3221  break;
3222 
3223  case WXK_NUMPAD_ADD: // '+' on NUM PAD
3224  case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3225  case WXK_PAGEUP:
3226  case WXK_PAGEDOWN:
3227  if (m_mustmove) DoMovement(m_mustmove);
3228 
3229  m_zoom_factor = 1;
3230  break;
3231 
3232  case WXK_ALT:
3233  m_modkeys &= ~wxMOD_ALT;
3234 #ifdef OCPN_ALT_MENUBAR
3235 #ifndef __WXOSX__
3236  // If the permanent menu bar is disabled, and we are not in the middle of
3237  // another key combo, then show the menu bar temporarily when Alt is
3238  // released (or hide it if already visible).
3239  if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3240  g_bTempShowMenuBar = !g_bTempShowMenuBar;
3241  parent_frame->ApplyGlobalSettings(false);
3242  }
3243  m_bMayToggleMenuBar = true;
3244 #endif
3245 #endif
3246  break;
3247 
3248  case WXK_CONTROL:
3249  m_modkeys &= ~wxMOD_CONTROL;
3250  break;
3251  }
3252 
3253  if (event.GetKeyCode() < 128) // ascii
3254  {
3255  int key_char = event.GetKeyCode();
3256 
3257  // Handle both QWERTY and AZERTY keyboard separately for a few control
3258  // codes
3259  if (!g_b_assume_azerty) {
3260  switch (key_char) {
3261  case '+':
3262  case '=':
3263  case '-':
3264  case '_':
3265  case 54:
3266  case 56: // '_' alpha/num pad
3267  DoMovement(m_mustmove);
3268 
3269  // m_zoom_factor = 1;
3270  break;
3271  case '[':
3272  case ']':
3273  DoMovement(m_mustmove);
3274  m_rotation_speed = 0;
3275  break;
3276  }
3277  } else {
3278  switch (key_char) {
3279  case 43:
3280  case 54: // '-' alpha/num pad
3281  case 56: // '_' alpha/num pad
3282  DoMovement(m_mustmove);
3283 
3284  m_zoom_factor = 1;
3285  break;
3286  }
3287  }
3288  }
3289  event.Skip();
3290 }
3291 
3292 void ChartCanvas::ToggleChartOutlines(void) {
3293  m_bShowOutlines = !m_bShowOutlines;
3294 
3295  Refresh(false);
3296 
3297 #ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3298  // needs a full refresh
3299  if (g_bopengl) InvalidateGL();
3300 #endif
3301 }
3302 
3303 void ChartCanvas::ToggleLookahead() {
3304  m_bLookAhead = !m_bLookAhead;
3305  m_OSoffsetx = 0; // center ownship
3306  m_OSoffsety = 0;
3307 }
3308 
3309 void ChartCanvas::SetUpMode(int mode) {
3310  m_upMode = mode;
3311 
3312  if (mode != NORTH_UP_MODE) {
3313  // Stuff the COGAvg table in case COGUp is selected
3314  double stuff = 0;
3315  if (!std::isnan(gCog)) stuff = gCog;
3316 
3317  if (g_COGAvgSec > 0) {
3318  for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3319  }
3320  g_COGAvg = stuff;
3321  gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3322  } else {
3323  if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3324  SetVPRotation(GetVPSkew());
3325  else
3326  SetVPRotation(0); /* reset to north up */
3327  }
3328 
3329  if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3330  GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3331 
3332  UpdateGPSCompassStatusBox(true);
3333  gFrame->DoChartUpdate();
3334 }
3335 
3336 bool ChartCanvas::DoCanvasCOGSet(void) {
3337  if (GetUpMode() == NORTH_UP_MODE) return false;
3338 
3339  if (std::isnan(g_COGAvg)) return true;
3340 
3341  double old_VPRotate = m_VPRotate;
3342 
3343  if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3344  m_VPRotate = -gHdt * PI / 180.;
3345  } else if (GetUpMode() == COURSE_UP_MODE)
3346  m_VPRotate = -g_COGAvg * PI / 180.;
3347 
3348  SetVPRotation(m_VPRotate);
3349  bool bnew_chart = DoCanvasUpdate();
3350 
3351  if ((bnew_chart) || (old_VPRotate != m_VPRotate)) ReloadVP();
3352 
3353  return true;
3354 }
3355 
3356 void ChartCanvas::StopMovement() {
3357  m_panx = m_pany = 0;
3358  m_panspeed = 0;
3359  m_zoom_factor = 1;
3360  m_rotation_speed = 0;
3361  m_mustmove = 0;
3362 #if 0
3363 #if !defined(__WXGTK__) && !defined(__WXQT__)
3364  SetFocus();
3365  gFrame->Raise();
3366 #endif
3367 #endif
3368 }
3369 
3370 /* instead of integrating in timer callbacks
3371  (which do not always get called fast enough)
3372  we can perform the integration of movement
3373  at each render frame based on the time change */
3374 bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3375  // Start/restart the stop movement timer
3376  if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3377 
3378  if (!pMovementTimer->IsRunning()) {
3379  // printf("timer not running, starting\n");
3380  pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3381  }
3382 
3383  if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3384  // already moving, gets called again because of key-repeat event
3385  return false;
3386  }
3387 
3388  m_last_movement_time = wxDateTime::UNow();
3389 
3390  /* jumpstart because paint gets called right away, if we want first frame to
3391  * move */
3392  // m_last_movement_time -= wxTimeSpan::Milliseconds(100);
3393 
3394  // Refresh( false );
3395 
3396  return true;
3397 }
3398 
3399 void ChartCanvas::DoTimedMovement() {
3400  if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3401  !m_rotation_speed)
3402  return; /* not moving */
3403 
3404  wxDateTime now = wxDateTime::UNow();
3405  long dt = 0;
3406  if (m_last_movement_time.IsValid())
3407  dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3408 
3409  m_last_movement_time = now;
3410 
3411  if (dt > 500) /* if we are running very slow, don't integrate too fast */
3412  dt = 500;
3413 
3414  DoMovement(dt);
3415 }
3416 
3417 void ChartCanvas::DoMovement(long dt) {
3418  /* if we get here quickly assume 1ms so that some movement occurs */
3419  if (dt == 0) dt = 1;
3420 
3421  m_mustmove -= dt;
3422  if (m_mustmove < 0) m_mustmove = 0;
3423 
3424  if (m_pan_drag.x || m_pan_drag.y) {
3425  PanCanvas(m_pan_drag.x, m_pan_drag.y);
3426  m_pan_drag.x = m_pan_drag.y = 0;
3427  }
3428 
3429  if (m_panx || m_pany) {
3430  const double slowpan = .1, maxpan = 2;
3431  if (m_modkeys == wxMOD_ALT)
3432  m_panspeed = slowpan;
3433  else {
3434  m_panspeed += (double)dt / 500; /* apply acceleration */
3435  m_panspeed = wxMin(maxpan, m_panspeed);
3436  }
3437  PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3438  }
3439 
3440  if (m_zoom_factor != 1) {
3441  double alpha = 400, beta = 1.5;
3442  double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3443 
3444  if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3445 
3446  if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3447 
3448  // Try to hit the zoom target exactly.
3449  // if(m_wheelzoom_stop_oneshot > 0)
3450  {
3451  if (zoom_factor > 1) {
3452  if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3453  zoom_factor = VPoint.chart_scale / m_zoom_target;
3454  }
3455 
3456  else if (zoom_factor < 1) {
3457  if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3458  zoom_factor = VPoint.chart_scale / m_zoom_target;
3459  }
3460  }
3461 
3462  if (fabs(zoom_factor - 1) > 1e-4)
3463  DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3464 
3465  if (m_wheelzoom_stop_oneshot > 0) {
3466  if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3467  m_wheelzoom_stop_oneshot = 0;
3468  StopMovement();
3469  }
3470 
3471  // Don't overshoot the zoom target.
3472  if (zoom_factor > 1) {
3473  if (VPoint.chart_scale <= m_zoom_target) {
3474  m_wheelzoom_stop_oneshot = 0;
3475  StopMovement();
3476  }
3477  } else if (zoom_factor < 1) {
3478  if (VPoint.chart_scale >= m_zoom_target) {
3479  m_wheelzoom_stop_oneshot = 0;
3480  StopMovement();
3481  }
3482  }
3483  }
3484  }
3485 
3486  if (m_rotation_speed) { /* in degrees per second */
3487  double speed = m_rotation_speed;
3488  if (m_modkeys == wxMOD_ALT) speed /= 10;
3489  DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3490  }
3491 }
3492 
3493 void ChartCanvas::SetColorScheme(ColorScheme cs) {
3494  SetAlertString(_T(""));
3495 
3496  // Setup ownship image pointers
3497  switch (cs) {
3498  case GLOBAL_COLOR_SCHEME_DAY:
3499  m_pos_image_red = &m_os_image_red_day;
3500  m_pos_image_grey = &m_os_image_grey_day;
3501  m_pos_image_yellow = &m_os_image_yellow_day;
3502  m_pos_image_user = m_pos_image_user_day;
3503  m_pos_image_user_grey = m_pos_image_user_grey_day;
3504  m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3505  m_cTideBitmap = m_bmTideDay;
3506  m_cCurrentBitmap = m_bmCurrentDay;
3507 
3508  break;
3509  case GLOBAL_COLOR_SCHEME_DUSK:
3510  m_pos_image_red = &m_os_image_red_dusk;
3511  m_pos_image_grey = &m_os_image_grey_dusk;
3512  m_pos_image_yellow = &m_os_image_yellow_dusk;
3513  m_pos_image_user = m_pos_image_user_dusk;
3514  m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3515  m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3516  m_cTideBitmap = m_bmTideDusk;
3517  m_cCurrentBitmap = m_bmCurrentDusk;
3518  break;
3519  case GLOBAL_COLOR_SCHEME_NIGHT:
3520  m_pos_image_red = &m_os_image_red_night;
3521  m_pos_image_grey = &m_os_image_grey_night;
3522  m_pos_image_yellow = &m_os_image_yellow_night;
3523  m_pos_image_user = m_pos_image_user_night;
3524  m_pos_image_user_grey = m_pos_image_user_grey_night;
3525  m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3526  m_cTideBitmap = m_bmTideNight;
3527  m_cCurrentBitmap = m_bmCurrentNight;
3528  break;
3529  default:
3530  m_pos_image_red = &m_os_image_red_day;
3531  m_pos_image_grey = &m_os_image_grey_day;
3532  m_pos_image_yellow = &m_os_image_yellow_day;
3533  m_pos_image_user = m_pos_image_user_day;
3534  m_pos_image_user_grey = m_pos_image_user_grey_day;
3535  m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3536  m_cTideBitmap = m_bmTideDay;
3537  m_cCurrentBitmap = m_bmCurrentDay;
3538  break;
3539  }
3540 
3541  CreateDepthUnitEmbossMaps(cs);
3542  CreateOZEmbossMapData(cs);
3543 
3544  // Set up fog effect base color
3545  m_fog_color = wxColor(
3546  170, 195, 240); // this is gshhs (backgound world chart) ocean color
3547  float dim = 1.0;
3548  switch (cs) {
3549  case GLOBAL_COLOR_SCHEME_DUSK:
3550  dim = 0.5;
3551  break;
3552  case GLOBAL_COLOR_SCHEME_NIGHT:
3553  dim = 0.25;
3554  break;
3555  default:
3556  break;
3557  }
3558  m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3559  m_fog_color.Blue() * dim);
3560 
3561  // Really dark
3562 #if 0
3563  if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3564  SetBackgroundColour( wxColour(0,0,0) );
3565 
3566  SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3567  }
3568  else{
3569  SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3570 #ifndef __WXMAC__
3571  SetBackgroundColour( wxNullColour );
3572 #endif
3573  }
3574 #endif
3575 
3576  //UpdateToolbarColorScheme(cs);
3577 
3578  m_Piano->SetColorScheme(cs);
3579 
3580  m_Compass->SetColorScheme(cs);
3581 
3582  if (m_muiBar) m_muiBar->SetColorScheme(cs);
3583 
3584  if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3585 #ifdef ocpnUSE_GL
3586  if (g_bopengl && m_glcc) {
3587  m_glcc->SetColorScheme(cs);
3588  g_glTextureManager->ClearAllRasterTextures();
3589  // m_glcc->FlushFBO();
3590  }
3591 #endif
3592  SetbTCUpdate(true); // force re-render of tide/current locators
3593  m_brepaint_piano = true;
3594 
3595  ReloadVP();
3596 
3597  m_cs = cs;
3598 }
3599 
3600 wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3601  wxImage img = Bitmap.ConvertToImage();
3602  int sx = img.GetWidth();
3603  int sy = img.GetHeight();
3604 
3605  wxImage new_img(img);
3606 
3607  for (int i = 0; i < sx; i++) {
3608  for (int j = 0; j < sy; j++) {
3609  if (!img.IsTransparent(i, j)) {
3610  new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3611  (unsigned char)(img.GetGreen(i, j) * factor),
3612  (unsigned char)(img.GetBlue(i, j) * factor));
3613  }
3614  }
3615  }
3616 
3617  wxBitmap ret = wxBitmap(new_img);
3618 
3619  return ret;
3620 }
3621 
3622 void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3623  int max) {
3624  wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3625  40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3626 
3627  if (!m_pBrightPopup) {
3628  // Calculate size
3629  int x, y;
3630  GetTextExtent(_T("MAX"), &x, &y, NULL, NULL, pfont);
3631 
3632  m_pBrightPopup = new TimedPopupWin(this, 3);
3633 
3634  m_pBrightPopup->SetSize(x, y);
3635  m_pBrightPopup->Move(120, 120);
3636  }
3637 
3638  int bmpsx = m_pBrightPopup->GetSize().x;
3639  int bmpsy = m_pBrightPopup->GetSize().y;
3640 
3641  wxBitmap bmp(bmpsx, bmpsx);
3642  wxMemoryDC mdc(bmp);
3643 
3644  mdc.SetTextForeground(GetGlobalColor(_T("GREEN4")));
3645  mdc.SetBackground(wxBrush(GetGlobalColor(_T("UINFD"))));
3646  mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3647  mdc.SetBrush(wxBrush(GetGlobalColor(_T("UINFD"))));
3648  mdc.Clear();
3649 
3650  mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3651 
3652  mdc.SetFont(*pfont);
3653  wxString val;
3654 
3655  if (brightness == max)
3656  val = _T("MAX");
3657  else if (brightness == min)
3658  val = _T("MIN");
3659  else
3660  val.Printf(_T("%3d"), brightness);
3661 
3662  mdc.DrawText(val, 0, 0);
3663 
3664  mdc.SelectObject(wxNullBitmap);
3665 
3666  m_pBrightPopup->SetBitmap(bmp);
3667  m_pBrightPopup->Show();
3668  m_pBrightPopup->Refresh();
3669 }
3670 
3671 void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3672  m_b_rot_hidef = true;
3673  ReloadVP();
3674 }
3675 
3676 void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3677  if (!g_bRollover) return;
3678 
3679  bool b_need_refresh = false;
3680 
3681  wxSize win_size = GetSize() * m_displayScale;
3682  if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3683 
3684  // Handle the AIS Rollover Window first
3685  bool showAISRollover = false;
3686  if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3687  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3688  SelectItem *pFind = pSelectAIS->FindSelection(
3689  ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3690  if (pFind) {
3691  int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3692  auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3693 
3694  if (ptarget) {
3695  showAISRollover = true;
3696 
3697  if (NULL == m_pAISRolloverWin) {
3698  m_pAISRolloverWin = new RolloverWin(this);
3699  m_pAISRolloverWin->IsActive(false);
3700  b_need_refresh = true;
3701  } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3702  m_AISRollover_MMSI != FoundAIS_MMSI) {
3703  // Sometimes the mouse moves fast enough to get over a new AIS
3704  // target before the one-shot has fired to remove the old target.
3705  // Result: wrong target data is shown.
3706  // Detect this case,close the existing rollover ASAP, and restart
3707  // the timer.
3708  m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3709  m_pAISRolloverWin->IsActive(false);
3710  m_AISRollover_MMSI = 0;
3711  Refresh();
3712  return;
3713  }
3714 
3715  m_AISRollover_MMSI = FoundAIS_MMSI;
3716 
3717  if (!m_pAISRolloverWin->IsActive()) {
3718  wxString s = ptarget->GetRolloverString();
3719  m_pAISRolloverWin->SetString(s);
3720 
3721  m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3722  AIS_ROLLOVER, win_size);
3723  m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3724  m_pAISRolloverWin->IsActive(true);
3725  b_need_refresh = true;
3726  }
3727  }
3728  } else {
3729  m_AISRollover_MMSI = 0;
3730  showAISRollover = false;
3731  }
3732  }
3733 
3734  // Maybe turn the rollover off
3735  if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3736  m_pAISRolloverWin->IsActive(false);
3737  m_AISRollover_MMSI = 0;
3738  b_need_refresh = true;
3739  }
3740 
3741  // Now the Route info rollover
3742  // Show the route segment info
3743  bool showRouteRollover = false;
3744 
3745  if (NULL == m_pRolloverRouteSeg) {
3746  // Get a list of all selectable sgements, and search for the first
3747  // visible segment as the rollover target.
3748 
3749  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3750  SelectableItemList SelList = pSelect->FindSelectionList(
3751  ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3752  wxSelectableItemListNode *node = SelList.GetFirst();
3753  while (node) {
3754  SelectItem *pFindSel = node->GetData();
3755 
3756  Route *pr = (Route *)pFindSel->m_pData3; // candidate
3757 
3758  if (pr && pr->IsVisible()) {
3759  m_pRolloverRouteSeg = pFindSel;
3760  showRouteRollover = true;
3761 
3762  if (NULL == m_pRouteRolloverWin) {
3763  m_pRouteRolloverWin = new RolloverWin(this, 10);
3764  m_pRouteRolloverWin->IsActive(false);
3765  }
3766 
3767  if (!m_pRouteRolloverWin->IsActive()) {
3768  wxString s;
3769  RoutePoint *segShow_point_a =
3770  (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
3771  RoutePoint *segShow_point_b =
3772  (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
3773 
3774  double brg, dist;
3775  DistanceBearingMercator(
3776  segShow_point_b->m_lat, segShow_point_b->m_lon,
3777  segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
3778 
3779  if (!pr->m_bIsInLayer)
3780  s.Append(_("Route") + _T(": "));
3781  else
3782  s.Append(_("Layer Route: "));
3783 
3784  if (pr->m_RouteNameString.IsEmpty())
3785  s.Append(_("(unnamed)"));
3786  else
3787  s.Append(pr->m_RouteNameString);
3788 
3789  s << _T("\n") << _("Total Length: ")
3790  << FormatDistanceAdaptive(pr->m_route_length) << _T("\n")
3791  << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
3792  << segShow_point_b->GetName() << _T("\n");
3793 
3794  if (g_bShowTrue)
3795  s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)floor(brg+0.5),
3796  0x00B0);
3797  if (g_bShowMag) {
3798  double latAverage =
3799  (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
3800  double lonAverage =
3801  (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
3802  double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
3803 
3804  s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8), (int)floor(varBrg+0.5),
3805  0x00B0);
3806  }
3807 
3808  s << FormatDistanceAdaptive(dist);
3809 
3810  // Compute and display cumulative distance from route start point to
3811  // current leg end point and RNG,TTG,ETA from ship to current leg end
3812  // point for active route
3813  double shiptoEndLeg = 0.;
3814  bool validActive = false;
3815  if (pr->IsActive() &&
3816  pr->pRoutePointList->GetFirst()->GetData()->m_bIsActive)
3817  validActive = true;
3818 
3819  if (segShow_point_a != pr->pRoutePointList->GetFirst()->GetData()) {
3820  wxRoutePointListNode *node =
3821  (pr->pRoutePointList)->GetFirst()->GetNext();
3822  RoutePoint *prp;
3823  float dist_to_endleg = 0;
3824  wxString t;
3825 
3826  while (node) {
3827  prp = node->GetData();
3828  if (validActive)
3829  shiptoEndLeg += prp->m_seg_len;
3830  else if (prp->m_bIsActive)
3831  validActive = true;
3832  dist_to_endleg += prp->m_seg_len;
3833  if (prp->IsSame(segShow_point_a)) break;
3834  node = node->GetNext();
3835  }
3836  s << _T(" (+") << FormatDistanceAdaptive(dist_to_endleg) << _T(")");
3837  }
3838  // write from ship to end selected leg point data if the route is
3839  // active
3840  if (validActive) {
3841  s << _T("\n") << _("From Ship To") << _T(" ")
3842  << segShow_point_b->GetName() << _T("\n");
3843  shiptoEndLeg +=
3844  g_pRouteMan
3845  ->GetCurrentRngToActivePoint(); // add distance from ship
3846  // to active point
3847  shiptoEndLeg +=
3848  segShow_point_b
3849  ->m_seg_len; // add the lenght of the selected leg
3850  s << FormatDistanceAdaptive(shiptoEndLeg);
3851  // ensure sog/cog are valid and vmg is positive to keep data
3852  // coherent
3853  double vmg = 0.;
3854  if (!std::isnan(gCog) && !std::isnan(gSog))
3855  vmg = gSog *
3856  cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
3857  PI / 180.);
3858  if (vmg > 0.) {
3859  float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
3860  wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
3861  s << _T(" - ")
3862  << wxString(ttg_sec > SECONDS_PER_DAY
3863  ? ttg_span.Format(_("%Dd %H:%M"))
3864  : ttg_span.Format(_("%H:%M")));
3865  wxDateTime dtnow, eta;
3866  eta = dtnow.SetToCurrent().Add(ttg_span);
3867  s << _T(" - ") << eta.Format(_T("%b")).Mid(0, 4)
3868  << eta.Format(_T(" %d %H:%M"));
3869  } else
3870  s << _T(" ---- ----");
3871  }
3872  m_pRouteRolloverWin->SetString(s);
3873 
3874  m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3875  LEG_ROLLOVER, win_size);
3876  m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
3877  m_pRouteRolloverWin->IsActive(true);
3878  b_need_refresh = true;
3879  showRouteRollover = true;
3880  break;
3881  }
3882  } else
3883  node = node->GetNext();
3884  }
3885  } else {
3886  // Is the cursor still in select radius, and not timed out?
3887  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3888  if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
3889  m_pRolloverRouteSeg))
3890  showRouteRollover = false;
3891  else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
3892  showRouteRollover = false;
3893  else
3894  showRouteRollover = true;
3895  }
3896 
3897  // If currently creating a route, do not show this rollover window
3898  if (m_routeState) showRouteRollover = false;
3899 
3900  // Similar for AIS target rollover window
3901  if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
3902  showRouteRollover = false;
3903 
3904  if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
3905  !showRouteRollover) {
3906  m_pRouteRolloverWin->IsActive(false);
3907  m_pRolloverRouteSeg = NULL;
3908  m_pRouteRolloverWin->Destroy();
3909  m_pRouteRolloverWin = NULL;
3910  b_need_refresh = true;
3911  } else if (m_pRouteRolloverWin && showRouteRollover) {
3912  m_pRouteRolloverWin->IsActive(true);
3913  b_need_refresh = true;
3914  }
3915 
3916  // Now the Track info rollover
3917  // Show the track segment info
3918  bool showTrackRollover = false;
3919 
3920  if (NULL == m_pRolloverTrackSeg) {
3921  // Get a list of all selectable sgements, and search for the first
3922  // visible segment as the rollover target.
3923 
3924  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3925  SelectableItemList SelList = pSelect->FindSelectionList(
3926  ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
3927  wxSelectableItemListNode *node = SelList.GetFirst();
3928  while (node) {
3929  SelectItem *pFindSel = node->GetData();
3930 
3931  Track *pt = (Track *)pFindSel->m_pData3; // candidate
3932 
3933  if (pt && pt->IsVisible()) {
3934  m_pRolloverTrackSeg = pFindSel;
3935  showTrackRollover = true;
3936 
3937  if (NULL == m_pTrackRolloverWin) {
3938  m_pTrackRolloverWin = new RolloverWin(this, 10);
3939  m_pTrackRolloverWin->IsActive(false);
3940  }
3941 
3942  if (!m_pTrackRolloverWin->IsActive()) {
3943  wxString s;
3944  TrackPoint *segShow_point_a =
3945  (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
3946  TrackPoint *segShow_point_b =
3947  (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
3948 
3949  double brg, dist;
3950  DistanceBearingMercator(
3951  segShow_point_b->m_lat, segShow_point_b->m_lon,
3952  segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
3953 
3954  if (!pt->m_bIsInLayer)
3955  s.Append(_("Track") + _T(": "));
3956  else
3957  s.Append(_("Layer Track: "));
3958 
3959  if (pt->GetName().IsEmpty())
3960  s.Append(_("(unnamed)"));
3961  else
3962  s.Append(pt->GetName());
3963  double tlenght = pt->Length();
3964  s << _T("\n") << _("Total Track: ")
3965  << FormatDistanceAdaptive(tlenght);
3966  if (pt->GetLastPoint()->GetTimeString() &&
3967  pt->GetPoint(0)->GetTimeString()) {
3968  wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
3969  wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
3970  if (lastPointTime.IsValid() && zeroPointTime.IsValid()){
3971  wxTimeSpan ttime = lastPointTime - zeroPointTime;
3972  double htime = ttime.GetSeconds().ToDouble() / 3600.;
3973  s << wxString::Format(_T(" %.1f "), (float)(tlenght / htime))
3974  << getUsrSpeedUnit();
3975  s << wxString(htime > 24. ? ttime.Format(_T(" %Dd %H:%M"))
3976  : ttime.Format(_T(" %H:%M")));
3977  }
3978  }
3979 
3980  if (g_bShowTrackPointTime && strlen(segShow_point_b->GetTimeString()))
3981  s << _T("\n") << _("Segment Created: ")
3982  << segShow_point_b->GetTimeString();
3983 
3984  s << _T("\n");
3985  if (g_bShowTrue)
3986  s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
3987  0x00B0);
3988 
3989  if (g_bShowMag) {
3990  double latAverage =
3991  (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
3992  double lonAverage =
3993  (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
3994  double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
3995 
3996  s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
3997  0x00B0);
3998  }
3999 
4000  s << FormatDistanceAdaptive(dist);
4001 
4002  if (segShow_point_a->GetTimeString() &&
4003  segShow_point_b->GetTimeString()) {
4004  wxDateTime apoint = segShow_point_a->GetCreateTime();
4005  wxDateTime bpoint = segShow_point_b->GetCreateTime();
4006  if (apoint.IsValid() && bpoint.IsValid()){
4007  double segmentSpeed =
4008  toUsrSpeed(dist / ((bpoint - apoint).GetSeconds()
4009  .ToDouble() /
4010  3600.));
4011  s << wxString::Format(_T(" %.1f "), (float)segmentSpeed)
4012  << getUsrSpeedUnit();
4013  }
4014  }
4015 
4016  m_pTrackRolloverWin->SetString(s);
4017 
4018  m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4019  LEG_ROLLOVER, win_size);
4020  m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4021  m_pTrackRolloverWin->IsActive(true);
4022  b_need_refresh = true;
4023  showTrackRollover = true;
4024  break;
4025  }
4026  } else
4027  node = node->GetNext();
4028  }
4029  } else {
4030  // Is the cursor still in select radius, and not timed out?
4031  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4032  if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4033  m_pRolloverTrackSeg))
4034  showTrackRollover = false;
4035  else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4036  showTrackRollover = false;
4037  else
4038  showTrackRollover = true;
4039  }
4040 
4041  // Similar for AIS target rollover window
4042  if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4043  showTrackRollover = false;
4044 
4045  // Similar for route rollover window
4046  if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4047  showTrackRollover = false;
4048 
4049  // TODO We onlt show tracks on primary canvas....
4050  // if(!IsPrimaryCanvas())
4051  // showTrackRollover = false;
4052 
4053  if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4054  !showTrackRollover) {
4055  m_pTrackRolloverWin->IsActive(false);
4056  m_pRolloverTrackSeg = NULL;
4057  m_pTrackRolloverWin->Destroy();
4058  m_pTrackRolloverWin = NULL;
4059  b_need_refresh = true;
4060  } else if (m_pTrackRolloverWin && showTrackRollover) {
4061  m_pTrackRolloverWin->IsActive(true);
4062  b_need_refresh = true;
4063  }
4064 
4065  if (b_need_refresh) Refresh();
4066 }
4067 
4068 void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4069  if ((GetShowENCLights() || m_bsectors_shown ) &&
4070  s57_CheckExtendedLightSectors( this, mouse_x, mouse_y, VPoint,
4071  extendedSectorLegs)) {
4072  if (!m_bsectors_shown) {
4073  ReloadVP(false);
4074  m_bsectors_shown = true;
4075  }
4076  } else {
4077  if (m_bsectors_shown) {
4078  ReloadVP(false);
4079  m_bsectors_shown = false;
4080  }
4081  }
4082 
4083 // This is here because GTK status window update is expensive..
4084 // cairo using pango rebuilds the font every time so is very
4085 // inefficient
4086 // Anyway, only update the status bar when this timer expires
4087 #if defined(__WXGTK__) || defined(__WXQT__)
4088  {
4089  // Check the absolute range of the cursor position
4090  // There could be a window wherein the chart geoereferencing is not
4091  // valid....
4092  double cursor_lat, cursor_lon;
4093  GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4094 
4095  if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4096  while (cursor_lon < -180.) cursor_lon += 360.;
4097 
4098  while (cursor_lon > 180.) cursor_lon -= 360.;
4099 
4100  SetCursorStatus(cursor_lat, cursor_lon);
4101  }
4102  }
4103 #endif
4104 }
4105 
4106 void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4107  if (!parent_frame->m_pStatusBar) return;
4108 
4109  wxString s1;
4110  s1 += _T(" ");
4111  s1 += toSDMM(1, cursor_lat);
4112  s1 += _T(" ");
4113  s1 += toSDMM(2, cursor_lon);
4114 
4115  if (STAT_FIELD_CURSOR_LL >= 0)
4116  parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4117 
4118  if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4119 
4120  double brg, dist;
4121  wxString sm;
4122  wxString st;
4123  DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4124  if (g_bShowMag)
4125  sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4126  if (g_bShowTrue)
4127  st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4128 
4129  wxString s = st + sm;
4130  s << FormatDistanceAdaptive(dist);
4131 
4132  // CUSTOMIZATION - LIVE ETA OPTION
4133  // -------------------------------------------------------
4134  // Calculate an "live" ETA based on route starting from the current
4135  // position of the boat and goes to the cursor of the mouse.
4136  // In any case, an standard ETA will be calculated with a default speed
4137  // of the boat to give an estimation of the route (in particular if GPS
4138  // is off).
4139 
4140  // Display only if option "live ETA" is selected in Settings > Display >
4141  // General.
4142  if (g_bShowLiveETA) {
4143  float realTimeETA;
4144  float boatSpeed;
4145  float boatSpeedDefault = g_defaultBoatSpeed;
4146 
4147  // Calculate Estimate Time to Arrival (ETA) in minutes
4148  // Check before is value not closed to zero (it will make an very big
4149  // number...)
4150  if (!std::isnan(gSog)) {
4151  boatSpeed = gSog;
4152  if (boatSpeed < 0.5) {
4153  realTimeETA = 0;
4154  } else {
4155  realTimeETA = dist / boatSpeed * 60;
4156  }
4157  } else {
4158  realTimeETA = 0;
4159  }
4160 
4161  // Add space after distance display
4162  s << " ";
4163  // Display ETA
4164  s << minutesToHoursDays(realTimeETA);
4165 
4166  // In any case, display also an ETA with default speed at 6knts
4167 
4168  s << " [@";
4169  s << wxString::Format(_T("%d"), (int)toUsrSpeed(boatSpeedDefault, -1));
4170  s << wxString::Format(_T("%s"), getUsrSpeedUnit(-1));
4171  s << " ";
4172  s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4173  s << "]";
4174  }
4175  // END OF - LIVE ETA OPTION
4176 
4177  parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4178 }
4179 
4180 // CUSTOMIZATION - FORMAT MINUTES
4181 // -------------------------------------------------------
4182 // New function to format minutes into a more readable format:
4183 // * Hours + minutes, or
4184 // * Days + hours.
4185 wxString minutesToHoursDays(float timeInMinutes) {
4186  wxString s;
4187 
4188  if (timeInMinutes == 0) {
4189  s << "--min";
4190  }
4191 
4192  // Less than 60min, keep time in minutes
4193  else if (timeInMinutes < 60 && timeInMinutes != 0) {
4194  s << wxString::Format(_T("%d"), (int)timeInMinutes);
4195  s << "min";
4196  }
4197 
4198  // Between 1h and less than 24h, display time in hours, minutes
4199  else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4200  int hours;
4201  int min;
4202  hours = (int)timeInMinutes / 60;
4203  min = (int)timeInMinutes % 60;
4204 
4205  if (min == 0) {
4206  s << wxString::Format(_T("%d"), hours);
4207  s << "h";
4208  } else {
4209  s << wxString::Format(_T("%d"), hours);
4210  s << "h";
4211  s << wxString::Format(_T("%d"), min);
4212  s << "min";
4213  }
4214 
4215  }
4216 
4217  // More than 24h, display time in days, hours
4218  else if (timeInMinutes > 24 * 60) {
4219  int days;
4220  int hours;
4221  days = (int)(timeInMinutes / 60) / 24;
4222  hours = (int)(timeInMinutes / 60) % 24;
4223 
4224  if (hours == 0) {
4225  s << wxString::Format(_T("%d"), days);
4226  s << "d";
4227  } else {
4228  s << wxString::Format(_T("%d"), days);
4229  s << "d";
4230  s << wxString::Format(_T("%d"), hours);
4231  s << "h";
4232  }
4233  }
4234 
4235  return s;
4236 }
4237 
4238 // END OF CUSTOMIZATION - FORMAT MINUTES
4239 // Thanks open source code ;-)
4240 // -------------------------------------------------------
4241 
4242 void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4243  double clat, clon;
4244  GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4245  *lat = clat;
4246  *lon = clon;
4247 }
4248 
4249 void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4250  wxPoint2DDouble *r) {
4251  return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4252 }
4253 
4254 void ChartCanvas::GetDoubleCanvasPointPixVP(ViewPort &vp, double rlat,
4255  double rlon, wxPoint2DDouble *r) {
4256  // If the Current Chart is a raster chart, and the
4257  // requested lat/long is within the boundaries of the chart,
4258  // and the VP is not rotated,
4259  // then use the embedded BSB chart georeferencing algorithm
4260  // for greater accuracy
4261  // Additionally, use chart embedded georef if the projection is TMERC
4262  // i.e. NOT MERCATOR and NOT POLYCONIC
4263 
4264  // If for some reason the chart rejects the request by returning an error,
4265  // then fall back to Viewport Projection estimate from canvas parameters
4266  if (!g_bopengl && m_singleChart &&
4267  (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4268  (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4269  ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4270  (m_singleChart->GetChartProjectionType() !=
4271  PROJECTION_TRANSVERSE_MERCATOR) &&
4272  (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4273  (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4274  (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4275  ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4276  // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4277  // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4278  // Cur_BSB_Ch->GetCOVRTablenPoints
4279  // ( 0 ), rlon,
4280  // rlat );
4281  // bInside = true;
4282  // if ( bInside )
4283  if (Cur_BSB_Ch) {
4284  // This is a Raster chart....
4285  // If the VP is changing, the raster chart parameters may not yet be
4286  // setup So do that before accessing the chart's embedded
4287  // georeferencing
4288  Cur_BSB_Ch->SetVPRasterParms(vp);
4289  double rpixxd, rpixyd;
4290  if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4291  r->m_x = rpixxd;
4292  r->m_y = rpixyd;
4293  return;
4294  }
4295  }
4296  }
4297 
4298  // if needed, use the VPoint scaling estimator,
4299  *r = vp.GetDoublePixFromLL(rlat, rlon);
4300 }
4301 
4302 // This routine might be deleted and all of the rendering improved
4303 // to have floating point accuracy
4304 bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4305  return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4306 }
4307 
4308 bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4309  wxPoint *r) {
4310  wxPoint2DDouble p;
4311  GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4312 
4313  // some projections give nan values when invisible values (other side of
4314  // world) are requested we should stop using integer coordinates or return
4315  // false here (and test it everywhere)
4316  if (std::isnan(p.m_x)) {
4317  *r = wxPoint(INVALID_COORD, INVALID_COORD);
4318  return false;
4319  }
4320 
4321  if( (abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6) )
4322  *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4323  else
4324  *r = wxPoint(INVALID_COORD, INVALID_COORD);
4325 
4326  return true;
4327 }
4328 
4329 void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4330  double &lon) {
4331  // If the Current Chart is a raster chart, and the
4332  // requested x,y is within the boundaries of the chart,
4333  // and the VP is not rotated,
4334  // then use the embedded BSB chart georeferencing algorithm
4335  // for greater accuracy
4336  // Additionally, use chart embedded georef if the projection is TMERC
4337  // i.e. NOT MERCATOR and NOT POLYCONIC
4338 
4339  // If for some reason the chart rejects the request by returning an error,
4340  // then fall back to Viewport Projection estimate from canvas parameters
4341  bool bUseVP = true;
4342 
4343  if (!g_bopengl && m_singleChart &&
4344  (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4345  (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4346  ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4347  (m_singleChart->GetChartProjectionType() !=
4348  PROJECTION_TRANSVERSE_MERCATOR) &&
4349  (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4350  (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4351  (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4352  ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4353 
4354  // TODO maybe need iterative process to validate bInside
4355  // first pass is mercator, then check chart boundaries
4356 
4357  if (Cur_BSB_Ch) {
4358  // This is a Raster chart....
4359  // If the VP is changing, the raster chart parameters may not yet be
4360  // setup So do that before accessing the chart's embedded
4361  // georeferencing
4362  Cur_BSB_Ch->SetVPRasterParms(GetVP());
4363 
4364  double slat, slon;
4365  if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4366  lat = slat;
4367 
4368  if (slon < -180.)
4369  slon += 360.;
4370  else if (slon > 180.)
4371  slon -= 360.;
4372 
4373  lon = slon;
4374  bUseVP = false;
4375  }
4376  }
4377  }
4378 
4379  // if needed, use the VPoint scaling estimator
4380  if (bUseVP) {
4381  GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4382  }
4383 }
4384 
4385 void ChartCanvas::ZoomCanvasSimple(double factor) {
4386  DoZoomCanvas(factor, false);
4387  extendedSectorLegs.clear();
4388 }
4389 
4390 void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4391  bool stoptimer) {
4392  m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4393 
4394  if (g_bsmoothpanzoom) {
4395  if (StartTimedMovement(stoptimer)) {
4396  m_mustmove += 150; /* for quick presses register as 200 ms duration */
4397  m_zoom_factor = factor;
4398  }
4399 
4400  m_zoom_target = VPoint.chart_scale / factor;
4401  } else {
4402  if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4403 
4404  DoZoomCanvas(factor, can_zoom_to_cursor);
4405  }
4406 
4407  extendedSectorLegs.clear();
4408 }
4409 
4410 void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4411  // possible on startup
4412  if (!ChartData) return;
4413  if (!m_pCurrentStack) return;
4414 
4415  if (g_bShowCompassWin) {
4416  m_bShowCompassWin = true;
4417  SetShowGPSCompassWindow(true); // Cancel effects of Ctrl-I
4418  }
4419 
4420  /* TODO: queue the quilted loading code to a background thread
4421  so yield is never called from here, and also rendering is not delayed */
4422 
4423  // Cannot allow Yield() re-entrancy here
4424  if (m_bzooming) return;
4425  m_bzooming = true;
4426 
4427  double old_ppm = GetVP().view_scale_ppm;
4428 
4429  // Capture current cursor position for zoom to cursor
4430  double zlat = m_cursor_lat;
4431  double zlon = m_cursor_lon;
4432 
4433  double proposed_scale_onscreen =
4434  GetVP().chart_scale /
4435  factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4436  bool b_do_zoom = false;
4437 
4438  if (factor > 1) {
4439  b_do_zoom = true;
4440 
4441  // double zoom_factor = factor;
4442 
4443  ChartBase *pc = NULL;
4444 
4445  if (!VPoint.b_quilt) {
4446  pc = m_singleChart;
4447  } else {
4448  int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4449  if (new_db_index >= 0)
4450  pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4451  else { // for whatever reason, no reference chart is known
4452  // Choose the smallest scale chart on the current stack
4453  // and then adjust for scale range
4454  int current_ref_stack_index = -1;
4455  if (m_pCurrentStack->nEntry) {
4456  int trial_index = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4457  m_pQuilt->SetReferenceChart(trial_index);
4458  new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4459  if (new_db_index >= 0)
4460  pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4461  }
4462  }
4463 
4464  if (m_pCurrentStack)
4465  m_pCurrentStack->SetCurrentEntryFromdbIndex(
4466  new_db_index); // highlite the correct bar entry
4467  }
4468 
4469  if (pc) {
4470  // double target_scale_ppm = GetVPScale() * zoom_factor;
4471  // proposed_scale_onscreen = GetCanvasScaleFactor() /
4472  // target_scale_ppm;
4473 
4474  // Query the chart to determine the appropriate zoom range
4475  double min_allowed_scale =
4476  g_maxzoomin; // Roughly, latitude dependent for mercator charts
4477 
4478  if (proposed_scale_onscreen < min_allowed_scale) {
4479  if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4480  m_zoom_factor = 1; /* stop zooming */
4481  b_do_zoom = false;
4482  } else
4483  proposed_scale_onscreen = min_allowed_scale;
4484  }
4485 
4486  } else {
4487  proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4488  }
4489 
4490  } else if (factor < 1) {
4491  b_do_zoom = true;
4492 
4493  ChartBase *pc = NULL;
4494 
4495  bool b_smallest = false;
4496 
4497  if (!VPoint.b_quilt) { // not quilted
4498  pc = m_singleChart;
4499 
4500  if (pc) {
4501  // If m_singleChart is not on the screen, unbound the zoomout
4502  LLBBox viewbox = VPoint.GetBBox();
4503  // BoundingBox chart_box;
4504  int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4505  double max_allowed_scale;
4506 
4507  max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4508 
4509  // We can allow essentially unbounded zoomout in single chart mode
4510  // if( ChartData->GetDBBoundingBox( current_index,
4511  // &chart_box ) &&
4512  // !viewbox.IntersectOut( chart_box ) )
4513  // // Clamp the minimum scale zoom-out to the value
4514  // specified by the chart max_allowed_scale =
4515  // wxMin(max_allowed_scale, 4.0 *
4516  // pc->GetNormalScaleMax(
4517  // GetCanvasScaleFactor(),
4518  // GetCanvasWidth() ) );
4519  if (proposed_scale_onscreen > max_allowed_scale) {
4520  m_zoom_factor = 1; /* stop zooming */
4521  proposed_scale_onscreen = max_allowed_scale;
4522  }
4523  }
4524 
4525  } else {
4526  int new_db_index = m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4527  if (new_db_index >= 0)
4528  pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4529 
4530  if (m_pCurrentStack)
4531  m_pCurrentStack->SetCurrentEntryFromdbIndex(
4532  new_db_index); // highlite the correct bar entry
4533 
4534  b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4535 
4536  if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4537  proposed_scale_onscreen =
4538  wxMin(proposed_scale_onscreen,
4539  GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4540  }
4541 
4542  // set a minimum scale
4543  if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4544  m_absolute_min_scale_ppm)
4545  b_do_zoom = false;
4546  }
4547 
4548  double new_scale =
4549  GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4550 
4551  if (b_do_zoom) {
4552  if (can_zoom_to_cursor && g_bEnableZoomToCursor) {
4553  // Arrange to combine the zoom and pan into one operation for smoother
4554  // appearance
4555  SetVPScale(new_scale, false); // adjust, but deferred refresh
4556 
4557  wxPoint r;
4558  GetCanvasPointPix(zlat, zlon, &r);
4559  PanCanvas(r.x - mouse_x, r.y - mouse_y); // this will give the Refresh()
4560  } else {
4561  SetVPScale(new_scale);
4562 
4563  if (m_bFollow) DoCanvasUpdate();
4564  }
4565  }
4566 
4567  m_bzooming = false;
4568 }
4569 int rot;
4570 void ChartCanvas::RotateCanvas(double dir) {
4571  //SetUpMode(NORTH_UP_MODE);
4572 
4573  if (g_bsmoothpanzoom) {
4574  if (StartTimedMovement()) {
4575  m_mustmove += 150; /* for quick presses register as 200 ms duration */
4576  m_rotation_speed = dir * 60;
4577  }
4578  } else {
4579  double speed = dir * 10;
4580  if (m_modkeys == wxMOD_ALT) speed /= 20;
4581  DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4582  }
4583 }
4584 
4585 void ChartCanvas::DoRotateCanvas(double rotation) {
4586  while (rotation < 0) rotation += 2 * PI;
4587  while (rotation > 2 * PI) rotation -= 2 * PI;
4588 
4589  if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4590 
4591  SetVPRotation(rotation);
4592  parent_frame->UpdateRotationState(VPoint.rotation);
4593 }
4594 
4595 void ChartCanvas::DoTiltCanvas(double tilt) {
4596  while (tilt < 0) tilt = 0;
4597  while (tilt > .95) tilt = .95;
4598 
4599  if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4600 
4601  VPoint.tilt = tilt;
4602  Refresh(false);
4603 }
4604 
4605 void ChartCanvas::TogglebFollow(void) {
4606  if (!m_bFollow)
4607  SetbFollow();
4608  else
4609  ClearbFollow();
4610 }
4611 
4612 void ChartCanvas::ClearbFollow(void) {
4613  m_bFollow = false; // update the follow flag
4614 
4615  parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4616 
4617  UpdateFollowButtonState();
4618 
4619  DoCanvasUpdate();
4620  ReloadVP();
4621  parent_frame->SetChartUpdatePeriod();
4622 }
4623 
4624 void ChartCanvas::SetbFollow(void) {
4625  JumpToPosition(gLat, gLon, GetVPScale());
4626  m_bFollow = true;
4627 
4628  parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4629 
4630  UpdateFollowButtonState();
4631 
4632  // Is the OWNSHIP on-screen?
4633  // If not, then reset the OWNSHIP offset to 0 (center screen)
4634  if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4635  (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4636  m_OSoffsetx = 0;
4637  m_OSoffsety = 0;
4638  }
4639 
4640  DoCanvasUpdate();
4641  ReloadVP();
4642  parent_frame->SetChartUpdatePeriod();
4643 }
4644 
4645 void ChartCanvas::UpdateFollowButtonState(void) {
4646  if (m_muiBar) {
4647  if (!m_bFollow)
4648  m_muiBar->SetFollowButtonState(0);
4649  else {
4650  if (m_bLookAhead)
4651  m_muiBar->SetFollowButtonState(2);
4652  else
4653  m_muiBar->SetFollowButtonState(1);
4654  }
4655  }
4656 
4657 #ifdef __ANDROID__
4658  if (!m_bFollow)
4659  androidSetFollowTool(0);
4660  else {
4661  if (m_bLookAhead)
4662  androidSetFollowTool(2);
4663  else
4664  androidSetFollowTool(1);
4665  }
4666 #endif
4667 }
4668 
4669 void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4670  if (lon > 180.0) lon -= 360.0;
4671  m_vLat = lat;
4672  m_vLon = lon;
4673  StopMovement();
4674  m_bFollow = false;
4675 
4676  if (!GetQuiltMode()) {
4677  double skew = 0;
4678  if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4679  SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4680  } else {
4681  if (scale_ppm != GetVPScale()) {
4682  // XXX should be done in SetViewPoint
4683  VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4684  AdjustQuiltRefChart();
4685  }
4686  SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4687  }
4688 
4689  ReloadVP();
4690 
4691  UpdateFollowButtonState();
4692 
4693  // TODO
4694  // if( g_pi_manager ) {
4695  // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4696  // }
4697 }
4698 
4699 bool ChartCanvas::PanCanvas(double dx, double dy) {
4700  if (!ChartData) return false;
4701 
4702  extendedSectorLegs.clear();
4703 
4704  // double clat = VPoint.clat, clon = VPoint.clon;
4705  double dlat, dlon;
4706  wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
4707 
4708  int iters = 0;
4709  for (;;) {
4710  GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
4711 
4712  if (iters++ > 5) return false;
4713  if (!std::isnan(dlat)) break;
4714 
4715  dx *= .5, dy *= .5;
4716  if (fabs(dx) < 1 && fabs(dy) < 1) return false;
4717  }
4718 
4719  // avoid overshooting the poles
4720  if (dlat > 90)
4721  dlat = 90;
4722  else if (dlat < -90)
4723  dlat = -90;
4724 
4725  if (dlon > 360.) dlon -= 360.;
4726  if (dlon < -360.) dlon += 360.;
4727 
4728  // This should not really be necessary, but round-trip georef on some
4729  // charts is not perfect, So we can get creep on repeated unidimensional
4730  // pans, and corrupt chart cacheing.......
4731 
4732  // But this only works on north-up projections
4733  // TODO: can we remove this now?
4734  // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
4735  // .001 ) ) {
4736  //
4737  // if( dx == 0 ) dlon = clon;
4738  // if( dy == 0 ) dlat = clat;
4739  // }
4740 
4741  int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
4742 
4743  SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
4744 
4745  if (VPoint.b_quilt) {
4746  int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
4747  if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
4748  // Tweak the scale slightly for a new ref chart
4749  ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
4750  if (pc) {
4751  double tweak_scale_ppm =
4752  pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
4753  SetVPScale(tweak_scale_ppm);
4754  }
4755  }
4756 
4757  if(new_ref_dbIndex == -1) {
4758  // for whatever reason, no reference chart is known
4759  // Probably panned out of the coverage region
4760  // If any charts are anywhere on-screen, choose the smallest
4761  // scale chart on the screen to be a new reference chart.
4762  int trial_index = -1;
4763  if (m_pCurrentStack->nEntry) {
4764  int trial_index =
4765  m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4766  }
4767 
4768  if (trial_index < 0) {
4769  auto full_screen_array = GetQuiltFullScreendbIndexArray();
4770  if (full_screen_array.size())
4771  trial_index = full_screen_array[full_screen_array.size()-1];
4772  }
4773 
4774  if (trial_index >= 0){
4775  m_pQuilt->SetReferenceChart(trial_index);
4776  SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
4777  ReloadVP();
4778  }
4779  }
4780  }
4781 
4782  // Turn off bFollow only if the ownship has left the screen
4783  double offx, offy;
4784  toSM(dlat, dlon, gLat, gLon, &offx, &offy);
4785 
4786  double offset_angle = atan2(offy, offx);
4787  double offset_distance = sqrt((offy * offy) + (offx * offx));
4788  double chart_angle = GetVPRotation();
4789  double target_angle = chart_angle - offset_angle;
4790  double d_east_mod = offset_distance * cos(target_angle);
4791  double d_north_mod = offset_distance * sin(target_angle);
4792 
4793  m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
4794  m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
4795 
4796  // m_OSoffsetx = offx * VPoint.view_scale_ppm;
4797  // m_OSoffsety = offy * VPoint.view_scale_ppm;
4798 
4799  if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4800  (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
4801  m_bFollow = false; // update the follow flag
4802 
4803  UpdateFollowButtonState();
4804  }
4805 
4806  Refresh(false);
4807 
4808  pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
4809 
4810  return true;
4811 }
4812 
4813 void ChartCanvas::ReloadVP(bool b_adjust) {
4814  if (g_brightness_init) SetScreenBrightness(g_nbrightness);
4815 
4816  LoadVP(VPoint, b_adjust);
4817 }
4818 
4819 void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
4820 #ifdef ocpnUSE_GL
4821  if (g_bopengl && m_glcc) {
4822  m_glcc->Invalidate();
4823  if (m_glcc->GetSize() != GetSize()) {
4824  m_glcc->SetSize(GetSize());
4825  }
4826  } else
4827 #endif
4828  {
4829  m_cache_vp.Invalidate();
4830  m_bm_cache_vp.Invalidate();
4831  }
4832 
4833  VPoint.Invalidate();
4834 
4835  if (m_pQuilt) m_pQuilt->Invalidate();
4836 
4837  // Make sure that the Selected Group is sensible...
4838  // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
4839  // m_groupIndex = 0;
4840  // if( !CheckGroup( m_groupIndex ) )
4841  // m_groupIndex = 0;
4842 
4843  SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
4844  vp.m_projection_type, b_adjust);
4845 
4846 }
4847 
4848 void ChartCanvas::SetQuiltRefChart(int dbIndex) {
4849  m_pQuilt->SetReferenceChart(dbIndex);
4850  VPoint.Invalidate();
4851  m_pQuilt->Invalidate();
4852 }
4853 
4854 double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
4855  if (m_pQuilt)
4856  return m_pQuilt->GetBestStartScale(dbi_hint, vp);
4857  else
4858  return vp.view_scale_ppm;
4859 }
4860 
4861 // Verify and adjust the current reference chart,
4862 // so that it will not lead to excessive overzoom or underzoom onscreen
4863 int ChartCanvas::AdjustQuiltRefChart() {
4864  int ret = -1;
4865  if (m_pQuilt) {
4866  wxASSERT(ChartData);
4867  ChartBase *pc =
4868  ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
4869  if (pc) {
4870  double min_ref_scale =
4871  pc->GetNormalScaleMin(m_canvas_scale_factor, false);
4872  double max_ref_scale =
4873  pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
4874 
4875  if (VPoint.chart_scale < min_ref_scale) {
4876  ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
4877  } else if (VPoint.chart_scale > max_ref_scale) {
4878  ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
4879  } else {
4880  bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
4881 
4882  int ref_family = pc->GetChartFamily();
4883 
4884  if (!brender_ok) {
4885  unsigned int target_stack_index = 0;
4886  int target_stack_index_check =
4887  m_pQuilt->GetExtendedStackIndexArray()
4888  [m_pQuilt->GetRefChartdbIndex()]; // Lookup
4889 
4890  if (wxNOT_FOUND != target_stack_index_check)
4891  target_stack_index = target_stack_index_check;
4892 
4893  int extended_array_count =
4894  m_pQuilt->GetExtendedStackIndexArray().size();
4895  while ((!brender_ok) &&
4896  ((int)target_stack_index < (extended_array_count - 1))) {
4897  target_stack_index++;
4898  int test_db_index =
4899  m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
4900 
4901  if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
4902  IsChartQuiltableRef(test_db_index)) {
4903  // open the target, and check the min_scale
4904  ChartBase *ptest_chart =
4905  ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
4906  if (ptest_chart) {
4907  brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
4908  }
4909  }
4910  }
4911 
4912  if (brender_ok) { // found a better reference chart
4913  int new_db_index =
4914  m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
4915  if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
4916  IsChartQuiltableRef(new_db_index)) {
4917  m_pQuilt->SetReferenceChart(new_db_index);
4918  ret = new_db_index;
4919  } else
4920  ret = m_pQuilt->GetRefChartdbIndex();
4921  } else
4922  ret = m_pQuilt->GetRefChartdbIndex();
4923 
4924  } else
4925  ret = m_pQuilt->GetRefChartdbIndex();
4926  }
4927  } else
4928  ret = -1;
4929  }
4930 
4931  return ret;
4932 }
4933 
4934 void ChartCanvas::UpdateCanvasOnGroupChange(void) {
4935  delete m_pCurrentStack;
4936  m_pCurrentStack = new ChartStack;
4937  wxASSERT(ChartData);
4938  ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
4939  m_groupIndex);
4940 
4941  if (m_pQuilt) {
4942  m_pQuilt->Compose(VPoint);
4943  SetFocus();
4944  }
4945 }
4946 
4947 bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
4948  double latNE, double lonNE) {
4949  // Center Point
4950  double latc = (latSW + latNE) / 2.0;
4951  double lonc = (lonSW + lonNE) / 2.0;
4952 
4953  // Get scale in ppm (latitude)
4954  double ne_easting, ne_northing;
4955  toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
4956 
4957  double sw_easting, sw_northing;
4958  toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
4959 
4960  double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
4961 
4962  return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
4963 }
4964 
4965 bool ChartCanvas::SetVPScale(double scale, bool refresh) {
4966  return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
4967  VPoint.rotation, VPoint.m_projection_type, true, refresh);
4968 }
4969 
4970 bool ChartCanvas::SetVPProjection(int projection) {
4971  if (!g_bopengl) // alternative projections require opengl
4972  return false;
4973 
4974  // the view scale varies depending on geographic location and projection
4975  // rescale to keep the relative scale on the screen the same
4976  double prev_true_scale_ppm = m_true_scale_ppm;
4977  return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
4978  VPoint.skew, VPoint.rotation, projection) &&
4979  SetVPScale(wxMax(
4980  VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
4981  m_absolute_min_scale_ppm));
4982 }
4983 
4984 bool ChartCanvas::SetViewPoint(double lat, double lon) {
4985  return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
4986  VPoint.rotation);
4987 }
4988 
4989 bool ChartCanvas::SetVPRotation(double angle) {
4990  return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm, VPoint.skew,
4991  angle);
4992 }
4993 
4994 bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
4995  double skew, double rotation, int projection,
4996  bool b_adjust, bool b_refresh) {
4997  bool b_ret = false;
4998 
4999  if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5000  skew -= 2 * PI;
5001 
5002  // Any sensible change?
5003  if (VPoint.IsValid()) {
5004  if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5005  (fabs(VPoint.skew - skew) < 1e-9) &&
5006  (fabs(VPoint.rotation - rotation) < 1e-9) &&
5007  (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5008  (VPoint.m_projection_type == projection ||
5009  projection == PROJECTION_UNKNOWN))
5010  return false;
5011  }
5012 
5013  if (VPoint.m_projection_type != projection)
5014  VPoint.InvalidateTransformCache(); // invalidate
5015 
5016  // Take a local copy of the last viewport
5017  ViewPort last_vp = VPoint;
5018 
5019  VPoint.skew = skew;
5020  VPoint.clat = lat;
5021  VPoint.clon = lon;
5022  VPoint.rotation = rotation;
5023  VPoint.view_scale_ppm = scale_ppm;
5024  if (projection != PROJECTION_UNKNOWN)
5025  VPoint.SetProjectionType(projection);
5026  else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5027  VPoint.SetProjectionType(PROJECTION_MERCATOR);
5028 
5029  // don't allow latitude above 88 for mercator (90 is infinity)
5030  if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5031  VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5032  if (VPoint.clat > 89.5)
5033  VPoint.clat = 89.5;
5034  else if (VPoint.clat < -89.5)
5035  VPoint.clat = -89.5;
5036  }
5037 
5038  // don't zoom out too far for transverse mercator polyconic until we resolve
5039  // issues
5040  if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5041  VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5042  VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5043 
5044  //SetVPRotation(rotation);
5045 
5046  if (!g_bopengl) // tilt is not possible without opengl
5047  VPoint.tilt = 0;
5048 
5049  if ((VPoint.pix_width <= 0) ||
5050  (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5051  return false;
5052 
5053  bool bwasValid = VPoint.IsValid();
5054  VPoint.Validate(); // Mark this ViewPoint as OK
5055 
5056  // Has the Viewport scale changed? If so, invalidate the vp
5057  if (last_vp.view_scale_ppm != scale_ppm) {
5058  m_cache_vp.Invalidate();
5059  InvalidateGL();
5060  }
5061 
5062  // A preliminary value, may be tweaked below
5063  VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5064 
5065  // recompute cursor position
5066  // and send to interested plugins if the mouse is actually in this window
5067 
5068  const wxPoint pt = wxGetMousePosition();
5069  int mouseX = pt.x - GetScreenPosition().x;
5070  int mouseY = pt.y - GetScreenPosition().y;
5071  if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5072  (mouseY < VPoint.pix_height)) {
5073  double lat, lon;
5074  GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5075  m_cursor_lat = lat;
5076  m_cursor_lon = lon;
5077  if (g_pi_manager) g_pi_manager->SendCursorLatLonToAllPlugIns(lat, lon);
5078  }
5079 
5080  if (!VPoint.b_quilt && m_singleChart) {
5081  VPoint.SetBoxes();
5082 
5083  // Allow the chart to adjust the new ViewPort for performance optimization
5084  // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5085  if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5086 
5087  // If there is a sensible change in the chart render, refresh the whole
5088  // screen
5089  if ((!m_cache_vp.IsValid()) ||
5090  (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5091  Refresh(false);
5092  b_ret = true;
5093  } else {
5094  wxPoint cp_last, cp_this;
5095  GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5096  GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5097 
5098  if (cp_last != cp_this) {
5099  Refresh(false);
5100  b_ret = true;
5101  }
5102  }
5103  // Create the stack
5104  if (m_pCurrentStack) {
5105  assert(ChartData != 0);
5106  int current_db_index;
5107  current_db_index =
5108  m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5109 
5110  ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5111  m_groupIndex);
5112  m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5113  }
5114 
5115  if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5116  }
5117 
5118  // Handle the quilted case
5119  if (VPoint.b_quilt) {
5120  if (last_vp.view_scale_ppm != scale_ppm)
5121  m_pQuilt->InvalidateAllQuiltPatchs();
5122 
5123  // Create the quilt
5124  if (ChartData /*&& ChartData->IsValid()*/) {
5125  if (!m_pCurrentStack) return false;
5126 
5127  int current_db_index;
5128  current_db_index =
5129  m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5130 
5131  ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5132  m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5133 
5134  // Check to see if the current quilt reference chart is in the new stack
5135  int current_ref_stack_index = -1;
5136  for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5137  if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5138  current_ref_stack_index = i;
5139  }
5140 
5141  if (g_bFullScreenQuilt) {
5142  current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5143  }
5144 
5145  // We might need a new Reference Chart
5146  bool b_needNewRef = false;
5147 
5148  // If the new stack does not contain the current ref chart....
5149  if ((-1 == current_ref_stack_index) &&
5150  (m_pQuilt->GetRefChartdbIndex() >= 0))
5151  b_needNewRef = true;
5152 
5153  // Would the current Ref Chart be excessively underzoomed?
5154  // We need to check this here to be sure, since we cannot know where the
5155  // reference chart was assigned. For instance, the reference chart may
5156  // have been selected from the config file, or from a long jump with a
5157  // chart family switch implicit. Anyway, we check to be sure....
5158  bool renderable = true;
5159  ChartBase *referenceChart =
5160  ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5161  if (referenceChart) {
5162  double chartMaxScale = referenceChart->GetNormalScaleMax(
5163  GetCanvasScaleFactor(), GetCanvasWidth());
5164  renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5165  }
5166  if (!renderable) b_needNewRef = true;
5167 
5168  // Need new refchart?
5169  if (b_needNewRef) {
5170  const ChartTableEntry &cte_ref =
5171  ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5172  int target_scale = cte_ref.GetScale();
5173  int target_type = cte_ref.GetChartType();
5174  int candidate_stack_index;
5175 
5176  // reset the ref chart in a way that does not lead to excessive
5177  // underzoom, for performance reasons Try to find a chart that is the
5178  // same type, and has a scale of just smaller than the current ref
5179  // chart
5180 
5181  candidate_stack_index = 0;
5182  while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5183  const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5184  m_pCurrentStack->GetDBIndex(candidate_stack_index));
5185  int candidate_scale = cte_candidate.GetScale();
5186  int candidate_type = cte_candidate.GetChartType();
5187 
5188  if ((candidate_scale >= target_scale) &&
5189  (candidate_type == target_type)) {
5190  bool renderable = true;
5191  ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5192  m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5193  if (tentative_referenceChart) {
5194  double chartMaxScale =
5195  tentative_referenceChart->GetNormalScaleMax(
5196  GetCanvasScaleFactor(), GetCanvasWidth());
5197  renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5198  }
5199 
5200  if (renderable) break;
5201  }
5202 
5203  candidate_stack_index++;
5204  }
5205 
5206  // If that did not work, look for a chart of just larger scale and
5207  // same type
5208  if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5209  candidate_stack_index = m_pCurrentStack->nEntry - 1;
5210  while (candidate_stack_index >= 0) {
5211  int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5212  if (idx >= 0) {
5213  const ChartTableEntry &cte_candidate =
5214  ChartData->GetChartTableEntry(idx);
5215  int candidate_scale = cte_candidate.GetScale();
5216  int candidate_type = cte_candidate.GetChartType();
5217 
5218  if ((candidate_scale <= target_scale) &&
5219  (candidate_type == target_type))
5220  break;
5221  }
5222  candidate_stack_index--;
5223  }
5224  }
5225 
5226  // and if that did not work, chose stack entry 0
5227  if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5228  (candidate_stack_index < 0))
5229  candidate_stack_index = 0;
5230 
5231  int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5232 
5233  m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5234  }
5235 
5236  if (!g_bopengl) {
5237  // Preset the VPoint projection type to match what the quilt projection
5238  // type will be
5239  int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5240 
5241  // Always keep the default Mercator projection if the reference chart is
5242  // not in the PatchList or the scale is too small for it to render.
5243 
5244  bool renderable = true;
5245  ChartBase *referenceChart =
5246  ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5247  if (referenceChart) {
5248  double chartMaxScale = referenceChart->GetNormalScaleMax(
5249  GetCanvasScaleFactor(), GetCanvasWidth());
5250  renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5251  proj = ChartData->GetDBChartProj(ref_db_index);
5252  } else
5253  proj = PROJECTION_MERCATOR;
5254 
5255  VPoint.b_MercatorProjectionOverride =
5256  (m_pQuilt->GetnCharts() == 0 || !renderable);
5257 
5258  if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5259 
5260  VPoint.SetProjectionType(proj);
5261  }
5262 
5263  VPoint.SetBoxes();
5264 
5265  // If this quilt will be a perceptible delta from the existing quilt,
5266  // then refresh the entire screen
5267  if (m_pQuilt->IsQuiltDelta(VPoint)) {
5268  // Allow the quilt to adjust the new ViewPort for performance
5269  // optimization This will normally be only a fractional (i.e.
5270  // sub-pixel) adjustment...
5271  if (b_adjust) m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5272 
5273  // ChartData->ClearCacheInUseFlags();
5274  // unsigned long hash1 = m_pQuilt->GetXStackHash();
5275 
5276  // wxStopWatch sw;
5277 
5278 #ifdef __ANDROID__
5279  // This is an optimization for panning on touch screen systems.
5280  // The quilt composition is deferred until the OnPaint() message gets
5281  // finally removed and processed from the message queue.
5282  // Takes advantage of the fact that touch-screen pan gestures are
5283  // usually short in distance,
5284  // so not requiring a full quilt rebuild until the pan gesture is
5285  // complete.
5286  if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5287  // qDebug() << "Force compose";
5288  m_pQuilt->Compose(VPoint);
5289  } else {
5290  m_pQuilt->Invalidate();
5291  }
5292 #else
5293  m_pQuilt->Compose(VPoint);
5294 #endif
5295 
5296  // printf("comp time %ld\n", sw.Time());
5297 
5298  // If the extended chart stack has changed, invalidate any cached
5299  // render bitmap
5300  // if(m_pQuilt->GetXStackHash() != hash1) {
5301  // m_bm_cache_vp.Invalidate();
5302  // InvalidateGL();
5303  // }
5304 
5305  ChartData->PurgeCacheUnusedCharts(0.7);
5306 
5307  if (b_refresh) Refresh(false);
5308 
5309  b_ret = true;
5310  }
5311  }
5312 
5313  VPoint.skew = 0.; // Quilting supports 0 Skew
5314  } else if (!g_bopengl) {
5315  OcpnProjType projection = PROJECTION_UNKNOWN;
5316  if (m_singleChart) // viewport projection must match chart projection
5317  // without opengl
5318  projection = m_singleChart->GetChartProjectionType();
5319  if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5320  VPoint.SetProjectionType(projection);
5321  }
5322 
5323  // Has the Viewport projection changed? If so, invalidate the vp
5324  if (last_vp.m_projection_type != VPoint.m_projection_type) {
5325  m_cache_vp.Invalidate();
5326  InvalidateGL();
5327  }
5328 
5329  UpdateCanvasControlBar(); // Refresh the Piano
5330 
5331  VPoint.chart_scale = 1.0; // fallback default value
5332 
5333  /*if (!VPoint.GetBBox().GetValid())*/ VPoint.SetBoxes();
5334 
5335  if (VPoint.GetBBox().GetValid()) {
5336  // Update the viewpoint reference scale
5337  if (m_singleChart)
5338  VPoint.ref_scale = m_singleChart->GetNativeScale();
5339  else {
5340 #ifdef __ANDROID__
5341  // This is an optimization for panning on touch screen systems.
5342  // See above.
5343  // Quilt might not be fully composed at this point, so for cm93
5344  // the reference scale may not be known.
5345  // In this case, do not update the VP ref_scale.
5346  if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5347  VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5348  }
5349 #else
5350  VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5351 #endif
5352  }
5353 
5354  // Calculate the on-screen displayed actual scale
5355  // by a simple traverse northward from the center point
5356  // of roughly one eighth of the canvas height
5357  wxPoint2DDouble r, r1;
5358 
5359  double delta_check =
5360  (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5361  delta_check /= 8.;
5362 
5363  double check_point = wxMin(89., VPoint.clat);
5364 
5365  while ((delta_check + check_point) > 90.) delta_check /= 2.;
5366 
5367  double rhumbDist;
5368  DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5369  VPoint.clon, 0, &rhumbDist);
5370 
5371  GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5372  GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5373  double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5374  ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5375 
5376  m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5377 
5378  // A fall back in case of very high zoom-out, giving delta_y == 0
5379  // which can probably only happen with vector charts
5380  if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5381 
5382  // Another fallback, for highly zoomed out charts
5383  // This adjustment makes the displayed TrueScale correspond to the
5384  // same algorithm used to calculate the chart zoom-out limit for
5385  // ChartDummy.
5386  if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5387 
5388  if (m_true_scale_ppm)
5389  VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5390  else
5391  VPoint.chart_scale = 1.0;
5392 
5393  // Create a nice renderable string
5394  double round_factor = 1000.;
5395  if (VPoint.chart_scale <= 1000.)
5396  round_factor = 10.;
5397  else if (VPoint.chart_scale <= 10000.)
5398  round_factor = 100.;
5399  else if (VPoint.chart_scale <= 100000.)
5400  round_factor = 1000.;
5401 
5402  // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5403  double retina_coef = 1;
5404  #ifdef ocpnUSE_GL
5405  #ifdef __WXOSX__
5406  if (g_bopengl) {
5407  retina_coef = GetContentScaleFactor();;
5408  }
5409  #endif
5410  #endif
5411 
5412  double true_scale_display =
5413  wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5414  wxString text;
5415 
5416  m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5417 
5418  if (m_displayed_scale_factor > 10.0)
5419  text.Printf(_T("%s %4.0f (%1.0fx)"), _("Scale"), true_scale_display,
5420  m_displayed_scale_factor);
5421  else if (m_displayed_scale_factor > 1.0)
5422  text.Printf(_T("%s %4.0f (%1.1fx)"), _("Scale"), true_scale_display,
5423  m_displayed_scale_factor);
5424  else if (m_displayed_scale_factor > 0.1) {
5425  double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5426  text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5427  } else if (m_displayed_scale_factor > 0.01) {
5428  double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5429  text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5430  } else {
5431  text.Printf(
5432  _T("%s %4.0f (---)"), _("Scale"),
5433  true_scale_display); // Generally, no chart, so no chart scale factor
5434  }
5435 
5436  m_scaleValue = true_scale_display;
5437  m_scaleText = text;
5438  if (m_muiBar) m_muiBar->UpdateDynamicValues();
5439 
5440  if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5441  (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5442  // Check to see if the text will fit in the StatusBar field...
5443  bool b_noshow = false;
5444  {
5445  int w = 0;
5446  int h;
5447  wxClientDC dc(parent_frame->GetStatusBar());
5448  if (dc.IsOk()) {
5449  wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5450  dc.SetFont(*templateFont);
5451  dc.GetTextExtent(text, &w, &h);
5452 
5453  // If text is too long for the allocated field, try to reduce the text
5454  // string a bit.
5455  wxRect rect;
5456  parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5457  if (w && w > rect.width) {
5458  text.Printf(_T("%s (%1.1fx)"), _("Scale"),
5459  m_displayed_scale_factor);
5460  }
5461 
5462  // Test again...if too big still, then give it up.
5463  dc.GetTextExtent(text, &w, &h);
5464 
5465  if (w && w > rect.width) {
5466  b_noshow = true;
5467  }
5468  }
5469  }
5470 
5471  if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5472  }
5473  }
5474 
5475  // Maintain member vLat/vLon
5476  m_vLat = VPoint.clat;
5477  m_vLon = VPoint.clon;
5478 
5479  return b_ret;
5480 }
5481 
5482 // Static Icon definitions for some symbols requiring
5483 // scaling/rotation/translation Very specific wxDC draw commands are
5484 // necessary to properly render these icons...See the code in
5485 // ShipDraw()
5486 
5487 // This icon was adapted and scaled from the S52 Presentation Library
5488 // version 3_03.
5489 // Symbol VECGND02
5490 
5491 static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5492 
5493 // This ownship icon was adapted and scaled from the S52 Presentation
5494 // Library version 3_03 Symbol OWNSHP05
5495 static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5496  -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5497 
5498 wxColour ChartCanvas::PredColor() {
5499  // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5500  // visibility.
5501  if (SHIP_NORMAL == m_ownship_state)
5502  return GetGlobalColor(_T ( "URED" ));
5503 
5504  else if (SHIP_LOWACCURACY == m_ownship_state)
5505  return GetGlobalColor(_T ( "YELO1" ));
5506 
5507  return GetGlobalColor(_T ( "NODTA" ));
5508 }
5509 
5510 wxColour ChartCanvas::ShipColor() {
5511  // Establish ship color
5512  // It changes color based on GPS and Chart accuracy/availability
5513 
5514  if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor(_T ( "GREY1" ));
5515 
5516  if (SHIP_LOWACCURACY == m_ownship_state)
5517  return GetGlobalColor(_T ( "YELO1" ));
5518 
5519  return GetGlobalColor(_T ( "URED" )); // default is OK
5520 }
5521 
5522 void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc, wxPoint lShipMidPoint) {
5523  dc.SetPen(wxPen(PredColor(), 2));
5524 
5525  if (SHIP_NORMAL == m_ownship_state)
5526  dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5527  else
5528  dc.SetBrush(wxBrush(GetGlobalColor(_T ( "YELO1" ))));
5529 
5530  dc.DrawEllipse(lShipMidPoint.x - 10, lShipMidPoint.y - 10, 20, 20);
5531  dc.DrawEllipse(lShipMidPoint.x - 6, lShipMidPoint.y - 6, 12, 12);
5532 
5533  dc.DrawLine(lShipMidPoint.x - 12, lShipMidPoint.y, lShipMidPoint.x + 12,
5534  lShipMidPoint.y);
5535  dc.DrawLine(lShipMidPoint.x, lShipMidPoint.y - 12, lShipMidPoint.x,
5536  lShipMidPoint.y + 12);
5537 }
5538 
5539 void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5540  wxPoint GPSOffsetPixels,
5541  wxPoint lGPSPoint) {
5542  // Develop a uniform length for course predictor line dash length, based on
5543  // physical display size Use this reference length to size all other graphics
5544  // elements
5545  float ref_dim = m_display_size_mm / 24;
5546  ref_dim = wxMin(ref_dim, 12);
5547  ref_dim = wxMax(ref_dim, 6);
5548 
5549  wxColour cPred;
5550  cPred.Set(g_cog_predictor_color);
5551  if(cPred == wxNullColour) cPred = PredColor();
5552 
5553  // Establish some graphic element line widths dependent on the platform
5554  // display resolution
5555  // double nominal_line_width_pix = wxMax(1.0,
5556  // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5557  // not less than 1 pixel
5558  double nominal_line_width_pix = wxMax(
5559  1.0,
5560  floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5561 
5562  // If the calculated value is greater than the config file spec value, then
5563  // use it.
5564  if (nominal_line_width_pix > g_cog_predictor_width)
5565  g_cog_predictor_width = nominal_line_width_pix;
5566 
5567  // Calculate ownship Position Predictor
5568  wxPoint lPredPoint, lHeadPoint;
5569 
5570  float pCog = std::isnan(gCog) ? 0 : gCog;
5571  float pSog = std::isnan(gSog) ? 0 : gSog;
5572 
5573  double pred_lat, pred_lon;
5574  ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5575  &pred_lat, &pred_lon);
5576  GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5577 
5578  // test to catch the case where COG/HDG line crosses the screen
5579  LLBBox box;
5580 
5581  // Should we draw the Head vector?
5582  // Compare the points lHeadPoint and lPredPoint
5583  // If they differ by more than n pixels, and the head vector is valid, then
5584  // render the head vector
5585 
5586  float ndelta_pix = 10.;
5587  double hdg_pred_lat, hdg_pred_lon;
5588  bool b_render_hdt = false;
5589  if (!std::isnan(gHdt)) {
5590  // Calculate ownship Heading pointer as a predictor
5591  ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5592  &hdg_pred_lon);
5593  GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5594  float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5595  powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5596  if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5597  box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5598  if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5599  }
5600  }
5601 
5602  // draw course over ground if they are longer than the ship
5603  wxPoint lShipMidPoint;
5604  lShipMidPoint.x = lGPSPoint.x + GPSOffsetPixels.x;
5605  lShipMidPoint.y = lGPSPoint.y + GPSOffsetPixels.y;
5606  float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5607  powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5608 
5609  if (lpp >= img_height / 2) {
5610  box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5611  if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5612  !std::isnan(gSog)) {
5613  // COG Predictor
5614  float dash_length = ref_dim;
5615  wxDash dash_long[2];
5616  dash_long[0] =
5617  (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
5618  g_cog_predictor_width); // Long dash , in mm <---------+
5619  dash_long[1] = dash_long[0] / 2.0; // Short gap
5620 
5621  // On ultra-hi-res displays, do not allow the dashes to be greater than
5622  // 250, since it is defined as (char)
5623  if (dash_length > 250.) {
5624  dash_long[0] = 250. / g_cog_predictor_width;
5625  dash_long[1] = dash_long[0] / 2;
5626  }
5627 
5628  wxPen ppPen2(cPred, g_cog_predictor_width, (wxPenStyle)g_cog_predictor_style);
5629  if(g_cog_predictor_style==(wxPenStyle)wxUSER_DASH)
5630  ppPen2.SetDashes(2, dash_long);
5631  dc.SetPen(ppPen2);
5632  dc.StrokeLine(
5633  lGPSPoint.x + GPSOffsetPixels.x, lGPSPoint.y + GPSOffsetPixels.y,
5634  lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
5635 
5636  if (g_cog_predictor_width > 1) {
5637  float line_width = g_cog_predictor_width / 3.;
5638 
5639  wxDash dash_long3[2];
5640  dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
5641  dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
5642 
5643  wxPen ppPen3(GetGlobalColor(_T ( "UBLCK" )), wxMax(1, line_width),
5644  (wxPenStyle)g_cog_predictor_style);
5645  if(g_cog_predictor_style==(wxPenStyle)wxUSER_DASH)
5646  ppPen3.SetDashes(2, dash_long3);
5647  dc.SetPen(ppPen3);
5648  dc.StrokeLine(
5649  lGPSPoint.x + GPSOffsetPixels.x, lGPSPoint.y + GPSOffsetPixels.y,
5650  lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
5651  }
5652 
5653  if (g_cog_predictor_endmarker) {
5654  // Prepare COG predictor endpoint icon
5655  double png_pred_icon_scale_factor = .4;
5656  if (g_ShipScaleFactorExp > 1.0)
5657  png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
5658  if (g_scaler)
5659  png_pred_icon_scale_factor *= 1.0 / g_scaler;
5660 
5661  wxPoint icon[4];
5662 
5663  float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
5664  (float)(lPredPoint.x - lShipMidPoint.x));
5665  cog_rad += (float)PI;
5666 
5667  for (int i = 0; i < 4; i++) {
5668  int j = i * 2;
5669  double pxa = (double)(s_png_pred_icon[j]);
5670  double pya = (double)(s_png_pred_icon[j + 1]);
5671 
5672  pya *= png_pred_icon_scale_factor;
5673  pxa *= png_pred_icon_scale_factor;
5674 
5675  double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
5676  double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
5677 
5678  icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
5679  icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
5680  }
5681 
5682  // Render COG endpoint icon
5683  wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), g_cog_predictor_width / 2,
5684  wxPENSTYLE_SOLID);
5685  dc.SetPen(ppPen1);
5686  dc.SetBrush(wxBrush(cPred));
5687 
5688  dc.StrokePolygon(4, icon);
5689  }
5690  }
5691  }
5692 
5693  // HDT Predictor
5694  if (b_render_hdt) {
5695  float hdt_dash_length = ref_dim * 0.4;
5696 
5697  cPred.Set(g_ownship_HDTpredictor_color);
5698  if(cPred == wxNullColour) cPred = PredColor();
5699  float hdt_width = (g_ownship_HDTpredictor_width>0?g_ownship_HDTpredictor_width:g_cog_predictor_width * 0.8);
5700  wxDash dash_short[2];
5701  dash_short[0] =
5702  (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
5703  hdt_width); // Short dash , in mm <---------+
5704  dash_short[1] =
5705  (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
5706  hdt_width); // Short gap |
5707 
5708  wxPen ppPen2(cPred, hdt_width,(wxPenStyle)g_ownship_HDTpredictor_style);
5709  if(g_ownship_HDTpredictor_style==(wxPenStyle)wxUSER_DASH)
5710  ppPen2.SetDashes(2, dash_short);
5711 
5712  dc.SetPen(ppPen2);
5713  dc.StrokeLine(
5714  lGPSPoint.x + GPSOffsetPixels.x, lGPSPoint.y + GPSOffsetPixels.y,
5715  lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
5716 
5717  wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
5718  dc.SetPen(ppPen1);
5719  dc.SetBrush(wxBrush(GetGlobalColor(_T ( "GREY2" ))));
5720 
5721  if (g_ownship_HDTpredictor_endmarker) {
5722  double nominal_circle_size_pixels = wxMax(
5723  4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
5724 
5725  // Scale the circle to ChartScaleFactor, slightly softened....
5726  if (g_ShipScaleFactorExp > 1.0)
5727  nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
5728 
5729  dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
5730  lHeadPoint.y + GPSOffsetPixels.y,
5731  nominal_circle_size_pixels / 2);
5732  }
5733  }
5734 
5735  // Draw radar rings if activated
5736  if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
5737  double factor = 1.00;
5738  if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
5739  factor = 1 / 1.852;
5740  else if (g_pNavAidRadarRingsStepUnits == 2){ // minutes (time)
5741  if (std::isnan(gSog))
5742  factor = 0.0;
5743  else
5744  factor = gSog/60;
5745  }
5746  factor *= g_fNavAidRadarRingsStep;
5747 
5748  double tlat, tlon;
5749  wxPoint r;
5750  ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
5751  GetCanvasPointPix(tlat, tlon, &r);
5752 
5753  double lpp = sqrt(pow((double)(lGPSPoint.x - r.x), 2) +
5754  pow((double)(lGPSPoint.y - r.y), 2));
5755  int pix_radius = (int)lpp;
5756 
5757  extern wxColor GetDimColor(wxColor c);
5758  wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
5759 
5760  wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
5761 
5762  dc.SetPen(ppPen1);
5763  dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
5764 
5765  for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
5766  dc.StrokeCircle(lGPSPoint.x, lGPSPoint.y, i * pix_radius);
5767  }
5768 }
5769 
5770 void ChartCanvas::ComputeShipScaleFactor(
5771  float icon_hdt, int ownShipWidth, int ownShipLength, wxPoint &lShipMidPoint,
5772  wxPoint &GPSOffsetPixels, wxPoint lGPSPoint, float &scale_factor_x,
5773  float &scale_factor_y) {
5774  float screenResolution = m_pix_per_mm;
5775 
5776  // Calculate the true ship length in exact pixels
5777  double ship_bow_lat, ship_bow_lon;
5778  ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
5779  &ship_bow_lat, &ship_bow_lon);
5780  wxPoint lShipBowPoint;
5781  wxPoint2DDouble b_point =
5782  GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
5783  wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
5784 
5785  float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
5786  powf((float)(b_point.m_y - a_point.m_y), 2));
5787 
5788  // And in mm
5789  float shipLength_mm = shipLength_px / screenResolution;
5790 
5791  // Set minimum ownship drawing size
5792  float ownship_min_mm = g_n_ownship_min_mm;
5793  ownship_min_mm = wxMax(ownship_min_mm, 1.0);
5794 
5795  // Calculate Nautical Miles distance from midships to gps antenna
5796  float hdt_ant = icon_hdt + 180.;
5797  float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
5798  float dx = g_n_gps_antenna_offset_x / 1852.;
5799  if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
5800  {
5801  hdt_ant = icon_hdt;
5802  dy = -dy;
5803  }
5804 
5805  // If the drawn ship size is going to be clamped, adjust the gps antenna
5806  // offsets
5807  if (shipLength_mm < ownship_min_mm) {
5808  dy /= shipLength_mm / ownship_min_mm;
5809  dx /= shipLength_mm / ownship_min_mm;
5810  }
5811 
5812  double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
5813 
5814  ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
5815  ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
5816  &ship_mid_lon1);
5817 
5818  GetCanvasPointPix(ship_mid_lat1, ship_mid_lon1, &lShipMidPoint);
5819  GPSOffsetPixels.x = lShipMidPoint.x - lGPSPoint.x;
5820  GPSOffsetPixels.y = lShipMidPoint.y - lGPSPoint.y;
5821 
5822  float scale_factor = shipLength_px / ownShipLength;
5823 
5824  // Calculate a scale factor that would produce a reasonably sized icon
5825  float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
5826 
5827  // And choose the correct one
5828  scale_factor = wxMax(scale_factor, scale_factor_min);
5829 
5830  scale_factor_y = scale_factor;
5831  scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
5832  ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
5833 }
5834 
5835 void ChartCanvas::ShipDraw(ocpnDC &dc) {
5836  if (!GetVP().IsValid()) return;
5837 
5838  wxPoint lGPSPoint, lShipMidPoint, GPSOffsetPixels(0, 0);
5839 
5840  // COG/SOG may be undefined in NMEA data stream
5841  float pCog = std::isnan(gCog) ? 0 : gCog;
5842  float pSog = std::isnan(gSog) ? 0 : gSog;
5843 
5844  GetCanvasPointPix(gLat, gLon, &lGPSPoint);
5845  lShipMidPoint = lGPSPoint;
5846 
5847  // Draw the icon rotated to the COG
5848  // or to the Hdt if available
5849  float icon_hdt = pCog;
5850  if (!std::isnan(gHdt)) icon_hdt = gHdt;
5851 
5852  // COG may be undefined in NMEA data stream
5853  if (std::isnan(icon_hdt)) icon_hdt = 0.0;
5854 
5855  // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
5856  // predictor
5857  double osd_head_lat, osd_head_lon;
5858  wxPoint osd_head_point;
5859 
5860  ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
5861  &osd_head_lon);
5862 
5863  GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
5864 
5865  float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.y),
5866  (float)(osd_head_point.x - lShipMidPoint.x));
5867  icon_rad += (float)PI;
5868 
5869  if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
5870 
5871  // Another draw test ,based on pixels, assuming the ship icon is a fixed
5872  // nominal size and is just barely outside the viewport ....
5873  BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
5874 
5875  // TODO: fix to include actual size of boat that will be rendered
5876  int img_height = 0;
5877  if (bb_screen.PointInBox(lShipMidPoint, 20)) {
5878  if (GetVP().chart_scale >
5879  300000) // According to S52, this should be 50,000
5880  {
5881  ShipDrawLargeScale(dc, lShipMidPoint);
5882  img_height = 20;
5883  } else {
5884  wxImage pos_image;
5885 
5886  // Substitute user ownship image if found
5887  if (m_pos_image_user)
5888  pos_image = m_pos_image_user->Copy();
5889  else if (SHIP_NORMAL == m_ownship_state)
5890  pos_image = m_pos_image_red->Copy();
5891  if (SHIP_LOWACCURACY == m_ownship_state)
5892  pos_image = m_pos_image_yellow->Copy();
5893  else if (SHIP_NORMAL != m_ownship_state)
5894  pos_image = m_pos_image_grey->Copy();
5895 
5896  // Substitute user ownship image if found
5897  if (m_pos_image_user) {
5898  pos_image = m_pos_image_user->Copy();
5899 
5900  if (SHIP_LOWACCURACY == m_ownship_state)
5901  pos_image = m_pos_image_user_yellow->Copy();
5902  else if (SHIP_NORMAL != m_ownship_state)
5903  pos_image = m_pos_image_user_grey->Copy();
5904  }
5905 
5906  img_height = pos_image.GetHeight();
5907 
5908  if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
5909  g_OwnShipIconType > 0) // use large ship
5910  {
5911  int ownShipWidth = 22; // Default values from s_ownship_icon
5912  int ownShipLength = 84;
5913  if (g_OwnShipIconType == 1) {
5914  ownShipWidth = pos_image.GetWidth();
5915  ownShipLength = pos_image.GetHeight();
5916  }
5917 
5918  float scale_factor_x, scale_factor_y;
5919  ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
5920  lShipMidPoint, GPSOffsetPixels, lGPSPoint,
5921  scale_factor_x, scale_factor_y);
5922 
5923  if (g_OwnShipIconType == 1) { // Scaled bitmap
5924  pos_image.Rescale(ownShipWidth * scale_factor_x,
5925  ownShipLength * scale_factor_y,
5926  wxIMAGE_QUALITY_HIGH);
5927  wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
5928  wxImage rot_image =
5929  pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
5930 
5931  // Simple sharpening algorithm.....
5932  for (int ip = 0; ip < rot_image.GetWidth(); ip++)
5933  for (int jp = 0; jp < rot_image.GetHeight(); jp++)
5934  if (rot_image.GetAlpha(ip, jp) > 64)
5935  rot_image.SetAlpha(ip, jp, 255);
5936 
5937  wxBitmap os_bm(rot_image);
5938 
5939  int w = os_bm.GetWidth();
5940  int h = os_bm.GetHeight();
5941  img_height = h;
5942 
5943  dc.DrawBitmap(os_bm, lShipMidPoint.x - w / 2, lShipMidPoint.y - h / 2,
5944  true);
5945 
5946  // Maintain dirty box,, missing in __WXMSW__ library
5947  dc.CalcBoundingBox(lShipMidPoint.x - w / 2, lShipMidPoint.y - h / 2);
5948  dc.CalcBoundingBox(lShipMidPoint.x - w / 2 + w,
5949  lShipMidPoint.y - h / 2 + h);
5950  }
5951 
5952  else if (g_OwnShipIconType == 2) { // Scaled Vector
5953  wxPoint ownship_icon[10];
5954 
5955  for (int i = 0; i < 10; i++) {
5956  int j = i * 2;
5957  float pxa = (float)(s_ownship_icon[j]);
5958  float pya = (float)(s_ownship_icon[j + 1]);
5959  pya *= scale_factor_y;
5960  pxa *= scale_factor_x;
5961 
5962  float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
5963  float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
5964 
5965  ownship_icon[i].x = (int)(px) + lShipMidPoint.x;
5966  ownship_icon[i].y = (int)(py) + lShipMidPoint.y;
5967  }
5968 
5969  wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), 1, wxPENSTYLE_SOLID);
5970  dc.SetPen(ppPen1);
5971  dc.SetBrush(wxBrush(ShipColor()));
5972 
5973  dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
5974 
5975  // draw reference point (midships) cross
5976  dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
5977  ownship_icon[7].y);
5978  dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
5979  ownship_icon[9].y);
5980  }
5981 
5982  img_height = ownShipLength * scale_factor_y;
5983 
5984  // Reference point, where the GPS antenna is
5985  int circle_rad = 3;
5986  if (m_pos_image_user) circle_rad = 1;
5987 
5988  dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
5989  dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
5990  dc.StrokeCircle(lGPSPoint.x, lGPSPoint.y, circle_rad);
5991  } else { // Fixed bitmap icon.
5992  /* non opengl, or suboptimal opengl via ocpndc: */
5993  wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
5994  wxImage rot_image =
5995  pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
5996 
5997  // Simple sharpening algorithm.....
5998  for (int ip = 0; ip < rot_image.GetWidth(); ip++)
5999  for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6000  if (rot_image.GetAlpha(ip, jp) > 64)
6001  rot_image.SetAlpha(ip, jp, 255);
6002 
6003  wxBitmap os_bm(rot_image);
6004 
6005  if (g_ShipScaleFactorExp > 1) {
6006  wxImage scaled_image = os_bm.ConvertToImage();
6007  double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6008  1.0; // soften the scale factor a bit
6009  os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6010  scaled_image.GetHeight() * factor,
6011  wxIMAGE_QUALITY_HIGH));
6012  }
6013  int w = os_bm.GetWidth();
6014  int h = os_bm.GetHeight();
6015  img_height = h;
6016 
6017  dc.DrawBitmap(os_bm, lShipMidPoint.x - w / 2, lShipMidPoint.y - h / 2,
6018  true);
6019 
6020  // Reference point, where the GPS antenna is
6021  int circle_rad = 3;
6022  if (m_pos_image_user) circle_rad = 1;
6023 
6024  dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6025  dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6026  dc.StrokeCircle(lShipMidPoint.x, lShipMidPoint.y, circle_rad);
6027 
6028  // Maintain dirty box,, missing in __WXMSW__ library
6029  dc.CalcBoundingBox(lShipMidPoint.x - w / 2, lShipMidPoint.y - h / 2);
6030  dc.CalcBoundingBox(lShipMidPoint.x - w / 2 + w,
6031  lShipMidPoint.y - h / 2 + h);
6032  }
6033  } // ownship draw
6034  }
6035 
6036  ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6037 }
6038 
6039 /* @ChartCanvas::CalcGridSpacing
6040  **
6041  ** Calculate the major and minor spacing between the lat/lon grid
6042  **
6043  ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6044  *window
6045  ** @param [w] MajorSpacing [float &] Major distance between grid lines
6046  ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6047  ** @return [void]
6048  */
6049 void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6050  float &MinorSpacing) {
6051  // table for calculating the distance between the grids
6052  // [0] view_scale ppm
6053  // [1] spacing between major grid lines in degrees
6054  // [2] spacing between minor grid lines in degrees
6055  const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6056  {.000001f, 45.0f, 15.0f},
6057  {.0002f, 30.0f, 10.0f},
6058  {.0003f, 10.0f, 2.0f},
6059  {.0008f, 5.0f, 1.0f},
6060  {.001f, 2.0f, 30.0f / 60.0f},
6061  {.003f, 1.0f, 20.0f / 60.0f},
6062  {.006f, 0.5f, 10.0f / 60.0f},
6063  {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6064  {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6065  {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6066  {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6067  {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6068  {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6069  {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6070  {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6071 
6072  unsigned int tabi;
6073  for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6074  if (view_scale_ppm < lltab[tabi][0]) break;
6075  MajorSpacing = lltab[tabi][1]; // major latitude distance
6076  MinorSpacing = lltab[tabi][2]; // minor latitude distance
6077  return;
6078 }
6079 /* @ChartCanvas::CalcGridText *************************************
6080  **
6081  ** Calculates text to display at the major grid lines
6082  **
6083  ** @param [r] latlon [float] latitude or longitude of grid line
6084  ** @param [r] spacing [float] distance between two major grid lines
6085  ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6086  **
6087  ** @return
6088  */
6089 
6090 wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6091  int deg = (int)fabs(latlon); // degrees
6092  float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6093  char postfix;
6094 
6095  // calculate postfix letter (NSEW)
6096  if (latlon > 0.0) {
6097  if (bPostfix) {
6098  postfix = 'N';
6099  } else {
6100  postfix = 'E';
6101  }
6102  } else if (latlon < 0.0) {
6103  if (bPostfix) {
6104  postfix = 'S';
6105  } else {
6106  postfix = 'W';
6107  }
6108  } else {
6109  postfix = ' '; // no postfix for equator and greenwich
6110  }
6111  // calculate text, display minutes only if spacing is smaller than one degree
6112 
6113  wxString ret;
6114  if (spacing >= 1.0) {
6115  ret.Printf(_T("%3d%c %c"), deg, 0x00b0, postfix);
6116  } else if (spacing >= (1.0 / 60.0)) {
6117  ret.Printf(_T("%3d%c%02.0f %c"), deg, 0x00b0, min, postfix);
6118  } else {
6119  ret.Printf(_T("%3d%c%02.2f %c"), deg, 0x00b0, min, postfix);
6120  }
6121 
6122  return ret;
6123 }
6124 
6125 /* @ChartCanvas::GridDraw *****************************************
6126  **
6127  ** Draws major and minor Lat/Lon Grid on the chart
6128  ** - distance between Grid-lm ines are calculated automatic
6129  ** - major grid lines will be across the whole chart window
6130  ** - minor grid lines will be 10 pixel at each edge of the chart window.
6131  **
6132  ** @param [w] dc [wxDC&] the wx drawing context
6133  **
6134  ** @return [void]
6135  ************************************************************************/
6136 void ChartCanvas::GridDraw(ocpnDC &dc) {
6137  if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6138 
6139  double nlat, elon, slat, wlon;
6140  float lat, lon;
6141  float dlon;
6142  float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6143  wxCoord w, h;
6144  wxPen GridPen(GetGlobalColor(_T ( "SNDG1" )), 1, wxPENSTYLE_SOLID);
6145  dc.SetPen(GridPen);
6146  dc.SetFont(*m_pgridFont);
6147  dc.SetTextForeground(GetGlobalColor(_T ( "SNDG1" )));
6148 
6149  w = m_canvas_width;
6150  h = m_canvas_height;
6151 
6152  GetCanvasPixPoint(0, 0, nlat,
6153  wlon); // get lat/lon of upper left point of the window
6154  GetCanvasPixPoint(w, h, slat,
6155  elon); // get lat/lon of lower right point of the window
6156  dlon =
6157  elon -
6158  wlon; // calculate how many degrees of longitude are shown in the window
6159  if (dlon < 0.0) // concider datum border at 180 degrees longitude
6160  {
6161  dlon = dlon + 360.0;
6162  }
6163  // calculate distance between latitude grid lines
6164  CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6165 
6166  // calculate position of first major latitude grid line
6167  lat = ceil(slat / gridlatMajor) * gridlatMajor;
6168 
6169  // Draw Major latitude grid lines and text
6170  while (lat < nlat) {
6171  wxPoint r;
6172  wxString st =
6173  CalcGridText(lat, gridlatMajor, true); // get text for grid line
6174  GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6175  dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6176  dc.DrawText(st, 0, r.y); // draw text
6177  lat = lat + gridlatMajor;
6178 
6179  if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6180  }
6181 
6182  // calculate position of first minor latitude grid line
6183  lat = ceil(slat / gridlatMinor) * gridlatMinor;
6184 
6185  // Draw minor latitude grid lines
6186  while (lat < nlat) {
6187  wxPoint r;
6188  GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6189  dc.DrawLine(0, r.y, 10, r.y, false);
6190  dc.DrawLine(w - 10, r.y, w, r.y, false);
6191  lat = lat + gridlatMinor;
6192  }
6193 
6194  // calculate distance between grid lines
6195  CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6196 
6197  // calculate position of first major latitude grid line
6198  lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6199 
6200  // draw major longitude grid lines
6201  for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6202  wxPoint r;
6203  wxString st = CalcGridText(lon, gridlonMajor, false);
6204  GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6205  dc.DrawLine(r.x, 0, r.x, h, false);
6206  dc.DrawText(st, r.x, 0);
6207  lon = lon + gridlonMajor;
6208  if (lon > 180.0) {
6209  lon = lon - 360.0;
6210  }
6211 
6212  if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6213  }
6214 
6215  // calculate position of first minor longitude grid line
6216  lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6217  // draw minor longitude grid lines
6218  for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6219  wxPoint r;
6220  GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6221  dc.DrawLine(r.x, 0, r.x, 10, false);
6222  dc.DrawLine(r.x, h - 10, r.x, h, false);
6223  lon = lon + gridlonMinor;
6224  if (lon > 180.0) {
6225  lon = lon - 360.0;
6226  }
6227  }
6228 }
6229 
6230 void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6231  if (0 ) {
6232  double blat, blon, tlat, tlon;
6233  wxPoint r;
6234 
6235  int x_origin = m_bDisplayGrid ? 60 : 20;
6236  int y_origin = m_canvas_height - 50;
6237 
6238  float dist;
6239  int count;
6240  wxPen pen1, pen2;
6241 
6242  if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6243  {
6244  dist = 10.0;
6245  count = 5;
6246  pen1 = wxPen(GetGlobalColor(_T ( "SNDG2" )), 3, wxPENSTYLE_SOLID);
6247  pen2 = wxPen(GetGlobalColor(_T ( "SNDG1" )), 3, wxPENSTYLE_SOLID);
6248  } else // Draw 1 mile scale as SCALEB10
6249  {
6250  dist = 1.0;
6251  count = 10;
6252  pen1 = wxPen(GetGlobalColor(_T ( "SCLBR" )), 3, wxPENSTYLE_SOLID);
6253  pen2 = wxPen(GetGlobalColor(_T ( "CHGRD" )), 3, wxPENSTYLE_SOLID);
6254  }
6255 
6256  GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6257  double rotation = -VPoint.rotation;
6258 
6259  ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6260  GetCanvasPointPix(tlat, tlon, &r);
6261  int l1 = (y_origin - r.y) / count;
6262 
6263  for (int i = 0; i < count; i++) {
6264  int y = l1 * i;
6265  if (i & 1)
6266  dc.SetPen(pen1);
6267  else
6268  dc.SetPen(pen2);
6269 
6270  dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6271  }
6272  } else {
6273  double blat, blon, tlat, tlon;
6274 
6275  int x_origin = 5.0 * GetPixPerMM();
6276  int chartbar_height = GetChartbarHeight();
6277  // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6278  // if (style->chartStatusWindowTransparent)
6279  // chartbar_height = 0;
6280  int y_origin = m_canvas_height - chartbar_height - 5;
6281 #ifdef __WXOSX__
6282  if (!g_bopengl)
6283  y_origin = m_canvas_height/GetContentScaleFactor() - chartbar_height - 5;
6284 #endif
6285 
6286  GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6287  GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6288 
6289  double d;
6290  ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6291  d /= 2;
6292 
6293  int unit = g_iDistanceFormat;
6294  if (d < .5 &&
6295  (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6296  unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6297 
6298  // nice number
6299  float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6300  float places = floor(logdist), rem = logdist - places;
6301  dist = pow(10, places);
6302 
6303  if (rem < .2)
6304  dist /= 5;
6305  else if (rem < .5)
6306  dist /= 2;
6307 
6308  wxString s = wxString::Format(_T("%g "), dist) + getUsrDistanceUnit(unit);
6309  wxPen pen1 = wxPen(GetGlobalColor(_T ( "UBLCK" )), 3, wxPENSTYLE_SOLID);
6310  double rotation = -VPoint.rotation;
6311 
6312  ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6313  &tlat, &tlon);
6314  wxPoint r;
6315  GetCanvasPointPix(tlat, tlon, &r);
6316  int l1 = r.x - x_origin;
6317 
6318  m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6319  12); // Store this for later reference
6320 
6321  dc.SetPen(pen1);
6322 
6323  dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6324  dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6325  dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6326 
6327  dc.SetFont(*m_pgridFont);
6328  dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
6329  int w, h;
6330  dc.GetTextExtent(s, &w, &h);
6331  dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6332  }
6333 }
6334 
6335 void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6336  // Constants?
6337  double da_min = 2.;
6338  double da_max = 6.;
6339  double ra_min = 0.;
6340  double ra_max = 40.;
6341 
6342  wxPen pen_save = dc.GetPen();
6343 
6344  wxDateTime now = wxDateTime::Now();
6345 
6346  dc.SetPen(pen);
6347 
6348  int x0, y0, x1, y1;
6349 
6350  x0 = x1 = x + radius; // Start point
6351  y0 = y1 = y;
6352  double angle = 0.;
6353  int i = 0;
6354 
6355  while (angle < 360.) {
6356  double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6357  angle += da;
6358 
6359  if (angle > 360.) angle = 360.;
6360 
6361  double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6362 
6363  double r;
6364  if (i & 1)
6365  r = radius + ra;
6366  else
6367  r = radius - ra;
6368 
6369  x1 = (int)(x + cos(angle * PI / 180.) * r);
6370  y1 = (int)(y + sin(angle * PI / 180.) * r);
6371 
6372  dc.DrawLine(x0, y0, x1, y1);
6373 
6374  x0 = x1;
6375  y0 = y1;
6376 
6377  i++;
6378  }
6379 
6380  dc.DrawLine(x + radius, y, x1, y1); // closure
6381 
6382  dc.SetPen(pen_save);
6383 }
6384 
6385 static bool bAnchorSoundPlaying = false;
6386 
6387 static void onAnchorSoundFinished(void *ptr) {
6388  g_anchorwatch_sound->UnLoad();
6389  bAnchorSoundPlaying = false;
6390 }
6391 
6392 void ChartCanvas::AlertDraw(ocpnDC &dc) {
6393  // Visual and audio alert for anchorwatch goes here
6394  bool play_sound = false;
6395  if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6396  if (AnchorAlertOn1) {
6397  wxPoint TargetPoint;
6398  GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
6399  &TargetPoint);
6400  JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6401  TargetPoint.y, 100);
6402  play_sound = true;
6403  }
6404  } else
6405  AnchorAlertOn1 = false;
6406 
6407  if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6408  if (AnchorAlertOn2) {
6409  wxPoint TargetPoint;
6410  GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
6411  &TargetPoint);
6412  JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6413  TargetPoint.y, 100);
6414  play_sound = true;
6415  }
6416  } else
6417  AnchorAlertOn2 = false;
6418 
6419  if (play_sound) {
6420  if(!bAnchorSoundPlaying) {
6421  auto cmd_sound = dynamic_cast<SystemCmdSound*>(g_anchorwatch_sound);
6422  if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6423  g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6424  if (g_anchorwatch_sound->IsOk()) {
6425  bAnchorSoundPlaying = true;
6426  g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6427  g_anchorwatch_sound->Play();
6428  }
6429  }
6430  }
6431 }
6432 
6433 void ChartCanvas::UpdateShips() {
6434  // Get the rectangle in the current dc which bounds the "ownship" symbol
6435 
6436  wxClientDC dc(this);
6437  if (!dc.IsOk()) return;
6438 
6439  wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6440  if (!test_bitmap.IsOk())
6441  return;
6442 
6443  wxMemoryDC temp_dc(test_bitmap);
6444 
6445  temp_dc.ResetBoundingBox();
6446  temp_dc.DestroyClippingRegion();
6447  temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6448 
6449  // Draw the ownship on the temp_dc
6450  ocpnDC ocpndc = ocpnDC(temp_dc);
6451  ShipDraw(ocpndc);
6452 
6453  if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6454  TrackPoint *p = g_pActiveTrack->GetLastPoint();
6455  if (p) {
6456  wxPoint px;
6457  GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6458  ocpndc.CalcBoundingBox(px.x, px.y);
6459  }
6460  }
6461 
6462  ship_draw_rect =
6463  wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6464  temp_dc.MaxY() - temp_dc.MinY());
6465 
6466  wxRect own_ship_update_rect = ship_draw_rect;
6467 
6468  if (!own_ship_update_rect.IsEmpty()) {
6469  // The required invalidate rectangle is the union of the last drawn
6470  // rectangle and this drawn rectangle
6471  own_ship_update_rect.Union(ship_draw_last_rect);
6472  own_ship_update_rect.Inflate(2);
6473  }
6474 
6475  if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6476 
6477  ship_draw_last_rect = ship_draw_rect;
6478 
6479  temp_dc.SelectObject(wxNullBitmap);
6480 }
6481 
6482 void ChartCanvas::UpdateAlerts() {
6483  // Get the rectangle in the current dc which bounds the detected Alert
6484  // targets
6485 
6486  // Use this dc
6487  wxClientDC dc(this);
6488 
6489  // Get dc boundary
6490  int sx, sy;
6491  dc.GetSize(&sx, &sy);
6492 
6493  // Need a bitmap
6494  wxBitmap test_bitmap(sx, sy, -1);
6495 
6496  // Create a memory DC
6497  wxMemoryDC temp_dc;
6498  temp_dc.SelectObject(test_bitmap);
6499 
6500  temp_dc.ResetBoundingBox();
6501  temp_dc.DestroyClippingRegion();
6502  temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6503 
6504  // Draw the Alert Targets on the temp_dc
6505  ocpnDC ocpndc = ocpnDC(temp_dc);
6506  AlertDraw(ocpndc);
6507 
6508  // Retrieve the drawing extents
6509  wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6510  temp_dc.MaxX() - temp_dc.MinX(),
6511  temp_dc.MaxY() - temp_dc.MinY());
6512 
6513  if (!alert_rect.IsEmpty())
6514  alert_rect.Inflate(2); // clear all drawing artifacts
6515 
6516  if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6517  // The required invalidate rectangle is the union of the last drawn
6518  // rectangle and this drawn rectangle
6519  wxRect alert_update_rect = alert_draw_rect;
6520  alert_update_rect.Union(alert_rect);
6521 
6522  // Invalidate the rectangular region
6523  RefreshRect(alert_update_rect, false);
6524  }
6525 
6526  // Save this rectangle for next time
6527  alert_draw_rect = alert_rect;
6528 
6529  temp_dc.SelectObject(wxNullBitmap); // clean up
6530 }
6531 
6532 void ChartCanvas::UpdateAIS() {
6533  if (!g_pAIS) return;
6534 
6535  // Get the rectangle in the current dc which bounds the detected AIS targets
6536 
6537  // Use this dc
6538  wxClientDC dc(this);
6539 
6540  // Get dc boundary
6541  int sx, sy;
6542  dc.GetSize(&sx, &sy);
6543 
6544  wxRect ais_rect;
6545 
6546  // How many targets are there?
6547 
6548  // If more than "some number", it will be cheaper to refresh the entire
6549  // screen than to build update rectangles for each target.
6550  if (g_pAIS->GetTargetList().size() > 10) {
6551  ais_rect = wxRect(0, 0, sx, sy); // full screen
6552  } else {
6553  // Need a bitmap
6554  wxBitmap test_bitmap(sx, sy, -1);
6555 
6556  // Create a memory DC
6557  wxMemoryDC temp_dc;
6558  temp_dc.SelectObject(test_bitmap);
6559 
6560  temp_dc.ResetBoundingBox();
6561  temp_dc.DestroyClippingRegion();
6562  temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6563 
6564  // Draw the AIS Targets on the temp_dc
6565  ocpnDC ocpndc = ocpnDC(temp_dc);
6566  AISDraw(ocpndc, GetVP(), this);
6567  AISDrawAreaNotices(ocpndc, GetVP(), this);
6568 
6569  // Retrieve the drawing extents
6570  ais_rect =
6571  wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6572  temp_dc.MaxY() - temp_dc.MinY());
6573 
6574  if (!ais_rect.IsEmpty())
6575  ais_rect.Inflate(2); // clear all drawing artifacts
6576 
6577  temp_dc.SelectObject(wxNullBitmap); // clean up
6578  }
6579 
6580  if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6581  // The required invalidate rectangle is the union of the last drawn
6582  // rectangle and this drawn rectangle
6583  wxRect ais_update_rect = ais_draw_rect;
6584  ais_update_rect.Union(ais_rect);
6585 
6586  // Invalidate the rectangular region
6587  RefreshRect(ais_update_rect, false);
6588  }
6589 
6590  // Save this rectangle for next time
6591  ais_draw_rect = ais_rect;
6592 }
6593 
6594 void ChartCanvas::ToggleCPAWarn() {
6595  if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6596  wxString mess;
6597  if (g_bCPAWarn) {
6598  g_bTCPA_Max = true;
6599  mess = _("ON");
6600  } else {
6601  g_bTCPA_Max = false;
6602  mess = _("OFF");
6603  }
6604  // Print to status bar if available.
6605  if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
6606  parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
6607  } else {
6608  if (!g_AisFirstTimeUse) {
6609  OCPNMessageBox(this,
6610  _("CPA Alarm is switched") + _T(" ") + mess.MakeLower(),
6611  _("CPA") + _T(" ") + mess, 4, 4);
6612  }
6613  }
6614 }
6615 
6616 void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
6617 
6618 void ChartCanvas::OnSize(wxSizeEvent &event) {
6619  if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1))
6620  return;
6621  GetClientSize(&m_canvas_width, &m_canvas_height);
6622 
6623 #ifdef __WXOSX__
6624  // Support scaled HDPI displays.
6625  m_displayScale = GetContentScaleFactor();
6626 #endif
6627 
6628 
6629  m_canvas_width *= m_displayScale;
6630  m_canvas_height *= m_displayScale;
6631 
6632  // Resize the current viewport
6633  VPoint.pix_width = m_canvas_width;
6634  VPoint.pix_height = m_canvas_height;
6635 
6636  // Get some canvas metrics
6637 
6638  // Rescale to current value, in order to rebuild VPoint data
6639  // structures for new canvas size
6640  SetVPScale(GetVPScale());
6641 
6642  m_absolute_min_scale_ppm =
6643  m_canvas_width /
6644  (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
6645 
6646  // Inform the parent Frame that I am being resized...
6647  gFrame->ProcessCanvasResize();
6648 
6649  // if MUIBar is active, size the bar
6650  // if(g_useMUI && !m_muiBar){ // rebuild if
6651  // necessary
6652  // m_muiBar = new MUIBar(this, wxHORIZONTAL);
6653  // m_muiBarHOSize = m_muiBar->GetSize();
6654  // }
6655 
6656  if (m_muiBar) {
6657  SetMUIBarPosition();
6658  UpdateFollowButtonState();
6659  m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
6660  }
6661 
6662  // Set up the scroll margins
6663  xr_margin = m_canvas_width * 95 / 100;
6664  xl_margin = m_canvas_width * 5 / 100;
6665  yt_margin = m_canvas_height * 5 / 100;
6666  yb_margin = m_canvas_height * 95 / 100;
6667 
6668  if (m_pQuilt)
6669  m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
6670 
6671  // Resize the scratch BM
6672  delete pscratch_bm;
6673  pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
6674  m_brepaint_piano = true;
6675 
6676  // Resize the Route Calculation BM
6677  m_dc_route.SelectObject(wxNullBitmap);
6678  delete proute_bm;
6679  proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
6680  m_dc_route.SelectObject(*proute_bm);
6681 
6682  // Resize the saved Bitmap
6683  m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
6684 
6685  // Resize the working Bitmap
6686  m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
6687 
6688  // Rescale again, to capture all the changes for new canvas size
6689  SetVPScale(GetVPScale());
6690 
6691 #ifdef ocpnUSE_GL
6692  if (/*g_bopengl &&*/ m_glcc) {
6693  //FIXME (dave) This can go away?
6694  m_glcc->OnSize(event);
6695  }
6696 #endif
6697 
6698  FormatPianoKeys();
6699  // Invalidate the whole window
6700  ReloadVP();
6701 }
6702 
6703 void ChartCanvas::ProcessNewGUIScale() {
6704  //m_muiBar->Hide();
6705  delete m_muiBar;
6706  m_muiBar = 0;
6707 
6708  CreateMUIBar();
6709 }
6710 
6711 void ChartCanvas::CreateMUIBar() {
6712  if (g_useMUI && !m_muiBar) { // rebuild if necessary
6713 
6714  // We need to update the m_bENCGroup flag, at least for the initial creation
6715  // of a MUIBar
6716  if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
6717 
6718  m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
6719  m_muiBar->SetColorScheme(m_cs);
6720  m_muiBarHOSize = m_muiBar->m_size;
6721  }
6722 
6723  if (m_muiBar) {
6724  SetMUIBarPosition();
6725  UpdateFollowButtonState();
6726  m_muiBar->UpdateDynamicValues();
6727  m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
6728  }
6729 }
6730 
6731 void ChartCanvas::SetMUIBarPosition() {
6732  // if MUIBar is active, size the bar
6733  if (m_muiBar) {
6734  // We estimate the piano width based on the canvas width
6735  int pianoWidth = GetClientSize().x * 0.6f;
6736  // If the piano already exists, we can use its exact width
6737  //if(m_Piano)
6738  //pianoWidth = m_Piano->GetWidth();
6739 
6740  if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
6741  if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
6742  delete m_muiBar;
6743  m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
6744  m_muiBar->SetColorScheme(m_cs);
6745  }
6746  }
6747 
6748  if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
6749  if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
6750  delete m_muiBar;
6751  m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
6752  m_muiBar->SetColorScheme(m_cs);
6753  }
6754  }
6755 
6756  m_muiBar->SetBestPosition();
6757  }
6758 }
6759 
6760 void ChartCanvas::DestroyMuiBar() {
6761  if (m_muiBar) {
6762  delete m_muiBar;
6763  m_muiBar = NULL;
6764  }
6765 }
6766 
6767 void ChartCanvas::ShowCompositeInfoWindow(int x, int n_charts, int scale,
6768  const std::vector<int> &index_vector) {
6769  if (n_charts > 0) {
6770  if (NULL == m_pCIWin) {
6771  m_pCIWin = new ChInfoWin(this);
6772  m_pCIWin->Hide();
6773  }
6774 
6775  if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
6776  wxString s;
6777 
6778  s = _("Composite of ");
6779 
6780  wxString s1;
6781  s1.Printf( "%d ", n_charts);
6782  if (n_charts > 1)
6783  s1 += _("charts");
6784  else
6785  s1 += _("chart");
6786  s += s1;
6787  s += '\n';
6788 
6789  s1.Printf(_("Chart scale"));
6790  s1 += ": ";
6791  wxString s2;
6792  s2.Printf("1:%d\n", scale);
6793  s += s1;
6794  s += s2;
6795 
6796  s1 = _("Zoom in for more information");
6797  s += s1;
6798  s += '\n';
6799 
6800  int char_width = s1.Length();
6801  int char_height = 3;
6802 
6803  if (g_bChartBarEx) {
6804  s += '\n';
6805  int j = 0;
6806  for (int i : index_vector ) {
6807  const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
6808  wxString path = cte.GetFullSystemPath();
6809  s += path;
6810  s += '\n';
6811  char_height++;
6812  char_width = wxMax(char_width, path.Length());
6813  if (j++ >= 9) break;
6814  }
6815  if (j >= 9) {
6816  s += " .\n .\n .\n";
6817  char_height += 3;
6818  }
6819  s += '\n';
6820  char_height += 1;
6821 
6822  char_width += 4; // Fluff
6823  }
6824 
6825  m_pCIWin->SetString(s);
6826 
6827  m_pCIWin->FitToChars(char_width, char_height);
6828 
6829  wxPoint p;
6830  p.x = x / GetContentScaleFactor();
6831  if ((p.x + m_pCIWin->GetWinSize().x) >
6832  (m_canvas_width / GetContentScaleFactor()))
6833  p.x = ((m_canvas_width / GetContentScaleFactor()) -
6834  m_pCIWin->GetWinSize().x) /
6835  2; // centered
6836 
6837  p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
6838  4 - m_pCIWin->GetWinSize().y;
6839 
6840  m_pCIWin->dbIndex = 0;
6841  m_pCIWin->chart_scale = 0;
6842  m_pCIWin->SetPosition(p);
6843  m_pCIWin->SetBitmap();
6844  m_pCIWin->Refresh();
6845  m_pCIWin->Show();
6846  }
6847  } else {
6848  HideChartInfoWindow();
6849  }
6850 }
6851 
6852 void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
6853  if (dbIndex >= 0) {
6854  if (NULL == m_pCIWin) {
6855  m_pCIWin = new ChInfoWin(this);
6856  m_pCIWin->Hide();
6857  }
6858 
6859  if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
6860  wxString s;
6861  ChartBase *pc = NULL;
6862 
6863  // TOCTOU race but worst case will reload chart.
6864  // need to lock it or the background spooler may evict charts in
6865  // OpenChartFromDBAndLock
6866  if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
6867  pc = ChartData->OpenChartFromDBAndLock(
6868  dbIndex, FULL_INIT); // this must come from cache
6869 
6870  int char_width, char_height;
6871  s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
6872  if (pc) ChartData->UnLockCacheChart(dbIndex);
6873 
6874  m_pCIWin->SetString(s);
6875  m_pCIWin->FitToChars(char_width, char_height);
6876 
6877  wxPoint p;
6878  p.x = x / GetContentScaleFactor();
6879  if ((p.x + m_pCIWin->GetWinSize().x) > (m_canvas_width / GetContentScaleFactor()))
6880  p.x = ((m_canvas_width / GetContentScaleFactor())
6881  - m_pCIWin->GetWinSize().x) / 2; // centered
6882 
6883  p.y =
6884  (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor()
6885  - 4 - m_pCIWin->GetWinSize().y;
6886 
6887  m_pCIWin->dbIndex = dbIndex;
6888  m_pCIWin->SetPosition(p);
6889  m_pCIWin->SetBitmap();
6890  m_pCIWin->Refresh();
6891  m_pCIWin->Show();
6892  }
6893  } else {
6894  HideChartInfoWindow();
6895  }
6896 }
6897 
6898 void ChartCanvas::HideChartInfoWindow(void) {
6899  if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
6900  m_pCIWin->Hide();
6901  m_pCIWin->Destroy();
6902  m_pCIWin = NULL;
6903 
6904 #ifdef __ANDROID__
6905  androidForceFullRepaint();
6906 #endif
6907  }
6908 }
6909 
6910 void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
6911  wxMouseEvent ev(wxEVT_MOTION);
6912  ev.m_x = mouse_x;
6913  ev.m_y = mouse_y;
6914  ev.m_leftDown = mouse_leftisdown;
6915 
6916  wxEvtHandler *evthp = GetEventHandler();
6917 
6918  ::wxPostEvent(evthp, ev);
6919 }
6920 
6921 void ChartCanvas::MovementTimerEvent(wxTimerEvent &) { DoTimedMovement(); }
6922 
6923 void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
6924 
6925 bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
6926  int delta) {
6927  if (m_disable_edge_pan) return false;
6928 
6929  bool bft = false;
6930  int pan_margin = m_canvas_width * margin / 100;
6931  int pan_timer_set = 200;
6932  double pan_delta = GetVP().pix_width * delta / 100;
6933  int pan_x = 0;
6934  int pan_y = 0;
6935 
6936  if (x > m_canvas_width - pan_margin) {
6937  bft = true;
6938  pan_x = pan_delta;
6939  }
6940 
6941  else if (x < pan_margin) {
6942  bft = true;
6943  pan_x = -pan_delta;
6944  }
6945 
6946  if (y < pan_margin) {
6947  bft = true;
6948  pan_y = -pan_delta;
6949  }
6950 
6951  else if (y > m_canvas_height - pan_margin) {
6952  bft = true;
6953  pan_y = pan_delta;
6954  }
6955 
6956  // Of course, if dragging, and the mouse left button is not down, we must
6957  // stop the event injection
6958  if (bdragging) {
6959  if (!g_btouch) {
6960  wxMouseState state = ::wxGetMouseState();
6961 #if wxCHECK_VERSION(3, 0, 0)
6962  if (!state.LeftIsDown())
6963 #else
6964  if (!state.LeftDown())
6965 #endif
6966  bft = false;
6967  }
6968  }
6969  if ((bft) && !pPanTimer->IsRunning()) {
6970  PanCanvas(pan_x, pan_y);
6971  pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
6972  return true;
6973  }
6974 
6975  // This mouse event must not be due to pan timer event injector
6976  // Mouse is out of the pan zone, so prevent any orphan event injection
6977  if ((!bft) && pPanTimer->IsRunning()) {
6978  pPanTimer->Stop();
6979  }
6980 
6981  return (false);
6982 }
6983 
6984 // Look for waypoints at the current position.
6985 // Used to determine what a mouse event should act on.
6986 
6987 void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
6988  bool setBeingEdited) {
6989  m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
6990  m_pRoutePointEditTarget = NULL;
6991  m_pFoundPoint = NULL;
6992 
6993  SelectItem *pFind = NULL;
6994  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
6995  SelectableItemList SelList = pSelect->FindSelectionList(
6996  ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
6997  wxSelectableItemListNode *node = SelList.GetFirst();
6998  while (node) {
6999  pFind = node->GetData();
7000 
7001  RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7002 
7003  // Get an array of all routes using this point
7004  m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7005 
7006  // Use route array to determine actual visibility for the point
7007  bool brp_viz = false;
7008  if (m_pEditRouteArray) {
7009  for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7010  Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7011  if (pr->IsVisible()) {
7012  brp_viz = true;
7013  break;
7014  }
7015  }
7016  } else
7017  brp_viz = frp->IsVisible(); // isolated point
7018 
7019  if (brp_viz) {
7020  // Use route array to rubberband all affected routes
7021  if (m_pEditRouteArray) // Editing Waypoint as part of route
7022  {
7023  for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7024  Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7025  pr->m_bIsBeingEdited = setBeingEdited;
7026  }
7027  m_bRouteEditing = setBeingEdited;
7028  } else // editing Mark
7029  {
7030  frp->m_bRPIsBeingEdited = setBeingEdited;
7031  m_bMarkEditing = setBeingEdited;
7032  }
7033 
7034  m_pRoutePointEditTarget = frp;
7035  m_pFoundPoint = pFind;
7036  break; // out of the while(node)
7037  }
7038 
7039  node = node->GetNext();
7040  } // while (node)
7041 }
7042 
7043 void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7044  if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7045  singleClickEventIsValid = false;
7046  m_DoubleClickTimer->Stop();
7047 }
7048 
7049 bool leftIsDown;
7050 
7051 bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7052  if (!m_bChartDragging && !m_bDrawingRoute) {
7053  if (m_Compass && m_Compass->IsShown() &&
7054  m_Compass->GetRect().Contains(event.GetPosition())) {
7055  if (m_Compass->MouseEvent(event)) {
7056  cursor_region = CENTER;
7057  if (!g_btouch) SetCanvasCursor(event);
7058  return true;
7059  }
7060  }
7061 
7062  if (MouseEventToolbar(event))
7063  return true;
7064 
7065  if (MouseEventChartBar(event))
7066  return true;
7067 
7068  if (MouseEventMUIBar(event))
7069  return true;
7070 
7071  if (MouseEventIENCBar(event))
7072  return true;
7073 
7074  }
7075  return false;
7076 }
7077 
7078 bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7079  if (!g_bShowChartBar) return false;
7080 
7081  if (!m_Piano->MouseEvent(event)) return false;
7082 
7083  cursor_region = CENTER;
7084  if (!g_btouch) SetCanvasCursor(event);
7085  return true;
7086 }
7087 
7088 bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7089  if (!IsPrimaryCanvas())
7090  return false;
7091 
7092  if (g_MainToolbar) {
7093  if (!g_MainToolbar->MouseEvent(event))
7094  return false;
7095  else
7096  g_MainToolbar->RefreshToolbar();
7097  }
7098 
7099  cursor_region = CENTER;
7100  if (!g_btouch) SetCanvasCursor(event);
7101  return true;
7102 }
7103 
7104 bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7105  if (!IsPrimaryCanvas())
7106  return false;
7107 
7108  if (g_iENCToolbar) {
7109  if (!g_iENCToolbar->MouseEvent(event))
7110  return false;
7111  else {
7112  g_iENCToolbar->RefreshToolbar();
7113  return true;
7114  }
7115  }
7116  return false;
7117 }
7118 
7119 bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7120  if (m_muiBar) {
7121  if (!m_muiBar->MouseEvent(event)) return false;
7122  }
7123 
7124  cursor_region = CENTER;
7125  if (!g_btouch) SetCanvasCursor(event);
7126  return true;
7127 }
7128 
7129 bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7130  int x, y;
7131 
7132  bool bret = false;
7133 
7134  event.GetPosition(&x, &y);
7135 
7136  x *= m_displayScale;
7137  y *= m_displayScale;
7138 
7139  m_MouseDragging = event.Dragging();
7140 
7141  // Some systems produce null drag events, where the pointer position has not
7142  // changed from the previous value. Detect this case, and abort further
7143  // processing (FS#1748)
7144 #ifdef __WXMSW__
7145  if (event.Dragging()) {
7146  if ((x == mouse_x) && (y == mouse_y)) return true;
7147  }
7148 #endif
7149 
7150  mouse_x = x;
7151  mouse_y = y;
7152  mouse_leftisdown = event.LeftDown();
7153  GetCanvasPixPoint(x, y, m_cursor_lat, m_cursor_lon);
7154 
7155  // Establish the event region
7156  cursor_region = CENTER;
7157 
7158  int chartbar_height = GetChartbarHeight();
7159 
7160  if (m_Compass && m_Compass->IsShown() &&
7161  m_Compass->GetRect().Contains(event.GetPosition())) {
7162  cursor_region = CENTER;
7163  } else if (x > xr_margin) {
7164  cursor_region = MID_RIGHT;
7165  } else if (x < xl_margin) {
7166  cursor_region = MID_LEFT;
7167  } else if (y > yb_margin - chartbar_height &&
7168  y < m_canvas_height - chartbar_height) {
7169  cursor_region = MID_TOP;
7170  } else if (y < yt_margin) {
7171  cursor_region = MID_BOT;
7172  } else {
7173  cursor_region = CENTER;
7174  }
7175 
7176  if (!g_btouch) SetCanvasCursor(event);
7177 
7178  // Protect from leftUp's coming from event handlers in child
7179  // windows who return focus to the canvas.
7180  leftIsDown = event.LeftDown();
7181 
7182 #ifndef __WXOSX__
7183  if (event.LeftDown()) {
7184  if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7185  // The menu bar is temporarily visible due to alt having been pressed.
7186  // Clicking will hide it, and do nothing else.
7187  g_bTempShowMenuBar = false;
7188  parent_frame->ApplyGlobalSettings(false);
7189  return (true);
7190  }
7191  }
7192 #endif
7193 
7194  // Update modifiers here; some window managers never send the key event
7195  m_modkeys = 0;
7196  if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7197  if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7198 
7199 #ifdef __WXMSW__
7200  // TODO Test carefully in other platforms, remove ifdef....
7201  if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7202  if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7203 #endif
7204 
7205  if (g_pi_manager)
7206  if (g_pi_manager->SendMouseEventToPlugins(event))
7207  return (true); // PlugIn did something, and does not want the canvas to
7208  // do anything else
7209 
7210  // Capture LeftUp's and time them, unless it already came from the timer.
7211 
7212  if (b_handle_dclick && event.LeftUp() && !singleClickEventIsValid) {
7213  // Ignore the second LeftUp after the DClick.
7214  if (m_DoubleClickTimer->IsRunning()) {
7215  m_DoubleClickTimer->Stop();
7216  return (true);
7217  }
7218 
7219  // Save the event for later running if there is no DClick.
7220  m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7221  singleClickEvent = event;
7222  singleClickEventIsValid = true;
7223  return (true);
7224  }
7225 
7226  // This logic is necessary on MSW to handle the case where
7227  // a context (right-click) menu is dismissed without action
7228  // by clicking on the chart surface.
7229  // We need to avoid an unintentional pan by eating some clicks...
7230 #ifdef __WXMSW__
7231  if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7232  if (g_click_stop > 0) {
7233  g_click_stop--;
7234  return (true);
7235  }
7236  }
7237 #endif
7238 
7239  // Kick off the Rotation control timer
7240  if (GetUpMode() == COURSE_UP_MODE) {
7241  m_b_rot_hidef = false;
7242  pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7243  } else
7244  pRotDefTimer->Stop();
7245 
7246  // Retrigger the route leg / AIS target popup timer
7247  bool bRoll = !g_btouch;
7248 #ifdef __ANDROID__
7249  bRoll = g_bRollover;
7250 #endif
7251  if (bRoll) {
7252  if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7253  (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7254  (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7255  m_RolloverPopupTimer.Start(
7256  10,
7257  wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7258  else
7259  m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7260  }
7261 
7262  // Retrigger the cursor tracking timer
7263  pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7264 
7265 // Show cursor position on Status Bar, if present
7266 // except for GTK, under which status bar updates are very slow
7267 // due to Update() call.
7268 // In this case, as a workaround, update the status window
7269 // after an interval timer (pCurTrackTimer) pops, which will happen
7270 // whenever the mouse has stopped moving for specified interval.
7271 // See the method OnCursorTrackTimerEvent()
7272 #if !defined(__WXGTK__) && !defined(__WXQT__)
7273  SetCursorStatus(m_cursor_lat, m_cursor_lon);
7274 #endif
7275 
7276  // Send the current cursor lat/lon to all PlugIns requesting it
7277  if (g_pi_manager) {
7278  // Occasionally, MSW will produce nonsense events on right click....
7279  // This results in an error in cursor geo position, so we skip this case
7280  if ((x >= 0) && (y >= 0))
7281  g_pi_manager->SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7282  }
7283 
7284  if (!g_btouch) {
7285  if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7286  wxPoint p = ClientToScreen(wxPoint(x, y));
7287  }
7288  }
7289 
7290  if (1 ) {
7291  // Route Creation Rubber Banding
7292  if (m_routeState >= 2) {
7293  r_rband.x = x;
7294  r_rband.y = y;
7295  m_bDrawingRoute = true;
7296 
7297  if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7298  Refresh(false);
7299  }
7300 
7301  // Measure Tool Rubber Banding
7302  if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7303  r_rband.x = x;
7304  r_rband.y = y;
7305  m_bDrawingRoute = true;
7306 
7307  if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7308  Refresh(false);
7309  }
7310  }
7311  return bret;
7312 }
7313 
7314 void ChartCanvas::CallPopupMenu(int x, int y) {
7315  int mx, my;
7316  mx = x;
7317  my = y;
7318 
7319  last_drag.x = mx;
7320  last_drag.y = my;
7321  if (m_routeState) { // creating route?
7322  InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
7323  return;
7324  }
7325  // General Right Click
7326  // Look for selectable objects
7327  double slat, slon;
7328  slat = m_cursor_lat;
7329  slon = m_cursor_lon;
7330 
7331 #if defined(__WXMAC__) || defined(__ANDROID__)
7332  wxScreenDC sdc;
7333  ocpnDC dc(sdc);
7334 #else
7335  wxClientDC cdc(GetParent());
7336  ocpnDC dc(cdc);
7337 #endif
7338 
7339  SelectItem *pFindAIS;
7340  SelectItem *pFindRP;
7341  SelectItem *pFindRouteSeg;
7342  SelectItem *pFindTrackSeg;
7343  SelectItem *pFindCurrent = NULL;
7344  SelectItem *pFindTide = NULL;
7345 
7346  // Deselect any current objects
7347  if (m_pSelectedRoute) {
7348  m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7349  m_pSelectedRoute->DeSelectRoute();
7350 #ifdef ocpnUSE_GL
7351  if (g_bopengl && m_glcc) {
7352  InvalidateGL();
7353  Update();
7354  } else
7355 #endif
7356  RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7357  }
7358 
7359  if (m_pFoundRoutePoint) {
7360  m_pFoundRoutePoint->m_bPtIsSelected = false;
7361  RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
7362  RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
7363  }
7364 
7367  if (g_btouch && m_pRoutePointEditTarget) {
7368  m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7369  m_pRoutePointEditTarget->m_bPtIsSelected = false;
7370  RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7371  }
7372 
7373  // Get all the selectable things at the cursor
7374  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7375  pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7376  pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7377  pFindRouteSeg =
7378  pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7379  pFindTrackSeg =
7380  pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7381 
7382  if (m_bShowCurrent)
7383  pFindCurrent =
7384  pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7385 
7386  if (m_bShowTide) // look for tide stations
7387  pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7388 
7389  int seltype = 0;
7390 
7391  // Try for AIS targets first
7392  if (pFindAIS) {
7393  m_FoundAIS_MMSI = pFindAIS->GetUserData();
7394 
7395  // Make sure the target data is available
7396  if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
7397  seltype |= SELTYPE_AISTARGET;
7398  }
7399 
7400  // Now the various Route Parts
7401 
7402  m_pFoundRoutePoint = NULL;
7403  if (pFindRP) {
7404  RoutePoint *pFirstVizPoint = NULL;
7405  RoutePoint *pFoundActiveRoutePoint = NULL;
7406  RoutePoint *pFoundVizRoutePoint = NULL;
7407  Route *pSelectedActiveRoute = NULL;
7408  Route *pSelectedVizRoute = NULL;
7409 
7410  // There is at least one routepoint, so get the whole list
7411  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7412  SelectableItemList SelList =
7413  pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7414  wxSelectableItemListNode *node = SelList.GetFirst();
7415  while (node) {
7416  SelectItem *pFindSel = node->GetData();
7417 
7418  RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7419 
7420  // Get an array of all routes using this point
7421  wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7422 
7423  // Use route array (if any) to determine actual visibility for this point
7424  bool brp_viz = false;
7425  if (proute_array) {
7426  for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7427  Route *pr = (Route *)proute_array->Item(ir);
7428  if (pr->IsVisible()) {
7429  brp_viz = true;
7430  break;
7431  }
7432  }
7433  if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7434  // but still exists as a waypoint
7435  brp_viz = prp->IsVisible(); // so treat as isolated point
7436 
7437  } else
7438  brp_viz = prp->IsVisible(); // isolated point
7439 
7440  if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7441 
7442  // Use route array to choose the appropriate route
7443  // Give preference to any active route, otherwise select the first visible
7444  // route in the array for this point
7445  m_pSelectedRoute = NULL;
7446  if (proute_array) {
7447  for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7448  Route *pr = (Route *)proute_array->Item(ir);
7449  if (pr->m_bRtIsActive) {
7450  pSelectedActiveRoute = pr;
7451  pFoundActiveRoutePoint = prp;
7452  break;
7453  }
7454  }
7455 
7456  if (NULL == pSelectedVizRoute) {
7457  for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7458  Route *pr = (Route *)proute_array->Item(ir);
7459  if (pr->IsVisible()) {
7460  pSelectedVizRoute = pr;
7461  pFoundVizRoutePoint = prp;
7462  break;
7463  }
7464  }
7465  }
7466 
7467  delete proute_array;
7468  }
7469 
7470  node = node->GetNext();
7471  }
7472 
7473  // Now choose the "best" selections
7474  if (pFoundActiveRoutePoint) {
7475  m_pFoundRoutePoint = pFoundActiveRoutePoint;
7476  m_pSelectedRoute = pSelectedActiveRoute;
7477  } else if (pFoundVizRoutePoint) {
7478  m_pFoundRoutePoint = pFoundVizRoutePoint;
7479  m_pSelectedRoute = pSelectedVizRoute;
7480  } else
7481  // default is first visible point in list
7482  m_pFoundRoutePoint = pFirstVizPoint;
7483 
7484  if (m_pSelectedRoute) {
7485  if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7486  } else if (m_pFoundRoutePoint)
7487  seltype |= SELTYPE_MARKPOINT;
7488 
7489  // Highlite the selected point, to verify the proper right click
7490  // selection
7491  if (m_pFoundRoutePoint) {
7492  m_pFoundRoutePoint->m_bPtIsSelected = true;
7493  wxRect wp_rect;
7494  RoutePointGui(*m_pFoundRoutePoint).CalculateDCRect(m_dc_route, this, &wp_rect);
7495  RefreshRect(wp_rect, true);
7496  }
7497  }
7498 
7499  // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7500  // routes But call the popup handler with identifier appropriate to the type
7501  if (pFindRouteSeg) // there is at least one select item
7502  {
7503  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7504  SelectableItemList SelList =
7505  pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7506 
7507  if (NULL == m_pSelectedRoute) // the case where a segment only is selected
7508  {
7509  // Choose the first visible route containing segment in the list
7510  wxSelectableItemListNode *node = SelList.GetFirst();
7511  while (node) {
7512  SelectItem *pFindSel = node->GetData();
7513 
7514  Route *pr = (Route *)pFindSel->m_pData3;
7515  if (pr->IsVisible()) {
7516  m_pSelectedRoute = pr;
7517  break;
7518  }
7519  node = node->GetNext();
7520  }
7521  }
7522 
7523  if (m_pSelectedRoute) {
7524  if (NULL == m_pFoundRoutePoint)
7525  m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7526 
7527  m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7528  if (m_pSelectedRoute->m_bRtIsSelected) {
7529 #ifdef ocpnUSE_GL
7530  if (g_bopengl && m_glcc) {
7531  InvalidateGL();
7532  Update();
7533  } else
7534 #endif
7535  RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7536  }
7537 
7538  seltype |= SELTYPE_ROUTESEGMENT;
7539  }
7540  }
7541 
7542  if (pFindTrackSeg) {
7543  m_pSelectedTrack = NULL;
7544  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7545  SelectableItemList SelList =
7546  pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7547 
7548  // Choose the first visible track containing segment in the list
7549  wxSelectableItemListNode *node = SelList.GetFirst();
7550  while (node) {
7551  SelectItem *pFindSel = node->GetData();
7552 
7553  Track *pt = (Track *)pFindSel->m_pData3;
7554  if (pt->IsVisible()) {
7555  m_pSelectedTrack = pt;
7556  break;
7557  }
7558  node = node->GetNext();
7559  }
7560 
7561  if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7562  }
7563 
7564  bool bseltc = false;
7565  // if(0 == seltype)
7566  {
7567  if (pFindCurrent) {
7568  // There may be multiple current entries at the same point.
7569  // For example, there often is a current substation (with directions
7570  // specified) co-located with its master. We want to select the
7571  // substation, so that the direction will be properly indicated on the
7572  // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
7573  // substation)
7574  IDX_entry *pIDX_best_candidate;
7575 
7576  SelectItem *pFind = NULL;
7577  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7578  SelectableItemList SelList = pSelectTC->FindSelectionList(
7579  ctx, m_cursor_lat, m_cursor_lon, SELTYPE_CURRENTPOINT);
7580 
7581  // Default is first entry
7582  wxSelectableItemListNode *node = SelList.GetFirst();
7583  pFind = node->GetData();
7584  pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7585 
7586  if (SelList.GetCount() > 1) {
7587  node = node->GetNext();
7588  while (node) {
7589  pFind = node->GetData();
7590  IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
7591  if (pIDX_candidate->IDX_type == 'c') {
7592  pIDX_best_candidate = pIDX_candidate;
7593  break;
7594  }
7595 
7596  node = node->GetNext();
7597  } // while (node)
7598  } else {
7599  wxSelectableItemListNode *node = SelList.GetFirst();
7600  pFind = node->GetData();
7601  pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7602  }
7603 
7604  m_pIDXCandidate = pIDX_best_candidate;
7605 
7606  if (0 == seltype) {
7607  DrawTCWindow(x, y, (void *)pIDX_best_candidate);
7608  Refresh(false);
7609  bseltc = true;
7610  } else
7611  seltype |= SELTYPE_CURRENTPOINT;
7612  }
7613 
7614  else if (pFindTide) {
7615  m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
7616 
7617  if (0 == seltype) {
7618  DrawTCWindow(x, y, (void *)pFindTide->m_pData1);
7619  Refresh(false);
7620  bseltc = true;
7621  } else
7622  seltype |= SELTYPE_TIDEPOINT;
7623  }
7624  }
7625 
7626  if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7627 
7628  if (!bseltc) {
7629  InvokeCanvasMenu(x, y, seltype);
7630 
7631  // Clean up if not deleted in InvokeCanvasMenu
7632  if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
7633  m_pSelectedRoute->m_bRtIsSelected = false;
7634  }
7635 
7636  m_pSelectedRoute = NULL;
7637 
7638  if (m_pFoundRoutePoint) {
7639  if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
7640  m_pFoundRoutePoint->m_bPtIsSelected = false;
7641  }
7642  m_pFoundRoutePoint = NULL;
7643 
7644  Refresh(true);
7645  }
7646 
7647  // Seth: Is this refresh needed?
7648  Refresh(false); // needed for MSW, not GTK Why??
7649 }
7650 bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
7651  // For now just bail out completely if the point clicked is not on the chart
7652  if (std::isnan(m_cursor_lat)) return false;
7653 
7654  // Mouse Clicks
7655  bool ret = false; // return true if processed
7656 
7657  int x, y, mx, my;
7658  event.GetPosition(&x, &y);
7659  mx = x;
7660  my = y;
7661 
7662  // Calculate meaningful SelectRadius
7663  float SelectRadius;
7664  SelectRadius = g_Platform->GetSelectRadiusPix() /
7665  (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
7666 
7668  // We start with Double Click processing. The first left click just starts a
7669  // timer and is remembered, then we actually do something if there is a
7670  // LeftDClick. If there is, the two single clicks are ignored.
7671 
7672  if (event.LeftDClick() && (cursor_region == CENTER)) {
7673  m_DoubleClickTimer->Start();
7674  singleClickEventIsValid = false;
7675 
7676  double zlat, zlon;
7677  GetCanvasPixPoint(x * g_current_monitor_dip_px_ratio, y * g_current_monitor_dip_px_ratio, zlat, zlon);
7678 
7679  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7680  if (m_bShowAIS) {
7681  SelectItem *pFindAIS;
7682  pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
7683 
7684  if (pFindAIS) {
7685  m_FoundAIS_MMSI = pFindAIS->GetUserData();
7686  if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
7687  wxWindow *pwin = wxDynamicCast(this, wxWindow);
7688  ShowAISTargetQueryDialog(pwin, m_FoundAIS_MMSI);
7689  }
7690  return true;
7691  }
7692  }
7693 
7694  SelectableItemList rpSelList =
7695  pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
7696  wxSelectableItemListNode *node = rpSelList.GetFirst();
7697  bool b_onRPtarget = false;
7698  while (node) {
7699  SelectItem *pFind = node->GetData();
7700  RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7701  if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
7702  b_onRPtarget = true;
7703  break;
7704  }
7705  node = node->GetNext();
7706  }
7707 
7708  // Double tap with selected RoutePoint or Mark
7709 
7710  if (m_pRoutePointEditTarget) {
7711  if (b_onRPtarget) {
7712  ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
7713  return true;
7714  } else {
7715  m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7716  m_pRoutePointEditTarget->m_bPtIsSelected = false;
7717  if (g_btouch) RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7718  wxRect wp_rect;
7719  RoutePointGui(*m_pRoutePointEditTarget).CalculateDCRect(m_dc_route, this, &wp_rect);
7720  m_pRoutePointEditTarget = NULL; // cancel selection
7721  RefreshRect(wp_rect, true);
7722  return true;
7723  }
7724  } else {
7725  node = rpSelList.GetFirst();
7726  if (node) {
7727  SelectItem *pFind = node->GetData();
7728  RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7729  if (frp) {
7730  wxArrayPtrVoid *proute_array =
7731  g_pRouteMan->GetRouteArrayContaining(frp);
7732 
7733  // Use route array (if any) to determine actual visibility for this
7734  // point
7735  bool brp_viz = false;
7736  if (proute_array) {
7737  for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7738  Route *pr = (Route *)proute_array->Item(ir);
7739  if (pr->IsVisible()) {
7740  brp_viz = true;
7741  break;
7742  }
7743  }
7744  if (!brp_viz &&
7745  frp->IsShared()) // is not visible as part of route, but still
7746  // exists as a waypoint
7747  brp_viz = frp->IsVisible(); // so treat as isolated point
7748  } else
7749  brp_viz = frp->IsVisible(); // isolated point
7750 
7751  if (brp_viz) {
7752  ShowMarkPropertiesDialog(frp);
7753  return true;
7754  }
7755  }
7756  }
7757  }
7758 
7759  SelectItem *cursorItem;
7760  cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
7761 
7762  if (cursorItem) {
7763  Route *pr = (Route *)cursorItem->m_pData3;
7764  if (pr->IsVisible()) {
7765  ShowRoutePropertiesDialog(_("Route Properties"), pr);
7766  return true;
7767  }
7768  }
7769 
7770  cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
7771 
7772  if (cursorItem) {
7773  Track *pt = (Track *)cursorItem->m_pData3;
7774  if (pt->IsVisible()) {
7775  ShowTrackPropertiesDialog(pt);
7776  return true;
7777  }
7778  }
7779 
7780  // Found no object to act on, so show chart info.
7781 
7782  ShowObjectQueryWindow(x, y, zlat, zlon);
7783  return true;
7784  }
7785 
7787  if (event.LeftDown()) {
7788  // This really should not be needed, but....
7789  // on Windows, when using wxAUIManager, sometimes the focus is lost
7790  // when clicking into another pane, e.g.the AIS target list, and then back
7791  // to this pane. Oddly, some mouse events are not lost, however. Like this
7792  // one....
7793  SetFocus();
7794 
7795  last_drag.x = mx;
7796  last_drag.y = my;
7797  leftIsDown = true;
7798 
7799  if (!g_btouch) {
7800  if (m_routeState) // creating route?
7801  {
7802  double rlat, rlon;
7803  bool appending = false;
7804  bool inserting = false;
7805  Route *tail = 0;
7806 
7807  SetCursor(*pCursorPencil);
7808  rlat = m_cursor_lat;
7809  rlon = m_cursor_lon;
7810 
7811  m_bRouteEditing = true;
7812 
7813  if (m_routeState == 1) {
7814  m_pMouseRoute = new Route();
7815  pRouteList->Append(m_pMouseRoute);
7816  r_rband.x = x;
7817  r_rband.y = y;
7818  }
7819 
7820  // Check to see if there is a nearby point which may be reused
7821  RoutePoint *pMousePoint = NULL;
7822 
7823  // Calculate meaningful SelectRadius
7824  double nearby_radius_meters =
7825  g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
7826 
7827  RoutePoint *pNearbyPoint =
7828  pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
7829  if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
7830  !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
7831  wxArrayPtrVoid *proute_array =
7832  g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
7833 
7834  // Use route array (if any) to determine actual visibility for this
7835  // point
7836  bool brp_viz = false;
7837  if (proute_array) {
7838  for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7839  Route *pr = (Route *)proute_array->Item(ir);
7840  if (pr->IsVisible()) {
7841  brp_viz = true;
7842  break;
7843  }
7844  }
7845  if (!brp_viz &&
7846  pNearbyPoint->IsShared()) // is not visible as part of route,
7847  // but still exists as a waypoint
7848  brp_viz =
7849  pNearbyPoint->IsVisible(); // so treat as isolated point
7850  } else
7851  brp_viz = pNearbyPoint->IsVisible(); // isolated point
7852 
7853  if (brp_viz) {
7854  m_FinishRouteOnKillFocus =
7855  false; // Avoid route finish on focus change for message dialog
7856  int dlg_return = OCPNMessageBox(
7857  this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
7858  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
7859  m_FinishRouteOnKillFocus = true;
7860 
7861  if (dlg_return == wxID_YES) {
7862  pMousePoint = pNearbyPoint;
7863 
7864  // Using existing waypoint, so nothing to delete for undo.
7865  if (m_routeState > 1)
7866  undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
7867  Undo_HasParent, NULL);
7868 
7869  tail =
7870  g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
7871  bool procede = false;
7872  if (tail) {
7873  procede = true;
7874  // if (pMousePoint == tail->GetLastPoint()) procede = false;
7875  if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
7876  procede = false;
7877  }
7878 
7879  if (procede) {
7880  int dlg_return;
7881  m_FinishRouteOnKillFocus = false;
7882  if (m_routeState ==
7883  1) { // first point in new route, preceeding route to be
7884  // added? Not touch case
7885 
7886  wxString dmsg =
7887  _("Insert first part of this route in the new route?");
7888  if (tail->GetIndexOf(pMousePoint) ==
7889  tail->GetnPoints()) // Starting on last point of another
7890  // route?
7891  dmsg = _("Insert this route in the new route?");
7892 
7893  if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
7894  dlg_return = OCPNMessageBox(
7895  this, dmsg, _("OpenCPN Route Create"),
7896  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
7897  m_FinishRouteOnKillFocus = true;
7898 
7899  if (dlg_return == wxID_YES) {
7900  inserting = true; // part of the other route will be
7901  // preceeding the new route
7902  }
7903  }
7904  } else {
7905  wxString dmsg =
7906  _("Append last part of this route to the new route?");
7907  if (tail->GetIndexOf(pMousePoint) == 1)
7908  dmsg = _(
7909  "Append this route to the new route?"); // Picking the
7910  // first point
7911  // of another
7912  // route?
7913 
7914  if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
7915  dlg_return = OCPNMessageBox(
7916  this, dmsg, _("OpenCPN Route Create"),
7917  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
7918  m_FinishRouteOnKillFocus = true;
7919 
7920  if (dlg_return == wxID_YES) {
7921  appending = true; // part of the other route will be
7922  // appended to the new route
7923  }
7924  }
7925  }
7926  }
7927 
7928  // check all other routes to see if this point appears in any
7929  // other route If it appears in NO other route, then it should e
7930  // considered an isolated mark
7931  if (!FindRouteContainingWaypoint(pMousePoint))
7932  pMousePoint->SetShared(true);
7933  }
7934  }
7935  }
7936 
7937  if (NULL == pMousePoint) { // need a new point
7938  pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
7939  _T(""), wxEmptyString);
7940  pMousePoint->SetNameShown(false);
7941 
7942  pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
7943  pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
7944 
7945  if (m_routeState > 1)
7946  undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
7947  Undo_IsOrphanded, NULL);
7948  }
7949 
7950  if (m_pMouseRoute) {
7951  if (m_routeState == 1) {
7952  // First point in the route.
7953  m_pMouseRoute->AddPoint(pMousePoint);
7954  } else {
7955  if (m_pMouseRoute->m_NextLegGreatCircle) {
7956  double rhumbBearing, rhumbDist, gcBearing, gcDist;
7957  DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
7958  &rhumbBearing, &rhumbDist);
7959  Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
7960  rlat, &gcDist, &gcBearing, NULL);
7961  double gcDistNM = gcDist / 1852.0;
7962 
7963  // Empirically found expression to get reasonable route segments.
7964  int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
7965  pow(rhumbDist - gcDistNM - 1, 0.5);
7966 
7967  wxString msg;
7968  msg << _("For this leg the Great Circle route is ")
7969  << FormatDistanceAdaptive(rhumbDist - gcDistNM)
7970  << _(" shorter than rhumbline.\n\n")
7971  << _("Would you like include the Great Circle routing points "
7972  "for this leg?");
7973 
7974  m_FinishRouteOnKillFocus = false;
7975  m_disable_edge_pan = true; // This helps on OS X if MessageBox
7976  // does not fully capture mouse
7977 
7978  int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
7979  wxYES_NO | wxNO_DEFAULT);
7980 
7981  m_disable_edge_pan = false;
7982  m_FinishRouteOnKillFocus = true;
7983 
7984  if (answer == wxID_YES) {
7985  RoutePoint *gcPoint;
7986  RoutePoint *prevGcPoint = m_prev_pMousePoint;
7987  wxRealPoint gcCoord;
7988 
7989  for (int i = 1; i <= segmentCount; i++) {
7990  double fraction = (double)i * (1.0 / (double)segmentCount);
7991  Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
7992  gcDist * fraction, gcBearing,
7993  &gcCoord.x, &gcCoord.y, NULL);
7994 
7995  if (i < segmentCount) {
7996  gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
7997  _T(""), wxEmptyString);
7998  gcPoint->SetNameShown(false);
7999  pConfig->AddNewWayPoint(gcPoint, -1);
8000  pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8001  gcPoint);
8002  } else {
8003  gcPoint = pMousePoint; // Last point, previously exsisting!
8004  }
8005 
8006  m_pMouseRoute->AddPoint(gcPoint);
8007  pSelect->AddSelectableRouteSegment(
8008  prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8009  gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8010  prevGcPoint = gcPoint;
8011  }
8012 
8013  undo->CancelUndoableAction(true);
8014 
8015  } else {
8016  m_pMouseRoute->AddPoint(pMousePoint);
8017  pSelect->AddSelectableRouteSegment(
8018  m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8019  pMousePoint, m_pMouseRoute);
8020  undo->AfterUndoableAction(m_pMouseRoute);
8021  }
8022  } else {
8023  // Ordinary rhumblinesegment.
8024  m_pMouseRoute->AddPoint(pMousePoint);
8025  pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8026  rlon, m_prev_pMousePoint,
8027  pMousePoint, m_pMouseRoute);
8028  undo->AfterUndoableAction(m_pMouseRoute);
8029  }
8030  }
8031  }
8032  m_prev_rlat = rlat;
8033  m_prev_rlon = rlon;
8034  m_prev_pMousePoint = pMousePoint;
8035  if (m_pMouseRoute)
8036  m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8037 
8038  m_routeState++;
8039 
8040  if (appending ||
8041  inserting) { // Appending a route or making a new route
8042  int connect = tail->GetIndexOf(pMousePoint);
8043  if (connect == 1) {
8044  inserting = false; // there is nothing to insert
8045  appending = true; // so append
8046  }
8047  int length = tail->GetnPoints();
8048 
8049  int i;
8050  int start, stop;
8051  if (appending) {
8052  start = connect + 1;
8053  stop = length;
8054  } else { // inserting
8055  start = 1;
8056  stop = connect;
8057  m_pMouseRoute->RemovePoint(
8058  m_pMouseRoute
8059  ->GetLastPoint()); // Remove the first and only point
8060  }
8061  for (i = start; i <= stop; i++) {
8062  m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8063  if (m_pMouseRoute)
8064  m_pMouseRoute->m_lastMousePointIndex =
8065  m_pMouseRoute->GetnPoints();
8066  m_routeState++;
8067  gFrame->RefreshAllCanvas();
8068  ret = true;
8069  }
8070  m_prev_rlat =
8071  m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8072  m_prev_rlon =
8073  m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8074  m_pMouseRoute->FinalizeForRendering();
8075  }
8076  gFrame->RefreshAllCanvas();
8077  ret = true;
8078  }
8079 
8080  else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8081  {
8082  SetCursor(*pCursorPencil);
8083 
8084  if (!m_pMeasureRoute) {
8085  m_pMeasureRoute = new Route();
8086  pRouteList->Append(m_pMeasureRoute);
8087  }
8088 
8089  if (m_nMeasureState == 1) {
8090  r_rband.x = x;
8091  r_rband.y = y;
8092  }
8093 
8094  RoutePoint *pMousePoint = new RoutePoint(
8095  m_cursor_lat, m_cursor_lon, wxString(_T ( "circle" )),
8096  wxEmptyString, wxEmptyString);
8097  pMousePoint->m_bShowName = false;
8098  pMousePoint->SetShowWaypointRangeRings(false);
8099 
8100  m_pMeasureRoute->AddPoint(pMousePoint);
8101 
8102  m_prev_rlat = m_cursor_lat;
8103  m_prev_rlon = m_cursor_lon;
8104  m_prev_pMousePoint = pMousePoint;
8105  m_pMeasureRoute->m_lastMousePointIndex =
8106  m_pMeasureRoute->GetnPoints();
8107 
8108  m_nMeasureState++;
8109  gFrame->RefreshAllCanvas();
8110  ret = true;
8111  }
8112 
8113  else {
8114  FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8115  }
8116  } // !g_btouch
8117  else { // g_btouch
8118 
8119  if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8120  // if near screen edge, pan with injection
8121  // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8122  // return;
8123  // }
8124  }
8125  }
8126 
8127  if (ret) return true;
8128  }
8129 
8130  if (event.Dragging()) {
8131  // in touch screen mode ensure the finger/cursor is on the selected point's
8132  // radius to allow dragging
8133  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8134  if (g_btouch) {
8135  if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8136  SelectItem *pFind = NULL;
8137  SelectableItemList SelList = pSelect->FindSelectionList(
8138  ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8139  wxSelectableItemListNode *node = SelList.GetFirst();
8140  while (node) {
8141  pFind = node->GetData();
8142  RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8143  if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8144  node = node->GetNext();
8145  }
8146  }
8147 
8148  // Check for use of dragHandle
8149  if (m_pRoutePointEditTarget &&
8150  m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8151  SelectItem *pFind = NULL;
8152  SelectableItemList SelList = pSelect->FindSelectionList(
8153  ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8154  wxSelectableItemListNode *node = SelList.GetFirst();
8155  while (node) {
8156  pFind = node->GetData();
8157  RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8158  if (m_pRoutePointEditTarget == frp) {
8159  m_bIsInRadius = true;
8160  break;
8161  }
8162  node = node->GetNext();
8163  }
8164 
8165  if (!m_dragoffsetSet) {
8166  RoutePointGui(*m_pRoutePointEditTarget).PresetDragOffset(this, mouse_x, mouse_y);
8167  m_dragoffsetSet = true;
8168  }
8169  }
8170  }
8171 
8172  if (m_bRouteEditing && m_pRoutePointEditTarget) {
8173  bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8174 
8175  if (NULL == g_pMarkInfoDialog) {
8176  if (g_bWayPointPreventDragging) DraggingAllowed = false;
8177  } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8178  DraggingAllowed = false;
8179 
8180  if (m_pRoutePointEditTarget &&
8181  (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8182  DraggingAllowed = false;
8183 
8184  if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8185 
8186  if (DraggingAllowed) {
8187  if (!undo->InUndoableAction()) {
8188  undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8189  Undo_NeedsCopy, m_pFoundPoint);
8190  }
8191 
8192  // Get the update rectangle for the union of the un-edited routes
8193  wxRect pre_rect;
8194 
8195  if (!g_bopengl && m_pEditRouteArray) {
8196  for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8197  Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8198  // Need to validate route pointer
8199  // Route may be gone due to drgging close to ownship with
8200  // "Delete On Arrival" state set, as in the case of
8201  // navigating to an isolated waypoint on a temporary route
8202  if (g_pRouteMan->IsRouteValid(pr)) {
8203  wxRect route_rect;
8204  RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8205  pre_rect.Union(route_rect);
8206  }
8207  }
8208  }
8209 
8210  double new_cursor_lat = m_cursor_lat;
8211  double new_cursor_lon = m_cursor_lon;
8212 
8213  if (CheckEdgePan(x, y, true, 5, 2))
8214  GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8215 
8216  // update the point itself
8217  if (g_btouch) {
8218  // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8219  // new_cursor_lat, new_cursor_lon);
8220  RoutePointGui(*m_pRoutePointEditTarget).SetPointFromDraghandlePoint(this, mouse_x,
8221  mouse_y);
8222  // update the Drag Handle entry in the pSelect list
8223  pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8224  m_pRoutePointEditTarget,
8225  SELTYPE_DRAGHANDLE);
8226  m_pFoundPoint->m_slat =
8227  m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8228  m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8229  } else {
8230  m_pRoutePointEditTarget->m_lat =
8231  new_cursor_lat; // update the RoutePoint entry
8232  m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8233  m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8234  m_pFoundPoint->m_slat =
8235  new_cursor_lat; // update the SelectList entry
8236  m_pFoundPoint->m_slon = new_cursor_lon;
8237  }
8238 
8239  // Update the MarkProperties Dialog, if currently shown
8240  if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8241  if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8242  g_pMarkInfoDialog->UpdateProperties(true);
8243  }
8244 
8245  if (g_bopengl) {
8246  // InvalidateGL();
8247  Refresh(false);
8248  } else {
8249  // Get the update rectangle for the edited route
8250  wxRect post_rect;
8251 
8252  if (m_pEditRouteArray) {
8253  for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8254  ir++) {
8255  Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8256  if (g_pRouteMan->IsRouteValid(pr)) {
8257  wxRect route_rect;
8258  RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8259  post_rect.Union(route_rect);
8260  }
8261  }
8262  }
8263 
8264  // Invalidate the union region
8265  pre_rect.Union(post_rect);
8266  RefreshRect(pre_rect, false);
8267  }
8268  gFrame->RefreshCanvasOther(this);
8269  m_bRoutePoinDragging = true;
8270  }
8271  ret = true;
8272  } // if Route Editing
8273 
8274  else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8275  bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8276 
8277  if (NULL == g_pMarkInfoDialog) {
8278  if (g_bWayPointPreventDragging) DraggingAllowed = false;
8279  } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8280  DraggingAllowed = false;
8281 
8282  if (m_pRoutePointEditTarget &&
8283  (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8284  DraggingAllowed = false;
8285 
8286  if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8287 
8288  if (DraggingAllowed) {
8289  if (!undo->InUndoableAction()) {
8290  undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8291  Undo_NeedsCopy, m_pFoundPoint);
8292  }
8293 
8294  // The mark may be an anchorwatch
8295  double lpp1 = 0.;
8296  double lpp2 = 0.;
8297  double lppmax;
8298 
8299  if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8300  lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8301  }
8302  if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8303  lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8304  }
8305  lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8306 
8307  // Get the update rectangle for the un-edited mark
8308  wxRect pre_rect;
8309  if (!g_bopengl) {
8310  RoutePointGui(*m_pRoutePointEditTarget).CalculateDCRect(m_dc_route, this, &pre_rect);
8311  if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8312  pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8313  (int)(lppmax - (pre_rect.height / 2)));
8314  }
8315 
8316  // update the point itself
8317  if (g_btouch) {
8318  // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8319  // m_cursor_lat, m_cursor_lon);
8320  RoutePointGui(*m_pRoutePointEditTarget).SetPointFromDraghandlePoint(this, mouse_x,
8321  mouse_y);
8322  // update the Drag Handle entry in the pSelect list
8323  pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
8324  m_pRoutePointEditTarget,
8325  SELTYPE_DRAGHANDLE);
8326  m_pFoundPoint->m_slat =
8327  m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8328  m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8329  } else {
8330  m_pRoutePointEditTarget->m_lat =
8331  m_cursor_lat; // update the RoutePoint entry
8332  m_pRoutePointEditTarget->m_lon = m_cursor_lon;
8333  m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8334  m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
8335  m_pFoundPoint->m_slon = m_cursor_lon;
8336  }
8337 
8338  // Update the MarkProperties Dialog, if currently shown
8339  if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8340  if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8341  g_pMarkInfoDialog->UpdateProperties(true);
8342  }
8343 
8344  // Invalidate the union region
8345  if (g_bopengl) {
8346  if (!g_btouch) InvalidateGL();
8347  Refresh(false);
8348  } else {
8349  // Get the update rectangle for the edited mark
8350  wxRect post_rect;
8351  RoutePointGui(*m_pRoutePointEditTarget).CalculateDCRect(m_dc_route, this,
8352  &post_rect);
8353  if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
8354  post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
8355  (int)(lppmax - (post_rect.height / 2)));
8356 
8357  // Invalidate the union region
8358  pre_rect.Union(post_rect);
8359  RefreshRect(pre_rect, false);
8360  }
8361  gFrame->RefreshCanvasOther(this);
8362  m_bRoutePoinDragging = true;
8363  }
8364  ret = true;
8365  }
8366 
8367  if (ret) return true;
8368  } // dragging
8369 
8370  if (event.LeftUp()) {
8371  bool b_startedit_route = false;
8372  m_dragoffsetSet = false;
8373 
8374  if (g_btouch) {
8375  m_bChartDragging = false;
8376  m_bIsInRadius = false;
8377 
8378  if (m_routeState) // creating route?
8379  {
8380  if (m_bedge_pan) {
8381  m_bedge_pan = false;
8382  return false;
8383  }
8384 
8385  double rlat, rlon;
8386  bool appending = false;
8387  bool inserting = false;
8388  Route *tail = 0;
8389 
8390  rlat = m_cursor_lat;
8391  rlon = m_cursor_lon;
8392 
8393  if (m_pRoutePointEditTarget) {
8394  m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8395  m_pRoutePointEditTarget->m_bPtIsSelected = false;
8396  if (!g_bopengl) {
8397  wxRect wp_rect;
8398  RoutePointGui(*m_pRoutePointEditTarget)
8399  .CalculateDCRect(m_dc_route, this, &wp_rect);
8400  RefreshRect(wp_rect, true);
8401  }
8402  m_pRoutePointEditTarget = NULL;
8403  }
8404  m_bRouteEditing = true;
8405 
8406  if (m_routeState == 1) {
8407  m_pMouseRoute = new Route();
8408  m_pMouseRoute->SetHiLite(50);
8409  pRouteList->Append(m_pMouseRoute);
8410  r_rband.x = x;
8411  r_rband.y = y;
8412  }
8413 
8414  // Check to see if there is a nearby point which may be reused
8415  RoutePoint *pMousePoint = NULL;
8416 
8417  // Calculate meaningful SelectRadius
8418  double nearby_radius_meters =
8419  g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8420 
8421  RoutePoint *pNearbyPoint =
8422  pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8423  if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8424  !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8425  int dlg_return;
8426 #ifndef __WXOSX__
8427  m_FinishRouteOnKillFocus =
8428  false; // Avoid route finish on focus change for message dialog
8429  dlg_return = OCPNMessageBox(
8430  this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
8431  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8432  m_FinishRouteOnKillFocus = true;
8433 #else
8434  dlg_return = wxID_YES;
8435 #endif
8436  if (dlg_return == wxID_YES) {
8437  pMousePoint = pNearbyPoint;
8438 
8439  // Using existing waypoint, so nothing to delete for undo.
8440  if (m_routeState > 1)
8441  undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8442  Undo_HasParent, NULL);
8443  tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8444 
8445  bool procede = false;
8446  if (tail) {
8447  procede = true;
8448  // if (pMousePoint == tail->GetLastPoint()) procede = false;
8449  if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8450  procede = false;
8451  }
8452 
8453  if (procede) {
8454  int dlg_return;
8455  m_FinishRouteOnKillFocus = false;
8456  if (m_routeState == 1) { // first point in new route, preceeding
8457  // route to be added? touch case
8458 
8459  wxString dmsg =
8460  _("Insert first part of this route in the new route?");
8461  if (tail->GetIndexOf(pMousePoint) ==
8462  tail->GetnPoints()) // Starting on last point of another
8463  // route?
8464  dmsg = _("Insert this route in the new route?");
8465 
8466  if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8467  dlg_return =
8468  OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8469  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8470  m_FinishRouteOnKillFocus = true;
8471 
8472  if (dlg_return == wxID_YES) {
8473  inserting = true; // part of the other route will be
8474  // preceeding the new route
8475  }
8476  }
8477  } else {
8478  wxString dmsg =
8479  _("Append last part of this route to the new route?");
8480  if (tail->GetIndexOf(pMousePoint) == 1)
8481  dmsg = _(
8482  "Append this route to the new route?"); // Picking the
8483  // first point of
8484  // another route?
8485 
8486  if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8487  dlg_return =
8488  OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8489  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8490  m_FinishRouteOnKillFocus = true;
8491 
8492  if (dlg_return == wxID_YES) {
8493  appending = true; // part of the other route will be
8494  // appended to the new route
8495  }
8496  }
8497  }
8498  }
8499 
8500  // check all other routes to see if this point appears in any other
8501  // route If it appears in NO other route, then it should e
8502  // considered an isolated mark
8503  if (!FindRouteContainingWaypoint(pMousePoint))
8504  pMousePoint->SetShared(true);
8505  }
8506  }
8507 
8508  if (NULL == pMousePoint) { // need a new point
8509  pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8510  _T(""), wxEmptyString);
8511  pMousePoint->SetNameShown(false);
8512 
8513  pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8514  pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8515 
8516  if (m_routeState > 1)
8517  undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8518  Undo_IsOrphanded, NULL);
8519  }
8520 
8521  if (m_routeState == 1) {
8522  // First point in the route.
8523  m_pMouseRoute->AddPoint(pMousePoint);
8524  } else {
8525  if (m_pMouseRoute->m_NextLegGreatCircle) {
8526  double rhumbBearing, rhumbDist, gcBearing, gcDist;
8527  DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8528  &rhumbBearing, &rhumbDist);
8529  Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
8530  &gcDist, &gcBearing, NULL);
8531  double gcDistNM = gcDist / 1852.0;
8532 
8533  // Empirically found expression to get reasonable route segments.
8534  int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8535  pow(rhumbDist - gcDistNM - 1, 0.5);
8536 
8537  wxString msg;
8538  msg << _("For this leg the Great Circle route is ")
8539  << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8540  << _(" shorter than rhumbline.\n\n")
8541  << _("Would you like include the Great Circle routing points "
8542  "for this leg?");
8543 
8544 #ifndef __WXOSX__
8545  m_FinishRouteOnKillFocus = false;
8546  int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8547  wxYES_NO | wxNO_DEFAULT);
8548  m_FinishRouteOnKillFocus = true;
8549 #else
8550  int answer = wxID_NO;
8551 #endif
8552 
8553  if (answer == wxID_YES) {
8554  RoutePoint *gcPoint;
8555  RoutePoint *prevGcPoint = m_prev_pMousePoint;
8556  wxRealPoint gcCoord;
8557 
8558  for (int i = 1; i <= segmentCount; i++) {
8559  double fraction = (double)i * (1.0 / (double)segmentCount);
8560  Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8561  gcDist * fraction, gcBearing,
8562  &gcCoord.x, &gcCoord.y, NULL);
8563 
8564  if (i < segmentCount) {
8565  gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
8566  _T(""), wxEmptyString);
8567  gcPoint->SetNameShown(false);
8568  pConfig->AddNewWayPoint(gcPoint, -1);
8569  pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8570  gcPoint);
8571  } else {
8572  gcPoint = pMousePoint; // Last point, previously exsisting!
8573  }
8574 
8575  m_pMouseRoute->AddPoint(gcPoint);
8576  pSelect->AddSelectableRouteSegment(
8577  prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8578  gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8579  prevGcPoint = gcPoint;
8580  }
8581 
8582  undo->CancelUndoableAction(true);
8583 
8584  } else {
8585  m_pMouseRoute->AddPoint(pMousePoint);
8586  pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8587  rlon, m_prev_pMousePoint,
8588  pMousePoint, m_pMouseRoute);
8589  undo->AfterUndoableAction(m_pMouseRoute);
8590  }
8591  } else {
8592  // Ordinary rhumblinesegment.
8593  m_pMouseRoute->AddPoint(pMousePoint);
8594  pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8595  rlon, m_prev_pMousePoint,
8596  pMousePoint, m_pMouseRoute);
8597  undo->AfterUndoableAction(m_pMouseRoute);
8598  }
8599  }
8600 
8601  m_prev_rlat = rlat;
8602  m_prev_rlon = rlon;
8603  m_prev_pMousePoint = pMousePoint;
8604  m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8605 
8606  m_routeState++;
8607 
8608  if (appending ||
8609  inserting) { // Appending a route or making a new route
8610  int connect = tail->GetIndexOf(pMousePoint);
8611  if (connect == 1) {
8612  inserting = false; // there is nothing to insert
8613  appending = true; // so append
8614  }
8615  int length = tail->GetnPoints();
8616 
8617  int i;
8618  int start, stop;
8619  if (appending) {
8620  start = connect + 1;
8621  stop = length;
8622  } else { // inserting
8623  start = 1;
8624  stop = connect;
8625  m_pMouseRoute->RemovePoint(
8626  m_pMouseRoute
8627  ->GetLastPoint()); // Remove the first and only point
8628  }
8629  for (i = start; i <= stop; i++) {
8630  m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8631  if (m_pMouseRoute)
8632  m_pMouseRoute->m_lastMousePointIndex =
8633  m_pMouseRoute->GetnPoints();
8634  m_routeState++;
8635  gFrame->RefreshAllCanvas();
8636  ret = true;
8637  }
8638  m_prev_rlat =
8639  m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8640  m_prev_rlon =
8641  m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8642  m_pMouseRoute->FinalizeForRendering();
8643  }
8644 
8645  Refresh(true);
8646  ret = true;
8647  } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
8648  {
8649  if (m_bedge_pan) {
8650  m_bedge_pan = false;
8651  return false;
8652  }
8653 
8654  if (m_nMeasureState == 1) {
8655  m_pMeasureRoute = new Route();
8656  pRouteList->Append(m_pMeasureRoute);
8657  r_rband.x = x;
8658  r_rband.y = y;
8659  }
8660 
8661  if (m_pMeasureRoute){
8662  RoutePoint *pMousePoint = new RoutePoint(m_cursor_lat, m_cursor_lon,
8663  wxString(_T ( "circle" )),
8664  wxEmptyString, wxEmptyString);
8665  pMousePoint->m_bShowName = false;
8666 
8667  m_pMeasureRoute->AddPoint(pMousePoint);
8668 
8669  m_prev_rlat = m_cursor_lat;
8670  m_prev_rlon = m_cursor_lon;
8671  m_prev_pMousePoint = pMousePoint;
8672  m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8673 
8674  m_nMeasureState++;
8675  }
8676  else {
8677  CancelMeasureRoute();
8678  }
8679 
8680  Refresh(true);
8681  ret = true;
8682  } else {
8683  bool bSelectAllowed = true;
8684  if (NULL == g_pMarkInfoDialog) {
8685  if (g_bWayPointPreventDragging) bSelectAllowed = false;
8686  } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8687  bSelectAllowed = false;
8688 
8689  /*if this left up happens at the end of a route point dragging and if
8690  the cursor/thumb is on the draghandle icon, not on the point iself a new
8691  selection will select nothing and the drag will never be ended, so the
8692  legs around this point never selectable. At this step we don't need a
8693  new selection, just keep the previoulsly selected and dragged point */
8694  if (m_bRoutePoinDragging) bSelectAllowed = false;
8695 
8696  if (bSelectAllowed) {
8697  bool b_was_editing_mark = m_bMarkEditing;
8698  bool b_was_editing_route = m_bRouteEditing;
8699  FindRoutePointsAtCursor(SelectRadius,
8700  true); // Possibly selecting a point in a
8701  // route for later dragging
8702 
8703  /*route and a mark points in layer can't be dragged so should't be
8704  * selected and no draghandle icon*/
8705  if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
8706  m_pRoutePointEditTarget = NULL;
8707 
8708  if (!b_was_editing_route) {
8709  if (m_pEditRouteArray) {
8710  b_startedit_route = true;
8711 
8712  // Hide the track and route rollover during route point edit, not
8713  // needed, and may be confusing
8714  if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
8715  m_pTrackRolloverWin->IsActive(false);
8716  }
8717  if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
8718  m_pRouteRolloverWin->IsActive(false);
8719  }
8720 
8721  wxRect pre_rect;
8722  for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8723  ir++) {
8724  Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8725  // Need to validate route pointer
8726  // Route may be gone due to drgging close to ownship with
8727  // "Delete On Arrival" state set, as in the case of
8728  // navigating to an isolated waypoint on a temporary route
8729  if (g_pRouteMan->IsRouteValid(pr)) {
8730  // pr->SetHiLite(50);
8731  wxRect route_rect;
8732  RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8733  pre_rect.Union(route_rect);
8734  }
8735  }
8736  RefreshRect(pre_rect, true);
8737  }
8738  } else {
8739  b_startedit_route = false;
8740  }
8741 
8742  // Mark editing
8743  if (m_pRoutePointEditTarget) {
8744  if (b_was_editing_mark ||
8745  b_was_editing_route) { // kill previous hilight
8746  if (m_lastRoutePointEditTarget) {
8747  m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
8748  m_lastRoutePointEditTarget->m_bPtIsSelected = false;
8749  RoutePointGui(*m_lastRoutePointEditTarget).EnableDragHandle(false);
8750  pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
8751  SELTYPE_DRAGHANDLE);
8752  }
8753  }
8754 
8755  if (m_pRoutePointEditTarget) {
8756  m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
8757  m_pRoutePointEditTarget->m_bPtIsSelected = true;
8758  RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
8759  wxPoint2DDouble dragHandlePoint =
8760  RoutePointGui(*m_pRoutePointEditTarget).GetDragHandlePoint(this);
8761  pSelect->AddSelectablePoint(
8762  dragHandlePoint.m_y, dragHandlePoint.m_x,
8763  m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
8764  }
8765  } else { // Deselect everything
8766  if (m_lastRoutePointEditTarget) {
8767  m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
8768  m_lastRoutePointEditTarget->m_bPtIsSelected = false;
8769  RoutePointGui(*m_lastRoutePointEditTarget).EnableDragHandle(false);
8770  pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
8771  SELTYPE_DRAGHANDLE);
8772 
8773  // Clear any routes being edited, probably orphans
8774  wxArrayPtrVoid *lastEditRouteArray =
8775  g_pRouteMan->GetRouteArrayContaining(
8776  m_lastRoutePointEditTarget);
8777  if (lastEditRouteArray) {
8778  for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
8779  ir++) {
8780  Route *pr = (Route *)lastEditRouteArray->Item(ir);
8781  if (g_pRouteMan->IsRouteValid(pr)) {
8782  pr->m_bIsBeingEdited = false;
8783  }
8784  }
8785  }
8786  }
8787  }
8788 
8789  // Do the refresh
8790 
8791  if (g_bopengl) {
8792  InvalidateGL();
8793  Refresh(false);
8794  } else {
8795  if (m_lastRoutePointEditTarget) {
8796  wxRect wp_rect;
8797  RoutePointGui(*m_lastRoutePointEditTarget).CalculateDCRect(m_dc_route, this,
8798  &wp_rect);
8799  RefreshRect(wp_rect, true);
8800  }
8801 
8802  if (m_pRoutePointEditTarget) {
8803  wxRect wp_rect;
8804  RoutePointGui(*m_pRoutePointEditTarget).CalculateDCRect(m_dc_route, this,
8805  &wp_rect);
8806  RefreshRect(wp_rect, true);
8807  }
8808  }
8809  }
8810  } // bSelectAllowed
8811 
8812  // Check to see if there is a route or AIS target under the cursor
8813  // If so, start the rollover timer which creates the popup
8814  SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8815  bool b_start_rollover = false;
8816  if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
8817  SelectItem *pFind = pSelectAIS->FindSelection(
8818  ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
8819  if (pFind) b_start_rollover = true;
8820  }
8821 
8822  if (!b_start_rollover && !b_startedit_route) {
8823  SelectableItemList SelList = pSelect->FindSelectionList(
8824  ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
8825  wxSelectableItemListNode *node = SelList.GetFirst();
8826  while (node) {
8827  SelectItem *pFindSel = node->GetData();
8828 
8829  Route *pr = (Route *)pFindSel->m_pData3; // candidate
8830 
8831  if (pr && pr->IsVisible()) {
8832  b_start_rollover = true;
8833  break;
8834  }
8835  node = node->GetNext();
8836  } // while
8837  }
8838 
8839  if (!b_start_rollover && !b_startedit_route) {
8840  SelectableItemList SelList = pSelect->FindSelectionList(
8841  ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
8842  wxSelectableItemListNode *node = SelList.GetFirst();
8843  while (node) {
8844  SelectItem *pFindSel = node->GetData();
8845 
8846  Track *tr = (Track *)pFindSel->m_pData3; // candidate
8847 
8848  if (tr && tr->IsVisible()) {
8849  b_start_rollover = true;
8850  break;
8851  }
8852  node = node->GetNext();
8853  } // while
8854  }
8855 
8856  if (b_start_rollover)
8857  m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
8858  wxTIMER_ONE_SHOT);
8859  Route *tail = 0;
8860  Route *current = 0;
8861  bool appending = false;
8862  bool inserting = false;
8863  int connect = 0;
8864  if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
8865  // drag
8866  if (m_pRoutePointEditTarget) {
8867  // Check to see if there is a nearby point which may replace the
8868  // dragged one
8869  RoutePoint *pMousePoint = NULL;
8870 
8871  int index_last;
8872  if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
8873  double nearby_radius_meters =
8874  g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8875  RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
8876  m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
8877  nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
8878  if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
8879  pWayPointMan->IsReallyVisible(pNearbyPoint)) {
8880  bool duplicate =
8881  false; // ensure we won't create duplicate point in routes
8882  if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
8883  for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8884  ir++) {
8885  Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8886  if (pr && pr->pRoutePointList) {
8887  if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
8888  wxNOT_FOUND) {
8889  duplicate = true;
8890  break;
8891  }
8892  }
8893  }
8894  }
8895 
8896  // Special case:
8897  // Allow "re-use" of a route's waypoints iff it is a simple
8898  // isolated route. This allows, for instance, creation of a closed
8899  // polygon route
8900  if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
8901 
8902  if (!duplicate) {
8903  int dlg_return;
8904  dlg_return =
8905  OCPNMessageBox(this,
8906  _("Replace this RoutePoint by the nearby "
8907  "Waypoint?"),
8908  _("OpenCPN RoutePoint change"),
8909  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8910  if (dlg_return == wxID_YES) {
8911  /*double confirmation if the dragged point has been manually
8912  * created which can be important and could be deleted
8913  * unintentionally*/
8914 
8915  tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
8916  pNearbyPoint);
8917  current = FindRouteContainingWaypoint(
8918  m_pRoutePointEditTarget);
8919 
8920  if (tail && current && (tail != current)) {
8921  int dlg_return1;
8922  connect = tail->GetIndexOf(pNearbyPoint);
8923  int index_current_route =
8924  current->GetIndexOf(m_pRoutePointEditTarget);
8925  index_last = current->GetIndexOf(current->GetLastPoint());
8926  dlg_return1 = wxID_NO;
8927  if (index_last ==
8928  index_current_route) { // we are dragging the last
8929  // point of the route
8930  if (connect != tail->GetnPoints()) { // anything to do?
8931 
8932  wxString dmsg(
8933  _("Last part of route to be appended to dragged "
8934  "route?"));
8935  if (connect == 1)
8936  dmsg =
8937  _("Full route to be appended to dragged route?");
8938 
8939  dlg_return1 = OCPNMessageBox(
8940  this, dmsg, _("OpenCPN Route Create"),
8941  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8942  if (dlg_return1 == wxID_YES) {
8943  appending = true;
8944  }
8945  }
8946  } else if (index_current_route ==
8947  1) { // dragging the first point of the route
8948  if (connect != 1) { // anything to do?
8949 
8950  wxString dmsg(
8951  _("First part of route to be inserted into dragged "
8952  "route?"));
8953  if (connect == tail->GetnPoints())
8954  dmsg = _(
8955  "Full route to be inserted into dragged route?");
8956 
8957  dlg_return1 = OCPNMessageBox(
8958  this, dmsg, _("OpenCPN Route Create"),
8959  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8960  if (dlg_return1 == wxID_YES) {
8961  inserting = true;
8962  }
8963  }
8964  }
8965  }
8966 
8967  if (m_pRoutePointEditTarget->IsShared()) {
8968  // dlg_return = wxID_NO;
8969  dlg_return = OCPNMessageBox(
8970  this,
8971  _("Do you really want to delete and replace this "
8972  "WayPoint") +
8973  _T("\n") + _("which has been created manually?"),
8974  ("OpenCPN RoutePoint warning"),
8975  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8976  }
8977  }
8978  if (dlg_return == wxID_YES) {
8979  pMousePoint = pNearbyPoint;
8980  if (pMousePoint->m_bIsolatedMark) {
8981  pMousePoint->SetShared(true);
8982  }
8983  pMousePoint->m_bIsolatedMark =
8984  false; // definitely no longer isolated
8985  pMousePoint->m_bIsInRoute = true;
8986  }
8987  }
8988  }
8989  }
8990  if (!pMousePoint)
8991  pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
8992 
8993  if (m_pEditRouteArray) {
8994  for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8995  ir++) {
8996  Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8997  if (g_pRouteMan->IsRouteValid(pr)) {
8998  if (pMousePoint) { // remove the dragged point and insert the
8999  // nearby
9000  int nRP =
9001  pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9002 
9003  pSelect->DeleteAllSelectableRoutePoints(pr);
9004  pSelect->DeleteAllSelectableRouteSegments(pr);
9005 
9006  pr->pRoutePointList->Insert(nRP, pMousePoint);
9007  pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9008 
9009  pSelect->AddAllSelectableRouteSegments(pr);
9010  pSelect->AddAllSelectableRoutePoints(pr);
9011  }
9012  pr->FinalizeForRendering();
9013  pr->UpdateSegmentDistances();
9014  if (m_bRoutePoinDragging) pConfig->UpdateRoute(pr);
9015  }
9016  }
9017  }
9018 
9019  // Update the RouteProperties Dialog, if currently shown
9020  if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9021  if (m_pEditRouteArray) {
9022  for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9023  ir++) {
9024  Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9025  if (g_pRouteMan->IsRouteValid(pr)) {
9026  if (pRoutePropDialog->GetRoute() == pr) {
9027  pRoutePropDialog->SetRouteAndUpdate(pr, true);
9028  }
9029  /* cannot edit track points anyway
9030  else if ( ( NULL !=
9031  pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9032  pTrackPropDialog->m_pTrack == pr ) {
9033  pTrackPropDialog->SetTrackAndUpdate(
9034  pr );
9035  }
9036  */
9037  }
9038  }
9039  }
9040  }
9041  if (pMousePoint) { // clear all about the dragged point
9042  pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9043  pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9044  // Hide mark properties dialog if open on the replaced point
9045  if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9046  if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9047  g_pMarkInfoDialog->Hide();
9048 
9049  delete m_pRoutePointEditTarget;
9050  m_lastRoutePointEditTarget = NULL;
9051  m_pRoutePointEditTarget = NULL;
9052  undo->AfterUndoableAction(pMousePoint);
9053  undo->InvalidateUndo();
9054  }
9055  }
9056  }
9057 
9058  else if (m_bMarkEditing) { // End of way point drag
9059  if (m_pRoutePointEditTarget)
9060  if (m_bRoutePoinDragging)
9061  pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9062  }
9063 
9064  if (m_pRoutePointEditTarget)
9065  undo->AfterUndoableAction(m_pRoutePointEditTarget);
9066 
9067  if (!m_pRoutePointEditTarget) {
9068  delete m_pEditRouteArray;
9069  m_pEditRouteArray = NULL;
9070  m_bRouteEditing = false;
9071  }
9072  m_bRoutePoinDragging = false;
9073 
9074  if (appending) { // Appending to the route of which the last point is
9075  // dragged onto another route
9076 
9077  // copy tail from connect until length to end of current after dragging
9078 
9079  int length = tail->GetnPoints();
9080  for (int i = connect + 1; i <= length; i++) {
9081  current->AddPointAndSegment(tail->GetPoint(i), false);
9082  if (current) current->m_lastMousePointIndex = current->GetnPoints();
9083  m_routeState++;
9084  gFrame->RefreshAllCanvas();
9085  ret = true;
9086  }
9087  current->FinalizeForRendering();
9088  current->m_bIsBeingEdited = false;
9089  FinishRoute();
9090  g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9091  }
9092  if (inserting) {
9093  pSelect->DeleteAllSelectableRoutePoints(current);
9094  pSelect->DeleteAllSelectableRouteSegments(current);
9095  for (int i = 1; i < connect; i++) { // numbering in the tail route
9096  current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9097  }
9098  pSelect->AddAllSelectableRouteSegments(current);
9099  pSelect->AddAllSelectableRoutePoints(current);
9100  current->FinalizeForRendering();
9101  current->m_bIsBeingEdited = false;
9102  g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9103  }
9104 
9105  // Update the RouteProperties Dialog, if currently shown
9106  if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9107  if (m_pEditRouteArray) {
9108  for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9109  Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9110  if (g_pRouteMan->IsRouteValid(pr)) {
9111  if (pRoutePropDialog->GetRoute() == pr) {
9112  pRoutePropDialog->SetRouteAndUpdate(pr, true);
9113  }
9114  }
9115  }
9116  }
9117  }
9118 
9119  } // g_btouch
9120 
9121  else { // !g_btouch
9122  if (m_bRouteEditing) { // End of RoutePoint drag
9123  Route *tail = 0;
9124  Route *current = 0;
9125  bool appending = false;
9126  bool inserting = false;
9127  int connect = 0;
9128  int index_last;
9129  if (m_pRoutePointEditTarget) {
9130  m_pRoutePointEditTarget->m_bBlink = false;
9131  // Check to see if there is a nearby point which may replace the
9132  // dragged one
9133  RoutePoint *pMousePoint = NULL;
9134  if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9135  double nearby_radius_meters =
9136  g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9137  RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9138  m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9139  nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9140  if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9141  pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9142  bool duplicate = false; // don't create duplicate point in routes
9143  if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9144  for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9145  ir++) {
9146  Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9147  if (pr && pr->pRoutePointList) {
9148  if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9149  wxNOT_FOUND) {
9150  duplicate = true;
9151  break;
9152  }
9153  }
9154  }
9155  }
9156 
9157  // Special case:
9158  // Allow "re-use" of a route's waypoints iff it is a simple
9159  // isolated route. This allows, for instance, creation of a closed
9160  // polygon route
9161  if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9162 
9163  if (!duplicate) {
9164  int dlg_return;
9165  dlg_return =
9166  OCPNMessageBox(this,
9167  _("Replace this RoutePoint by the nearby "
9168  "Waypoint?"),
9169  _("OpenCPN RoutePoint change"),
9170  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9171  if (dlg_return == wxID_YES) {
9172  /*double confirmation if the dragged point has been manually
9173  * created which can be important and could be deleted
9174  * unintentionally*/
9175  tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9176  pNearbyPoint);
9177  current = FindRouteContainingWaypoint(
9178  m_pRoutePointEditTarget);
9179 
9180  if (tail && current && (tail != current)) {
9181  int dlg_return1;
9182  connect = tail->GetIndexOf(pNearbyPoint);
9183  int index_current_route =
9184  current->GetIndexOf(m_pRoutePointEditTarget);
9185  index_last = current->GetIndexOf(current->GetLastPoint());
9186  dlg_return1 = wxID_NO;
9187  if (index_last ==
9188  index_current_route) { // we are dragging the last
9189  // point of the route
9190  if (connect != tail->GetnPoints()) { // anything to do?
9191 
9192  wxString dmsg(
9193  _("Last part of route to be appended to dragged "
9194  "route?"));
9195  if (connect == 1)
9196  dmsg =
9197  _("Full route to be appended to dragged route?");
9198 
9199  dlg_return1 = OCPNMessageBox(
9200  this, dmsg, _("OpenCPN Route Create"),
9201  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9202  if (dlg_return1 == wxID_YES) {
9203  appending = true;
9204  }
9205  }
9206  } else if (index_current_route ==
9207  1) { // dragging the first point of the route
9208  if (connect != 1) { // anything to do?
9209 
9210  wxString dmsg(
9211  _("First part of route to be inserted into dragged "
9212  "route?"));
9213  if (connect == tail->GetnPoints())
9214  dmsg = _(
9215  "Full route to be inserted into dragged route?");
9216 
9217  dlg_return1 = OCPNMessageBox(
9218  this, dmsg, _("OpenCPN Route Create"),
9219  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9220  if (dlg_return1 == wxID_YES) {
9221  inserting = true;
9222  }
9223  }
9224  }
9225  }
9226 
9227  if (m_pRoutePointEditTarget->IsShared()) {
9228  dlg_return = wxID_NO;
9229  dlg_return = OCPNMessageBox(
9230  this,
9231  _("Do you really want to delete and replace this "
9232  "WayPoint") +
9233  _T("\n") + _("which has been created manually?"),
9234  ("OpenCPN RoutePoint warning"),
9235  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9236  }
9237  }
9238  if (dlg_return == wxID_YES) {
9239  pMousePoint = pNearbyPoint;
9240  if (pMousePoint->m_bIsolatedMark) {
9241  pMousePoint->SetShared(true);
9242  }
9243  pMousePoint->m_bIsolatedMark =
9244  false; // definitely no longer isolated
9245  pMousePoint->m_bIsInRoute = true;
9246  }
9247  }
9248  }
9249  }
9250  if (!pMousePoint)
9251  pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9252 
9253  if (m_pEditRouteArray) {
9254  for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9255  ir++) {
9256  Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9257  if (g_pRouteMan->IsRouteValid(pr)) {
9258  if (pMousePoint) { // replace dragged point by nearby one
9259  int nRP =
9260  pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9261 
9262  pSelect->DeleteAllSelectableRoutePoints(pr);
9263  pSelect->DeleteAllSelectableRouteSegments(pr);
9264 
9265  pr->pRoutePointList->Insert(nRP, pMousePoint);
9266  pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9267 
9268  pSelect->AddAllSelectableRouteSegments(pr);
9269  pSelect->AddAllSelectableRoutePoints(pr);
9270  }
9271  pr->FinalizeForRendering();
9272  pr->UpdateSegmentDistances();
9273  pr->m_bIsBeingEdited = false;
9274 
9275  if (m_bRoutePoinDragging) pConfig->UpdateRoute(pr);
9276 
9277  pr->SetHiLite(0);
9278  }
9279  }
9280  Refresh(false);
9281  }
9282 
9283  if (appending) {
9284  // copy tail from connect until length to end of current after
9285  // dragging
9286 
9287  int length = tail->GetnPoints();
9288  for (int i = connect + 1; i <= length; i++) {
9289  current->AddPointAndSegment(tail->GetPoint(i), false);
9290  if (current)
9291  current->m_lastMousePointIndex = current->GetnPoints();
9292  m_routeState++;
9293  gFrame->RefreshAllCanvas();
9294  ret = true;
9295  }
9296  current->FinalizeForRendering();
9297  current->m_bIsBeingEdited = false;
9298  FinishRoute();
9299  g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9300  }
9301  if (inserting) {
9302  pSelect->DeleteAllSelectableRoutePoints(current);
9303  pSelect->DeleteAllSelectableRouteSegments(current);
9304  for (int i = 1; i < connect; i++) { // numbering in the tail route
9305  current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9306  }
9307  pSelect->AddAllSelectableRouteSegments(current);
9308  pSelect->AddAllSelectableRoutePoints(current);
9309  current->FinalizeForRendering();
9310  current->m_bIsBeingEdited = false;
9311  g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9312  }
9313 
9314  // Update the RouteProperties Dialog, if currently shown
9315  if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9316  if (m_pEditRouteArray) {
9317  for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9318  ir++) {
9319  Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9320  if (g_pRouteMan->IsRouteValid(pr)) {
9321  if (pRoutePropDialog->GetRoute() == pr) {
9322  pRoutePropDialog->SetRouteAndUpdate(pr, true);
9323  }
9324  }
9325  }
9326  }
9327  }
9328 
9329  if (pMousePoint) {
9330  pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9331  pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9332  // Hide mark properties dialog if open on the replaced point
9333  if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9334  if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9335  g_pMarkInfoDialog->Hide();
9336 
9337  delete m_pRoutePointEditTarget;
9338  m_lastRoutePointEditTarget = NULL;
9339  undo->AfterUndoableAction(pMousePoint);
9340  undo->InvalidateUndo();
9341  } else {
9342  m_pRoutePointEditTarget->m_bPtIsSelected = false;
9343  m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9344 
9345  undo->AfterUndoableAction(m_pRoutePointEditTarget);
9346  }
9347 
9348  delete m_pEditRouteArray;
9349  m_pEditRouteArray = NULL;
9350  }
9351 
9352  InvalidateGL();
9353  m_bRouteEditing = false;
9354  m_pRoutePointEditTarget = NULL;
9355 
9356  //if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9357  ret = true;
9358  }
9359 
9360  else if (m_bMarkEditing) { // end of Waypoint drag
9361  if (m_pRoutePointEditTarget) {
9362  if (m_bRoutePoinDragging)
9363  pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9364  undo->AfterUndoableAction(m_pRoutePointEditTarget);
9365  m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9366  if (!g_bopengl) {
9367  wxRect wp_rect;
9368  RoutePointGui(*m_pRoutePointEditTarget)
9369  .CalculateDCRect(m_dc_route, this, &wp_rect);
9370  m_pRoutePointEditTarget->m_bPtIsSelected = false;
9371  RefreshRect(wp_rect, true);
9372  }
9373  }
9374  m_pRoutePointEditTarget = NULL;
9375  m_bMarkEditing = false;
9376  //if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9377  ret = true;
9378  }
9379 
9380  else if (leftIsDown) { // left click for chart center
9381  leftIsDown = false;
9382  ret = false;
9383 
9384  if (!g_btouch) {
9385  if (!m_bChartDragging && !m_bMeasure_Active) {
9386  } else {
9387  m_bChartDragging = false;
9388  }
9389  }
9390  }
9391  m_bRoutePoinDragging = false;
9392  } // !btouch
9393 
9394  if (ret) return true;
9395  } // left up
9396 
9397  if (event.RightDown()) {
9398  SetFocus(); // This is to let a plugin know which canvas is right-clicked
9399  last_drag.x = mx;
9400  last_drag.y = my;
9401 
9402  if (g_btouch) {
9403  // if( m_pRoutePointEditTarget )
9404  // return false;
9405  }
9406 
9407  ret = true;
9408  m_FinishRouteOnKillFocus = false;
9409  CallPopupMenu(mx, my);
9410  m_FinishRouteOnKillFocus = true;
9411  } // Right down
9412 
9413  return ret;
9414 }
9415 
9416 bool panleftIsDown;
9417 
9418 bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
9419  int x, y;
9420  event.GetPosition(&x, &y);
9421 
9422  x *= m_displayScale;
9423  y *= m_displayScale;
9424 
9425  // Check for wheel rotation
9426  // ideally, should be just longer than the time between
9427  // processing accumulated mouse events from the event queue
9428  // as would happen during screen redraws.
9429  int wheel_dir = event.GetWheelRotation();
9430 
9431  if (wheel_dir) {
9432  int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
9433  wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
9434 
9435  double factor = g_mouse_zoom_sensitivity;
9436  if (wheel_dir < 0) factor = 1 / factor;
9437 
9438  if (g_bsmoothpanzoom) {
9439  if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
9440  if (wheel_dir == m_last_wheel_dir) {
9441  m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
9442  // m_zoom_target /= factor;
9443  } else
9444  StopMovement();
9445  } else {
9446  m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
9447  m_wheelstopwatch.Start(0);
9448  // m_zoom_target = VPoint.chart_scale / factor;
9449  }
9450  }
9451 
9452  m_last_wheel_dir = wheel_dir;
9453 
9454  ZoomCanvas(factor, true, false);
9455  }
9456 
9457  if (event.LeftDown()) {
9458  // Skip the first left click if it will cause a canvas focus shift
9459  if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
9460  // printf("focus shift\n");
9461  return false;
9462  }
9463 
9464  last_drag.x = x, last_drag.y = y;
9465  panleftIsDown = true;
9466  }
9467 
9468  if (event.LeftUp()) {
9469  if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
9470  // seen here.
9471  panleftIsDown = false;
9472 
9473  if (!g_btouch) {
9474  if (!m_bChartDragging && !m_bMeasure_Active) {
9475  switch (cursor_region) {
9476  case MID_RIGHT: {
9477  PanCanvas(100, 0);
9478  break;
9479  }
9480 
9481  case MID_LEFT: {
9482  PanCanvas(-100, 0);
9483  break;
9484  }
9485 
9486  case MID_TOP: {
9487  PanCanvas(0, 100);
9488  break;
9489  }
9490 
9491  case MID_BOT: {
9492  PanCanvas(0, -100);
9493  break;
9494  }
9495 
9496  case CENTER: {
9497  PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
9498  break;
9499  }
9500  }
9501  } else {
9502  m_bChartDragging = false;
9503  }
9504  }
9505  }
9506  }
9507 
9508  if (event.Dragging() && event.LeftIsDown()) {
9509  /*
9510  * fixed dragging.
9511  * On my Surface Pro 3 running Arch Linux there is no mouse down event
9512  * before the drag event. Hence, as there is no mouse down event, last_drag
9513  * is not reset before the drag. And that results in one single drag
9514  * session, meaning you cannot drag the map a few miles north, lift your
9515  * finger, and the go even further north. Instead, the map resets itself
9516  * always to the very first drag start (since there is not reset of
9517  * last_drag).
9518  *
9519  * Besides, should not left down and dragging be enough of a situation to
9520  * start a drag procedure?
9521  *
9522  * Anyways, guarded it to be active in touch situations only.
9523  */
9524  if (g_btouch) {
9525  if (false == m_bChartDragging) {
9526  last_drag.x = x, last_drag.y = y;
9527  m_bChartDragging = true;
9528  }
9529  }
9530 
9531  if ((last_drag.x != x) || (last_drag.y != y)) {
9532  if(!m_routeState){ // Correct fault on wx32/gtk3, uncommanded dragging on route create.
9533  // github #2994
9534  m_bChartDragging = true;
9535  StartTimedMovement();
9536  m_pan_drag.x += last_drag.x - x;
9537  m_pan_drag.y += last_drag.y - y;
9538 
9539  last_drag.x = x, last_drag.y = y;
9540  }
9541 
9542  if (g_btouch) {
9543  if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
9544  // deactivate next LeftUp to ovoid creating an unexpected point
9545  m_DoubleClickTimer->Start();
9546  singleClickEventIsValid = false;
9547  }
9548  }
9549  }
9550  }
9551 
9552  return true;
9553 }
9554 
9555 void ChartCanvas::MouseEvent(wxMouseEvent &event) {
9556  if (MouseEventOverlayWindows(event)) return;
9557 
9558  if (MouseEventSetup(event)) return; // handled, no further action required
9559 
9560  if (!MouseEventProcessObjects(event)) MouseEventProcessCanvas(event);
9561 }
9562 
9563 void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
9564  // Switch to the appropriate cursor on mouse movement
9565 
9566  wxCursor *ptarget_cursor = pCursorArrow;
9567  if (!pPlugIn_Cursor) {
9568  ptarget_cursor = pCursorArrow;
9569  if ((!m_routeState) &&
9570  (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
9571  if (cursor_region == MID_RIGHT) {
9572  ptarget_cursor = pCursorRight;
9573  } else if (cursor_region == MID_LEFT) {
9574  ptarget_cursor = pCursorLeft;
9575  } else if (cursor_region == MID_TOP) {
9576  ptarget_cursor = pCursorDown;
9577  } else if (cursor_region == MID_BOT) {
9578  ptarget_cursor = pCursorUp;
9579  } else {
9580  ptarget_cursor = pCursorArrow;
9581  }
9582  } else if (m_bMeasure_Active ||
9583  m_routeState) // If Measure tool use Pencil Cursor
9584  ptarget_cursor = pCursorPencil;
9585  } else {
9586  ptarget_cursor = pPlugIn_Cursor;
9587  }
9588 
9589  SetCursor(*ptarget_cursor);
9590 }
9591 
9592 void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
9593  SetCursor(*pCursorArrow);
9594 }
9595 
9596 void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
9597  ChartPlugInWrapper *target_plugin_chart = NULL;
9598  s57chart *Chs57 = NULL;
9599  wxFileName file;
9600  wxArrayString files;
9601 
9602  ChartBase *target_chart = GetChartAtCursor();
9603  if (target_chart) {
9604  file.Assign(target_chart->GetFullPath());
9605  if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
9606  (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
9607  target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
9608  else
9609  Chs57 = dynamic_cast<s57chart *>(target_chart);
9610  } else { // target_chart = null, might be mbtiles
9611  std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
9612  unsigned int im = stackIndexArray.size();
9613  int scale = 2147483647; // max 32b integer
9614  if (VPoint.b_quilt && im > 0) {
9615  for (unsigned int is = 0; is < im; is++) {
9616  if (ChartData->GetDBChartType(stackIndexArray[is]) ==
9617  CHART_TYPE_MBTILES) {
9618  if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
9619  double lat, lon;
9620  VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
9621  if (ChartData->GetChartTableEntry(stackIndexArray[is])
9622  .GetBBox()
9623  .Contains(lat, lon)) {
9624  if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
9625  scale) {
9626  scale =
9627  ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
9628  file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
9629  }
9630  }
9631  }
9632  }
9633  }
9634  }
9635 
9636  std::vector<Ais8_001_22 *> area_notices;
9637 
9638  if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
9639  float vp_scale = GetVPScale();
9640 
9641  for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
9642  auto target_data = target.second;
9643  if (!target_data->area_notices.empty()) {
9644  for (auto &ani : target_data->area_notices) {
9645  Ais8_001_22 &area_notice = ani.second;
9646 
9647  BoundingBox bbox;
9648 
9649  for (Ais8_001_22_SubAreaList::iterator sa =
9650  area_notice.sub_areas.begin();
9651  sa != area_notice.sub_areas.end(); ++sa) {
9652  switch (sa->shape) {
9653  case AIS8_001_22_SHAPE_CIRCLE: {
9654  wxPoint target_point;
9655  GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
9656  bbox.Expand(target_point);
9657  if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
9658  break;
9659  }
9660  case AIS8_001_22_SHAPE_RECT: {
9661  wxPoint target_point;
9662  GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
9663  bbox.Expand(target_point);
9664  if (sa->e_dim_m > sa->n_dim_m) bbox.EnLarge(sa->e_dim_m * vp_scale);
9665  else bbox.EnLarge(sa->n_dim_m * vp_scale);
9666  break;
9667  }
9668  case AIS8_001_22_SHAPE_POLYGON:
9669  case AIS8_001_22_SHAPE_POLYLINE: {
9670  for (int i = 0; i < 4; ++i) {
9671  double lat = sa->latitude;
9672  double lon = sa->longitude;
9673  ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
9674  &lat, &lon);
9675  wxPoint target_point;
9676  GetCanvasPointPix(lat, lon, &target_point);
9677  bbox.Expand(target_point);
9678  }
9679  break;
9680  }
9681  case AIS8_001_22_SHAPE_SECTOR: {
9682  double lat1 = sa->latitude;
9683  double lon1 = sa->longitude;
9684  double lat, lon;
9685  wxPoint target_point;
9686  GetCanvasPointPix(lat1, lon1, &target_point);
9687  bbox.Expand(target_point);
9688  for (int i = 0; i < 18; ++i) {
9689  ll_gc_ll(lat1, lon1, sa->left_bound_deg + i * (sa->right_bound_deg - sa->left_bound_deg) / 18 , sa->radius_m / 1852.0,
9690  &lat, &lon);
9691  GetCanvasPointPix(lat, lon, &target_point);
9692  bbox.Expand(target_point);
9693  }
9694  ll_gc_ll(lat1, lon1, sa->right_bound_deg , sa->radius_m / 1852.0,
9695  &lat, &lon);
9696  GetCanvasPointPix(lat, lon, &target_point);
9697  bbox.Expand(target_point);
9698  break;
9699  }
9700  }
9701  }
9702 
9703  if (bbox.GetValid() && bbox.PointInBox(x, y)) {
9704  area_notices.push_back(&area_notice);
9705  }
9706  }
9707  }
9708  }
9709  }
9710 
9711  if (target_chart || !area_notices.empty() || file.HasName()) {
9712  // Go get the array of all objects at the cursor lat/lon
9713  int sel_rad_pix = 5;
9714  float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
9715 
9716  // Make sure we always get the lights from an object, even if we are
9717  // currently not displaying lights on the chart.
9718 
9719  SetCursor(wxCURSOR_WAIT);
9720  bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
9721  if (!lightsVis) SetShowENCLights(true);
9722  ;
9723 
9724  ListOfObjRazRules *rule_list = NULL;
9725  ListOfPI_S57Obj *pi_rule_list = NULL;
9726  if (Chs57)
9727  rule_list =
9728  Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
9729  else if (target_plugin_chart)
9730  pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
9731  target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
9732 
9733  ListOfObjRazRules *overlay_rule_list = NULL;
9734  ChartBase *overlay_chart = GetOverlayChartAtCursor();
9735  s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
9736 
9737  if (CHs57_Overlay) {
9738  overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
9739  zlat, zlon, SelectRadius, &GetVP());
9740  }
9741 
9742  if (!lightsVis) SetShowENCLights(false);
9743 
9744  wxString objText;
9745  wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
9746  wxString face = dFont->GetFaceName();
9747 
9748  if (NULL == g_pObjectQueryDialog) {
9749  g_pObjectQueryDialog =
9750  new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
9751  wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
9752  }
9753 
9754  wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
9755  wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
9756 
9757 #ifdef __WXOSX__
9758  // Auto Adjustment for dark mode
9759  fg = g_pObjectQueryDialog->GetForegroundColour();
9760 #endif
9761 
9762  objText.Printf(
9763  _T("<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>"),
9764  bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
9765 
9766 #ifdef __WXOSX__
9767  int points = dFont->GetPointSize();
9768 #else
9769  int points = dFont->GetPointSize() + 1;
9770 #endif
9771 
9772  int sizes[7];
9773  for (int i = -2; i < 5; i++) {
9774  sizes[i + 2] = points + i + (i > 0 ? i : 0);
9775  }
9776  g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
9777 
9778  if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += _T("<i>");
9779 
9780  if (overlay_rule_list && CHs57_Overlay) {
9781  objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
9782  objText << _T("<hr noshade>");
9783  }
9784 
9785  for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
9786  an != area_notices.end(); ++an) {
9787  objText << _T( "<b>AIS Area Notice:</b> " );
9788  objText << ais8_001_22_notice_names[(*an)->notice_type];
9789  for (std::vector<Ais8_001_22_SubArea>::iterator sa =
9790  (*an)->sub_areas.begin();
9791  sa != (*an)->sub_areas.end(); ++sa)
9792  if (!sa->text.empty()) objText << sa->text;
9793  objText << _T( "<br>expires: " ) << (*an)->expiry_time.Format();
9794  objText << _T( "<hr noshade>" );
9795  }
9796 
9797  if (Chs57)
9798  objText << Chs57->CreateObjDescriptions(rule_list);
9799  else if (target_plugin_chart)
9800  objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
9801  pi_rule_list);
9802 
9803  if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << _T("</i>");
9804 
9805  // Add the additional info files
9806  wxString AddFiles, filenameOK;
9807  int filecount = 0;
9808  if (!target_plugin_chart) { // plugincharts shoud take care of this in the
9809  // plugin
9810 
9811  AddFiles = wxString::Format(
9812  _T("<hr noshade><br><b>Additional info files attached to: </b> ")
9813  _T("<font ")
9814  _T("size=-2>%s</font><br><table border=0 cellspacing=0 ")
9815  _T("cellpadding=3>"),
9816  file.GetFullName());
9817  file.Normalize();
9818  file.Assign(file.GetPath(), wxT(""));
9819  wxDir dir( file.GetFullPath() );
9820  wxString filename;
9821  bool cont = dir.GetFirst( &filename, "", wxDIR_FILES );
9822  while ( cont )
9823  {
9824  file.Assign( dir.GetNameWithSep().append( filename) );
9825  wxString FormatString = _T("<td valign=top><font size=-2><a href=\"%s\">%s</a></font></td>");
9826  if( g_ObjQFileExt.Find( file.GetExt().Lower() ) != wxNOT_FOUND )
9827  {
9828  filenameOK=file.GetFullPath();//remember last valid name
9829  // we are making a 3 columns table. New row only every third file
9830  if ( 3*((int)filecount/3) == filecount )
9831  FormatString.Prepend(_T("<tr>")); // new row
9832  else
9833  FormatString.Prepend(_T("<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>")); // an empty spacer column
9834 
9835  AddFiles << wxString::Format(FormatString, file.GetFullPath(), file.GetFullName());
9836  filecount++;
9837  }
9838  cont = dir.GetNext(&filename);
9839  }
9840  objText << AddFiles << _T("</table>");
9841  }
9842  objText << _T("</font>");
9843  objText << _T("</body></html>");
9844 
9845  if (Chs57 || target_plugin_chart || (filecount > 1)) {
9846  g_pObjectQueryDialog->SetHTMLPage(objText);
9847  g_pObjectQueryDialog->Show();
9848  }
9849  if ((!Chs57 && filecount == 1)){ // only one file?, show direktly
9850  //generate an event to avoid double code
9851  wxHtmlLinkInfo hli(filenameOK);
9852  wxHtmlLinkEvent hle(1, hli);
9853  g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
9854  }
9855 
9856  if (rule_list) rule_list->Clear();
9857  delete rule_list;
9858 
9859  if (overlay_rule_list) overlay_rule_list->Clear();
9860  delete overlay_rule_list;
9861 
9862  if (pi_rule_list) pi_rule_list->Clear();
9863  delete pi_rule_list;
9864 
9865  SetCursor(wxCURSOR_ARROW);
9866  }
9867 }
9868 
9869 void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
9870  bool bNew = false;
9871  if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
9872  // Dialog
9873  g_pMarkInfoDialog = new MarkInfoDlg(this);
9874  bNew = true;
9875  }
9876 
9877  if (1 /*g_bresponsive*/) {
9878  wxSize canvas_size = GetSize();
9879 
9880  int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
9881  g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
9882 
9883  g_pMarkInfoDialog->Layout();
9884 
9885  wxPoint canvas_pos = GetPosition();
9886  wxSize fitted_size = g_pMarkInfoDialog->GetSize();
9887 
9888  bool newFit = false;
9889  if (canvas_size.x < fitted_size.x) {
9890  fitted_size.x = canvas_size.x - 40;
9891  if (canvas_size.y < fitted_size.y)
9892  fitted_size.y -= 40; // scrollbar added
9893  }
9894  if (canvas_size.y < fitted_size.y) {
9895  fitted_size.y = canvas_size.y - 40;
9896  if (canvas_size.x < fitted_size.x)
9897  fitted_size.x -= 40; // scrollbar added
9898  }
9899 
9900  if (newFit) {
9901  g_pMarkInfoDialog->SetSize(fitted_size);
9902  g_pMarkInfoDialog->Centre();
9903  }
9904  }
9905 
9906  markPoint->m_bRPIsBeingEdited = false;
9907 
9908  wxString title_base = _("Waypoint Properties");
9909  if (!markPoint->m_bIsInRoute)
9910  title_base = _("Mark Properties");
9911 
9912  g_pMarkInfoDialog->SetRoutePoints(std::vector<RoutePoint*> {markPoint});
9913  g_pMarkInfoDialog->UpdateProperties();
9914  if (markPoint->m_bIsInLayer) {
9915  wxString caption(wxString::Format(_T("%s, %s: %s"),
9916  title_base, _("Layer"),
9917  GetLayerName(markPoint->m_LayerID)));
9918  g_pMarkInfoDialog->SetDialogTitle(caption);
9919  } else
9920  g_pMarkInfoDialog->SetDialogTitle(title_base);
9921 
9922  g_pMarkInfoDialog->Show();
9923  g_pMarkInfoDialog->Raise();
9924  g_pMarkInfoDialog->InitialFocus();
9925  if (bNew) g_pMarkInfoDialog->CenterOnScreen();
9926 }
9927 
9928 void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
9929  pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
9930  pRoutePropDialog->SetRouteAndUpdate(selected);
9931  // pNew->UpdateProperties();
9932  pRoutePropDialog->Show();
9933  pRoutePropDialog->Raise();
9934  return;
9935  pRoutePropDialog = RoutePropDlgImpl::getInstance(
9936  this); // There is one global instance of the RouteProp Dialog
9937 
9938  if (g_bresponsive) {
9939  wxSize canvas_size = GetSize();
9940  wxPoint canvas_pos = GetPosition();
9941  wxSize fitted_size = pRoutePropDialog->GetSize();
9942  ;
9943 
9944  if (canvas_size.x < fitted_size.x) {
9945  fitted_size.x = canvas_size.x;
9946  if (canvas_size.y < fitted_size.y)
9947  fitted_size.y -= 20; // scrollbar added
9948  }
9949  if (canvas_size.y < fitted_size.y) {
9950  fitted_size.y = canvas_size.y;
9951  if (canvas_size.x < fitted_size.x)
9952  fitted_size.x -= 20; // scrollbar added
9953  }
9954 
9955  pRoutePropDialog->SetSize(fitted_size);
9956  pRoutePropDialog->Centre();
9957 
9958  // int xp = (canvas_size.x - fitted_size.x)/2;
9959  // int yp = (canvas_size.y - fitted_size.y)/2;
9960 
9961  wxPoint xxp = ClientToScreen(canvas_pos);
9962  // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
9963  }
9964 
9965  pRoutePropDialog->SetRouteAndUpdate(selected);
9966 
9967  pRoutePropDialog->Show();
9968 
9969  Refresh(false);
9970 }
9971 
9972 void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
9973  pTrackPropDialog = TrackPropDlg::getInstance(
9974  this); // There is one global instance of the RouteProp Dialog
9975 
9976  pTrackPropDialog->SetTrackAndUpdate(selected);
9977  pTrackPropDialog->UpdateProperties();
9978 
9979  pTrackPropDialog->Show();
9980 
9981  Refresh(false);
9982 }
9983 
9984 void pupHandler_PasteWaypoint() {
9985  Kml kml;
9986 
9987  int pasteBuffer = kml.ParsePasteBuffer();
9988  RoutePoint *pasted = kml.GetParsedRoutePoint();
9989  if (!pasted) return;
9990 
9991  double nearby_radius_meters =
9992  g_Platform->GetSelectRadiusPix() /
9993  gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
9994 
9995  RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
9996  pasted->m_lat, pasted->m_lon, nearby_radius_meters);
9997 
9998  int answer = wxID_NO;
9999  if (nearPoint && !nearPoint->m_bIsInLayer) {
10000  wxString msg;
10001  msg << _(
10002  "There is an existing waypoint at the same location as the one you are "
10003  "pasting. Would you like to merge the pasted data with it?\n\n");
10004  msg << _("Answering 'No' will create a new waypoint at the same location.");
10005  answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10006  (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10007  }
10008 
10009  if (answer == wxID_YES) {
10010  nearPoint->SetName(pasted->GetName());
10011  nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10012  if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10013  pRouteManagerDialog->UpdateWptListCtrl();
10014  }
10015 
10016  if (answer == wxID_NO) {
10017  RoutePoint *newPoint = new RoutePoint(pasted);
10018  newPoint->m_bIsolatedMark = true;
10019  pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10020  newPoint);
10021  pConfig->AddNewWayPoint(newPoint, -1);
10022  pWayPointMan->AddRoutePoint(newPoint);
10023  if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10024  pRouteManagerDialog->UpdateWptListCtrl();
10025  if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10026  RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10027  }
10028 
10029  gFrame->InvalidateAllGL();
10030  gFrame->RefreshAllCanvas(false);
10031 }
10032 
10033 void pupHandler_PasteRoute() {
10034  Kml kml;
10035 
10036  int pasteBuffer = kml.ParsePasteBuffer();
10037  Route *pasted = kml.GetParsedRoute();
10038  if (!pasted) return;
10039 
10040  double nearby_radius_meters =
10041  g_Platform->GetSelectRadiusPix() /
10042  gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10043 
10044  RoutePoint *curPoint;
10045  RoutePoint *nearPoint;
10046  RoutePoint *prevPoint = NULL;
10047 
10048  bool mergepoints = false;
10049  bool createNewRoute = true;
10050  int existingWaypointCounter = 0;
10051 
10052  for (int i = 1; i <= pasted->GetnPoints(); i++) {
10053  curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10054  nearPoint = pWayPointMan->GetNearbyWaypoint(
10055  curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10056  if (nearPoint) {
10057  mergepoints = true;
10058  existingWaypointCounter++;
10059  // Small hack here to avoid both extending RoutePoint and repeating all
10060  // the GetNearbyWaypoint calculations. Use existin data field in
10061  // RoutePoint as temporary storage.
10062  curPoint->m_bPtIsSelected = true;
10063  }
10064  }
10065 
10066  int answer = wxID_NO;
10067  if (mergepoints) {
10068  wxString msg;
10069  msg << _(
10070  "There are existing waypoints at the same location as some of the ones "
10071  "you are pasting. Would you like to just merge the pasted data into "
10072  "them?\n\n");
10073  msg << _("Answering 'No' will create all new waypoints for this route.");
10074  answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10075  (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10076 
10077  if (answer == wxID_CANCEL) {
10078  return;
10079  }
10080  }
10081 
10082  // If all waypoints exist since before, and a route with the same name, we
10083  // don't create a new route.
10084  if (mergepoints && answer == wxID_YES &&
10085  existingWaypointCounter == pasted->GetnPoints()) {
10086  wxRouteListNode *route_node = pRouteList->GetFirst();
10087  while (route_node) {
10088  Route *proute = route_node->GetData();
10089 
10090  if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10091  createNewRoute = false;
10092  break;
10093  }
10094  route_node = route_node->GetNext();
10095  }
10096  }
10097 
10098  Route *newRoute = 0;
10099  RoutePoint *newPoint = 0;
10100 
10101  if (createNewRoute) {
10102  newRoute = new Route();
10103  newRoute->m_RouteNameString = pasted->m_RouteNameString;
10104  }
10105 
10106  for (int i = 1; i <= pasted->GetnPoints(); i++) {
10107  curPoint = pasted->GetPoint(i);
10108  if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10109  curPoint->m_bPtIsSelected = false;
10110  newPoint = pWayPointMan->GetNearbyWaypoint(
10111  curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10112  newPoint->SetName(curPoint->GetName());
10113  newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10114 
10115  if (createNewRoute) newRoute->AddPoint(newPoint);
10116  } else {
10117  curPoint->m_bPtIsSelected = false;
10118 
10119  newPoint = new RoutePoint(curPoint);
10120  newPoint->m_bIsolatedMark = false;
10121  newPoint->SetIconName(_T("circle"));
10122  newPoint->m_bIsVisible = true;
10123  newPoint->m_bShowName = false;
10124  newPoint->SetShared(false);
10125 
10126  newRoute->AddPoint(newPoint);
10127  pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10128  newPoint);
10129  pConfig->AddNewWayPoint(newPoint, -1);
10130  pWayPointMan->AddRoutePoint(newPoint);
10131  }
10132  if (i > 1 && createNewRoute)
10133  pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10134  curPoint->m_lat, curPoint->m_lon,
10135  prevPoint, newPoint, newRoute);
10136  prevPoint = newPoint;
10137  }
10138 
10139  if (createNewRoute) {
10140  pRouteList->Append(newRoute);
10141  pConfig->AddNewRoute(newRoute); // use auto next num
10142 
10143  if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10144  pRoutePropDialog->SetRouteAndUpdate(newRoute);
10145  }
10146 
10147  if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10148  pRouteManagerDialog->UpdateRouteListCtrl();
10149  pRouteManagerDialog->UpdateWptListCtrl();
10150  }
10151  gFrame->InvalidateAllGL();
10152  gFrame->RefreshAllCanvas(false);
10153  }
10154  if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10155  RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10156 }
10157 
10158 void pupHandler_PasteTrack() {
10159  Kml kml;
10160 
10161  int pasteBuffer = kml.ParsePasteBuffer();
10162  Track *pasted = kml.GetParsedTrack();
10163  if (!pasted) return;
10164 
10165  TrackPoint *curPoint;
10166 
10167  Track *newTrack = new Track();
10168  TrackPoint *newPoint;
10169  TrackPoint *prevPoint = NULL;
10170 
10171  newTrack->SetName(pasted->GetName());
10172 
10173  for (int i = 0; i < pasted->GetnPoints(); i++) {
10174  curPoint = pasted->GetPoint(i);
10175 
10176  newPoint = new TrackPoint(curPoint);
10177 
10178  wxDateTime now = wxDateTime::Now();
10179  newPoint->SetCreateTime(curPoint->GetCreateTime());
10180 
10181  newTrack->AddPoint(newPoint);
10182 
10183  if (prevPoint)
10184  pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10185  newPoint->m_lat, newPoint->m_lon,
10186  prevPoint, newPoint, newTrack);
10187 
10188  prevPoint = newPoint;
10189  }
10190 
10191  g_TrackList.push_back(newTrack);
10192  pConfig->AddNewTrack(newTrack);
10193 
10194  gFrame->InvalidateAllGL();
10195  gFrame->RefreshAllCanvas(false);
10196 }
10197 
10198 bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
10199  m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
10200  m_pFoundRoutePoint, m_FoundAIS_MMSI,
10201  m_pIDXCandidate);
10202 
10203  Connect(
10204  wxEVT_COMMAND_MENU_SELECTED,
10205  (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10206 
10207  m_canvasMenu->CanvasPopupMenu(x, y, seltype);
10208 
10209  Disconnect(
10210  wxEVT_COMMAND_MENU_SELECTED,
10211  (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10212 
10213  delete m_canvasMenu;
10214  m_canvasMenu = NULL;
10215 
10216 #ifdef __WXQT__
10217  // gFrame->SurfaceToolbar();
10218  // g_MainToolbar->Raise();
10219 #endif
10220 
10221  return true;
10222 }
10223 
10224 void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
10225  // Pass menu events from the canvas to the menu handler
10226  // This is necessarily in ChartCanvas since that is the menu's parent.
10227  if (m_canvasMenu) {
10228  m_canvasMenu->PopupMenuHandler(event);
10229  }
10230  return;
10231 }
10232 
10233 void ChartCanvas::StartRoute(void) {
10234  // Do not allow more than one canvas to create a route at one time.
10235  if (g_brouteCreating) return;
10236 
10237  if (g_MainToolbar) g_MainToolbar->DisableTooltips();
10238 
10239  g_brouteCreating = true;
10240  m_routeState = 1;
10241  m_bDrawingRoute = false;
10242  SetCursor(*pCursorPencil);
10243  //SetCanvasToolbarItemState(ID_ROUTE, true);
10244  gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
10245 
10246  HideGlobalToolbar();
10247 
10248 #ifdef __ANDROID__
10249  androidSetRouteAnnunciator(true);
10250 #endif
10251 }
10252 
10253 void ChartCanvas::FinishRoute(void) {
10254  m_routeState = 0;
10255  m_prev_pMousePoint = NULL;
10256  m_bDrawingRoute = false;
10257 
10258  //SetCanvasToolbarItemState(ID_ROUTE, false);
10259  gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
10260 #ifdef __ANDROID__
10261  androidSetRouteAnnunciator(false);
10262 #endif
10263 
10264  SetCursor(*pCursorArrow);
10265 
10266  if (m_pMouseRoute) {
10267  if (m_bAppendingRoute)
10268  pConfig->UpdateRoute(m_pMouseRoute);
10269  else {
10270  if (m_pMouseRoute->GetnPoints() > 1) {
10271  pConfig->AddNewRoute(m_pMouseRoute);
10272  } else {
10273  g_pRouteMan->DeleteRoute(m_pMouseRoute,
10274  NavObjectChanges::getInstance());
10275  m_pMouseRoute = NULL;
10276  }
10277  }
10278  if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
10279 
10280  if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
10281  (pRoutePropDialog->IsShown())) {
10282  pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
10283  }
10284 
10285  if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
10286  if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10287  pRouteManagerDialog->UpdateRouteListCtrl();
10288  }
10289  }
10290  m_bAppendingRoute = false;
10291  m_pMouseRoute = NULL;
10292 
10293  m_pSelectedRoute = NULL;
10294 
10295  undo->InvalidateUndo();
10296  gFrame->RefreshAllCanvas(true);
10297 
10298  if (g_MainToolbar) g_MainToolbar->EnableTooltips();
10299 
10300  ShowGlobalToolbar();
10301 
10302  g_brouteCreating = false;
10303 }
10304 
10305 void ChartCanvas::HideGlobalToolbar() {
10306  if (m_canvasIndex == 0) {
10307  m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
10308  }
10309 }
10310 
10311 void ChartCanvas::ShowGlobalToolbar() {
10312  if (m_canvasIndex == 0) {
10313  if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
10314  }
10315 }
10316 
10317 void ChartCanvas::ShowAISTargetList(void) {
10318  if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
10319  g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
10320  }
10321 
10322  g_pAISTargetList->UpdateAISTargetList();
10323 }
10324 
10325 void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
10326  if (!m_bShowOutlines) return;
10327 
10328  if (!ChartData) return;
10329 
10330  int nEntry = ChartData->GetChartTableEntries();
10331 
10332  for (int i = 0; i < nEntry; i++) {
10333  ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
10334 
10335  // Check to see if the candidate chart is in the currently active group
10336  bool b_group_draw = false;
10337  if (m_groupIndex > 0) {
10338  for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
10339  int index = pt->GetGroupArray()[ig];
10340  if (m_groupIndex == index) {
10341  b_group_draw = true;
10342  break;
10343  }
10344  }
10345  } else
10346  b_group_draw = true;
10347 
10348  if (b_group_draw) RenderChartOutline(dc, i, vp);
10349  }
10350 
10351  // On CM93 Composite Charts, draw the outlines of the next smaller
10352  // scale cell
10353  cm93compchart *pcm93 = NULL;
10354  if (VPoint.b_quilt) {
10355  for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
10356  if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
10357  pcm93 = (cm93compchart *)pch;
10358  break;
10359  }
10360  } else if (m_singleChart &&
10361  (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
10362  pcm93 = (cm93compchart *)m_singleChart;
10363 
10364  if (pcm93) {
10365  double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
10366  double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
10367 
10368  if (zoom_factor > 8.0) {
10369  wxPen mPen(GetGlobalColor(_T("UINFM")), 2, wxPENSTYLE_SHORT_DASH);
10370  dc.SetPen(mPen);
10371  } else {
10372  wxPen mPen(GetGlobalColor(_T("UINFM")), 1, wxPENSTYLE_SOLID);
10373  dc.SetPen(mPen);
10374  }
10375 
10376  pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
10377  }
10378 }
10379 
10380 void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
10381 #ifdef ocpnUSE_GL
10382  if (g_bopengl && m_glcc) {
10383  /* opengl version specially optimized */
10384  m_glcc->RenderChartOutline(dc, dbIndex, vp);
10385  return;
10386  }
10387 #endif
10388 
10389  if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
10390  if (!ChartData->IsChartAvailable(dbIndex)) return;
10391  }
10392 
10393  float plylat, plylon;
10394  float plylat1, plylon1;
10395 
10396  int pixx, pixy, pixx1, pixy1;
10397 
10398  LLBBox box;
10399  ChartData->GetDBBoundingBox(dbIndex, box);
10400 
10401  // Don't draw an outline in the case where the chart covers the entire world
10402  // */
10403  if (box.GetLonRange() == 360) return;
10404 
10405  double lon_bias = 0;
10406  // chart is outside of viewport lat/lon bounding box
10407  if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
10408 
10409  int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10410 
10411  if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
10412  dc.SetPen(wxPen(GetGlobalColor(_T ( "YELO1" )), 1, wxPENSTYLE_SOLID));
10413 
10414  else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
10415  dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFG" )), 1, wxPENSTYLE_SOLID));
10416 
10417  else
10418  dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFR" )), 1, wxPENSTYLE_SOLID));
10419 
10420  // Are there any aux ply entries?
10421  int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
10422  if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
10423  {
10424  wxPoint r, r1;
10425 
10426  ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10427  plylon += lon_bias;
10428 
10429  GetCanvasPointPix(plylat, plylon, &r);
10430  pixx = r.x;
10431  pixy = r.y;
10432 
10433  for (int i = 0; i < nPly - 1; i++) {
10434  ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
10435  plylon1 += lon_bias;
10436 
10437  GetCanvasPointPix(plylat1, plylon1, &r1);
10438  pixx1 = r1.x;
10439  pixy1 = r1.y;
10440 
10441  int pixxs1 = pixx1;
10442  int pixys1 = pixy1;
10443 
10444  bool b_skip = false;
10445 
10446  if (vp.chart_scale > 5e7) {
10447  // calculate projected distance between these two points in meters
10448  double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
10449  pow((double)(pixy1 - pixy), 2)) /
10450  vp.view_scale_ppm;
10451 
10452  if (dist > 0.0) {
10453  // calculate GC distance between these two points in meters
10454  double distgc =
10455  DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
10456 
10457  // If the distances are nonsense, it means that the scale is very
10458  // small and the segment wrapped the world So skip it....
10459  // TODO improve this to draw two segments
10460  if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
10461  b_skip = true;
10462  } else
10463  b_skip = true;
10464  }
10465 
10466  ClipResult res = cohen_sutherland_line_clip_i(
10467  &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
10468  if (res != Invisible && !b_skip)
10469  dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
10470 
10471  plylat = plylat1;
10472  plylon = plylon1;
10473  pixx = pixxs1;
10474  pixy = pixys1;
10475  }
10476 
10477  ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
10478  plylon1 += lon_bias;
10479 
10480  GetCanvasPointPix(plylat1, plylon1, &r1);
10481  pixx1 = r1.x;
10482  pixy1 = r1.y;
10483 
10484  ClipResult res = cohen_sutherland_line_clip_i(
10485  &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
10486  if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
10487  }
10488 
10489  else // Use Aux PlyPoints
10490  {
10491  wxPoint r, r1;
10492 
10493  int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
10494  for (int j = 0; j < nAuxPlyEntries; j++) {
10495  int nAuxPly =
10496  ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
10497  GetCanvasPointPix(plylat, plylon, &r);
10498  pixx = r.x;
10499  pixy = r.y;
10500 
10501  for (int i = 0; i < nAuxPly - 1; i++) {
10502  ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
10503 
10504  GetCanvasPointPix(plylat1, plylon1, &r1);
10505  pixx1 = r1.x;
10506  pixy1 = r1.y;
10507 
10508  int pixxs1 = pixx1;
10509  int pixys1 = pixy1;
10510 
10511  bool b_skip = false;
10512 
10513  if (vp.chart_scale > 5e7) {
10514  // calculate projected distance between these two points in meters
10515  double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
10516  ((pixy1 - pixy) * (pixy1 - pixy))) /
10517  vp.view_scale_ppm;
10518  if (dist > 0.0) {
10519  // calculate GC distance between these two points in meters
10520  double distgc =
10521  DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
10522 
10523  // If the distances are nonsense, it means that the scale is very
10524  // small and the segment wrapped the world So skip it....
10525  // TODO improve this to draw two segments
10526  if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
10527  b_skip = true;
10528  } else
10529  b_skip = true;
10530  }
10531 
10532  ClipResult res = cohen_sutherland_line_clip_i(
10533  &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
10534  if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
10535 
10536  plylat = plylat1;
10537  plylon = plylon1;
10538  pixx = pixxs1;
10539  pixy = pixys1;
10540  }
10541 
10542  ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
10543  GetCanvasPointPix(plylat1, plylon1, &r1);
10544  pixx1 = r1.x;
10545  pixy1 = r1.y;
10546 
10547  ClipResult res = cohen_sutherland_line_clip_i(
10548  &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
10549  if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
10550  }
10551  }
10552 }
10553 
10554 static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
10555  const wxString &second) {
10556  wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
10557 
10558  int pointsize = dFont->GetPointSize();
10559  pointsize /= OCPN_GetWinDIPScaleFactor();
10560 
10561  wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
10562  pointsize, dFont->GetFamily(), dFont->GetStyle(),
10563  dFont->GetWeight(), false, dFont->GetFaceName());
10564 
10565 
10566  dc.SetFont(*psRLI_font);
10567 
10568  int w1, h1;
10569  int w2 = 0;
10570  int h2 = 0;
10571  int h, w;
10572 
10573  int xp, yp;
10574  int hilite_offset = 3;
10575 #ifdef __WXMAC__
10576  wxScreenDC sdc;
10577  sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
10578  if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
10579 #else
10580  dc.GetTextExtent(first, &w1, &h1);
10581  if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
10582 #endif
10583 
10584  h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
10585  h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
10586 
10587  w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
10588  w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
10589 
10590  h = h1 + h2;
10591 
10592  xp = ref_point.x - w;
10593  yp = ref_point.y;
10594  yp += hilite_offset;
10595 
10596  AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor(_T ( "YELO1" )), 172);
10597 
10598  dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" ))));
10599  dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
10600 
10601  dc.DrawText(first, xp, yp);
10602  if (second.Len()) dc.DrawText(second, xp, yp + h1);
10603 }
10604 
10605 void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
10606  if (!g_bAllowShipToActive) return;
10607 
10608  Route *rt = g_pRouteMan->GetpActiveRoute();
10609  if (!rt) return;
10610 
10611  if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
10612  wxPoint2DDouble pa, pb;
10613  GetDoubleCanvasPointPix(gLat, gLon, &pa);
10614  GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
10615 
10616  // set pen
10617  int width =
10618  g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
10619  if (rt->m_width != wxPENSTYLE_INVALID)
10620  width = rt->m_width; // set route pen style if any
10621  wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
10622  g_shipToActiveStyle, 5)]; // get setting pen style
10623  if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
10624  wxColour color =
10625  g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
10626  : // set setting route pen color
10627  g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
10628  wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
10629 
10630  dc.SetPen(*mypen);
10631  dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
10632 
10633  if (!Use_Opengl)
10634  RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x, (int)pb.m_y,
10635  GetVP(), true);
10636 
10637 #ifdef ocpnUSE_GL
10638  else {
10639 
10640 #ifdef USE_ANDROID_GLES2
10641  dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
10642 #else
10643  if (style != wxPENSTYLE_SOLID) {
10644  if (glChartCanvas::dash_map.find(style) !=
10645  glChartCanvas::dash_map.end()) {
10646  mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
10647  dc.SetPen(*mypen);
10648  }
10649  }
10650  dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
10651 #endif
10652 
10653  RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
10654  (int)pb.m_y, GetVP());
10655  }
10656 #endif
10657  }
10658 }
10659 
10660 void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
10661  Route *route = 0;
10662  if (m_routeState >= 2) route = m_pMouseRoute;
10663  if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
10664  route = m_pMeasureRoute;
10665 
10666  if (!route) return;
10667 
10668  // Validate route pointer
10669  if( !g_pRouteMan->IsRouteValid(route) )
10670  return;
10671 
10672  double render_lat = m_cursor_lat;
10673  double render_lon = m_cursor_lon;
10674 
10675  int np = route->GetnPoints();
10676  if (np) {
10677  if (g_btouch && (np > 1)) np--;
10678  RoutePoint rp = route->GetPoint(np);
10679  render_lat = rp.m_lat;
10680  render_lon = rp.m_lon;
10681  }
10682 
10683  double rhumbBearing, rhumbDist;
10684  DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
10685  &rhumbBearing, &rhumbDist);
10686  double brg = rhumbBearing;
10687  double dist = rhumbDist;
10688 
10689  // Skip GreatCircle rubberbanding on touch devices.
10690  if (!g_btouch) {
10691  double gcBearing, gcBearing2, gcDist;
10692  Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
10693  m_cursor_lat, &gcDist, &gcBearing,
10694  &gcBearing2);
10695  double gcDistm = gcDist / 1852.0;
10696 
10697  if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
10698  rhumbBearing = 90.;
10699 
10700  wxPoint destPoint, lastPoint;
10701 
10702  route->m_NextLegGreatCircle = false;
10703  int milesDiff = rhumbDist - gcDistm;
10704  if (milesDiff > 1) {
10705  brg = gcBearing;
10706  dist = gcDistm;
10707  route->m_NextLegGreatCircle = true;
10708  }
10709 
10710  //FIXME (MacOS, the first segment is rendered wrong)
10711  RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex, &lastPoint);
10712 
10713  if (route->m_NextLegGreatCircle) {
10714  for (int i = 1; i <= milesDiff; i++) {
10715  double p = (double)i * (1.0 / (double)milesDiff);
10716  double pLat, pLon;
10717  Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
10718  &pLon, &pLat, &gcBearing2);
10719  destPoint = VPoint.GetPixFromLL(pLat, pLon);
10720  RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(), false);
10721  lastPoint = destPoint;
10722  }
10723  } else {
10724  if (r_rband.x && r_rband.y) { // RubberBand disabled?
10725  RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(), false);
10726  if (m_bMeasure_DistCircle) {
10727  double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
10728  powf((float)(r_rband.y - lastPoint.y), 2));
10729 
10730  dc.SetPen(*g_pRouteMan->GetRoutePen());
10731  dc.SetBrush(*wxTRANSPARENT_BRUSH);
10732  dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
10733  }
10734  }
10735  }
10736  }
10737 
10738  wxString routeInfo;
10739  if (g_bShowTrue)
10740  routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
10741  0x00B0);
10742 
10743  if (g_bShowMag) {
10744  double latAverage = (m_cursor_lat + render_lat) / 2;
10745  double lonAverage = (m_cursor_lon + render_lon) / 2;
10746  double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
10747 
10748  routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8), (int)varBrg,
10749  0x00B0);
10750  }
10751 
10752  routeInfo << _T(" ") << FormatDistanceAdaptive(dist);
10753 
10754  wxString s0;
10755  if (!route->m_bIsInLayer)
10756  s0.Append(_("Route") + _T(": "));
10757  else
10758  s0.Append(_("Layer Route: "));
10759 
10760  double disp_length = route->m_route_length;
10761  if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
10762  s0 += FormatDistanceAdaptive(disp_length);
10763 
10764  RouteLegInfo(dc, r_rband, routeInfo, s0);
10765 
10766  m_brepaint_piano = true;
10767 }
10768 
10769 void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
10770  if (!m_bShowVisibleSectors) return;
10771 
10772  if (g_bDeferredInitDone) {
10773  // need to re-evaluate sectors?
10774  double rhumbBearing, rhumbDist;
10775  DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
10776  &rhumbBearing, &rhumbDist);
10777 
10778  if (rhumbDist > 0.05) // miles
10779  {
10780  s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
10781  m_sectorlegsVisible);
10782  m_sector_glat = gLat;
10783  m_sector_glon = gLon;
10784  }
10785  s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
10786  }
10787 }
10788 
10789 void ChartCanvas::WarpPointerDeferred(int x, int y) {
10790  warp_x = x;
10791  warp_y = y;
10792  warp_flag = true;
10793 }
10794 
10795 int s_msg;
10796 
10797 void ChartCanvas::UpdateCanvasS52PLIBConfig() {
10798  if (!ps52plib) return;
10799 
10800  if (VPoint.b_quilt) { // quilted
10801  if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
10802 
10803  if (m_pQuilt->IsQuiltVector()) {
10804  if (ps52plib->GetStateHash() != m_s52StateHash) {
10805  UpdateS52State();
10806  m_s52StateHash = ps52plib->GetStateHash();
10807  }
10808  }
10809  } else {
10810  if (ps52plib->GetStateHash() != m_s52StateHash) {
10811  UpdateS52State();
10812  m_s52StateHash = ps52plib->GetStateHash();
10813  }
10814  }
10815 
10816  // Plugin charts
10817  bool bSendPlibState = true;
10818  if (VPoint.b_quilt) { // quilted
10819  if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
10820  }
10821 
10822  if (bSendPlibState) {
10823  wxJSONValue v;
10824  v[_T("OpenCPN Version Major")] = VERSION_MAJOR;
10825  v[_T("OpenCPN Version Minor")] = VERSION_MINOR;
10826  v[_T("OpenCPN Version Patch")] = VERSION_PATCH;
10827  v[_T("OpenCPN Version Date")] = VERSION_DATE;
10828  v[_T("OpenCPN Version Full")] = VERSION_FULL;
10829 
10830  // S52PLIB state
10831  v[_T("OpenCPN S52PLIB ShowText")] = GetShowENCText();
10832  v[_T("OpenCPN S52PLIB ShowSoundings")] = GetShowENCDepth();
10833  v[_T("OpenCPN S52PLIB ShowLights")] = GetShowENCLights();
10834  v[_T("OpenCPN S52PLIB ShowAnchorConditions")] =
10835  m_encShowAnchor;
10836  v[_T("OpenCPN S52PLIB ShowQualityOfData")] =
10837  GetShowENCDataQual();
10838  v[_T("OpenCPN S52PLIB ShowATONLabel")] = GetShowENCBuoyLabels();
10839  v[_T("OpenCPN S52PLIB ShowLightDescription")] = GetShowENCLightDesc();
10840 
10841  v[_T("OpenCPN S52PLIB DisplayCategory")] = GetENCDisplayCategory();
10842 
10843  v[_T("OpenCPN S52PLIB SoundingsFactor")] = g_ENCSoundingScaleFactor;
10844  v[_T("OpenCPN S52PLIB TextFactor")] = g_ENCTextScaleFactor;
10845 
10846  // Global S52 options
10847 
10848  v[_T("OpenCPN S52PLIB MetaDisplay")] = ps52plib->m_bShowMeta;
10849  v[_T("OpenCPN S52PLIB DeclutterText")] = ps52plib->m_bDeClutterText;
10850  v[_T("OpenCPN S52PLIB ShowNationalText")] = ps52plib->m_bShowNationalTexts;
10851  v[_T("OpenCPN S52PLIB ShowImportantTextOnly")] = ps52plib->m_bShowS57ImportantTextOnly;
10852  v[_T("OpenCPN S52PLIB UseSCAMIN")] = ps52plib->m_bUseSCAMIN;
10853  v[_T("OpenCPN S52PLIB UseSUPER_SCAMIN")] = ps52plib->m_bUseSUPER_SCAMIN;
10854  v[_T("OpenCPN S52PLIB SymbolStyle")] = ps52plib->m_nSymbolStyle;
10855  v[_T("OpenCPN S52PLIB BoundaryStyle")] = ps52plib->m_nBoundaryStyle;
10856  v[_T("OpenCPN S52PLIB ColorShades")] = S52_getMarinerParam( S52_MAR_TWO_SHADES );
10857 
10858  // Some global GUI parameters, for completeness
10859  v[_T("OpenCPN Zoom Mod Vector")] = g_chart_zoom_modifier_vector;
10860  v[_T("OpenCPN Zoom Mod Other")] = g_chart_zoom_modifier_raster;
10861  v[_T("OpenCPN Scale Factor Exp")] = g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
10862  v[_T("OpenCPN Display Width")] = (int)g_display_size_mm;
10863 
10864  wxJSONWriter w;
10865  wxString out;
10866  w.Write(v, out);
10867 
10868  if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
10869  g_pi_manager->SendMessageToAllPlugins(wxString(_T("OpenCPN Config")),
10870  out);
10871  g_lastS52PLIBPluginMessage = out;
10872  }
10873  }
10874 }
10875 int spaint;
10876 int s_in_update;
10877 void ChartCanvas::OnPaint(wxPaintEvent &event) {
10878  wxPaintDC dc(this);
10879 
10880  // GetToolbar()->Show( m_bToolbarEnable );
10881 
10882  // Paint updates may have been externally disabled (temporarily, to avoid
10883  // Yield() recursion performance loss) It is important that the wxPaintDC is
10884  // built, even if we elect to not process this paint message. Otherwise, the
10885  // paint message may not be removed from the message queue, esp on Windows.
10886  // (FS#1213) This would lead to a deadlock condition in ::wxYield()
10887 
10888  if (!m_b_paint_enable) {
10889  return;
10890  }
10891 
10892  // If necessary, reconfigure the S52 PLIB
10893  UpdateCanvasS52PLIBConfig();
10894 
10895 #ifdef ocpnUSE_GL
10896  if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
10897 
10898  if (m_glcc && g_bopengl) {
10899  if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
10900  s_in_update++;
10901  m_glcc->Update();
10902  s_in_update--;
10903  }
10904 
10905  return;
10906  }
10907 #endif
10908 
10909  if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
10910 
10911  wxRegion ru = GetUpdateRegion();
10912 
10913  int rx, ry, rwidth, rheight;
10914  ru.GetBox(rx, ry, rwidth, rheight);
10915  // printf("%d Onpaint update region box: %d %d %d %d\n", spaint++, rx, ry,
10916  // rwidth, rheight);
10917 
10918 #ifdef ocpnUSE_DIBSECTION
10919  ocpnMemDC temp_dc;
10920 #else
10921  wxMemoryDC temp_dc;
10922 #endif
10923 
10924  long height = GetVP().pix_height;
10925 
10926 #ifdef __WXMAC__
10927  // On OS X we have to explicitly extend the region for the piano area
10928  ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
10929  if (!style->chartStatusWindowTransparent && g_bShowChartBar)
10930  height += m_Piano->GetHeight();
10931 #endif // __WXMAC__
10932  wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
10933 
10934  // In case Thumbnail is shown, set up dc clipper and blt iterator regions
10935  if (pthumbwin) {
10936  int thumbx, thumby, thumbsx, thumbsy;
10937  pthumbwin->GetPosition(&thumbx, &thumby);
10938  pthumbwin->GetSize(&thumbsx, &thumbsy);
10939  wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
10940 
10941  if (pthumbwin->IsShown()) {
10942  rgn_chart.Subtract(rgn_thumbwin);
10943  ru.Subtract(rgn_thumbwin);
10944  }
10945  }
10946 
10947  // subtract the chart bar if it isn't transparent, and determine if we need to
10948  // paint it
10949  wxRegion rgn_blit = ru;
10950  if (g_bShowChartBar) {
10951  wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
10952  GetClientSize().x, m_Piano->GetHeight());
10953 
10954  ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
10955  if (ru.Contains(chart_bar_rect) != wxOutRegion) {
10956  if (style->chartStatusWindowTransparent)
10957  m_brepaint_piano = true;
10958  else
10959  ru.Subtract(chart_bar_rect);
10960  }
10961  }
10962 
10963  if (m_Compass && m_Compass->IsShown()) {
10964  wxRect compassRect = m_Compass->GetRect();
10965  if (ru.Contains(compassRect) != wxOutRegion) {
10966  ru.Subtract(compassRect);
10967  }
10968  }
10969 
10970  // Is this viewpoint the same as the previously painted one?
10971  bool b_newview = true;
10972 
10973  if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
10974  (m_cache_vp.rotation == VPoint.rotation) &&
10975  (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
10976  m_cache_vp.IsValid()) {
10977  b_newview = false;
10978  }
10979 
10980  // If the ViewPort is skewed or rotated, we may be able to use the cached
10981  // rotated bitmap.
10982  bool b_rcache_ok = false;
10983  if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
10984  b_rcache_ok = !b_newview;
10985 
10986  // Make a special VP
10987  if (VPoint.b_MercatorProjectionOverride)
10988  VPoint.SetProjectionType(PROJECTION_MERCATOR);
10989  ViewPort svp = VPoint;
10990 
10991  svp.pix_width = svp.rv_rect.width;
10992  svp.pix_height = svp.rv_rect.height;
10993 
10994  // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
10995  // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
10996  // VPoint.rv_rect.height);
10997 
10998  OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
10999 
11000  // If we are going to use the cached rotated image, there is no need to fetch
11001  // any chart data and this will do it...
11002  if (b_rcache_ok) chart_get_region.Clear();
11003 
11004  // Blit pan acceleration
11005  if (VPoint.b_quilt) // quilted
11006  {
11007  if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11008 
11009  bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11010 
11011  bool busy = false;
11012  if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11013  m_cache_vp.rotation != VPoint.rotation)) {
11014  AbstractPlatform::ShowBusySpinner();
11015  busy = true;
11016  }
11017 
11018  if ((m_working_bm.GetWidth() != svp.pix_width) ||
11019  (m_working_bm.GetHeight() != svp.pix_height))
11020  m_working_bm.Create(svp.pix_width, svp.pix_height,
11021  -1); // make sure the target is big enoug
11022 
11023  if (fabs(VPoint.rotation) < 0.01) {
11024  bool b_save = true;
11025 
11026  if (g_SencThreadManager) {
11027  if (g_SencThreadManager->GetJobCount()) {
11028  b_save = false;
11029  m_cache_vp.Invalidate();
11030  }
11031  }
11032 
11033  // If the saved wxBitmap from last OnPaint is useable
11034  // calculate the blit parameters
11035 
11036  // We can only do screen blit painting if subsequent ViewPorts differ by
11037  // whole pixels So, in small scale bFollow mode, force the full screen
11038  // render. This seems a hack....There may be better logic here.....
11039 
11040  // if(m_bFollow)
11041  // b_save = false;
11042 
11043  if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11044  if (b_newview) {
11045  wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11046  wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11047 
11048  int dy = c_new.y - c_old.y;
11049  int dx = c_new.x - c_old.x;
11050 
11051  // printf("In OnPaint Trying Blit dx: %d
11052  // dy:%d\n\n", dx, dy);
11053 
11054  if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11055  if (dx || dy) {
11056  // Blit the reuseable portion of the cached wxBitmap to a working
11057  // bitmap
11058  temp_dc.SelectObject(m_working_bm);
11059 
11060  wxMemoryDC cache_dc;
11061  cache_dc.SelectObject(m_cached_chart_bm);
11062 
11063  if (dy > 0) {
11064  if (dx > 0) {
11065  temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11066  VPoint.pix_height - dy, &cache_dc, dx, dy);
11067  } else {
11068  temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11069  VPoint.pix_height - dy, &cache_dc, 0, dy);
11070  }
11071 
11072  } else {
11073  if (dx > 0) {
11074  temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11075  VPoint.pix_height + dy, &cache_dc, dx, 0);
11076  } else {
11077  temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11078  VPoint.pix_height + dy, &cache_dc, 0, 0);
11079  }
11080  }
11081 
11082  OCPNRegion update_region;
11083  if (dy) {
11084  if (dy > 0)
11085  update_region.Union(
11086  wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11087  else
11088  update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11089  }
11090 
11091  if (dx) {
11092  if (dx > 0)
11093  update_region.Union(
11094  wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11095  else
11096  update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11097  }
11098 
11099  // Render the new region
11100  m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11101  update_region);
11102  cache_dc.SelectObject(wxNullBitmap);
11103  } else {
11104  // No sensible (dx, dy) change in the view, so use the cached
11105  // member bitmap
11106  temp_dc.SelectObject(m_cached_chart_bm);
11107  b_save = false;
11108  }
11109  m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11110 
11111  } else // not blitable
11112  {
11113  temp_dc.SelectObject(m_working_bm);
11114  m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11115  chart_get_region);
11116  }
11117  } else {
11118  // No change in the view, so use the cached member bitmap2
11119  temp_dc.SelectObject(m_cached_chart_bm);
11120  b_save = false;
11121  }
11122  } else // cached bitmap is not yet valid
11123  {
11124  temp_dc.SelectObject(m_working_bm);
11125  m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11126  chart_get_region);
11127  }
11128 
11129  // Save the fully rendered quilt image as a wxBitmap member of this class
11130  if (b_save) {
11131  // if((m_cached_chart_bm.GetWidth() !=
11132  // svp.pix_width) ||
11133  // (m_cached_chart_bm.GetHeight() !=
11134  // svp.pix_height))
11135  // m_cached_chart_bm.Create(svp.pix_width,
11136  // svp.pix_height, -1); // target wxBitmap
11137  // is big enough
11138  wxMemoryDC scratch_dc_0;
11139  scratch_dc_0.SelectObject(m_cached_chart_bm);
11140  scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11141 
11142  scratch_dc_0.SelectObject(wxNullBitmap);
11143 
11144  m_bm_cache_vp =
11145  VPoint; // save the ViewPort associated with the cached wxBitmap
11146  }
11147  }
11148 
11149  else // quilted, rotated
11150  {
11151  temp_dc.SelectObject(m_working_bm);
11152  OCPNRegion chart_get_all_region(
11153  wxRect(0, 0, svp.pix_width, svp.pix_height));
11154  m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11155  chart_get_all_region);
11156  }
11157 
11158  AbstractPlatform::HideBusySpinner();
11159 
11160  }
11161 
11162  else // not quilted
11163  {
11164  if (!m_singleChart) {
11165  dc.SetBackground(wxBrush(*wxLIGHT_GREY));
11166  dc.Clear();
11167  return;
11168  }
11169 
11170  if (!chart_get_region.IsEmpty()) {
11171  m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
11172  }
11173  }
11174 
11175  if (temp_dc.IsOk()) {
11176  // Arrange to render the World Chart vector data behind the rendered
11177  // current chart so that uncovered canvas areas show at least the world
11178  // chart.
11179  OCPNRegion chartValidRegion;
11180  if (!VPoint.b_quilt) {
11181  // Make a region covering the current chart on the canvas
11182 
11183  if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
11184  m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
11185  else {
11186  // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
11187  // require that the viewport passed here have pix_width and pix_height
11188  // set to the actual display, not the virtual (rv_rect) sizes
11189  // (the vector calculations require the virtual sizes in svp)
11190 
11191  m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
11192  chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
11193  }
11194  } else
11195  chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
11196 
11197  temp_dc.DestroyClippingRegion();
11198 
11199  // Copy current chart region
11200  OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
11201 
11202  if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
11203 
11204  if (!backgroundRegion.IsEmpty()) {
11205  // Draw the Background Chart only in the areas NOT covered by the
11206  // current chart view
11207 
11208  /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
11209  clipping regions with more than 1 rectangle so... */
11210  wxColour water = pWorldBackgroundChart->water;
11211  if (water.IsOk()) {
11212  temp_dc.SetPen(*wxTRANSPARENT_PEN);
11213  temp_dc.SetBrush(wxBrush(water));
11214  OCPNRegionIterator upd(backgroundRegion); // get the update rect list
11215  while (upd.HaveRects()) {
11216  wxRect rect = upd.GetRect();
11217  temp_dc.DrawRectangle(rect);
11218  upd.NextRect();
11219  }
11220  }
11221  // Associate with temp_dc
11222  wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
11223  temp_dc.SetDeviceClippingRegion(*clip_region);
11224  delete clip_region;
11225 
11226  ocpnDC bgdc(temp_dc);
11227  double r = VPoint.rotation;
11228  SetVPRotation(VPoint.skew);
11229 
11230  //pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
11231  gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
11232 
11233  SetVPRotation(r);
11234  }
11235  } // temp_dc.IsOk();
11236 
11237  wxMemoryDC *pChartDC = &temp_dc;
11238  wxMemoryDC rotd_dc;
11239 
11240  if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
11241  // Can we use the current rotated image cache?
11242  if (!b_rcache_ok) {
11243 #ifdef __WXMSW__
11244  wxMemoryDC tbase_dc;
11245  wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
11246  tbase_dc.SelectObject(bm_base);
11247  tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11248  tbase_dc.SelectObject(wxNullBitmap);
11249 #else
11250  const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
11251 #endif
11252 
11253  wxImage base_image;
11254  if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
11255 
11256  // Use a local static image rotator to improve wxWidgets code profile
11257  // Especially, on GTK the wxRound and wxRealPoint functions are very
11258  // expensive.....
11259 
11260  double angle = GetVP().skew - GetVP().rotation;
11261  wxImage ri;
11262  bool b_rot_ok = false;
11263  if (base_image.IsOk()) {
11264  ViewPort rot_vp = GetVP();
11265 
11266  m_b_rot_hidef = false;
11267 
11268  ri = Image_Rotate(
11269  base_image, angle,
11270  wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
11271  m_b_rot_hidef, &m_roffset);
11272 
11273  if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11274  (rot_vp.rotation == VPoint.rotation) &&
11275  (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
11276  rot_vp.IsValid() && (ri.IsOk())) {
11277  b_rot_ok = true;
11278  }
11279  }
11280 
11281  if (b_rot_ok) {
11282  delete m_prot_bm;
11283  m_prot_bm = new wxBitmap(ri);
11284  }
11285 
11286  m_roffset.x += VPoint.rv_rect.x;
11287  m_roffset.y += VPoint.rv_rect.y;
11288  }
11289 
11290  if (m_prot_bm && m_prot_bm->IsOk()) {
11291  rotd_dc.SelectObject(*m_prot_bm);
11292  pChartDC = &rotd_dc;
11293  } else {
11294  pChartDC = &temp_dc;
11295  m_roffset = wxPoint(0, 0);
11296  }
11297  } else { // unrotated
11298  pChartDC = &temp_dc;
11299  m_roffset = wxPoint(0, 0);
11300  }
11301 
11302  wxPoint offset = m_roffset;
11303 
11304  // Save the PixelCache viewpoint for next time
11305  m_cache_vp = VPoint;
11306 
11307  // Set up a scratch DC for overlay objects
11308  wxMemoryDC mscratch_dc;
11309  mscratch_dc.SelectObject(*pscratch_bm);
11310 
11311  mscratch_dc.ResetBoundingBox();
11312  mscratch_dc.DestroyClippingRegion();
11313  mscratch_dc.SetDeviceClippingRegion(rgn_chart);
11314 
11315  // Blit the externally invalidated areas of the chart onto the scratch dc
11316  wxRegionIterator upd(rgn_blit); // get the update rect list
11317  while (upd) {
11318  wxRect rect = upd.GetRect();
11319 
11320  mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
11321  rect.x - offset.x, rect.y - offset.y);
11322  upd++;
11323  }
11324 
11325  // If multi-canvas, indicate which canvas has keyboard focus
11326  // by drawing a simple blue bar at the top.
11327  if (g_canvasConfig != 0) { // multi-canvas?
11328  if (this == wxWindow::FindFocus()) {
11329  g_focusCanvas = this;
11330 
11331  wxColour colour = GetGlobalColor(_T("BLUE4"));
11332  mscratch_dc.SetPen(wxPen(colour));
11333  mscratch_dc.SetBrush(wxBrush(colour));
11334 
11335  wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
11336  mscratch_dc.DrawRectangle(activeRect);
11337  }
11338  }
11339 
11340  // Any MBtiles?
11341  std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
11342  unsigned int im = stackIndexArray.size();
11343  if (VPoint.b_quilt && im > 0) {
11344  std::vector<int> tiles_to_show;
11345  for (unsigned int is = 0; is < im; is++) {
11346  const ChartTableEntry &cte =
11347  ChartData->GetChartTableEntry(stackIndexArray[is]);
11348  if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
11349  continue;
11350  }
11351  if (cte.GetChartType() == CHART_TYPE_MBTILES) {
11352  tiles_to_show.push_back(stackIndexArray[is]);
11353  }
11354  }
11355 
11356  if (tiles_to_show.size())
11357  SetAlertString(_("MBTile requires OpenGL to be enabled"));
11358  }
11359 
11360  // May get an unexpected OnPaint call while switching display modes
11361  // Guard for that.
11362  if (!g_bopengl) {
11363  // Draw the rest of the overlay objects directly on the scratch dc
11364  ocpnDC scratch_dc(mscratch_dc);
11365  DrawOverlayObjects(scratch_dc, ru);
11366 
11367  if (m_bShowTide) {
11368  RebuildTideSelectList(GetVP().GetBBox());
11369  DrawAllTidesInBBox(scratch_dc, GetVP().GetBBox());
11370  }
11371 
11372  if (m_bShowCurrent) {
11373  RebuildCurrentSelectList(GetVP().GetBBox());
11374  DrawAllCurrentsInBBox(scratch_dc, GetVP().GetBBox());
11375  }
11376 
11377  if (m_brepaint_piano && g_bShowChartBar) {
11378  m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), mscratch_dc);
11379  }
11380 
11381  if (m_Compass) m_Compass->Paint(scratch_dc);
11382 
11383  RenderAlertMessage(mscratch_dc, GetVP());
11384  }
11385 
11386  // quiting?
11387  if (g_bquiting) {
11388 #ifdef ocpnUSE_DIBSECTION
11389  ocpnMemDC q_dc;
11390 #else
11391  wxMemoryDC q_dc;
11392 #endif
11393  wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
11394  q_dc.SelectObject(qbm);
11395 
11396  // Get a copy of the screen
11397  q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
11398 
11399  // Draw a rectangle over the screen with a stipple brush
11400  wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
11401  q_dc.SetBrush(qbr);
11402  q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
11403 
11404  // Blit back into source
11405  mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
11406  wxCOPY);
11407 
11408  q_dc.SelectObject(wxNullBitmap);
11409  }
11410 #if 0
11411  // It is possible that this two-step method may be reuired for some platforms.
11412  // So, retain in the code base to aid recovery if necessary
11413 
11414  // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
11415  if( VPoint.b_quilt ) {
11416  if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
11417  ChartBase *chart = m_pQuilt->GetRefChart();
11418  if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
11419 
11420  // Clear the text Global declutter list
11421  ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
11422  if(ChPI)
11423  ChPI->ClearPLIBTextList();
11424  else{
11425  if(ps52plib)
11426  ps52plib->ClearTextList();
11427  }
11428 
11429  wxMemoryDC t_dc;
11430  wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
11431 
11432  wxColor maskBackground = wxColour(1,0,0);
11433  t_dc.SelectObject( qbm );
11434  t_dc.SetBackground(wxBrush(maskBackground));
11435  t_dc.Clear();
11436 
11437  // Copy the scratch DC into the new bitmap
11438  t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
11439 
11440  // Render the text to the new bitmap
11441  OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
11442  m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
11443 
11444  // Copy the new bitmap back to the scratch dc
11445  wxRegionIterator upd_final( ru );
11446  while( upd_final ) {
11447  wxRect rect = upd_final.GetRect();
11448  scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
11449  upd_final++;
11450  }
11451 
11452  t_dc.SelectObject( wxNullBitmap );
11453  }
11454  }
11455  }
11456 #endif
11457  // Direct rendering model...
11458  if (VPoint.b_quilt) {
11459  if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
11460  ChartBase *chart = m_pQuilt->GetRefChart();
11461  if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
11462  // Clear the text Global declutter list
11463  ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
11464  if (ChPI)
11465  ChPI->ClearPLIBTextList();
11466  else {
11467  if (ps52plib) ps52plib->ClearTextList();
11468  }
11469 
11470  // Render the text directly to the scratch bitmap
11471  OCPNRegion chart_all_text_region(
11472  wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
11473 
11474  if (g_bShowChartBar && m_Piano) {
11475  wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
11476  GetVP().pix_width, m_Piano->GetHeight());
11477 
11478  ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11479  if (!style->chartStatusWindowTransparent)
11480  chart_all_text_region.Subtract(chart_bar_rect);
11481  }
11482 
11483  if (m_Compass && m_Compass->IsShown()) {
11484  wxRect compassRect = m_Compass->GetRect();
11485  if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
11486  chart_all_text_region.Subtract(compassRect);
11487  }
11488  }
11489 
11490  mscratch_dc.DestroyClippingRegion();
11491 
11492  m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
11493  chart_all_text_region);
11494  }
11495  }
11496  }
11497 
11498  // And finally, blit the scratch dc onto the physical dc
11499  wxRegionIterator upd_final(rgn_blit);
11500  while (upd_final) {
11501  wxRect rect = upd_final.GetRect();
11502  dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
11503  rect.y);
11504  upd_final++;
11505  }
11506 
11507  // Test code to validate the dc drawing rectangle....
11508  /*
11509  wxRegionIterator upd_ru ( rgn_blit ); // get the update rect list
11510  while ( upd_ru )
11511  {
11512  wxRect rect = upd_ru.GetRect();
11513 
11514  dc.SetPen(wxPen(*wxRED));
11515  dc.SetBrush(wxBrush(*wxRED, wxTRANSPARENT));
11516  dc.DrawRectangle(rect);
11517  upd_ru ++ ;
11518  }
11519  */
11520 
11521  // Deselect the chart bitmap from the temp_dc, so that it will not be
11522  // destroyed in the temp_dc dtor
11523  temp_dc.SelectObject(wxNullBitmap);
11524  // And for the scratch bitmap
11525  mscratch_dc.SelectObject(wxNullBitmap);
11526 
11527  dc.DestroyClippingRegion();
11528 
11529  PaintCleanup();
11530 }
11531 
11532 void ChartCanvas::PaintCleanup() {
11533  // Handle the current graphic window, if present
11534 
11535  if (pCwin) {
11536  pCwin->Show();
11537  if (m_bTCupdate) {
11538  pCwin->Refresh();
11539  pCwin->Update();
11540  }
11541  }
11542 
11543  // And set flags for next time
11544  m_bTCupdate = false;
11545 
11546  // Handle deferred WarpPointer
11547  if (warp_flag) {
11548  WarpPointer(warp_x, warp_y);
11549  warp_flag = false;
11550  }
11551 
11552  // Start movement timer, this runs nearly immediately.
11553  // the reason we cannot simply call it directly is the
11554  // refresh events it emits may be blocked from this paint event
11555  pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
11556 }
11557 
11558 #if 0
11559 wxColour GetErrorGraphicColor(double val)
11560 {
11561  /*
11562  double valm = wxMin(val_max, val);
11563 
11564  unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
11565  unsigned char red = (unsigned char)(255 * (valm/val_max));
11566 
11567  wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
11568 
11569  hv.saturation = 1.0;
11570  hv.value = 1.0;
11571 
11572  wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
11573  return wxColour(rv.red, rv.green, rv.blue);
11574  */
11575 
11576  // HTML colors taken from NOAA WW3 Web representation
11577  wxColour c;
11578  if((val > 0) && (val < 1)) c.Set(_T("#002ad9"));
11579  else if((val >= 1) && (val < 2)) c.Set(_T("#006ed9"));
11580  else if((val >= 2) && (val < 3)) c.Set(_T("#00b2d9"));
11581  else if((val >= 3) && (val < 4)) c.Set(_T("#00d4d4"));
11582  else if((val >= 4) && (val < 5)) c.Set(_T("#00d9a6"));
11583  else if((val >= 5) && (val < 7)) c.Set(_T("#00d900"));
11584  else if((val >= 7) && (val < 9)) c.Set(_T("#95d900"));
11585  else if((val >= 9) && (val < 12)) c.Set(_T("#d9d900"));
11586  else if((val >= 12) && (val < 15)) c.Set(_T("#d9ae00"));
11587  else if((val >= 15) && (val < 18)) c.Set(_T("#d98300"));
11588  else if((val >= 18) && (val < 21)) c.Set(_T("#d95700"));
11589  else if((val >= 21) && (val < 24)) c.Set(_T("#d90000"));
11590  else if((val >= 24) && (val < 27)) c.Set(_T("#ae0000"));
11591  else if((val >= 27) && (val < 30)) c.Set(_T("#8c0000"));
11592  else if((val >= 30) && (val < 36)) c.Set(_T("#870000"));
11593  else if((val >= 36) && (val < 42)) c.Set(_T("#690000"));
11594  else if((val >= 42) && (val < 48)) c.Set(_T("#550000"));
11595  else if( val >= 48) c.Set(_T("#410000"));
11596 
11597  return c;
11598 }
11599 
11600 void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
11601 {
11602  wxImage gr_image(vp->pix_width, vp->pix_height);
11603  gr_image.InitAlpha();
11604 
11605  double maxval = -10000;
11606  double minval = 10000;
11607 
11608  double rlat, rlon;
11609  double glat, glon;
11610 
11611  GetCanvasPixPoint(0, 0, rlat, rlon);
11612 
11613  for(int i=1; i < vp->pix_height-1; i++)
11614  {
11615  for(int j=0; j < vp->pix_width; j++)
11616  {
11617  // Reference mercator value
11618 // vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
11619 
11620  // Georef value
11621  GetCanvasPixPoint(j, i, glat, glon);
11622 
11623  maxval = wxMax(maxval, (glat - rlat));
11624  minval = wxMin(minval, (glat - rlat));
11625 
11626  }
11627  rlat = glat;
11628  }
11629 
11630  GetCanvasPixPoint(0, 0, rlat, rlon);
11631  for(int i=1; i < vp->pix_height-1; i++)
11632  {
11633  for(int j=0; j < vp->pix_width; j++)
11634  {
11635  // Reference mercator value
11636 // vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
11637 
11638  // Georef value
11639  GetCanvasPixPoint(j, i, glat, glon);
11640 
11641  double f = ((glat - rlat)-minval)/(maxval - minval);
11642 
11643  double dy = (f * 40);
11644 
11645  wxColour c = GetErrorGraphicColor(dy);
11646  unsigned char r = c.Red();
11647  unsigned char g = c.Green();
11648  unsigned char b = c.Blue();
11649 
11650  gr_image.SetRGB(j, i, r,g,b);
11651  if((glat - rlat )!= 0)
11652  gr_image.SetAlpha(j, i, 128);
11653  else
11654  gr_image.SetAlpha(j, i, 255);
11655 
11656  }
11657  rlat = glat;
11658  }
11659 
11660  // Create a Bitmap
11661  wxBitmap *pbm = new wxBitmap(gr_image);
11662  wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
11663  pbm->SetMask(gr_mask);
11664 
11665  pmdc->DrawBitmap(*pbm, 0,0);
11666 
11667  delete pbm;
11668 
11669 }
11670 
11671 #endif
11672 
11673 void ChartCanvas::CancelMouseRoute() {
11674  m_routeState = 0;
11675  m_pMouseRoute = NULL;
11676  m_bDrawingRoute = false;
11677 }
11678 
11679 int ChartCanvas::GetNextContextMenuId() {
11680  return CanvasMenuHandler::GetNextContextMenuId();
11681 }
11682 
11683 bool ChartCanvas::SetCursor(const wxCursor &c) {
11684 #ifdef ocpnUSE_GL
11685  if (g_bopengl && m_glcc)
11686  return m_glcc->SetCursor(c);
11687  else
11688 #endif
11689  return wxWindow::SetCursor(c);
11690 }
11691 
11692 void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
11693  if (g_bquiting) return;
11694  // Keep the mouse position members up to date
11695  GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
11696 
11697  // Retrigger the route leg popup timer
11698  // This handles the case when the chart is moving in auto-follow mode,
11699  // but no user mouse input is made. The timer handler may Hide() the
11700  // popup if the chart moved enough n.b. We use slightly longer oneshot
11701  // value to allow this method's Refresh() to complete before potentially
11702  // getting another Refresh() in the popup timer handler.
11703  if (!m_RolloverPopupTimer.IsRunning() &&
11704  ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
11705  (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
11706  (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
11707  m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
11708 
11709 #ifdef ocpnUSE_GL
11710  if (m_glcc && g_bopengl) {
11711  // We need to invalidate the FBO cache to ensure repaint of "grounded"
11712  // overlay objects.
11713  if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
11714 
11715  m_glcc->Refresh(eraseBackground,
11716  NULL); // We always are going to render the entire screen
11717  // anyway, so make
11718  // sure that the window managers understand the invalid area
11719  // is actually the entire client area.
11720 
11721  // We need to selectively Refresh some child windows, if they are visible.
11722  // Note that some children are refreshed elsewhere on timer ticks, so don't
11723  // need attention here.
11724 
11725  // Thumbnail chart
11726  if (pthumbwin && pthumbwin->IsShown()) {
11727  pthumbwin->Raise();
11728  pthumbwin->Refresh(false);
11729  }
11730 
11731  // ChartInfo window
11732  if (m_pCIWin && m_pCIWin->IsShown()) {
11733  m_pCIWin->Raise();
11734  m_pCIWin->Refresh(false);
11735  }
11736 
11737  // if(g_MainToolbar)
11738  // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
11739 
11740  } else
11741 #endif
11742  wxWindow::Refresh(eraseBackground, rect);
11743 }
11744 
11745 void ChartCanvas::Update() {
11746  if (m_glcc && g_bopengl) {
11747 #ifdef ocpnUSE_GL
11748  m_glcc->Update();
11749 #endif
11750  } else
11751  wxWindow::Update();
11752 }
11753 
11754 void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
11755  if (!pemboss) return;
11756  int x = pemboss->x, y = pemboss->y;
11757  const double factor = 200;
11758 
11759  wxASSERT_MSG(dc.GetDC(), wxT("DrawEmboss has no dc (opengl?)"));
11760  wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
11761  wxASSERT_MSG(pmdc, wxT("dc to EmbossCanvas not a memory dc"));
11762 
11763  // Grab a snipped image out of the chart
11764  wxMemoryDC snip_dc;
11765  wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
11766  snip_dc.SelectObject(snip_bmp);
11767 
11768  snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
11769  snip_dc.SelectObject(wxNullBitmap);
11770 
11771  wxImage snip_img = snip_bmp.ConvertToImage();
11772 
11773  // Apply Emboss map to the snip image
11774  unsigned char *pdata = snip_img.GetData();
11775  if (pdata) {
11776  for (int y = 0; y < pemboss->height; y++) {
11777  int map_index = (y * pemboss->width);
11778  for (int x = 0; x < pemboss->width; x++) {
11779  double val = (pemboss->pmap[map_index] * factor) / 256.;
11780 
11781  int nred = (int)((*pdata) + val);
11782  nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
11783  *pdata++ = (unsigned char)nred;
11784 
11785  int ngreen = (int)((*pdata) + val);
11786  ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
11787  *pdata++ = (unsigned char)ngreen;
11788 
11789  int nblue = (int)((*pdata) + val);
11790  nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
11791  *pdata++ = (unsigned char)nblue;
11792 
11793  map_index++;
11794  }
11795  }
11796  }
11797 
11798  // Convert embossed snip to a bitmap
11799  wxBitmap emb_bmp(snip_img);
11800 
11801  // Map to another memoryDC
11802  wxMemoryDC result_dc;
11803  result_dc.SelectObject(emb_bmp);
11804 
11805  // Blit to target
11806  pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
11807 
11808  result_dc.SelectObject(wxNullBitmap);
11809 }
11810 
11811 emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
11812  double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
11813 
11814  if (GetQuiltMode()) {
11815  // disable Overzoom indicator for MBTiles
11816  int refIndex = GetQuiltRefChartdbIndex();
11817  if (refIndex >= 0) {
11818  const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
11819  ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
11820  if (current_type == CHART_TYPE_MBTILES) {
11821  ChartBase *pChart = m_pQuilt->GetRefChart();
11822  ChartMBTiles *ptc = dynamic_cast<ChartMBTiles *>(pChart);
11823  if (ptc) {
11824  zoom_factor = ptc->GetZoomFactor();
11825  }
11826  }
11827  }
11828 
11829  if (zoom_factor <= 3.9) return NULL;
11830  } else {
11831  if (m_singleChart) {
11832  if (zoom_factor <= 3.9) return NULL;
11833  } else
11834  return NULL;
11835  }
11836 
11837  if (m_pEM_OverZoom) {
11838  m_pEM_OverZoom->x = 4;
11839  m_pEM_OverZoom->y = 0;
11840  if (g_MainToolbar && IsPrimaryCanvas()) {
11841  wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
11842  m_pEM_OverZoom->x = masterToolbarRect.width + 4;
11843  }
11844  }
11845  return m_pEM_OverZoom;
11846 }
11847 
11848 void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
11849  GridDraw(dc);
11850 
11851  // bool pluginOverlayRender = true;
11852  //
11853  // if(g_canvasConfig > 0){ // Multi canvas
11854  // if(IsPrimaryCanvas())
11855  // pluginOverlayRender = false;
11856  // }
11857 
11858  g_overlayCanvas = this;
11859 
11860  if (g_pi_manager) {
11861  g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
11862  g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex, OVERLAY_LEGACY);
11863  }
11864 
11865  AISDrawAreaNotices(dc, GetVP(), this);
11866 
11867  wxDC *pdc = dc.GetDC();
11868  if (pdc) {
11869  pdc->DestroyClippingRegion();
11870  wxDCClipper(*pdc, ru);
11871  }
11872 
11873  if (m_bShowNavobjects) {
11874  DrawAllTracksInBBox(dc, GetVP().GetBBox());
11875  DrawAllRoutesInBBox(dc, GetVP().GetBBox());
11876  DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
11877  DrawAnchorWatchPoints(dc);
11878  } else {
11879  DrawActiveTrackInBBox(dc, GetVP().GetBBox());
11880  DrawActiveRouteInBBox(dc, GetVP().GetBBox());
11881  }
11882 
11883  AISDraw(dc, GetVP(), this);
11884  ShipDraw(dc);
11885  AlertDraw(dc);
11886 
11887  RenderVisibleSectorLights(dc);
11888 
11889  RenderAllChartOutlines(dc, GetVP());
11890  RenderRouteLegs(dc);
11891  RenderShipToActive(dc, false);
11892  ScaleBarDraw(dc);
11893  s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
11894  if (g_pi_manager) {
11895  g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex, OVERLAY_OVER_SHIPS);
11896  }
11897 
11898  DrawEmboss(dc, EmbossDepthScale());
11899  DrawEmboss(dc, EmbossOverzoomIndicator(dc));
11900  if (g_pi_manager) {
11901  g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex, OVERLAY_OVER_EMBOSS);
11902  }
11903  if(!g_PrintingInProgress){
11904  if (IsPrimaryCanvas()) {
11905  if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
11906  }
11907 
11908  if (IsPrimaryCanvas()) {
11909  if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
11910  }
11911 
11912  if (m_muiBar)
11913  m_muiBar->DrawDC( dc, 1.0);
11914 
11915  if (m_pTrackRolloverWin) {
11916  m_pTrackRolloverWin->Draw(dc);
11917  m_brepaint_piano = true;
11918  }
11919 
11920  if (m_pRouteRolloverWin) {
11921  m_pRouteRolloverWin->Draw(dc);
11922  m_brepaint_piano = true;
11923  }
11924 
11925  if (m_pAISRolloverWin) {
11926  m_pAISRolloverWin->Draw(dc);
11927  m_brepaint_piano = true;
11928  }
11929  }
11930  if (g_pi_manager) {
11931  g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex, OVERLAY_OVER_UI);
11932  }
11933 }
11934 
11935 emboss_data *ChartCanvas::EmbossDepthScale() {
11936  if (!m_bShowDepthUnits) return NULL;
11937 
11938  int depth_unit_type = DEPTH_UNIT_UNKNOWN;
11939 
11940  if (GetQuiltMode()) {
11941  wxString s = m_pQuilt->GetQuiltDepthUnit();
11942  s.MakeUpper();
11943  if (s == _T("FEET"))
11944  depth_unit_type = DEPTH_UNIT_FEET;
11945  else if (s.StartsWith(_T("FATHOMS")))
11946  depth_unit_type = DEPTH_UNIT_FATHOMS;
11947  else if (s.StartsWith(_T("METERS")))
11948  depth_unit_type = DEPTH_UNIT_METERS;
11949  else if (s.StartsWith(_T("METRES")))
11950  depth_unit_type = DEPTH_UNIT_METERS;
11951  else if (s.StartsWith(_T("METRIC")))
11952  depth_unit_type = DEPTH_UNIT_METERS;
11953  else if (s.StartsWith(_T("METER")))
11954  depth_unit_type = DEPTH_UNIT_METERS;
11955 
11956  } else {
11957  if (m_singleChart) {
11958  depth_unit_type = m_singleChart->GetDepthUnitType();
11959  if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
11960  depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
11961  }
11962  }
11963 
11964  emboss_data *ped = NULL;
11965  switch (depth_unit_type) {
11966  case DEPTH_UNIT_FEET:
11967  ped = m_pEM_Feet;
11968  break;
11969  case DEPTH_UNIT_METERS:
11970  ped = m_pEM_Meters;
11971  break;
11972  case DEPTH_UNIT_FATHOMS:
11973  ped = m_pEM_Fathoms;
11974  break;
11975  default:
11976  return NULL;
11977  }
11978 
11979  ped->x = (GetVP().pix_width - ped->width);
11980 
11981  if (m_Compass && m_bShowCompassWin) {
11982  wxRect r = m_Compass->GetRect();
11983  ped->y = r.y + r.height;
11984  } else {
11985  ped->y = 40;
11986  }
11987  return ped;
11988 }
11989 
11990 void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
11991  ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11992  wxFont font;
11993  if (style->embossFont == wxEmptyString) {
11994  wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
11995  font = *dFont;
11996  font.SetPointSize(60);
11997  font.SetWeight(wxFONTWEIGHT_BOLD);
11998  } else
11999  font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12000  wxFONTWEIGHT_BOLD, false, style->embossFont);
12001 
12002  int emboss_width = 500;
12003  int emboss_height = 200;
12004 
12005  // Free any existing emboss maps
12006  delete m_pEM_Feet;
12007  delete m_pEM_Meters;
12008  delete m_pEM_Fathoms;
12009 
12010  // Create the 3 DepthUnit emboss map structures
12011  m_pEM_Feet =
12012  CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12013  m_pEM_Meters =
12014  CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12015  m_pEM_Fathoms =
12016  CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12017 }
12018 
12019 #define OVERZOOM_TEXT _("OverZoom")
12020 
12021 void ChartCanvas::SetOverzoomFont() {
12022  ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12023  int w, h;
12024 
12025  wxFont font;
12026  if (style->embossFont == wxEmptyString) {
12027  wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12028  font = *dFont;
12029  font.SetPointSize(40);
12030  font.SetWeight(wxFONTWEIGHT_BOLD);
12031  } else
12032  font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12033  wxFONTWEIGHT_BOLD, false, style->embossFont);
12034 
12035  wxClientDC dc(this);
12036  dc.SetFont(font);
12037  dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12038 
12039  while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12040  font.SetPointSize(font.GetPointSize() - 1);
12041  dc.SetFont(font);
12042  dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12043  }
12044  m_overzoomFont = font;
12045  m_overzoomTextWidth = w;
12046  m_overzoomTextHeight = h;
12047 }
12048 
12049 void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12050  delete m_pEM_OverZoom;
12051 
12052  if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12053  m_pEM_OverZoom =
12054  CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12055  m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12056 }
12057 
12058 emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12059  int height, const wxString &str,
12060  ColorScheme cs) {
12061  int *pmap;
12062 
12063  // Create a temporary bitmap
12064  wxBitmap bmp(width, height, -1);
12065 
12066  // Create a memory DC
12067  wxMemoryDC temp_dc;
12068  temp_dc.SelectObject(bmp);
12069 
12070  // Paint on it
12071  temp_dc.SetBackground(*wxWHITE_BRUSH);
12072  temp_dc.SetTextBackground(*wxWHITE);
12073  temp_dc.SetTextForeground(*wxBLACK);
12074 
12075  temp_dc.Clear();
12076 
12077  temp_dc.SetFont(font);
12078 
12079  int str_w, str_h;
12080  temp_dc.GetTextExtent(str, &str_w, &str_h);
12081  // temp_dc.DrawText( str, width - str_w - 10, 10 );
12082  temp_dc.DrawText(str, 1, 1);
12083 
12084  // Deselect the bitmap
12085  temp_dc.SelectObject(wxNullBitmap);
12086 
12087  // Convert bitmap the wxImage for manipulation
12088  wxImage img = bmp.ConvertToImage();
12089 
12090  int image_width = str_w * 105 / 100;
12091  int image_height = str_h * 105 / 100;
12092  wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12093  wxMin(image_height, img.GetHeight()));
12094  wxImage imgs = img.GetSubImage(r);
12095 
12096  double val_factor;
12097  switch (cs) {
12098  case GLOBAL_COLOR_SCHEME_DAY:
12099  default:
12100  val_factor = 1;
12101  break;
12102  case GLOBAL_COLOR_SCHEME_DUSK:
12103  val_factor = .5;
12104  break;
12105  case GLOBAL_COLOR_SCHEME_NIGHT:
12106  val_factor = .25;
12107  break;
12108  }
12109 
12110  int val;
12111  int index;
12112  const int w = imgs.GetWidth();
12113  const int h = imgs.GetHeight();
12114  pmap = (int *)calloc(w * h * sizeof(int), 1);
12115  // Create emboss map by differentiating the emboss image
12116  // and storing integer results in pmap
12117  // n.b. since the image is B/W, it is sufficient to check
12118  // one channel (i.e. red) only
12119  for (int y = 1; y < h - 1; y++) {
12120  for (int x = 1; x < w - 1; x++) {
12121  val =
12122  img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
12123  val = (int)(val * val_factor);
12124  index = (y * w) + x;
12125  pmap[index] = val;
12126  }
12127  }
12128 
12129  emboss_data *pret = new emboss_data;
12130  pret->pmap = pmap;
12131  pret->width = w;
12132  pret->height = h;
12133 
12134  return pret;
12135 }
12136 
12137 void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12138  Track *active_track = NULL;
12139  for (Track* pTrackDraw : g_TrackList) {
12140  if (g_pActiveTrack == pTrackDraw) {
12141  active_track = pTrackDraw;
12142  continue;
12143  }
12144 
12145  TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
12146  }
12147 
12148  if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12149 }
12150 
12151 void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12152  Track *active_track = NULL;
12153  for (Track* pTrackDraw : g_TrackList) {
12154  if (g_pActiveTrack == pTrackDraw) {
12155  active_track = pTrackDraw;
12156  break;
12157  }
12158  }
12159  if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12160 }
12161 
12162 void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12163  Route *active_route = NULL;
12164 
12165  for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12166  node = node->GetNext()) {
12167  Route *pRouteDraw = node->GetData();
12168  if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12169  active_route = pRouteDraw;
12170  continue;
12171  }
12172 
12173  // if(m_canvasIndex == 1)
12174  RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
12175  }
12176 
12177  // Draw any active or selected route (or track) last, so that is is always on
12178  // top
12179  if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12180 }
12181 
12182 void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12183  Route *active_route = NULL;
12184 
12185  for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12186  node = node->GetNext()) {
12187  Route *pRouteDraw = node->GetData();
12188  if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12189  active_route = pRouteDraw;
12190  break;
12191  }
12192  }
12193  if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12194 }
12195 
12196 void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12197  if (!pWayPointMan) return;
12198 
12199  wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12200 
12201  while (node) {
12202  RoutePoint *pWP = node->GetData();
12203  if (pWP) {
12204  if (pWP->m_bIsInRoute) {
12205  node = node->GetNext();
12206  continue;
12207  }
12208 
12209  /* technically incorrect... waypoint has bounding box */
12210  if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
12211  RoutePointGui(*pWP).Draw(dc, this, NULL);
12212  else {
12213  // Are Range Rings enabled?
12214  if (pWP->GetShowWaypointRangeRings() &&
12215  (pWP->GetWaypointRangeRingsNumber() > 0)) {
12216  double factor = 1.00;
12217  if (pWP->GetWaypointRangeRingsStepUnits() ==
12218  1) // convert kilometers to NMi
12219  factor = 1 / 1.852;
12220 
12221  double radius = factor * pWP->GetWaypointRangeRingsNumber() *
12222  pWP->GetWaypointRangeRingsStep() / 60.;
12223  radius *= 2; // Fudge factor
12224 
12225  LLBBox radar_box;
12226  radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
12227  pWP->m_lat + radius, pWP->m_lon + radius);
12228  if (!BltBBox.IntersectOut(radar_box)) {
12229  RoutePointGui(*pWP).Draw(dc, this, NULL);
12230  }
12231  }
12232  }
12233  }
12234 
12235  node = node->GetNext();
12236  }
12237 }
12238 
12239 void ChartCanvas::DrawBlinkObjects(void) {
12240  // All RoutePoints
12241  wxRect update_rect;
12242 
12243  if (!pWayPointMan) return;
12244 
12245  wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12246 
12247  while (node) {
12248  RoutePoint *pWP = node->GetData();
12249  if (pWP) {
12250  if (pWP->m_bBlink) {
12251  update_rect.Union(pWP->CurrentRect_in_DC);
12252  }
12253  }
12254 
12255  node = node->GetNext();
12256  }
12257  if (!update_rect.IsEmpty()) RefreshRect(update_rect);
12258 }
12259 
12260 void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
12261  // draw anchor watch rings, if activated
12262 
12263  if (pAnchorWatchPoint1 || pAnchorWatchPoint2) {
12264  wxPoint r1, r2;
12265  wxPoint lAnchorPoint1, lAnchorPoint2;
12266  double lpp1 = 0.0;
12267  double lpp2 = 0.0;
12268  if (pAnchorWatchPoint1) {
12269  lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
12270  GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
12271  &lAnchorPoint1);
12272  }
12273  if (pAnchorWatchPoint2) {
12274  lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
12275  GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
12276  &lAnchorPoint2);
12277  }
12278 
12279  wxPen ppPeng(GetGlobalColor(_T ( "UGREN" )), 2);
12280  wxPen ppPenr(GetGlobalColor(_T ( "URED" )), 2);
12281 
12282  wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
12283  wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
12284  dc.SetBrush(*ppBrush);
12285 
12286  if (lpp1 > 0) {
12287  dc.SetPen(ppPeng);
12288  dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12289  }
12290 
12291  if (lpp2 > 0) {
12292  dc.SetPen(ppPeng);
12293  dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12294  }
12295 
12296  if (lpp1 < 0) {
12297  dc.SetPen(ppPenr);
12298  dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12299  }
12300 
12301  if (lpp2 < 0) {
12302  dc.SetPen(ppPenr);
12303  dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12304  }
12305  }
12306 }
12307 
12308 double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
12309  double lpp = 0.;
12310  wxPoint r1;
12311  wxPoint lAnchorPoint;
12312  double d1 = 0.0;
12313  double dabs;
12314  double tlat1, tlon1;
12315 
12316  if (pAnchorWatchPoint) {
12317  (pAnchorWatchPoint->GetName()).ToDouble(&d1);
12318  d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
12319  dabs = fabs(d1 / 1852.);
12320  ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
12321  &tlat1, &tlon1);
12322  GetCanvasPointPix(tlat1, tlon1, &r1);
12323  GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
12324  &lAnchorPoint);
12325  lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
12326  pow((double)(lAnchorPoint.y - r1.y), 2));
12327 
12328  // This is an entry watch
12329  if (d1 < 0) lpp = -lpp;
12330  }
12331  return lpp;
12332 }
12333 
12334 //------------------------------------------------------------------------------------------
12335 // Tides Support
12336 //------------------------------------------------------------------------------------------
12337 void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
12338  if (!ptcmgr) return;
12339 
12340  pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
12341 
12342  for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12343  const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12344  double lon = pIDX->IDX_lon;
12345  double lat = pIDX->IDX_lat;
12346 
12347  char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12348  if ((type == 't') || (type == 'T')) {
12349  if (BBox.Contains(lat, lon)) {
12350  // Manage the point selection list
12351  pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
12352  }
12353  }
12354  }
12355 }
12356 
12357 extern wxDateTime gTimeSource;
12358 
12359 void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
12360  if (!ptcmgr) return;
12361 
12362  wxDateTime this_now = gTimeSource;
12363  bool cur_time = !gTimeSource.IsValid();
12364  if (cur_time) this_now = wxDateTime::Now();
12365  time_t t_this_now = this_now.GetTicks();
12366 
12367  wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
12368  GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
12369  wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
12370  GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )), 1,
12371  wxPENSTYLE_SOLID);
12372  wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
12373  GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )), 1,
12374  wxPENSTYLE_SOLID);
12375 
12376  wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
12377  GetGlobalColor(_T ( "GREEN1" )), wxBRUSHSTYLE_SOLID);
12378  // wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush (
12379  // GetGlobalColor ( _T ( "UINFD" ) ), wxSOLID );
12380  wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
12381  GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )),
12382  wxBRUSHSTYLE_SOLID);
12383  wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
12384  GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )),
12385  wxBRUSHSTYLE_SOLID);
12386 
12387  wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
12388  dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
12389  int font_size = wxMax(10, dFont->GetPointSize());
12390  font_size /= g_Platform->GetDisplayDIPMult(this);
12391  wxFont *plabelFont =
12392  FontMgr::Get().FindOrCreateFont(font_size,
12393  dFont->GetFamily(), dFont->GetStyle(),
12394  dFont->GetWeight(), false,
12395  dFont->GetFaceName());
12396 
12397  dc.SetPen(*pblack_pen);
12398  dc.SetBrush(*pgreen_brush);
12399 
12400  wxBitmap bm;
12401  switch (m_cs) {
12402  case GLOBAL_COLOR_SCHEME_DAY:
12403  bm = m_bmTideDay;
12404  break;
12405  case GLOBAL_COLOR_SCHEME_DUSK:
12406  bm = m_bmTideDusk;
12407  break;
12408  case GLOBAL_COLOR_SCHEME_NIGHT:
12409  bm = m_bmTideNight;
12410  break;
12411  default:
12412  bm = m_bmTideDay;
12413  break;
12414  }
12415 
12416  int bmw = bm.GetWidth();
12417  int bmh = bm.GetHeight();
12418 
12419  float scale_factor = 1.0;
12420 
12421  // Set the onscreen size of the symbol
12422  // Compensate for various display resolutions
12423  float icon_pixelRefDim = 45;
12424 
12425 #if 0
12426  float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *25 / 1000; // Intended physical rendered size onscreen
12427  nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 8);
12428  nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 15);
12429  float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
12430 #endif
12431 
12432 #ifndef __ANDROID__
12433  // another method is simply to declare that the icon shall be x times the size
12434  // of a raster symbol (e.g.BOYLAT)
12435  // This is a bit of a hack that will suffice until until we get fully
12436  // scalable ENC symbol sets
12437  // float nominal_icon_size_pixels = 48; // 3 x 16
12438  // float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
12439 
12440  // or, x times size of text font
12441  wxScreenDC sdc;
12442  int height;
12443  sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
12444  height *= g_Platform->GetDisplayDIPMult(this);
12445  float nominal_icon_size_pixels = 48; // 3 x 16
12446  float pix_factor = (2 * height) / nominal_icon_size_pixels;
12447 
12448 
12449 #else
12450  // Yet another method goes like this:
12451  // Set the onscreen size of the symbol
12452  // Compensate for various display resolutions
12453  // Develop empirically, making a symbol about 16 mm tall
12454  double symHeight =
12455  icon_pixelRefDim /
12456  GetPixPerMM(); // from draw instructions, symbol is xx pix high
12457  double targetHeight0 = 16.0;
12458 
12459  // But we want to scale the size down for smaller displays
12460  double displaySize = m_display_size_mm;
12461  displaySize = wxMax(displaySize, 100);
12462 
12463  float targetHeight = wxMin(targetHeight0, displaySize / 15);
12464 
12465  double pix_factor = targetHeight / symHeight;
12466 #endif
12467 
12468  scale_factor *= pix_factor;
12469 
12470  float user_scale_factor = g_ChartScaleFactorExp;
12471  if (g_ChartScaleFactorExp > 1.0)
12472  user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
12473  1.2; // soften the scale factor a bit
12474 
12475  scale_factor *= user_scale_factor;
12476  scale_factor *= GetContentScaleFactor();
12477 
12478 
12479  {
12480  double marge = 0.05;
12481  std::vector<LLBBox> drawn_boxes;
12482  for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12483  const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12484 
12485  char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12486  if ((type == 't') || (type == 'T')) // only Tides
12487  {
12488  double lon = pIDX->IDX_lon;
12489  double lat = pIDX->IDX_lat;
12490 
12491  if (BBox.ContainsMarge(lat, lon, marge)) {
12492 
12493  // Avoid drawing detailed graphic for duplicate tide stations
12494  if (GetVP().chart_scale < 500000){
12495  bool bdrawn = false;
12496  for (size_t i = 0; i < drawn_boxes.size(); i++){
12497  if (drawn_boxes[i].Contains(lat, lon)){
12498  bdrawn = true;
12499  break;
12500  }
12501  }
12502  if (bdrawn)
12503  continue; // the station loop
12504 
12505  LLBBox this_box;
12506  this_box.Set(lat, lon, lat, lon);
12507  this_box.EnLarge(.005);
12508  drawn_boxes.push_back(this_box);
12509  }
12510 
12511  wxPoint r;
12512  GetCanvasPointPix(lat, lon, &r);
12513  // draw standard icons
12514  if (GetVP().chart_scale > 500000) {
12515  dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
12516  }
12517  // draw "extended" icons
12518  else {
12519  dc.SetFont(*plabelFont);
12520  {
12521  {
12522  float val, nowlev;
12523  float ltleve = 0.;
12524  float htleve = 0.;
12525  time_t tctime;
12526  time_t lttime = 0;
12527  time_t httime = 0;
12528  bool wt;
12529  // define if flood or ebb in the last ten minutes and verify if
12530  // data are useable
12531  if (ptcmgr->GetTideFlowSens(
12532  t_this_now, BACKWARD_TEN_MINUTES_STEP,
12533  pIDX->IDX_rec_num, nowlev, val, wt)) {
12534  // search forward the first HW or LW near "now" ( starting at
12535  // "now" - ten minutes )
12536  ptcmgr->GetHightOrLowTide(
12537  t_this_now + BACKWARD_TEN_MINUTES_STEP,
12538  FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
12539  wt, pIDX->IDX_rec_num, val, tctime);
12540  if (wt) {
12541  httime = tctime;
12542  htleve = val;
12543  } else {
12544  lttime = tctime;
12545  ltleve = val;
12546  }
12547  wt = !wt;
12548 
12549  // then search opposite tide near "now"
12550  if (tctime > t_this_now) // search backward
12551  ptcmgr->GetHightOrLowTide(
12552  t_this_now, BACKWARD_TEN_MINUTES_STEP,
12553  BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
12554  pIDX->IDX_rec_num, val, tctime);
12555  else
12556  // or search forward
12557  ptcmgr->GetHightOrLowTide(
12558  t_this_now, FORWARD_TEN_MINUTES_STEP,
12559  FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
12560  val, tctime);
12561  if (wt) {
12562  httime = tctime;
12563  htleve = val;
12564  } else {
12565  lttime = tctime;
12566  ltleve = val;
12567  }
12568 
12569  // draw the tide rectangle:
12570 
12571  // tide icon rectangle has default pre-scaled width = 12 ,
12572  // height = 45
12573  int width = (int)(12 * scale_factor + 0.5);
12574  int height = (int)(45 * scale_factor + 0.5);
12575  int linew = wxMax(1, (int)(scale_factor));
12576  int xDraw = r.x - (width / 2);
12577  int yDraw = r.y - (height / 2);
12578 
12579  // process tide state ( %height and flow sens )
12580  float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
12581  int hs = (httime > lttime) ? -4 : 4;
12582  hs *= (int)(scale_factor + 0.5);
12583  if (ts > 0.995 || ts < 0.005) hs = 0;
12584  int ht_y = (int)(height * ts);
12585 
12586  // draw yellow tide rectangle outlined in black
12587  pblack_pen->SetWidth(linew);
12588  dc.SetPen(*pblack_pen);
12589  dc.SetBrush(*pyelo_brush);
12590  dc.DrawRectangle(xDraw, yDraw, width, height);
12591 
12592  // draw blue rectangle as water height, smaller in width than
12593  // yellow rectangle
12594  dc.SetPen(*pblue_pen);
12595  dc.SetBrush(*pblue_brush);
12596  dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
12597  (width - (4 * linew)), height - ht_y);
12598 
12599  // draw sens arrows (ensure they are not "under-drawn" by top
12600  // line of blue rectangle )
12601  int hl;
12602  wxPoint arrow[3];
12603  arrow[0].x = xDraw + 2 * linew;
12604  arrow[1].x = xDraw + width / 2;
12605  arrow[2].x = xDraw + width - 2 * linew;
12606  pyelo_pen->SetWidth(linew);
12607  pblue_pen->SetWidth(linew);
12608  if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
12609  {
12610  hl = (int)(height * 0.25) + yDraw;
12611  arrow[0].y = hl;
12612  arrow[1].y = hl + hs;
12613  arrow[2].y = hl;
12614  if (ts < 0.15)
12615  dc.SetPen(*pyelo_pen);
12616  else
12617  dc.SetPen(*pblue_pen);
12618  dc.DrawLines(3, arrow);
12619  }
12620  if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
12621  {
12622  hl = (int)(height * 0.5) + yDraw;
12623  arrow[0].y = hl;
12624  arrow[1].y = hl + hs;
12625  arrow[2].y = hl;
12626  if (ts < 0.40)
12627  dc.SetPen(*pyelo_pen);
12628  else
12629  dc.SetPen(*pblue_pen);
12630  dc.DrawLines(3, arrow);
12631  }
12632  if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
12633  {
12634  hl = (int)(height * 0.75) + yDraw;
12635  arrow[0].y = hl;
12636  arrow[1].y = hl + hs;
12637  arrow[2].y = hl;
12638  if (ts < 0.65)
12639  dc.SetPen(*pyelo_pen);
12640  else
12641  dc.SetPen(*pblue_pen);
12642  dc.DrawLines(3, arrow);
12643  }
12644  // draw tide level text
12645  wxString s;
12646  s.Printf(_T("%3.1f"), nowlev);
12647  Station_Data *pmsd = pIDX->pref_sta_data; // write unit
12648  if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
12649  int wx1;
12650  dc.GetTextExtent(s, &wx1, NULL);
12651  wx1 *= g_Platform->GetDisplayDIPMult(this);
12652  dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
12653  }
12654  }
12655  }
12656  }
12657  }
12658  }
12659  }
12660  }
12661 }
12662 
12663 //------------------------------------------------------------------------------------------
12664 // Currents Support
12665 //------------------------------------------------------------------------------------------
12666 
12667 void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
12668  if (!ptcmgr) return;
12669 
12670  pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
12671 
12672  for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12673  const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12674  double lon = pIDX->IDX_lon;
12675  double lat = pIDX->IDX_lat;
12676 
12677  char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12678  if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
12679  if ((BBox.Contains(lat, lon))) {
12680  // Manage the point selection list
12681  pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
12682  }
12683  }
12684  }
12685 }
12686 
12687 void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
12688  if (!ptcmgr) return;
12689 
12690  float tcvalue, dir;
12691  bool bnew_val;
12692  char sbuf[20];
12693  wxFont *pTCFont;
12694  double lon_last = 0.;
12695  double lat_last = 0.;
12696  // arrow size for Raz Blanchard : 12 knots north
12697  double marge = 0.2;
12698  bool cur_time = !gTimeSource.IsValid();
12699 
12700  double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
12701  bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
12702 
12703  wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
12704  GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
12705  wxPen *porange_pen = wxThePenList->FindOrCreatePen(
12706  GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )), 1,
12707  wxPENSTYLE_SOLID);
12708  wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
12709  GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )),
12710  wxBRUSHSTYLE_SOLID);
12711  wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
12712  GetGlobalColor(_T ( "UIBDR" )), wxBRUSHSTYLE_SOLID);
12713  wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
12714  GetGlobalColor(_T ( "UINFD" )), wxBRUSHSTYLE_SOLID);
12715 
12716 
12717  double skew_angle = GetVPRotation();
12718 
12719  wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
12720  dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
12721  int font_size = wxMax(10, dFont->GetPointSize());
12722  font_size /= g_Platform->GetDisplayDIPMult(this);
12723  pTCFont =
12724  FontMgr::Get().FindOrCreateFont(font_size,
12725  dFont->GetFamily(), dFont->GetStyle(),
12726  dFont->GetWeight(), false,
12727  dFont->GetFaceName());
12728 
12729 
12730  float scale_factor = 1.0;
12731 
12732  // Set the onscreen size of the symbol
12733  // Compensate for various display resolutions
12734 
12735 #if 0
12736  float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *3 / 1000; // Intended physical rendered size onscreen
12737  nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 2);
12738  nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 4);
12739  float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
12740 #endif
12741 
12742 #if 0
12743  // another method is simply to declare that the icon shall be x times the size of a raster symbol (e.g.BOYLAT)
12744  // This is a bit of a hack that will suffice until until we get fully scalable ENC symbol sets
12745  float nominal_icon_size_pixels = 6; // 16 / 3
12746  float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
12747 #endif
12748 
12749 #ifndef __ANDROID__
12750  // or, x times size of text font
12751  wxScreenDC sdc;
12752  int height;
12753  sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
12754  height *= g_Platform->GetDisplayDIPMult(this);
12755  float nominal_icon_size_pixels = 15;
12756  float pix_factor = (1 * height) / nominal_icon_size_pixels;
12757 
12758 #else
12759  // Yet another method goes like this:
12760  // Set the onscreen size of the symbol
12761  // Compensate for various display resolutions
12762  // Develop empirically....
12763  float icon_pixelRefDim = 5;
12764 
12765  double symHeight =
12766  icon_pixelRefDim /
12767  GetPixPerMM(); // from draw instructions, symbol is xx pix high
12768  double targetHeight0 = 2.0;
12769 
12770  // But we want to scale the size down for smaller displays
12771  double displaySize = m_display_size_mm;
12772  displaySize = wxMax(displaySize, 100);
12773 
12774  float targetHeight = wxMin(targetHeight0, displaySize / 50);
12775  double pix_factor = targetHeight / symHeight;
12776 #endif
12777 
12778  scale_factor *= pix_factor;
12779 
12780  float user_scale_factor = g_ChartScaleFactorExp;
12781  if (g_ChartScaleFactorExp > 1.0)
12782  user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
12783  1.2; // soften the scale factor a bit
12784 
12785  scale_factor *= user_scale_factor;
12786 
12787  scale_factor *= GetContentScaleFactor();
12788 
12789  {
12790  for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12791  const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12792  double lon = pIDX->IDX_lon;
12793  double lat = pIDX->IDX_lat;
12794 
12795  char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12796  if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
12797  if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
12798  wxPoint r;
12799  GetCanvasPointPix(lat, lon, &r);
12800 
12801  wxPoint d[4]; // points of a diamond at the current station location
12802  int dd = (int)(5.0 * scale_factor + 0.5);
12803  d[0].x = r.x;
12804  d[0].y = r.y + dd;
12805  d[1].x = r.x + dd;
12806  d[1].y = r.y;
12807  d[2].x = r.x;
12808  d[2].y = r.y - dd;
12809  d[3].x = r.x - dd;
12810  d[3].y = r.y;
12811 
12812  if (1) {
12813  pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
12814  dc.SetPen(*pblack_pen);
12815  dc.SetBrush(*porange_brush);
12816  dc.DrawPolygon(4, d);
12817 
12818  if (type == 'C') {
12819  dc.SetBrush(*pblack_brush);
12820  dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
12821  }
12822 
12823  if (GetVP().chart_scale < 1000000) {
12824  if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
12825  continue;
12826  } else
12827  continue;
12828 
12829  if (1 /*type == 'c'*/) {
12830  {
12831  // Get the display pixel location of the current station
12832  int pixxc, pixyc;
12833  pixxc = r.x;
12834  pixyc = r.y;
12835 
12836  // Adjust drawing size using logarithmic scale. tcvalue is
12837  // current in knots
12838  double a1 = fabs(tcvalue) * 10.;
12839  // Current values <= 0.1 knot will have no arrow
12840  a1 = wxMax(1.0, a1);
12841  double a2 = log10(a1);
12842 
12843  float cscale = scale_factor * a2 * 0.4;
12844 
12845  porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
12846  dc.SetPen(*porange_pen);
12847  DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
12848  cscale);
12849  // Draw text, if enabled
12850 
12851  if (bDrawCurrentValues) {
12852  dc.SetFont(*pTCFont);
12853  snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
12854  dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
12855  }
12856  }
12857  } // scale
12858  }
12859  /* This is useful for debugging the TC database
12860  else
12861  {
12862  dc.SetPen ( *porange_pen );
12863  dc.SetBrush ( *pgray_brush );
12864  dc.DrawPolygon ( 4, d );
12865  }
12866  */
12867  }
12868  lon_last = lon;
12869  lat_last = lat;
12870  }
12871  }
12872  }
12873 }
12874 
12875 void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
12876  pCwin = new TCWin(this, x, y, pvIDX);
12877 }
12878 
12879 #define NUM_CURRENT_ARROW_POINTS 9
12880 static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
12881  wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
12882  wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
12883  wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
12884 
12885 void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
12886  double scale) {
12887  if (scale > 1e-2) {
12888  float sin_rot = sin(rot_angle * PI / 180.);
12889  float cos_rot = cos(rot_angle * PI / 180.);
12890 
12891  // Move to the first point
12892 
12893  float xt = CurrentArrowArray[0].x;
12894  float yt = CurrentArrowArray[0].y;
12895 
12896  float xp = (xt * cos_rot) - (yt * sin_rot);
12897  float yp = (xt * sin_rot) + (yt * cos_rot);
12898  int x1 = (int)(xp * scale);
12899  int y1 = (int)(yp * scale);
12900 
12901  // Walk thru the point list
12902  for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
12903  xt = CurrentArrowArray[ip].x;
12904  yt = CurrentArrowArray[ip].y;
12905 
12906  float xp = (xt * cos_rot) - (yt * sin_rot);
12907  float yp = (xt * sin_rot) + (yt * cos_rot);
12908  int x2 = (int)(xp * scale);
12909  int y2 = (int)(yp * scale);
12910 
12911  dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
12912 
12913  x1 = x2;
12914  y1 = y2;
12915  }
12916  }
12917 }
12918 
12919 wxString ChartCanvas::FindValidUploadPort() {
12920  wxString port;
12921  // Try to use the saved persistent upload port first
12922  if (!g_uploadConnection.IsEmpty() &&
12923  g_uploadConnection.StartsWith(_T("Serial"))) {
12924  port = g_uploadConnection;
12925  }
12926 
12927  else if (TheConnectionParams()) {
12928  // If there is no persistent upload port recorded (yet)
12929  // then use the first available serial connection which has output defined.
12930  for (size_t i = 0; i < TheConnectionParams()->Count(); i++) {
12931  ConnectionParams *cp = TheConnectionParams()->Item(i);
12932  if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
12933  port << _T("Serial:") << cp->Port;
12934  }
12935  }
12936  return port;
12937 }
12938 
12939 void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
12940  if (!win) return;
12941 
12942  if (NULL == g_pais_query_dialog_active) {
12943  int pos_x = g_ais_query_dialog_x;
12944  int pos_y = g_ais_query_dialog_y;
12945 
12946  if (g_pais_query_dialog_active) {
12947  g_pais_query_dialog_active->Destroy();
12948  g_pais_query_dialog_active = new AISTargetQueryDialog();
12949  } else {
12950  g_pais_query_dialog_active = new AISTargetQueryDialog();
12951  }
12952 
12953  g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
12954  wxPoint(pos_x, pos_y));
12955 
12956  g_pais_query_dialog_active->SetAutoCentre(g_btouch);
12957  g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
12958  g_pais_query_dialog_active->SetMMSI(mmsi);
12959  g_pais_query_dialog_active->UpdateText();
12960  wxSize sz = g_pais_query_dialog_active->GetSize();
12961 
12962  bool b_reset_pos = false;
12963 #ifdef __WXMSW__
12964  // Support MultiMonitor setups which an allow negative window positions.
12965  // If the requested window title bar does not intersect any installed
12966  // monitor, then default to simple primary monitor positioning.
12967  RECT frame_title_rect;
12968  frame_title_rect.left = pos_x;
12969  frame_title_rect.top = pos_y;
12970  frame_title_rect.right = pos_x + sz.x;
12971  frame_title_rect.bottom = pos_y + 30;
12972 
12973  if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
12974  b_reset_pos = true;
12975 #else
12976 
12977  // Make sure drag bar (title bar) of window intersects wxClient Area of
12978  // screen, with a little slop...
12979  wxRect window_title_rect; // conservative estimate
12980  window_title_rect.x = pos_x;
12981  window_title_rect.y = pos_y;
12982  window_title_rect.width = sz.x;
12983  window_title_rect.height = 30;
12984 
12985  wxRect ClientRect = wxGetClientDisplayRect();
12986  ClientRect.Deflate(
12987  60, 60); // Prevent the new window from being too close to the edge
12988  if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
12989 
12990 #endif
12991 
12992  if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
12993 
12994  } else {
12995  g_pais_query_dialog_active->SetMMSI(mmsi);
12996  g_pais_query_dialog_active->UpdateText();
12997  }
12998 
12999  g_pais_query_dialog_active->Show();
13000 }
13001 
13002 void ChartCanvas::ToggleCanvasQuiltMode(void) {
13003  bool cur_mode = GetQuiltMode();
13004 
13005  if (!GetQuiltMode())
13006  SetQuiltMode(true);
13007  else if (GetQuiltMode()) {
13008  SetQuiltMode(false);
13009  g_sticky_chart = GetQuiltReferenceChartIndex();
13010  }
13011 
13012  if (cur_mode != GetQuiltMode()) {
13013  SetupCanvasQuiltMode();
13014  DoCanvasUpdate();
13015  InvalidateGL();
13016  Refresh();
13017  }
13018  // TODO What to do about this?
13019  // g_bQuiltEnable = GetQuiltMode();
13020 
13021  // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13022  if (ps52plib) ps52plib->GenerateStateHash();
13023 
13024  if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13025  GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13026 }
13027 
13028 void ChartCanvas::DoCanvasStackDelta(int direction) {
13029  if (!GetQuiltMode()) {
13030  int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13031  if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13032  if ((current_stack_index + direction) < 0) return;
13033 
13034  if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13035  int new_dbIndex =
13036  GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13037 
13038  if (IsChartQuiltableRef(new_dbIndex)) {
13039  ToggleCanvasQuiltMode();
13040  SelectQuiltRefdbChart(new_dbIndex);
13041  m_bpersistent_quilt = false;
13042  }
13043  } else {
13044  SelectChartFromStack(current_stack_index + direction);
13045  }
13046  } else {
13047  std::vector<int> piano_chart_index_array =
13048  GetQuiltExtendedStackdbIndexArray();
13049  int refdb = GetQuiltRefChartdbIndex();
13050 
13051  // Find the ref chart in the stack
13052  int current_index = -1;
13053  for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13054  if (refdb == piano_chart_index_array[i]) {
13055  current_index = i;
13056  break;
13057  }
13058  }
13059  if (current_index == -1) return;
13060 
13061  const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13062  int target_family = ctet.GetChartFamily();
13063 
13064  int new_index = -1;
13065  int check_index = current_index + direction;
13066  bool found = false;
13067  int check_dbIndex = -1;
13068  int new_dbIndex = -1;
13069 
13070  // When quilted. switch within the same chart family
13071  while (!found &&
13072  (unsigned int)check_index < piano_chart_index_array.size() &&
13073  (check_index >= 0)) {
13074  check_dbIndex = piano_chart_index_array[check_index];
13075  const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13076  if (target_family == cte.GetChartFamily()) {
13077  found = true;
13078  new_index = check_index;
13079  new_dbIndex = check_dbIndex;
13080  break;
13081  }
13082 
13083  check_index += direction;
13084  }
13085 
13086  if (!found) return;
13087 
13088  if (!IsChartQuiltableRef(new_dbIndex)) {
13089  ToggleCanvasQuiltMode();
13090  SelectdbChart(new_dbIndex);
13091  m_bpersistent_quilt = true;
13092  } else {
13093  SelectQuiltRefChart(new_index);
13094  }
13095  }
13096 
13097  gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13098  // (checkmarks etc)
13099  SetQuiltChartHiLiteIndex(-1);
13100 
13101  ReloadVP();
13102 }
13103 
13104 //--------------------------------------------------------------------------------------------------------
13105 //
13106 // Toolbar support
13107 //
13108 //--------------------------------------------------------------------------------------------------------
13109 
13110 void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13111  // Handle the per-canvas toolbar clicks here
13112 
13113  switch (event.GetId()) {
13114  case ID_ZOOMIN: {
13115  StopMovement();
13116  ZoomCanvasSimple(g_plus_minus_zoom_factor);
13117  break;
13118  }
13119 
13120  case ID_ZOOMOUT: {
13121  StopMovement();
13122  ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13123  break;
13124  }
13125 
13126  case ID_STKUP:
13127  DoCanvasStackDelta(1);
13128  DoCanvasUpdate();
13129  break;
13130 
13131  case ID_STKDN:
13132  DoCanvasStackDelta(-1);
13133  DoCanvasUpdate();
13134  break;
13135 
13136  case ID_FOLLOW: {
13137  TogglebFollow();
13138  break;
13139  }
13140 
13141  case ID_CURRENT: {
13142  ShowCurrents(!GetbShowCurrent());
13143  ReloadVP();
13144  Refresh(false);
13145  break;
13146  }
13147 
13148  case ID_TIDE: {
13149  ShowTides(!GetbShowTide());
13150  ReloadVP();
13151  Refresh(false);
13152  break;
13153  }
13154 
13155  case ID_ROUTE: {
13156  if (0 == m_routeState) {
13157  StartRoute();
13158  } else {
13159  FinishRoute();
13160  }
13161 
13162 #ifdef __ANDROID__
13163  androidSetRouteAnnunciator(m_routeState == 1);
13164 #endif
13165  break;
13166  }
13167 
13168  case ID_AIS: {
13169  SetAISCanvasDisplayStyle(-1);
13170  break;
13171  }
13172 
13173  default:
13174  break;
13175  }
13176 
13177  // And then let gFrame handle the rest....
13178  event.Skip();
13179 }
13180 
13181 
13182 
13183 
13184 void ChartCanvas::SetShowAIS(bool show) {
13185  m_bShowAIS = show;
13186  if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13187  GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13188 }
13189 
13190 void ChartCanvas::SetAttenAIS(bool show) {
13191  m_bShowAISScaled = show;
13192  if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13193  GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13194 }
13195 
13196 void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
13197  // make some arrays to hold the dfferences between cycle steps
13198  // show all, scaled, hide all
13199  bool bShowAIS_Array[3] = {true, true, false};
13200  bool bShowScaled_Array[3] = {false, true, true};
13201  wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
13202  _("Attenuate less critical AIS targets"),
13203  _("Hide AIS Targets")};
13204  wxString iconName_Array[3] = {_T("AIS"), _T("AIS_Suppressed"),
13205  _T("AIS_Disabled")};
13206  int ArraySize = 3;
13207  int AIS_Toolbar_Switch = 0;
13208  if (StyleIndx == -1) { // -1 means coming from toolbar button
13209  // find current state of switch
13210  for (int i = 1; i < ArraySize; i++) {
13211  if ((bShowAIS_Array[i] == m_bShowAIS) &&
13212  (bShowScaled_Array[i] == m_bShowAISScaled))
13213  AIS_Toolbar_Switch = i;
13214  }
13215  AIS_Toolbar_Switch++; // we did click so continu with next item
13216  if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
13217  AIS_Toolbar_Switch++;
13218 
13219  } else { // coming from menu bar.
13220  AIS_Toolbar_Switch = StyleIndx;
13221  }
13222  // make sure we are not above array
13223  if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
13224 
13225  int AIS_Toolbar_Switch_Next =
13226  AIS_Toolbar_Switch + 1; // Find out what will happen at next click
13227  if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
13228  AIS_Toolbar_Switch_Next++;
13229  if (AIS_Toolbar_Switch_Next >= ArraySize)
13230  AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
13231 
13232  // Set found values to global and member variables
13233  m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
13234  m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
13235 }
13236 
13237 void ChartCanvas::TouchAISToolActive(void) {
13238  }
13239 
13240 void ChartCanvas::UpdateAISTBTool(void) {
13241 }
13242 
13243 //---------------------------------------------------------------------------------
13244 //
13245 // Compass/GPS status icon support
13246 //
13247 //---------------------------------------------------------------------------------
13248 
13249 void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
13250  // Look for change in overlap or positions
13251  bool b_update = false;
13252  int cc1_edge_comp = 2;
13253  wxRect rect = m_Compass->GetRect();
13254  wxSize parent_size = GetSize();
13255 
13256  parent_size *= m_displayScale;
13257 
13258  // check to see if it would overlap if it was in its home position (upper
13259  // right)
13260  wxPoint tentative_pt(parent_size.x - rect.width - cc1_edge_comp,
13261  g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
13262  wxRect tentative_rect(tentative_pt, rect.GetSize());
13263 
13264  m_Compass->Move(tentative_pt);
13265 
13266  if (m_Compass && m_Compass->IsShown())
13267  m_Compass->UpdateStatus(b_force_new | b_update);
13268 
13269  if (b_force_new | b_update) Refresh();
13270 }
13271 
13272 void ChartCanvas::SelectChartFromStack(int index, bool bDir,
13273  ChartTypeEnum New_Type,
13274  ChartFamilyEnum New_Family) {
13275  if (!GetpCurrentStack()) return;
13276  if (!ChartData) return;
13277 
13278  if (index < GetpCurrentStack()->nEntry) {
13279  // Open the new chart
13280  ChartBase *pTentative_Chart;
13281  pTentative_Chart = ChartData->OpenStackChartConditional(
13282  GetpCurrentStack(), index, bDir, New_Type, New_Family);
13283 
13284  if (pTentative_Chart) {
13285  if (m_singleChart) m_singleChart->Deactivate();
13286 
13287  m_singleChart = pTentative_Chart;
13288  m_singleChart->Activate();
13289 
13290  GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13291  GetpCurrentStack(), m_singleChart->GetFullPath());
13292  }
13293 
13294  // Setup the view
13295  double zLat, zLon;
13296  if (m_bFollow) {
13297  zLat = gLat;
13298  zLon = gLon;
13299  } else {
13300  zLat = m_vLat;
13301  zLon = m_vLon;
13302  }
13303 
13304  double best_scale_ppm = GetBestVPScale(m_singleChart);
13305  double rotation = GetVPRotation();
13306  double oldskew = GetVPSkew();
13307  double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
13308 
13309  if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
13310  if (fabs(oldskew) > 0.0001) rotation = 0.0;
13311  if (fabs(newskew) > 0.0001) rotation = newskew;
13312  }
13313 
13314  SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
13315 
13316  UpdateGPSCompassStatusBox(true); // Pick up the rotation
13317  }
13318 
13319  // refresh Piano
13320  int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
13321  if (idx < 0) return;
13322 
13323  std::vector<int> piano_active_chart_index_array;
13324  piano_active_chart_index_array.push_back(
13325  GetpCurrentStack()->GetCurrentEntrydbIndex());
13326  m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
13327 }
13328 
13329 void ChartCanvas::SelectdbChart(int dbindex) {
13330  if (!GetpCurrentStack()) return;
13331  if (!ChartData) return;
13332 
13333  if (dbindex >= 0) {
13334  // Open the new chart
13335  ChartBase *pTentative_Chart;
13336  pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
13337 
13338  if (pTentative_Chart) {
13339  if (m_singleChart) m_singleChart->Deactivate();
13340 
13341  m_singleChart = pTentative_Chart;
13342  m_singleChart->Activate();
13343 
13344  GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13345  GetpCurrentStack(), m_singleChart->GetFullPath());
13346  }
13347 
13348  // Setup the view
13349  double zLat, zLon;
13350  if (m_bFollow) {
13351  zLat = gLat;
13352  zLon = gLon;
13353  } else {
13354  zLat = m_vLat;
13355  zLon = m_vLon;
13356  }
13357 
13358  double best_scale_ppm = GetBestVPScale(m_singleChart);
13359 
13360  if (m_singleChart)
13361  SetViewPoint(zLat, zLon, best_scale_ppm,
13362  m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
13363 
13364  // SetChartUpdatePeriod( );
13365 
13366  // UpdateGPSCompassStatusBox(); // Pick up the rotation
13367  }
13368 
13369  // TODO refresh_Piano();
13370 }
13371 
13372 void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
13373  double target_scale = GetVP().view_scale_ppm;
13374 
13375  if (!GetQuiltMode()) {
13376  if (GetpCurrentStack()) {
13377  int stack_index = -1;
13378  for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
13379  int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
13380  if (check_dbIndex < 0) continue;
13381  const ChartTableEntry &cte =
13382  ChartData->GetChartTableEntry(check_dbIndex);
13383  if (type == cte.GetChartType()) {
13384  stack_index = i;
13385  break;
13386  } else if (family == cte.GetChartFamily()) {
13387  stack_index = i;
13388  break;
13389  }
13390  }
13391 
13392  if (stack_index >= 0) {
13393  SelectChartFromStack(stack_index);
13394  }
13395  }
13396  } else {
13397  int sel_dbIndex = -1;
13398  std::vector<int> piano_chart_index_array =
13399  GetQuiltExtendedStackdbIndexArray();
13400  for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13401  int check_dbIndex = piano_chart_index_array[i];
13402  const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13403  if (type == cte.GetChartType()) {
13404  if (IsChartQuiltableRef(check_dbIndex)) {
13405  sel_dbIndex = check_dbIndex;
13406  break;
13407  }
13408  } else if (family == cte.GetChartFamily()) {
13409  if (IsChartQuiltableRef(check_dbIndex)) {
13410  sel_dbIndex = check_dbIndex;
13411  break;
13412  }
13413  }
13414  }
13415 
13416  if (sel_dbIndex >= 0) {
13417  SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
13418  // Re-qualify the quilt reference chart selection
13419  AdjustQuiltRefChart();
13420  }
13421 
13422  // Now reset the scale to the target...
13423  SetVPScale(target_scale);
13424  }
13425 
13426  SetQuiltChartHiLiteIndex(-1);
13427 
13428  ReloadVP();
13429 }
13430 
13431 bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
13432  return std::find(m_tile_yesshow_index_array.begin(),
13433  m_tile_yesshow_index_array.end(),
13434  index) != m_tile_yesshow_index_array.end();
13435 }
13436 
13437 bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
13438  return std::find(m_tile_noshow_index_array.begin(),
13439  m_tile_noshow_index_array.end(),
13440  index) != m_tile_noshow_index_array.end();
13441 }
13442 
13443 void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
13444  if (std::find(m_tile_noshow_index_array.begin(),
13445  m_tile_noshow_index_array.end(),
13446  index) == m_tile_noshow_index_array.end()) {
13447  m_tile_noshow_index_array.push_back(index);
13448  }
13449 }
13450 
13451 //-------------------------------------------------------------------------------------------------------
13452 //
13453 // Piano support
13454 //
13455 //-------------------------------------------------------------------------------------------------------
13456 
13457 void ChartCanvas::HandlePianoClick(int selected_index,
13458  const std::vector<int> &selected_dbIndex_array)
13459 {
13460  if (g_boptionsactive)
13461  return; // Piano might be invalid due to chartset updates.
13462  if (!m_pCurrentStack) return;
13463  if (!ChartData) return;
13464 
13465  // stop movement or on slow computer we may get something like :
13466  // zoom out with the wheel (timer is set)
13467  // quickly click and display a chart, which may zoom in
13468  // but the delayed timer fires first and it zooms out again!
13469  StopMovement();
13470 
13471  // When switching by piano key click, we may appoint the new target chart to be
13472  // any chart in the composite array.
13473  // As an improvement to UX, find the chart that is "closest" to the current vp,
13474  // and select that chart. This will cause a jump to the centroid of that chart
13475 
13476  double distance = 25000; //RTW
13477  int closest_index = -1;
13478  for (int chart_index : selected_dbIndex_array) {
13479  const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
13480  double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
13481  double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
13482 
13483  // measure distance as Manhattan style
13484  double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
13485  if (test_distance < distance){
13486  distance = test_distance;
13487  closest_index = chart_index;
13488  }
13489  }
13490 
13491  int selected_dbIndex = selected_dbIndex_array[0];
13492  if (closest_index >= 0)
13493  selected_dbIndex = closest_index;
13494 
13495  if (!GetQuiltMode()) {
13496  if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
13497  if (IsChartQuiltableRef(selected_dbIndex)) {
13498  ToggleCanvasQuiltMode();
13499  SelectQuiltRefdbChart(selected_dbIndex);
13500  m_bpersistent_quilt = false;
13501  } else {
13502  SelectChartFromStack(selected_index);
13503  }
13504  } else {
13505  SelectChartFromStack(selected_index);
13506  g_sticky_chart = selected_dbIndex;
13507  }
13508 
13509  if (m_singleChart)
13510  GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
13511  } else {
13512  // Handle MBTiles overlays first
13513  // Left click simply toggles the noshow array index entry
13514  if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
13515  bool bfound = false;
13516  for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
13517  if (m_tile_noshow_index_array[i] ==
13518  selected_dbIndex) { // chart is in the noshow list
13519  m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
13520  i); // erase it
13521  bfound = true;
13522  break;
13523  }
13524  }
13525  if (!bfound) {
13526  m_tile_noshow_index_array.push_back(selected_dbIndex);
13527  }
13528 
13529  // If not already present, add this tileset to the "yes_show" array.
13530  if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
13531  m_tile_yesshow_index_array.push_back(selected_dbIndex);
13532  }
13533 
13534  else {
13535  if (IsChartQuiltableRef(selected_dbIndex)) {
13536  // if( ChartData ) ChartData->PurgeCache();
13537 
13538  // If the chart is a vector chart, and of very large scale,
13539  // then we had better set the new scale directly to avoid excessive
13540  // underzoom on, eg, Inland ENCs
13541  bool set_scale = false;
13542  if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
13543  if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
13544  set_scale = true;
13545  }
13546  }
13547 
13548  if (!set_scale) {
13549  SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
13550  } else {
13551  SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
13552 
13553  // Adjust scale so that the selected chart is underzoomed/overzoomed
13554  // by a controlled amount
13555  ChartBase *pc =
13556  ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
13557  if (pc) {
13558  double proposed_scale_onscreen =
13559  GetCanvasScaleFactor() / GetVPScale();
13560 
13561  if (g_bPreserveScaleOnX) {
13562  proposed_scale_onscreen =
13563  wxMin(proposed_scale_onscreen,
13564  100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
13565  GetCanvasWidth()));
13566  } else {
13567  proposed_scale_onscreen =
13568  wxMin(proposed_scale_onscreen,
13569  20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
13570  GetCanvasWidth()));
13571 
13572  proposed_scale_onscreen =
13573  wxMax(proposed_scale_onscreen,
13574  pc->GetNormalScaleMin(GetCanvasScaleFactor(),
13575  g_b_overzoom_x));
13576  }
13577 
13578  SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
13579  }
13580  }
13581  } else {
13582  ToggleCanvasQuiltMode();
13583  SelectdbChart(selected_dbIndex);
13584  m_bpersistent_quilt = true;
13585  }
13586  }
13587  }
13588 
13589  SetQuiltChartHiLiteIndex(-1);
13590  gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13591  // (checkmarks etc)
13592  HideChartInfoWindow();
13593  DoCanvasUpdate();
13594  ReloadVP(); // Pick up the new selections
13595 }
13596 
13597 void ChartCanvas::HandlePianoRClick(int x, int y, int selected_index,
13598  const std::vector<int> &selected_dbIndex_array)
13599 {
13600  if (g_boptionsactive)
13601  return; // Piano might be invalid due to chartset updates.
13602  if (!GetpCurrentStack()) return;
13603 
13604  PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
13605  UpdateCanvasControlBar();
13606 
13607  SetQuiltChartHiLiteIndex(-1);
13608 }
13609 
13610 void ChartCanvas::HandlePianoRollover(int selected_index,
13611  const std::vector<int> &selected_dbIndex_array,
13612  int n_charts, int scale) {
13613  if (g_boptionsactive)
13614  return; // Piano might be invalid due to chartset updates.
13615  if (!GetpCurrentStack()) return;
13616  if (!ChartData) return;
13617 
13618  if (ChartData->IsBusy()) return;
13619 
13620  wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
13621 
13622  if (!GetQuiltMode()) {
13623  ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
13624  } else {
13625  // Select the correct vector
13626  std::vector<int> piano_chart_index_array;
13627  if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
13628  piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
13629  if ((GetpCurrentStack()->nEntry > 1) ||
13630  (piano_chart_index_array.size() >= 1)) {
13631  ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
13632 
13633  SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
13634  ReloadVP(false); // no VP adjustment allowed
13635  } else if (GetpCurrentStack()->nEntry == 1) {
13636  const ChartTableEntry &cte =
13637  ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
13638  if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
13639  ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
13640  ReloadVP(false);
13641  } else if ((-1 == selected_index) &&
13642  (0 == selected_dbIndex_array.size())) {
13643  ShowChartInfoWindow(key_location.x, -1);
13644  }
13645  }
13646  }
13647  else {
13648  piano_chart_index_array = GetQuiltFullScreendbIndexArray();
13649 
13650  if ((GetpCurrentStack()->nEntry > 1) ||
13651  (piano_chart_index_array.size() >= 1)) {
13652 
13653  if(n_charts > 1)
13654  ShowCompositeInfoWindow(key_location.x, n_charts, scale, selected_dbIndex_array);
13655  else if(n_charts == 1)
13656  ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
13657 
13658  SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
13659  ReloadVP(false); // no VP adjustment allowed
13660  }
13661  }
13662  }
13663 }
13664 
13665 void ChartCanvas::ClearPianoRollover() {
13666  ClearQuiltChartHiLiteIndexArray();
13667  ShowChartInfoWindow(0, -1);
13668  std::vector<int> vec;
13669  ShowCompositeInfoWindow(0, 0, 0, vec);
13670  ReloadVP(false);
13671 }
13672 
13673 void ChartCanvas::UpdateCanvasControlBar(void) {
13674  if (m_pianoFrozen) return;
13675 
13676  if (!GetpCurrentStack()) return;
13677  if (!ChartData) return;
13678  if (!g_bShowChartBar) return;
13679 
13680  int sel_type = -1;
13681  int sel_family = -1;
13682 
13683  std::vector<int> piano_chart_index_array;
13684  std::vector<int> empty_piano_chart_index_array;
13685 
13686  wxString old_hash = m_Piano->GetStoredHash();
13687 
13688  if (GetQuiltMode()) {
13689  m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
13690  GetQuiltFullScreendbIndexArray());
13691 
13692  std::vector<int> piano_active_chart_index_array =
13693  GetQuiltCandidatedbIndexArray();
13694  m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
13695 
13696  std::vector<int> piano_eclipsed_chart_index_array =
13697  GetQuiltEclipsedStackdbIndexArray();
13698  m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
13699 
13700  m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
13701  m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
13702 
13703  sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
13704  sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
13705  } else {
13706  piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
13707  m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
13708  // TODO refresh_Piano();
13709 
13710  if (m_singleChart) {
13711  sel_type = m_singleChart->GetChartType();
13712  sel_family = m_singleChart->GetChartFamily();
13713  }
13714  }
13715 
13716  // Set up the TMerc and Skew arrays
13717  std::vector<int> piano_skew_chart_index_array;
13718  std::vector<int> piano_tmerc_chart_index_array;
13719  std::vector<int> piano_poly_chart_index_array;
13720 
13721  for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
13722  const ChartTableEntry &ctei =
13723  ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
13724  double skew_norm = ctei.GetChartSkew();
13725  if (skew_norm > 180.) skew_norm -= 360.;
13726 
13727  if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
13728  piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
13729 
13730  // Polyconic skewed charts should show as skewed
13731  else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
13732  if (fabs(skew_norm) > 1.)
13733  piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
13734  else
13735  piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
13736  } else if (fabs(skew_norm) > 1.)
13737  piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
13738  }
13739  m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
13740  m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
13741  m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
13742 
13743  wxString new_hash = m_Piano->GenerateAndStoreNewHash();
13744  if (new_hash != old_hash) {
13745  m_Piano->FormatKeys();
13746  HideChartInfoWindow();
13747  m_Piano->ResetRollover();
13748  SetQuiltChartHiLiteIndex(-1);
13749  m_brepaint_piano = true;
13750  }
13751 
13752  // Create a bitmask int that describes what Family/Type of charts are shown in
13753  // the bar, and notify the platform.
13754  int mask = 0;
13755  for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
13756  const ChartTableEntry &ctei =
13757  ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
13758  ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
13759  ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
13760  if (e == CHART_FAMILY_RASTER) mask |= 1;
13761  if (e == CHART_FAMILY_VECTOR) {
13762  if (t == CHART_TYPE_CM93COMP)
13763  mask |= 4;
13764  else
13765  mask |= 2;
13766  }
13767  }
13768 
13769  wxString s_indicated;
13770  if (sel_type == CHART_TYPE_CM93COMP)
13771  s_indicated = _T("cm93");
13772  else {
13773  if (sel_family == CHART_FAMILY_RASTER)
13774  s_indicated = _T("raster");
13775  else if (sel_family == CHART_FAMILY_VECTOR)
13776  s_indicated = _T("vector");
13777  }
13778 
13779  g_Platform->setChartTypeMaskSel(mask, s_indicated);
13780 }
13781 
13782 void ChartCanvas::FormatPianoKeys(void) { m_Piano->FormatKeys(); }
13783 
13784 void ChartCanvas::PianoPopupMenu(int x, int y, int selected_index,
13785  const std::vector<int> &selected_dbIndex_array) {
13786  if (!GetpCurrentStack()) return;
13787 
13788  // No context menu if quilting is disabled
13789  if (!GetQuiltMode()) return;
13790 
13791  m_piano_ctx_menu = new wxMenu();
13792 
13793  if(m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
13794 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
13795 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
13796 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
13797  }
13798  else {
13799 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen chartbar"));
13800 // Connect(ID_PIANO_CONTRACT_PIANO, wxEVT_COMMAND_MENU_SELECTED,
13801 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
13802 
13803  menu_selected_dbIndex = selected_dbIndex_array[0];
13804  menu_selected_index = selected_index;
13805 
13806  // Search the no-show array
13807  bool b_is_in_noshow = false;
13808  for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
13809  if (m_quilt_noshow_index_array[i] ==
13810  menu_selected_dbIndex) // chart is in the noshow list
13811  {
13812  b_is_in_noshow = true;
13813  break;
13814  }
13815  }
13816 
13817  if (b_is_in_noshow) {
13818  m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
13819  _("Show This Chart"));
13820  Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
13821  wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
13822  } else if (GetpCurrentStack()->nEntry > 1) {
13823  m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
13824  _("Hide This Chart"));
13825  Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
13826  wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
13827  }
13828  }
13829 
13830  wxPoint pos = wxPoint(x, y - 30);
13831 
13832  // Invoke the drop-down menu
13833  if (m_piano_ctx_menu->GetMenuItems().GetCount())
13834  PopupMenu(m_piano_ctx_menu, pos);
13835 
13836  delete m_piano_ctx_menu;
13837  m_piano_ctx_menu = NULL;
13838 
13839  HideChartInfoWindow();
13840  m_Piano->ResetRollover();
13841 
13842  SetQuiltChartHiLiteIndex(-1);
13843  ClearQuiltChartHiLiteIndexArray();
13844 
13845  ReloadVP();
13846 }
13847 
13848 void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
13849  for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
13850  if (m_quilt_noshow_index_array[i] ==
13851  menu_selected_dbIndex) // chart is in the noshow list
13852  {
13853  m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
13854  break;
13855  }
13856  }
13857 }
13858 
13859 void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
13860  if (!GetpCurrentStack()) return;
13861  if (!ChartData) return;
13862 
13863  RemoveChartFromQuilt(menu_selected_dbIndex);
13864 
13865  // It could happen that the chart being disabled is the reference
13866  // chart....
13867  if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
13868  int type = ChartData->GetDBChartType(menu_selected_dbIndex);
13869 
13870  int i = menu_selected_index + 1; // select next smaller scale chart
13871  bool b_success = false;
13872  while (i < GetpCurrentStack()->nEntry - 1) {
13873  int dbIndex = GetpCurrentStack()->GetDBIndex(i);
13874  if (type == ChartData->GetDBChartType(dbIndex)) {
13875  SelectQuiltRefChart(i);
13876  b_success = true;
13877  break;
13878  }
13879  i++;
13880  }
13881 
13882  // If that did not work, try to select the next larger scale compatible
13883  // chart
13884  if (!b_success) {
13885  i = menu_selected_index - 1;
13886  while (i > 0) {
13887  int dbIndex = GetpCurrentStack()->GetDBIndex(i);
13888  if (type == ChartData->GetDBChartType(dbIndex)) {
13889  SelectQuiltRefChart(i);
13890  b_success = true;
13891  break;
13892  }
13893  i--;
13894  }
13895  }
13896  }
13897 }
13898 
13899 void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
13900  // Remove the item from the list (if it appears) to avoid multiple addition
13901  for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
13902  if (m_quilt_noshow_index_array[i] ==
13903  dbIndex) // chart is already in the noshow list
13904  {
13905  m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
13906  break;
13907  }
13908  }
13909 
13910  m_quilt_noshow_index_array.push_back(dbIndex);
13911 }
13912 
13913 bool ChartCanvas::UpdateS52State() {
13914  bool retval = false;
13915  // printf(" update %d\n", IsPrimaryCanvas());
13916 
13917  if (ps52plib) {
13918  ps52plib->SetShowS57Text(m_encShowText);
13919  ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
13920  ps52plib->m_bShowSoundg = m_encShowDepth;
13921  ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
13922  ps52plib->m_bShowLdisText = m_encShowLightDesc;
13923 
13924  // Lights
13925  if (!m_encShowLights) // On, going off
13926  ps52plib->AddObjNoshow("LIGHTS");
13927  else // Off, going on
13928  ps52plib->RemoveObjNoshow("LIGHTS");
13929  ps52plib->SetLightsOff(!m_encShowLights);
13930  ps52plib->m_bExtendLightSectors = true;
13931 
13932  // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
13933  ps52plib->SetAnchorOn(m_encShowAnchor);
13934  ps52plib->SetQualityOfData(m_encShowDataQual);
13935  }
13936 
13937  return retval;
13938 }
13939 
13940 void ChartCanvas::SetShowENCDataQual(bool show) {
13941  m_encShowDataQual = show;
13942  if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13943  GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13944 
13945  m_s52StateHash = 0; // Force a S52 PLIB re-configure
13946 }
13947 
13948 void ChartCanvas::SetShowENCText(bool show) {
13949  m_encShowText = show;
13950  if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13951  GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13952 
13953  m_s52StateHash = 0; // Force a S52 PLIB re-configure
13954 }
13955 
13956 void ChartCanvas::SetENCDisplayCategory(int category) {
13957  m_encDisplayCategory = category;
13958  m_s52StateHash = 0; // Force a S52 PLIB re-configure
13959 }
13960 
13961 void ChartCanvas::SetShowENCDepth(bool show) {
13962  m_encShowDepth = show;
13963  if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13964  GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13965 
13966  m_s52StateHash = 0; // Force a S52 PLIB re-configure
13967 }
13968 
13969 void ChartCanvas::SetShowENCLightDesc(bool show) {
13970  m_encShowLightDesc = show;
13971  if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13972  GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13973 
13974  m_s52StateHash = 0; // Force a S52 PLIB re-configure
13975 }
13976 
13977 void ChartCanvas::SetShowENCBuoyLabels(bool show) {
13978  m_encShowBuoyLabels = show;
13979  m_s52StateHash = 0; // Force a S52 PLIB re-configure
13980 }
13981 
13982 void ChartCanvas::SetShowENCLights(bool show) {
13983  m_encShowLights = show;
13984  if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13985  GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13986 
13987  m_s52StateHash = 0; // Force a S52 PLIB re-configure
13988 }
13989 
13990 void ChartCanvas::SetShowENCAnchor(bool show) {
13991  m_encShowAnchor = show;
13992  if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13993  GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13994 
13995  m_s52StateHash = 0; // Force a S52 PLIB re-configure
13996 }
13997 
13998 wxRect ChartCanvas::GetMUIBarRect() {
13999  wxRect rv;
14000  if (m_muiBar) {
14001  rv = m_muiBar->GetRect();
14002  }
14003 
14004  return rv;
14005 }
14006 
14007 void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14008  if (!GetAlertString().IsEmpty()) {
14009  wxFont *pfont = wxTheFontList->FindOrCreateFont(
14010  10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14011 
14012  dc.SetFont(*pfont);
14013  dc.SetPen(*wxTRANSPARENT_PEN);
14014 
14015  dc.SetBrush(wxColour(243, 229, 47));
14016  int w, h;
14017  dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14018  h += 2;
14019  // int yp = vp.pix_height - 20 - h;
14020 
14021  wxRect sbr = GetScaleBarRect();
14022  int xp = sbr.x + sbr.width + 10;
14023  int yp = (sbr.y + sbr.height) - h;
14024 
14025  int wdraw = w + 10;
14026  dc.DrawRectangle(xp, yp, wdraw, h);
14027  dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14028  wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14029  }
14030 }
14031 
14032 //--------------------------------------------------------------------------------------------------------
14033 // Screen Brightness Control Support Routines
14034 //
14035 //--------------------------------------------------------------------------------------------------------
14036 
14037 #ifdef __UNIX__
14038 #define BRIGHT_XCALIB
14039 #define __OPCPN_USEICC__
14040 #endif
14041 
14042 #ifdef __OPCPN_USEICC__
14043 int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14044  double co_green, double co_blue);
14045 
14046 wxString temp_file_name;
14047 #endif
14048 
14049 #if 0
14050 class ocpnCurtain: public wxDialog
14051 {
14052  DECLARE_CLASS( ocpnCurtain )
14053  DECLARE_EVENT_TABLE()
14054 
14055 public:
14056  ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14057  ~ocpnCurtain( );
14058  bool ProcessEvent(wxEvent& event);
14059 
14060 };
14061 
14062 IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14063 
14064 BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14065 END_EVENT_TABLE()
14066 
14067 ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14068 {
14069  wxDialog::Create( parent, -1, _T("ocpnCurtain"), position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14070 }
14071 
14072 ocpnCurtain::~ocpnCurtain()
14073 {
14074 }
14075 
14076 bool ocpnCurtain::ProcessEvent(wxEvent& event)
14077 {
14078  GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14079  return GetParent()->GetEventHandler()->ProcessEvent(event);
14080 }
14081 #endif
14082 
14083 #ifdef _WIN32
14084 #include <windows.h>
14085 
14086 HMODULE hGDI32DLL;
14087 typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14088 typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14089 SetDeviceGammaRamp_ptr_type
14090  g_pSetDeviceGammaRamp; // the API entry points in the dll
14091 GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14092 
14093 WORD *g_pSavedGammaMap;
14094 
14095 #endif
14096 
14097 int InitScreenBrightness(void) {
14098 #ifdef _WIN32
14099 #ifdef ocpnUSE_GL
14100  if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14101  HDC hDC;
14102  BOOL bbr;
14103 
14104  if (NULL == hGDI32DLL) {
14105  hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14106 
14107  if (NULL != hGDI32DLL) {
14108  // Get the entry points of the required functions
14109  g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14110  hGDI32DLL, "SetDeviceGammaRamp");
14111  g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14112  hGDI32DLL, "GetDeviceGammaRamp");
14113 
14114  // If the functions are not found, unload the DLL and return false
14115  if ((NULL == g_pSetDeviceGammaRamp) ||
14116  (NULL == g_pGetDeviceGammaRamp)) {
14117  FreeLibrary(hGDI32DLL);
14118  hGDI32DLL = NULL;
14119  return 0;
14120  }
14121  }
14122  }
14123 
14124  // Interface is ready, so....
14125  // Get some storage
14126  if (!g_pSavedGammaMap) {
14127  g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14128 
14129  hDC = GetDC(NULL); // Get the full screen DC
14130  bbr = g_pGetDeviceGammaRamp(
14131  hDC, g_pSavedGammaMap); // Get the existing ramp table
14132  ReleaseDC(NULL, hDC); // Release the DC
14133  }
14134 
14135  // On Windows hosts, try to adjust the registry to allow full range
14136  // setting of Gamma table This is an undocumented Windows hack.....
14137  wxRegKey *pRegKey = new wxRegKey(
14138  _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows ")
14139  _T("NT\\CurrentVersion\\ICM"));
14140  if (!pRegKey->Exists()) pRegKey->Create();
14141  pRegKey->SetValue(_T("GdiIcmGammaRange"), 256);
14142 
14143  g_brightness_init = true;
14144  return 1;
14145  }
14146 #endif
14147 
14148  {
14149  if (NULL == g_pcurtain) {
14150  if (gFrame->CanSetTransparent()) {
14151  // Build the curtain window
14152  g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, _T(""),
14153  wxPoint(0, 0), ::wxGetDisplaySize(),
14154  wxNO_BORDER | wxTRANSPARENT_WINDOW |
14155  wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14156 
14157  // g_pcurtain = new ocpnCurtain(gFrame,
14158  // wxPoint(0,0),::wxGetDisplaySize(),
14159  // wxNO_BORDER | wxTRANSPARENT_WINDOW
14160  // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14161 
14162  g_pcurtain->Hide();
14163 
14164  HWND hWnd = GetHwndOf(g_pcurtain);
14165  SetWindowLong(hWnd, GWL_EXSTYLE,
14166  GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14167  g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14168  g_pcurtain->SetTransparent(0);
14169 
14170  g_pcurtain->Maximize();
14171  g_pcurtain->Show();
14172 
14173  // All of this is obtuse, but necessary for Windows...
14174  g_pcurtain->Enable();
14175  g_pcurtain->Disable();
14176 
14177  gFrame->Disable();
14178  gFrame->Enable();
14179  // SetFocus();
14180  }
14181  }
14182  g_brightness_init = true;
14183 
14184  return 1;
14185  }
14186 #else
14187  // Look for "xcalib" application
14188  wxString cmd(_T ( "xcalib -version" ));
14189 
14190  wxArrayString output;
14191  long r = wxExecute(cmd, output);
14192  if (0 != r)
14193  wxLogMessage(
14194  _T(" External application \"xcalib\" not found. Screen brightness ")
14195  _T("not changed."));
14196 
14197  g_brightness_init = true;
14198  return 0;
14199 #endif
14200 }
14201 
14202 int RestoreScreenBrightness(void) {
14203 #ifdef _WIN32
14204 
14205  if (g_pSavedGammaMap) {
14206  HDC hDC = GetDC(NULL); // Get the full screen DC
14207  g_pSetDeviceGammaRamp(hDC,
14208  g_pSavedGammaMap); // Restore the saved ramp table
14209  ReleaseDC(NULL, hDC); // Release the DC
14210 
14211  free(g_pSavedGammaMap);
14212  g_pSavedGammaMap = NULL;
14213  }
14214 
14215  if (g_pcurtain) {
14216  g_pcurtain->Close();
14217  g_pcurtain->Destroy();
14218  g_pcurtain = NULL;
14219  }
14220 
14221  g_brightness_init = false;
14222  return 1;
14223 
14224 #endif
14225 
14226 #ifdef BRIGHT_XCALIB
14227  if (g_brightness_init) {
14228  wxString cmd;
14229  cmd = _T("xcalib -clear");
14230  wxExecute(cmd, wxEXEC_ASYNC);
14231  g_brightness_init = false;
14232  }
14233 
14234  return 1;
14235 #endif
14236 
14237  return 0;
14238 }
14239 
14240 // Set brightness. [0..100]
14241 int SetScreenBrightness(int brightness) {
14242 #ifdef _WIN32
14243 
14244  // Under Windows, we use the SetDeviceGammaRamp function which exists in
14245  // some (most modern?) versions of gdi32.dll Load the required library dll,
14246  // if not already in place
14247 #ifdef ocpnUSE_GL
14248  if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14249  if (g_pcurtain) {
14250  g_pcurtain->Close();
14251  g_pcurtain->Destroy();
14252  g_pcurtain = NULL;
14253  }
14254 
14255  InitScreenBrightness();
14256 
14257  if (NULL == hGDI32DLL) {
14258  // Unicode stuff.....
14259  wchar_t wdll_name[80];
14260  MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
14261  LPCWSTR cstr = wdll_name;
14262 
14263  hGDI32DLL = LoadLibrary(cstr);
14264 
14265  if (NULL != hGDI32DLL) {
14266  // Get the entry points of the required functions
14267  g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14268  hGDI32DLL, "SetDeviceGammaRamp");
14269  g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14270  hGDI32DLL, "GetDeviceGammaRamp");
14271 
14272  // If the functions are not found, unload the DLL and return false
14273  if ((NULL == g_pSetDeviceGammaRamp) ||
14274  (NULL == g_pGetDeviceGammaRamp)) {
14275  FreeLibrary(hGDI32DLL);
14276  hGDI32DLL = NULL;
14277  return 0;
14278  }
14279  }
14280  }
14281 
14282  HDC hDC = GetDC(NULL); // Get the full screen DC
14283 
14284  /*
14285  int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
14286  if (cmcap != CM_GAMMA_RAMP)
14287  {
14288  wxLogMessage(_T(" Video hardware does not support brightness control by
14289  gamma ramp adjustment.")); return false;
14290  }
14291  */
14292 
14293  int increment = brightness * 256 / 100;
14294 
14295  // Build the Gamma Ramp table
14296  WORD GammaTable[3][256];
14297 
14298  int table_val = 0;
14299  for (int i = 0; i < 256; i++) {
14300  GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
14301  GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
14302  GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
14303 
14304  table_val += increment;
14305 
14306  if (table_val > 65535) table_val = 65535;
14307  }
14308 
14309  g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
14310  ReleaseDC(NULL, hDC); // Release the DC
14311 
14312  return 1;
14313  }
14314 #endif
14315 
14316  {
14317  if (g_pSavedGammaMap) {
14318  HDC hDC = GetDC(NULL); // Get the full screen DC
14319  g_pSetDeviceGammaRamp(hDC,
14320  g_pSavedGammaMap); // Restore the saved ramp table
14321  ReleaseDC(NULL, hDC); // Release the DC
14322  }
14323 
14324  if (brightness < 100) {
14325  if (NULL == g_pcurtain) InitScreenBrightness();
14326 
14327  if (g_pcurtain) {
14328  int sbrite = wxMax(1, brightness);
14329  sbrite = wxMin(100, sbrite);
14330 
14331  g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
14332  }
14333  } else {
14334  if (g_pcurtain) {
14335  g_pcurtain->Close();
14336  g_pcurtain->Destroy();
14337  g_pcurtain = NULL;
14338  }
14339  }
14340 
14341  return 1;
14342  }
14343 
14344 #endif
14345 
14346 #ifdef BRIGHT_XCALIB
14347 
14348  if (!g_brightness_init) {
14349  last_brightness = 100;
14350  g_brightness_init = true;
14351  temp_file_name = wxFileName::CreateTempFileName(_T(""));
14352  InitScreenBrightness();
14353  }
14354 
14355 #ifdef __OPCPN_USEICC__
14356  // Create a dead simple temporary ICC profile file, with gamma ramps set as
14357  // desired, and then activate this temporary profile using xcalib <filename>
14358  if (!CreateSimpleICCProfileFile(
14359  (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
14360  brightness * g_gamma_mult, brightness * b_gamma_mult)) {
14361  wxString cmd(_T ( "xcalib " ));
14362  cmd += temp_file_name;
14363 
14364  wxExecute(cmd, wxEXEC_ASYNC);
14365  }
14366 
14367 #else
14368  // Or, use "xcalib -co" to set overall contrast value
14369  // This is not as nice, since the -co parameter wants to be a fraction of
14370  // the current contrast, and values greater than 100 are not allowed. As a
14371  // result, increases of contrast must do a "-clear" step first, which
14372  // produces objectionable flashing.
14373  if (brightness > last_brightness) {
14374  wxString cmd;
14375  cmd = _T("xcalib -clear");
14376  wxExecute(cmd, wxEXEC_ASYNC);
14377 
14378  ::wxMilliSleep(10);
14379 
14380  int brite_adj = wxMax(1, brightness);
14381  cmd.Printf(_T("xcalib -co %2d -a"), brite_adj);
14382  wxExecute(cmd, wxEXEC_ASYNC);
14383  } else {
14384  int brite_adj = wxMax(1, brightness);
14385  int factor = (brite_adj * 100) / last_brightness;
14386  factor = wxMax(1, factor);
14387  wxString cmd;
14388  cmd.Printf(_T("xcalib -co %2d -a"), factor);
14389  wxExecute(cmd, wxEXEC_ASYNC);
14390  }
14391 
14392 #endif
14393 
14394  last_brightness = brightness;
14395 
14396 #endif
14397 
14398  return 0;
14399 }
14400 
14401 #ifdef __OPCPN_USEICC__
14402 
14403 #define MLUT_TAG 0x6d4c5554L
14404 #define VCGT_TAG 0x76636774L
14405 
14406 int GetIntEndian(unsigned char *s) {
14407  int ret;
14408  unsigned char *p;
14409  int i;
14410 
14411  p = (unsigned char *)&ret;
14412 
14413  if (1)
14414  for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
14415  else
14416  for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
14417 
14418  return ret;
14419 }
14420 
14421 unsigned short GetShortEndian(unsigned char *s) {
14422  unsigned short ret;
14423  unsigned char *p;
14424  int i;
14425 
14426  p = (unsigned char *)&ret;
14427 
14428  if (1)
14429  for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
14430  else
14431  for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
14432 
14433  return ret;
14434 }
14435 
14436 // Create a very simple Gamma correction file readable by xcalib
14437 int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14438  double co_green, double co_blue) {
14439  FILE *fp;
14440 
14441  if (file_name) {
14442  fp = fopen(file_name, "wb");
14443  if (!fp) return -1; /* file can not be created */
14444  } else
14445  return -1; /* filename char pointer not valid */
14446 
14447  // Write header
14448  char header[128];
14449  for (int i = 0; i < 128; i++) header[i] = 0;
14450 
14451  fwrite(header, 128, 1, fp);
14452 
14453  // Num tags
14454  int numTags0 = 1;
14455  int numTags = GetIntEndian((unsigned char *)&numTags0);
14456  fwrite(&numTags, 1, 4, fp);
14457 
14458  int tagName0 = VCGT_TAG;
14459  int tagName = GetIntEndian((unsigned char *)&tagName0);
14460  fwrite(&tagName, 1, 4, fp);
14461 
14462  int tagOffset0 = 128 + 4 * sizeof(int);
14463  int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
14464  fwrite(&tagOffset, 1, 4, fp);
14465 
14466  int tagSize0 = 1;
14467  int tagSize = GetIntEndian((unsigned char *)&tagSize0);
14468  fwrite(&tagSize, 1, 4, fp);
14469 
14470  fwrite(&tagName, 1, 4, fp); // another copy of tag
14471 
14472  fwrite(&tagName, 1, 4, fp); // dummy
14473 
14474  // Table type
14475 
14476  /* VideoCardGammaTable (The simplest type) */
14477  int gammatype0 = 0;
14478  int gammatype = GetIntEndian((unsigned char *)&gammatype0);
14479  fwrite(&gammatype, 1, 4, fp);
14480 
14481  int numChannels0 = 3;
14482  unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
14483  fwrite(&numChannels, 1, 2, fp);
14484 
14485  int numEntries0 = 256;
14486  unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
14487  fwrite(&numEntries, 1, 2, fp);
14488 
14489  int entrySize0 = 1;
14490  unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
14491  fwrite(&entrySize, 1, 2, fp);
14492 
14493  unsigned char ramp[256];
14494 
14495  // Red ramp
14496  for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
14497  fwrite(ramp, 256, 1, fp);
14498 
14499  // Green ramp
14500  for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
14501  fwrite(ramp, 256, 1, fp);
14502 
14503  // Blue ramp
14504  for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
14505  fwrite(ramp, 256, 1, fp);
14506 
14507  fclose(fp);
14508 
14509  return 0;
14510 }
14511 #endif // __OPCPN_USEICC__
Global state for AIS decoder.
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.
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition: chcanv.cpp:7129
Definition: IDX_entry.h:41
Definition: kml.h:54
Definition: MUIBar.h:58
Class MarkInfoDef.
Definition: MarkInfo.h:203
Definition: piano.h:71
Definition: Quilt.h:83
bool Compose(const ViewPort &vp)
Definition: Quilt.cpp:1694
Definition: route.h:75
bool ActivateNextPoint(Route *pr, bool skipped)
Definition: routeman.cpp:369
bool DeleteRoute(Route *pRoute, NavObjectChanges *nav_obj_changes)
Definition: routeman.cpp:751
Definition: select.h:54
Set of basemaps at different resolutions.
Definition: tcmgr.h:86
Definition: TCWin.h:46
Class TrackPropDlg.
Definition: TrackPropDlg.h:93
bool UpdateProperties()
Definition: track.h:78
Definition: undo.h:60
Definition: ocpndc.h:58
Global variables reflecting command line options and arguments.
Definition: Quilt.cpp:867